Redesign highscore screen (#908)

- Places 1-3 are shown on a podium with profile avatars and a crown for the winner
- Players without an avatar get a placeholder silhouette (new skin texture NoAvatar)
- Places 4-10 are listed below with a score bar for each entry
- The podium only appears once there is at least one highscore
- New entries are shown in gold with a slight pulse and gold sparkles
This commit is contained in:
McSchneidar authored and GitHub committed 2026-10-17 04:23:07 +02:00
1 parent e69f62809a
commit 61b2677f16
10 files changed
+752 -406

No files matched your search

@@ -1,5 +1,21 @@
ScreenHighscore
++++++++++
Version 4:
(2026 Oct 15)
Add:
<StaticPodium[1..3]> (podium for places 1-3)
<StaticAvatar[1..3]> (avatar of the profile on the podium, skin <NoAvatar> as placeholder)
<StaticCrown>
<ProgressBarScore[4..10]>
Remove:
<StaticTop1Bar>, <StaticTop2Bar>, <StaticTop3Bar>
Change:
Places 1-3 are shown on the podium, places 4-10 in the list below (no scrolling).
New entries are shown in the color <Top1Color> with a slight pulse.
++++++++++
Version 3:
@@ -370,6 +370,7 @@
<ToneHelper>ToneHelper.png</ToneHelper>
<Score>Score.png</Score>
<Star>Star.png</Star>
<NoAvatar>NoAvatar.png</NoAvatar>
<SongMenu>SongMenu.png</SongMenu>
<PauseBG>PauseBG.png</PauseBG>
<EnergyBeam>EnergyBeam.png</EnergyBeam>
+1
View File
@@ -370,6 +370,7 @@
<ToneHelper>ToneHelper.png</ToneHelper>
<Score>Score.png</Score>
<Star>Star.png</Star>
<NoAvatar>NoAvatar.png</NoAvatar>
<SongMenu>SongMenu.png</SongMenu>
<PauseBG>PauseBG.png</PauseBG>
<EnergyBeam>EnergyBeam.png</EnergyBeam>
+1
View File
@@ -370,6 +370,7 @@
<ToneHelper>ToneHelper.png</ToneHelper>
<Score>Score.png</Score>
<Star>Star.png</Star>
<NoAvatar>NoAvatar.png</NoAvatar>
<SongMenu>SongMenu.png</SongMenu>
<PauseBG>PauseBG.png</PauseBG>
<EnergyBeam>EnergyBeam.png</EnergyBeam>
+1
View File
@@ -370,6 +370,7 @@
<ToneHelper>ToneHelper.png</ToneHelper>
<Score>Score.png</Score>
<Star>Star.png</Star>
<NoAvatar>NoAvatar.png</NoAvatar>
<SongMenu>SongMenu.png</SongMenu>
<PauseBG>PauseBG.png</PauseBG>
<EnergyBeam>EnergyBeam.png</EnergyBeam>
+1
View File
@@ -370,6 +370,7 @@
<ToneHelper>ToneHelper.png</ToneHelper>
<Score>Score.png</Score>
<Star>Star.png</Star>
<NoAvatar>NoAvatar.png</NoAvatar>
<SongMenu>SongMenu.png</SongMenu>
<PauseBG>PauseBG.png</PauseBG>
<EnergyBeam>EnergyBeam.png</EnergyBeam>
+1
View File
@@ -370,6 +370,7 @@
<ToneHelper>ToneHelper.png</ToneHelper>
<Score>Score.png</Score>
<Star>Star.png</Star>
<NoAvatar>NoAvatar.png</NoAvatar>
<SongMenu>SongMenu.png</SongMenu>
<PauseBG>PauseBG.png</PauseBG>
<EnergyBeam>EnergyBeam.png</EnergyBeam>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because it is too large. Load diff
+167 -106
View File
@@ -17,10 +17,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
using VocaluxeLib.Menu;
using VocaluxeLib.Songs;
@@ -31,22 +33,28 @@ namespace Vocaluxe.Screens
// Version number for theme files. Increment it, if you've changed something on the theme files!
protected override int _ScreenVersion
{
get { return 3; }
get { return 4; }
}
private const int _NumEntrys = 10;
private const int _NumPodium = 3;
private const int _NumRows = _NumEntrys - _NumPodium;
private const string _TextSongName = "TextSongName";
private const string _TextSongMode = "TextSongMode";
private const string _StaticCrown = "StaticCrown";
private string[] _TextNumber;
private string[] _TextName;
private string[] _TextScore;
private string[] _TextDate;
private string[] _ParticleEffectNew;
private string[] _StaticPodium;
private string[] _StaticAvatar;
private string[] _ProgressBarScore;
private CTextureRef[] _PodiumAvatars;
private List<SDBScoreEntry>[] _Scores;
private List<int> _NewEntryIds;
private int _Round;
private int _Pos;
private bool _IsDuet;
private bool _FromScreenSong = false;
@@ -58,6 +66,17 @@ namespace Vocaluxe.Screens
private int _HighscoreStream = -1;
private bool _HasPlayedHighscoreSound = false;
private struct STextStyle
{
public float Height;
public float Y;
public SColorF Color;
}
private readonly Dictionary<string, STextStyle> _TextStyles = new Dictionary<string, STextStyle>();
private readonly Stopwatch _PulseTimer = new Stopwatch();
private SColorF _NewEntryColor;
private static int PlaySound(ESounds sound, int volume)
{
var streamId = CSound.PlaySound(sound, false);
@@ -106,13 +125,56 @@ namespace Vocaluxe.Screens
_ParticleEffectNew[i] = "ParticleEffectNew" + (i + 1);
}
_ThemeStatics = new string[] { "StaticTop1Bar", "StaticTop2Bar", "StaticTop3Bar" };
var statics = new List<string> { _StaticCrown };
_StaticPodium = new string[_NumPodium];
_StaticAvatar = new string[_NumPodium];
for (var i = 0; i < _NumPodium; i++)
{
_StaticPodium[i] = "StaticPodium" + (i + 1);
_StaticAvatar[i] = "StaticAvatar" + (i + 1);
statics.Add(_StaticPodium[i]);
statics.Add(_StaticAvatar[i]);
}
_ProgressBarScore = new string[_NumRows];
for (var i = 0; i < _NumRows; i++)
{
_ProgressBarScore[i] = "ProgressBarScore" + (i + _NumPodium + 1);
}
_ThemeStatics = statics.ToArray();
_ThemeTexts = texts.ToArray();
_ThemeParticleEffects = _ParticleEffectNew;
_ThemeProgressBars = _ProgressBarScore;
_PodiumAvatars = new CTextureRef[_NumPodium];
_NewEntryIds = new List<int>();
}
public override void LoadTheme(string xmlPath)
{
base.LoadTheme(xmlPath);
foreach (var avatar in _StaticAvatar)
{
_Statics[avatar].Aspect = EAspect.Crop;
}
_TextStyles.Clear();
foreach (var name in _TextNumber.Concat(_TextName).Concat(_TextScore).Concat(_TextDate))
{
_TextStyles[name] = new STextStyle { Height = _Texts[name].Font.Height, Y = _Texts[name].Y, Color = _Texts[name].Color };
}
if (!CBase.Themes.GetColor("Top1Color", PartyModeId, out _NewEntryColor))
{
_NewEntryColor = new SColorF(1f, 0.8f, 0f, 1f);
}
_NewEntryColor.A = 1f;
}
public override bool HandleInput(SKeyEvent keyEvent)
{
if (keyEvent.KeyPressed && !Char.IsControl(keyEvent.Unicode)) { }
@@ -126,22 +188,6 @@ namespace Vocaluxe.Screens
_LeaveScreen();
break;
case Keys.Down:
if (!_FromScreenSong)
{
_ChangePos(1);
}
break;
case Keys.Up:
if (!_FromScreenSong)
{
_ChangePos(-1);
}
break;
case Keys.Left:
_ChangeRound(-1);
break;
@@ -180,87 +226,93 @@ namespace Vocaluxe.Screens
}
}
_ChangePos(mouseEvent.Wheel);
return true;
}
public override bool UpdateGame()
{
var numScores = _Scores[_Round].Count;
_Statics["StaticTop1Bar"].Visible = numScores >= 1;
_Statics["StaticTop2Bar"].Visible = numScores >= 2;
_Statics["StaticTop3Bar"].Visible = numScores >= 3;
_Statics[_StaticCrown].Visible = numScores > 0;
foreach (var podium in _StaticPodium)
{
_Statics[podium].Visible = numScores > 0;
}
for (var p = 0; p < _NumEntrys; p++)
{
if (_Pos + p < _Scores[_Round].Count)
var isEntry = p < numScores;
var isNew = isEntry && _IsNewEntry(_Scores[_Round][p].Id);
_Texts[_TextNumber[p]].Visible = isEntry || (p < _NumPodium && numScores > 0);
_Texts[_TextName[p]].Visible = isEntry;
_Texts[_TextScore[p]].Visible = isEntry;
_Texts[_TextDate[p]].Visible = isEntry;
_ParticleEffects[_ParticleEffectNew[p]].Visible = isNew;
_SetNewEntryStyle(p, isNew);
_Texts[_TextNumber[p]].Text = (p + 1).ToString();
if (p < _NumPodium)
{
_Texts[_TextNumber[p]].Visible = true;
_Texts[_TextName[p]].Visible = true;
_Texts[_TextScore[p]].Visible = true;
_Texts[_TextDate[p]].Visible = true;
_Texts[_TextNumber[p]].Text = (_Pos + p + 1).ToString();
var name = _Scores[_Round][_Pos + p].Name;
name += " [" + CLanguage.Translate(Enum.GetName(typeof(EGameDifficulty), _Scores[_Round][_Pos + p].Difficulty)) + "]";
if (_IsDuet)
{
var voiceNr = _Scores[_Round][_Pos + p].VoiceNr;
string voiceName = null;
CSong song;
if (_FromScreenSong)
{
song = CSongs.GetSong(CScreenSong.getSelectedSongId());
}
else
{
song = CGame.GetSong(_Round);
}
if (song != null && song.Notes != null && song.Notes.VoiceNames != null)
{
try
{
voiceName = song.Notes.VoiceNames[voiceNr];
}
catch
{
voiceName = null; // Ungültiger Index
}
}
if (!string.IsNullOrWhiteSpace(voiceName))
{
name += " (" + voiceName + ")";
}
else
{
name += " (P" + (voiceNr + 1) + ")";
}
}
_Texts[_TextName[p]].Text = name;
_Texts[_TextScore[p]].Text = _Scores[_Round][_Pos + p].Score.ToString("D");
_Texts[_TextDate[p]].Text = _Scores[_Round][_Pos + p].Date;
_ParticleEffects[_ParticleEffectNew[p]].Visible = _IsNewEntry(_Scores[_Round][_Pos + p].Id);
if (_ParticleEffects[_ParticleEffectNew[p]].Visible && !_HasPlayedHighscoreSound)
{
_HighscoreStream = CScreenHighscore.PlaySound(ESounds.Highscore, CConfig.SoundEffectVolume);
_HasPlayedHighscoreSound = true;
}
_Statics[_StaticAvatar[p]].Texture = _PodiumAvatars[p];
_Statics[_StaticAvatar[p]].Visible = isEntry && _Statics[_StaticAvatar[p]].Texture != null;
}
else
{
_Texts[_TextNumber[p]].Visible = false;
_Texts[_TextName[p]].Visible = false;
_Texts[_TextScore[p]].Visible = false;
_Texts[_TextDate[p]].Visible = false;
_ParticleEffects[_ParticleEffectNew[p]].Visible = false;
var row = p - _NumPodium;
_ProgressBars[_ProgressBarScore[row]].Visible = isEntry;
var progress = isEntry ? (float)_Scores[_Round][p].Score / CSettings.MaxScore : 0f;
if (_ProgressBars[_ProgressBarScore[row]].Progress != progress)
{
_ProgressBars[_ProgressBarScore[row]].Progress = progress;
}
}
if (!isEntry)
{
continue;
}
var name = _Scores[_Round][p].Name;
name += " [" + CLanguage.Translate(Enum.GetName(typeof(EGameDifficulty), _Scores[_Round][p].Difficulty)) + "]";
if (_IsDuet)
{
var voiceNr = _Scores[_Round][p].VoiceNr;
string voiceName = null;
var song = _GetCurrentSong();
if (song != null && song.Notes != null && song.Notes.VoiceNames != null)
{
try
{
voiceName = song.Notes.VoiceNames[voiceNr];
}
catch
{
voiceName = null; // Ungültiger Index
}
}
if (!string.IsNullOrWhiteSpace(voiceName))
{
name += " (" + voiceName + ")";
}
else
{
name += " (P" + (voiceNr + 1) + ")";
}
}
_Texts[_TextName[p]].Text = name;
_Texts[_TextScore[p]].Text = _Scores[_Round][p].Score.ToString("D");
_Texts[_TextDate[p]].Text = _Scores[_Round][p].Date;
if (isNew && !_HasPlayedHighscoreSound)
{
_HighscoreStream = CScreenHighscore.PlaySound(ESounds.Highscore, CConfig.SoundEffectVolume);
_HasPlayedHighscoreSound = true;
}
}
@@ -271,8 +323,8 @@ namespace Vocaluxe.Screens
{
base.OnShow();
_HasPlayedHighscoreSound = false;
_PulseTimer.Restart();
_Round = 0;
_Pos = 0;
_NewEntryIds.Clear();
_AddScoresToDB();
_LoadScores();
@@ -281,6 +333,19 @@ namespace Vocaluxe.Screens
UpdateGame();
}
private void _SetNewEntryStyle(int entry, bool isNew)
{
var scale = isNew ? 1f + 0.03f * (float)Math.Sin(_PulseTimer.ElapsedMilliseconds / 1000.0 * Math.PI * 1.4) : 1f;
foreach (var name in new[] { _TextNumber[entry], _TextName[entry], _TextScore[entry], _TextDate[entry] })
{
var style = _TextStyles[name];
var text = _Texts[name];
text.Color = isNew ? _NewEntryColor : style.Color;
text.Font.Height = style.Height * scale;
text.Y = style.Y - style.Height * (scale - 1f) / 2f;
}
}
private bool _IsNewEntry(int id)
{
return _NewEntryIds.Any(t => t == id);
@@ -316,7 +381,6 @@ namespace Vocaluxe.Screens
private void _LoadScores()
{
_Pos = 0;
var rounds = CGame.NumRounds;
if (rounds == 0)
@@ -357,16 +421,12 @@ namespace Vocaluxe.Screens
_IsDuet = false;
var points = CGame.GetPoints();
CSong song;
if (_FromScreenSong)
for (var p = 0; p < _NumPodium; p++)
{
song = CSongs.GetSong(CScreenSong.getSelectedSongId());
}
else
{
song = CGame.GetSong(_Round);
_PodiumAvatars[p] = p < _Scores[_Round].Count ? _GetAvatarTexture(_Scores[_Round][p].Name) : null;
}
var song = _GetCurrentSong();
if (song == null)
{
return;
@@ -401,21 +461,22 @@ namespace Vocaluxe.Screens
_Texts[_TextSongMode].Text = "TR_GAMEMODE_NORMAL";
break;
}
_Pos = 0;
}
private void _ChangePos(int num)
private CSong _GetCurrentSong()
{
if (_Scores[_Round].Count == 0 || _Scores[_Round].Count <= _NumEntrys)
if (_FromScreenSong)
{
_Pos = 0;
}
else
{
_Pos += num;
_Pos = _Pos.Clamp(0, _Scores[_Round].Count - 1, true);
return CSongs.GetSong(CScreenSong.getSelectedSongId());
}
return CGame.GetSong(_Round);
}
private static CTextureRef _GetAvatarTexture(string playerName)
{
var profile = CProfiles.GetProfiles().FirstOrDefault(pr => pr.PlayerName == playerName && pr.Avatar != null);
return profile == null ? null : profile.Avatar.Texture;
}
private void _ChangeRound(int num)