mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 13:37:25 +08:00
Merge branch 'master' into hold-note-lighting
This commit is contained in:
commit
da34544fdc
@ -326,6 +326,16 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
Height = 250
|
Height = 250
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(score.HitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
if (GetColumnSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false)
|
||||||
|
Column.UnderlayElements.Add(CreateProxy());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
@ -79,7 +79,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
columnFlow = new ColumnFlow<Column>(definition)
|
columnFlow = new ColumnFlow<Column>(definition)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
|
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
@ -193,30 +193,46 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
new StatisticRow
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
||||||
|
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new StatisticRow
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList())
|
Columns = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new StatisticItem("Timing Distribution",
|
||||||
Height = 250
|
new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
}),
|
{
|
||||||
}
|
RelativeSizeAxes = Axes.X,
|
||||||
},
|
Height = 250
|
||||||
new StatisticRow
|
}),
|
||||||
{
|
}
|
||||||
Columns = new[]
|
},
|
||||||
|
new StatisticRow
|
||||||
{
|
{
|
||||||
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
Columns = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
|
||||||
Height = 250
|
{
|
||||||
}),
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(timedHitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,19 +161,34 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
new StatisticRow
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
||||||
|
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new StatisticRow
|
||||||
{
|
{
|
||||||
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList())
|
Columns = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
Height = 250
|
{
|
||||||
}),
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(timedHitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
70
osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Normal file
70
osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Scoring.Legacy;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class LegacyScoreDecoderTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeManiaReplay()
|
||||||
|
{
|
||||||
|
var decoder = new TestLegacyScoreDecoder();
|
||||||
|
|
||||||
|
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||||
|
{
|
||||||
|
var score = decoder.Parse(resourceStream);
|
||||||
|
|
||||||
|
Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID);
|
||||||
|
|
||||||
|
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||||
|
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
||||||
|
|
||||||
|
Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
|
||||||
|
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||||
|
Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
|
||||||
|
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||||
|
|
||||||
|
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLegacyScoreDecoder : LegacyScoreDecoder
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<int, Ruleset> rulesets = new Ruleset[]
|
||||||
|
{
|
||||||
|
new OsuRuleset(),
|
||||||
|
new TaikoRuleset(),
|
||||||
|
new CatchRuleset(),
|
||||||
|
new ManiaRuleset()
|
||||||
|
}.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID);
|
||||||
|
|
||||||
|
protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId];
|
||||||
|
|
||||||
|
protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
MD5Hash = md5Hash,
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
BaseDifficulty = new BeatmapDifficulty()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
osu.Game.Tests/Resources/Replays/mania-replay.osr
Normal file
BIN
osu.Game.Tests/Resources/Replays/mania-replay.osr
Normal file
Binary file not shown.
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Humanizer;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Ranking
|
||||||
|
{
|
||||||
|
public class TestSceneSimpleStatisticTable : OsuTestScene
|
||||||
|
{
|
||||||
|
private Container container;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 700,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4Extensions.FromHex("#333"),
|
||||||
|
},
|
||||||
|
container = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmpty()
|
||||||
|
{
|
||||||
|
AddStep("create with no items",
|
||||||
|
() => container.Add(new SimpleStatisticTable(2, Enumerable.Empty<SimpleStatisticItem>())));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManyItems(
|
||||||
|
[Values(1, 2, 3, 4, 12)] int itemCount,
|
||||||
|
[Values(1, 3, 5)] int columnCount)
|
||||||
|
{
|
||||||
|
AddStep($"create with {"item".ToQuantity(itemCount)}", () =>
|
||||||
|
{
|
||||||
|
var items = Enumerable.Range(1, itemCount)
|
||||||
|
.Select(i => new SimpleStatisticItem<int>($"Statistic #{i}")
|
||||||
|
{
|
||||||
|
Value = RNG.Next(100)
|
||||||
|
});
|
||||||
|
|
||||||
|
container.Add(new SimpleStatisticTable(columnCount, items));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -119,7 +119,7 @@ namespace osu.Game.Online.Chat
|
|||||||
case "http":
|
case "http":
|
||||||
case "https":
|
case "https":
|
||||||
// length > 3 since all these links need another argument to work
|
// length > 3 since all these links need another argument to work
|
||||||
if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh"))
|
if (args.Length > 3 && args[1] == "osu.ppy.sh")
|
||||||
{
|
{
|
||||||
switch (args[2])
|
switch (args[2])
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,6 @@ using osu.Game.Replays.Legacy;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using SharpCompress.Compressors.LZMA;
|
using SharpCompress.Compressors.LZMA;
|
||||||
|
|
||||||
@ -123,12 +122,12 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
|
|
||||||
protected void CalculateAccuracy(ScoreInfo score)
|
protected void CalculateAccuracy(ScoreInfo score)
|
||||||
{
|
{
|
||||||
score.Statistics.TryGetValue(HitResult.Miss, out int countMiss);
|
int countMiss = score.GetCountMiss() ?? 0;
|
||||||
score.Statistics.TryGetValue(HitResult.Meh, out int count50);
|
int count50 = score.GetCount50() ?? 0;
|
||||||
score.Statistics.TryGetValue(HitResult.Good, out int count100);
|
int count100 = score.GetCount100() ?? 0;
|
||||||
score.Statistics.TryGetValue(HitResult.Great, out int count300);
|
int count300 = score.GetCount300() ?? 0;
|
||||||
score.Statistics.TryGetValue(HitResult.Perfect, out int countGeki);
|
int countGeki = score.GetCountGeki() ?? 0;
|
||||||
score.Statistics.TryGetValue(HitResult.Ok, out int countKatu);
|
int countKatu = score.GetCountKatu() ?? 0;
|
||||||
|
|
||||||
switch (score.Ruleset.ID)
|
switch (score.Ruleset.ID)
|
||||||
{
|
{
|
||||||
@ -241,12 +240,15 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
var diff = Parsing.ParseFloat(split[0]);
|
var diff = Parsing.ParseFloat(split[0]);
|
||||||
|
var mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE);
|
||||||
|
var mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE);
|
||||||
|
|
||||||
lastTime += diff;
|
lastTime += diff;
|
||||||
|
|
||||||
if (i == 0 && diff == 0)
|
if (i < 2 && mouseX == 256 && mouseY == -500)
|
||||||
// osu-stable adds a zero-time frame before potentially valid negative user frames.
|
// at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively.
|
||||||
// we need to ignore this.
|
// both frames use a position of (256, -500).
|
||||||
|
// ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Todo: At some point we probably want to rewind and play back the negative-time frames
|
// Todo: At some point we probably want to rewind and play back the negative-time frames
|
||||||
@ -255,8 +257,8 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
currentFrame = convertFrame(new LegacyReplayFrame(lastTime,
|
currentFrame = convertFrame(new LegacyReplayFrame(lastTime,
|
||||||
Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE),
|
mouseX,
|
||||||
Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE),
|
mouseY,
|
||||||
(ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame);
|
(ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame);
|
||||||
|
|
||||||
replay.Frames.Add(currentFrame);
|
replay.Frames.Add(currentFrame);
|
||||||
|
@ -205,6 +205,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
const int line_end_offset = 120;
|
const int line_end_offset = 120;
|
||||||
|
|
||||||
smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint);
|
smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint);
|
||||||
|
smallRing.Delay(400).FadeColour(Color4.Black, 300);
|
||||||
|
|
||||||
lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint);
|
lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint);
|
||||||
lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint);
|
lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint);
|
||||||
|
81
osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs
Normal file
81
osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a simple statistic item (one that only needs textual display).
|
||||||
|
/// Richer visualisations should be done with <see cref="StatisticItem"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SimpleStatisticItem : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The text to display as the statistic's value.
|
||||||
|
/// </summary>
|
||||||
|
protected string Value
|
||||||
|
{
|
||||||
|
set => this.value.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly OsuSpriteText value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new simple statistic item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the statistic.</param>
|
||||||
|
protected SimpleStatisticItem(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
AddRange(new[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = Name,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Font = OsuFont.GetFont(size: 14)
|
||||||
|
},
|
||||||
|
value = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Strongly-typed generic specialisation for <see cref="SimpleStatisticItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class SimpleStatisticItem<TValue> : SimpleStatisticItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The statistic's value to be displayed.
|
||||||
|
/// </summary>
|
||||||
|
public new TValue Value
|
||||||
|
{
|
||||||
|
set => base.Value = DisplayValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to convert <see cref="Value"/> to a text representation.
|
||||||
|
/// Defaults to using <see cref="object.ToString"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual string DisplayValue(TValue value) => value.ToString();
|
||||||
|
|
||||||
|
public SimpleStatisticItem(string name)
|
||||||
|
: base(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs
Normal file
123
osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a table with simple statistics (ones that only need textual display).
|
||||||
|
/// Richer visualisations should be done with <see cref="StatisticRow"/>s and <see cref="StatisticItem"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class SimpleStatisticTable : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly SimpleStatisticItem[] items;
|
||||||
|
private readonly int columnCount;
|
||||||
|
|
||||||
|
private FillFlowContainer[] columns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a statistic row for the supplied <see cref="SimpleStatisticItem"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columnCount">The number of columns to layout the <paramref name="items"/> into.</param>
|
||||||
|
/// <param name="items">The <see cref="SimpleStatisticItem"/>s to display in this row.</param>
|
||||||
|
public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable<SimpleStatisticItem> items)
|
||||||
|
{
|
||||||
|
if (columnCount < 1)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(columnCount));
|
||||||
|
|
||||||
|
this.columnCount = columnCount;
|
||||||
|
this.items = items.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
columns = new FillFlowContainer[columnCount];
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize)
|
||||||
|
},
|
||||||
|
ColumnDimensions = createColumnDimensions().ToArray(),
|
||||||
|
Content = new[] { createColumns().ToArray() }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Length; ++i)
|
||||||
|
columns[i % columnCount].Add(items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Dimension> createColumnDimensions()
|
||||||
|
{
|
||||||
|
for (int column = 0; column < columnCount; ++column)
|
||||||
|
{
|
||||||
|
if (column > 0)
|
||||||
|
yield return new Dimension(GridSizeMode.Absolute, 30);
|
||||||
|
|
||||||
|
yield return new Dimension();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Drawable> createColumns()
|
||||||
|
{
|
||||||
|
for (int column = 0; column < columnCount; ++column)
|
||||||
|
{
|
||||||
|
if (column > 0)
|
||||||
|
{
|
||||||
|
yield return new Spacer
|
||||||
|
{
|
||||||
|
Alpha = items.Length > column ? 1 : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return columns[column] = createColumn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FillFlowContainer createColumn() => new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical
|
||||||
|
};
|
||||||
|
|
||||||
|
private class Spacer : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Spacer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Padding = new MarginPadding { Vertical = 4 };
|
||||||
|
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 3,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
CornerRadius = 2,
|
||||||
|
Masking = true,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4Extensions.FromHex("#222")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,33 +32,9 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
createHeader(item)
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(5, 0),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Circle
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Height = 9,
|
|
||||||
Width = 4,
|
|
||||||
Colour = Color4Extensions.FromHex("#00FFAA")
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Text = item.Name,
|
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
@ -78,5 +54,37 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Drawable createHeader(StatisticItem item)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(item.Name))
|
||||||
|
return Empty();
|
||||||
|
|
||||||
|
return new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Height = 9,
|
||||||
|
Width = 4,
|
||||||
|
Colour = Color4Extensions.FromHex("#00FFAA")
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Text = item.Name,
|
||||||
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="StatisticItem"/>, to be displayed inside a <see cref="StatisticRow"/> in the results screen.
|
/// Creates a new <see cref="StatisticItem"/>, to be displayed inside a <see cref="StatisticRow"/> in the results screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the item.</param>
|
/// <param name="name">The name of the item. Can be <see cref="string.Empty"/> to hide the item header.</param>
|
||||||
/// <param name="content">The <see cref="Drawable"/> content to be displayed.</param>
|
/// <param name="content">The <see cref="Drawable"/> content to be displayed.</param>
|
||||||
/// <param name="dimension">The <see cref="Dimension"/> of this item. This can be thought of as the column dimension of an encompassing <see cref="GridContainer"/>.</param>
|
/// <param name="dimension">The <see cref="Dimension"/> of this item. This can be thought of as the column dimension of an encompassing <see cref="GridContainer"/>.</param>
|
||||||
public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
|
public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
|
||||||
|
@ -94,14 +94,15 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(30, 15),
|
Spacing = new Vector2(30, 15),
|
||||||
|
Alpha = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
|
foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
|
||||||
{
|
{
|
||||||
rows.Add(new GridContainer
|
rows.Add(new GridContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
@ -125,6 +126,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
|
|
||||||
spinner.Hide();
|
spinner.Hide();
|
||||||
content.Add(d);
|
content.Add(d);
|
||||||
|
d.FadeIn(250, Easing.OutQuint);
|
||||||
}, localCancellationSource.Token);
|
}, localCancellationSource.Token);
|
||||||
}), localCancellationSource.Token);
|
}), localCancellationSource.Token);
|
||||||
}
|
}
|
||||||
|
39
osu.Game/Screens/Ranking/Statistics/UnstableRate.cs
Normal file
39
osu.Game/Screens/Ranking/Statistics/UnstableRate.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Statistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays the unstable rate statistic for a given play.
|
||||||
|
/// </summary>
|
||||||
|
public class UnstableRate : SimpleStatisticItem<double>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates and computes an <see cref="UnstableRate"/> statistic.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitEvents">Sequence of <see cref="HitEvent"/>s to calculate the unstable rate based on.</param>
|
||||||
|
public UnstableRate(IEnumerable<HitEvent> hitEvents)
|
||||||
|
: base("Unstable Rate")
|
||||||
|
{
|
||||||
|
var timeOffsets = hitEvents.Select(ev => ev.TimeOffset).ToArray();
|
||||||
|
Value = 10 * standardDeviation(timeOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double standardDeviation(double[] timeOffsets)
|
||||||
|
{
|
||||||
|
if (timeOffsets.Length == 0)
|
||||||
|
return double.NaN;
|
||||||
|
|
||||||
|
var mean = timeOffsets.Average();
|
||||||
|
var squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
|
||||||
|
return Math.Sqrt(squares / timeOffsets.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2");
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@ namespace osu.Game.Skinning
|
|||||||
public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR;
|
public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR;
|
||||||
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
|
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
|
||||||
public bool ShowJudgementLine = true;
|
public bool ShowJudgementLine = true;
|
||||||
|
public bool KeysUnderNotes;
|
||||||
|
|
||||||
public LegacyManiaSkinConfiguration(int keys)
|
public LegacyManiaSkinConfiguration(int keys)
|
||||||
{
|
{
|
||||||
|
@ -52,5 +52,6 @@ namespace osu.Game.Skinning
|
|||||||
Hit100,
|
Hit100,
|
||||||
Hit50,
|
Hit50,
|
||||||
Hit0,
|
Hit0,
|
||||||
|
KeysUnderNotes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,10 @@ namespace osu.Game.Skinning
|
|||||||
currentConfig.ShowJudgementLine = pair.Value == "1";
|
currentConfig.ShowJudgementLine = pair.Value == "1";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "KeysUnderNotes":
|
||||||
|
currentConfig.KeysUnderNotes = pair.Value == "1";
|
||||||
|
break;
|
||||||
|
|
||||||
case "LightingNWidth":
|
case "LightingNWidth":
|
||||||
parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
|
parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
|
||||||
break;
|
break;
|
||||||
|
@ -272,6 +272,9 @@ namespace osu.Game.Skinning
|
|||||||
case LegacyManiaSkinConfigurationLookups.Hit300:
|
case LegacyManiaSkinConfigurationLookups.Hit300:
|
||||||
case LegacyManiaSkinConfigurationLookups.Hit300g:
|
case LegacyManiaSkinConfigurationLookups.Hit300g:
|
||||||
return SkinUtils.As<TValue>(getManiaImage(existing, maniaLookup.Lookup.ToString()));
|
return SkinUtils.As<TValue>(getManiaImage(existing, maniaLookup.Lookup.ToString()));
|
||||||
|
|
||||||
|
case LegacyManiaSkinConfigurationLookups.KeysUnderNotes:
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<bool>(existing.KeysUnderNotes));
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
Loading…
Reference in New Issue
Block a user