mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 01:02:55 +08:00
Merge pull request #10251 from smoogipoo/additional-hit-results
This commit is contained in:
commit
9d07dce5e4
@ -11,9 +11,11 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -55,6 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
highAccuracyColour = colours.GreenLight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The statistics that appear in the table, in order of appearance.
|
||||
/// </summary>
|
||||
private readonly List<HitResult> statisticResultTypes = new List<HitResult>();
|
||||
|
||||
private bool showPerformancePoints;
|
||||
|
||||
public void DisplayScores(IReadOnlyList<ScoreInfo> scores, bool showPerformanceColumn)
|
||||
@ -65,11 +72,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
return;
|
||||
|
||||
showPerformancePoints = showPerformanceColumn;
|
||||
statisticResultTypes.Clear();
|
||||
|
||||
for (int i = 0; i < scores.Count; i++)
|
||||
backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height));
|
||||
|
||||
Columns = createHeaders(scores.FirstOrDefault());
|
||||
Columns = createHeaders(scores);
|
||||
Content = scores.Select((s, i) => createContent(i, s)).ToArray().ToRectangular();
|
||||
}
|
||||
|
||||
@ -79,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
backgroundFlow.Clear();
|
||||
}
|
||||
|
||||
private TableColumn[] createHeaders(ScoreInfo score)
|
||||
private TableColumn[] createHeaders(IReadOnlyList<ScoreInfo> scores)
|
||||
{
|
||||
var columns = new List<TableColumn>
|
||||
{
|
||||
@ -92,10 +100,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120))
|
||||
};
|
||||
|
||||
foreach (var statistic in score.SortedStatistics.Take(score.SortedStatistics.Count() - 1))
|
||||
columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60)));
|
||||
// All statistics across all scores, unordered.
|
||||
var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet();
|
||||
|
||||
columns.Add(new TableColumn(score.SortedStatistics.LastOrDefault().Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 45, maxSize: 95)));
|
||||
foreach (var result in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||
{
|
||||
if (!allScoreStatistics.Contains(result))
|
||||
continue;
|
||||
|
||||
columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60)));
|
||||
statisticResultTypes.Add(result);
|
||||
}
|
||||
|
||||
if (showPerformancePoints)
|
||||
columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30)));
|
||||
@ -148,13 +163,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var kvp in score.SortedStatistics)
|
||||
var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result);
|
||||
|
||||
foreach (var result in statisticResultTypes)
|
||||
{
|
||||
if (!availableStatistics.TryGetValue(result, out var stat))
|
||||
stat = (result, 0, null);
|
||||
|
||||
content.Add(new OsuSpriteText
|
||||
{
|
||||
Text = $"{kvp.Value}",
|
||||
Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}",
|
||||
Font = OsuFont.GetFont(size: text_size),
|
||||
Colour = kvp.Value == 0 ? Color4.Gray : Color4.White
|
||||
Colour = stat.count == 0 ? Color4.Gray : Color4.White
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
|
||||
ppColumn.Text = $@"{value.PP:N0}";
|
||||
|
||||
statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value));
|
||||
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount));
|
||||
modsColumn.Mods = value.Mods;
|
||||
|
||||
if (scoreManager != null)
|
||||
@ -125,9 +125,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
}
|
||||
}
|
||||
|
||||
private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width)
|
||||
private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width)
|
||||
{
|
||||
Text = count.ToString()
|
||||
Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}"
|
||||
};
|
||||
|
||||
private class InfoColumn : CompositeDrawable
|
||||
|
@ -2,15 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.ComponentModel;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
[HasOrderedElements]
|
||||
public enum HitResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the object has not been judged yet.
|
||||
/// </summary>
|
||||
[Description(@"")]
|
||||
[Order(14)]
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
@ -21,47 +24,156 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time).
|
||||
/// </remarks>
|
||||
[Description(@"Miss")]
|
||||
[Order(5)]
|
||||
Miss,
|
||||
|
||||
[Description(@"Meh")]
|
||||
[Order(4)]
|
||||
Meh,
|
||||
|
||||
/// <summary>
|
||||
/// Optional judgement.
|
||||
/// </summary>
|
||||
[Description(@"OK")]
|
||||
[Order(3)]
|
||||
Ok,
|
||||
|
||||
[Description(@"Good")]
|
||||
[Order(2)]
|
||||
Good,
|
||||
|
||||
[Description(@"Great")]
|
||||
[Order(1)]
|
||||
Great,
|
||||
|
||||
/// <summary>
|
||||
/// Optional judgement.
|
||||
/// </summary>
|
||||
[Description(@"Perfect")]
|
||||
[Order(0)]
|
||||
Perfect,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates small tick miss.
|
||||
/// </summary>
|
||||
[Order(11)]
|
||||
SmallTickMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a small tick hit.
|
||||
/// </summary>
|
||||
[Description(@"S Tick")]
|
||||
[Order(7)]
|
||||
SmallTickHit,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a large tick miss.
|
||||
/// </summary>
|
||||
[Order(10)]
|
||||
LargeTickMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a large tick hit.
|
||||
/// </summary>
|
||||
LargeTickHit
|
||||
[Description(@"L Tick")]
|
||||
[Order(6)]
|
||||
LargeTickHit,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a small bonus.
|
||||
/// </summary>
|
||||
[Description("S Bonus")]
|
||||
[Order(9)]
|
||||
SmallBonus,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a large bonus.
|
||||
/// </summary>
|
||||
[Description("L Bonus")]
|
||||
[Order(8)]
|
||||
LargeBonus,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a miss that should be ignored for scoring purposes.
|
||||
/// </summary>
|
||||
[Order(13)]
|
||||
IgnoreMiss,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a hit that should be ignored for scoring purposes.
|
||||
/// </summary>
|
||||
[Order(12)]
|
||||
IgnoreHit,
|
||||
}
|
||||
|
||||
public static class HitResultExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> increases/decreases the combo, and affects the combo portion of the score.
|
||||
/// </summary>
|
||||
public static bool AffectsCombo(this HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Miss:
|
||||
case HitResult.Meh:
|
||||
case HitResult.Ok:
|
||||
case HitResult.Good:
|
||||
case HitResult.Great:
|
||||
case HitResult.Perfect:
|
||||
case HitResult.LargeTickHit:
|
||||
case HitResult.LargeTickMiss:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> affects the accuracy portion of the score.
|
||||
/// </summary>
|
||||
public static bool AffectsAccuracy(this HitResult result)
|
||||
=> IsScorable(result) && !IsBonus(result);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> should be counted as bonus score.
|
||||
/// </summary>
|
||||
public static bool IsBonus(this HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.SmallBonus:
|
||||
case HitResult.LargeBonus:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> represents a successful hit.
|
||||
/// </summary>
|
||||
public static bool IsHit(this HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.None:
|
||||
case HitResult.IgnoreMiss:
|
||||
case HitResult.Miss:
|
||||
case HitResult.SmallTickMiss:
|
||||
case HitResult.LargeTickMiss:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a <see cref="HitResult"/> is scorable.
|
||||
/// </summary>
|
||||
public static bool IsScorable(this HitResult result) => result >= HitResult.Miss && result < HitResult.IgnoreMiss;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
@ -147,8 +148,6 @@ namespace osu.Game.Scoring
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>();
|
||||
|
||||
public IOrderedEnumerable<KeyValuePair<HitResult, int>> SortedStatistics => Statistics.OrderByDescending(pair => pair.Key);
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("Statistics")]
|
||||
public string StatisticsJson
|
||||
@ -186,6 +185,48 @@ namespace osu.Game.Scoring
|
||||
[JsonProperty("position")]
|
||||
public int? Position { get; set; }
|
||||
|
||||
public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay()
|
||||
{
|
||||
foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||
{
|
||||
if (key.IsBonus())
|
||||
continue;
|
||||
|
||||
int value = Statistics.GetOrDefault(key);
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case HitResult.SmallTickHit:
|
||||
{
|
||||
int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||
if (total > 0)
|
||||
yield return (key, value, total);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HitResult.LargeTickHit:
|
||||
{
|
||||
int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss);
|
||||
if (total > 0)
|
||||
yield return (key, value, total);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HitResult.SmallTickMiss:
|
||||
case HitResult.LargeTickMiss:
|
||||
break;
|
||||
|
||||
default:
|
||||
if (value > 0 || key == HitResult.Miss)
|
||||
yield return (key, value, null);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
protected class DeserializedMod : IMod
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Users;
|
||||
@ -116,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString()))
|
||||
ChildrenEnumerable = score.GetStatisticsForDisplay().Select(s => createStatistic(s.result, s.count, s.maxCount))
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@ -198,6 +199,9 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
};
|
||||
}
|
||||
|
||||
private Drawable createStatistic(HitResult result, int count, int? maxCount)
|
||||
=> createStatistic(result.GetDescription(), maxCount == null ? $"{count}" : $"{count}/{maxCount}");
|
||||
|
||||
private Drawable createStatistic(string key, string value) => new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -65,8 +65,9 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
};
|
||||
|
||||
var bottomStatistics = new List<StatisticDisplay>();
|
||||
foreach (var stat in score.SortedStatistics)
|
||||
bottomStatistics.Add(new HitResultStatistic(stat.Key, stat.Value));
|
||||
|
||||
foreach (var (key, value, maxCount) in score.GetStatisticsForDisplay())
|
||||
bottomStatistics.Add(new HitResultStatistic(key, value, maxCount));
|
||||
|
||||
statisticDisplays.AddRange(topStatistics);
|
||||
statisticDisplays.AddRange(bottomStatistics);
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -16,6 +17,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
public class CounterStatistic : StatisticDisplay
|
||||
{
|
||||
private readonly int count;
|
||||
private readonly int? maxCount;
|
||||
|
||||
private RollingCounter<int> counter;
|
||||
|
||||
@ -24,10 +26,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
/// </summary>
|
||||
/// <param name="header">The name of the statistic.</param>
|
||||
/// <param name="count">The value to display.</param>
|
||||
public CounterStatistic(string header, int count)
|
||||
/// <param name="maxCount">The maximum value of <paramref name="count"/>. Not displayed if null.</param>
|
||||
public CounterStatistic(string header, int count, int? maxCount = null)
|
||||
: base(header)
|
||||
{
|
||||
this.count = count;
|
||||
this.maxCount = maxCount;
|
||||
}
|
||||
|
||||
public override void Appear()
|
||||
@ -36,7 +40,33 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
counter.Current.Value = count;
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => counter = new Counter();
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
var container = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Child = counter = new Counter
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
};
|
||||
|
||||
if (maxCount != null)
|
||||
{
|
||||
container.Add(new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Torus.With(size: 12, fixedWidth: true),
|
||||
Spacing = new Vector2(-2, 0),
|
||||
Text = $"/{maxCount}"
|
||||
});
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private class Counter : RollingCounter<int>
|
||||
{
|
||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
{
|
||||
private readonly HitResult result;
|
||||
|
||||
public HitResultStatistic(HitResult result, int count)
|
||||
: base(result.GetDescription(), count)
|
||||
public HitResultStatistic(HitResult result, int count, int? maxCount = null)
|
||||
: base(result.GetDescription(), count, maxCount)
|
||||
{
|
||||
this.result = result;
|
||||
}
|
||||
|
@ -37,6 +37,12 @@ namespace osu.Game.Tests
|
||||
Statistics[HitResult.Meh] = 50;
|
||||
Statistics[HitResult.Good] = 100;
|
||||
Statistics[HitResult.Great] = 300;
|
||||
Statistics[HitResult.SmallTickHit] = 50;
|
||||
Statistics[HitResult.SmallTickMiss] = 25;
|
||||
Statistics[HitResult.LargeTickHit] = 100;
|
||||
Statistics[HitResult.LargeTickMiss] = 50;
|
||||
Statistics[HitResult.SmallBonus] = 10;
|
||||
Statistics[HitResult.SmallBonus] = 50;
|
||||
|
||||
Position = 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user