From 7a70d063428890ee92e843001b31c08433ebe3fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Aug 2020 15:35:37 +0900 Subject: [PATCH 1/4] Add support for custom LightingN paths --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index aebc229f7c..1e6102eaa4 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -116,6 +116,7 @@ namespace osu.Game.Skinning case string _ when pair.Key.StartsWith("KeyImage"): case string _ when pair.Key.StartsWith("Hit"): case string _ when pair.Key.StartsWith("Stage"): + case string _ when pair.Key.StartsWith("Lighting"): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 02d07eee45..10fb476728 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -173,6 +173,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.ShowJudgementLine: return SkinUtils.As(new Bindable(existing.ShowJudgementLine)); + case LegacyManiaSkinConfigurationLookups.ExplosionImage: + return SkinUtils.As(getManiaImage(existing, "LightingN")); + case LegacyManiaSkinConfigurationLookups.ExplosionScale: Debug.Assert(maniaLookup.TargetColumn != null); From 6c7475f085f1a06b238226d71f27e528b222fbf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Aug 2020 18:56:15 +0900 Subject: [PATCH 2/4] Fix snapped distances potentially exceeding the source distance This results in slider placement including "excess" length, where the curve is not applied to the placed path. This is generally not what we want. I considered adding a bool parameter (or enum) to change the floor/rounding mode, but on further examination I think this is what we always expect from this function. --- .../TestSceneHitObjectComposerDistanceSnapping.cs | 14 +++++++------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 +++++++- osu.Game/Rulesets/Edit/IPositionSnapProvider.cs | 1 + 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 168ec0f09d..bd34eaff63 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -169,17 +169,17 @@ namespace osu.Game.Tests.Editing [Test] public void GetSnappedDistanceFromDistance() { - assertSnappedDistance(50, 100); + assertSnappedDistance(50, 0); assertSnappedDistance(100, 100); - assertSnappedDistance(150, 200); + assertSnappedDistance(150, 100); assertSnappedDistance(200, 200); - assertSnappedDistance(250, 300); + assertSnappedDistance(250, 200); AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); assertSnappedDistance(50, 0); - assertSnappedDistance(100, 200); - assertSnappedDistance(150, 200); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); @@ -190,8 +190,8 @@ namespace osu.Game.Tests.Editing }); assertSnappedDistance(50, 0); - assertSnappedDistance(100, 200); - assertSnappedDistance(150, 200); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); assertSnappedDistance(400, 400); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c25fb03fd0..d0b06ce0ad 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -293,7 +293,13 @@ namespace osu.Game.Rulesets.Edit public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) { - var snappedEndTime = BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime); + double actualDuration = referenceTime + DistanceToDuration(referenceTime, distance); + + double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); + + // we don't want to exceed the actual duration and snap to a point in the future. + if (snappedEndTime > actualDuration) + snappedEndTime -= BeatSnapProvider.GetBeatLengthAtTime(referenceTime); return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index c854c06031..cce631464f 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -47,6 +47,7 @@ namespace osu.Game.Rulesets.Edit /// /// Converts an unsnapped distance to a snapped distance. + /// The returned distance will always be floored (as to never exceed the provided . /// /// The time of the timing point which resides in. /// The distance to convert. From 127330b8f9bb7ff7a9d03ad3db5044c002404f37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Aug 2020 20:57:31 +0900 Subject: [PATCH 3/4] Add 1ms lenience to avoid potential precision issues --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d0b06ce0ad..f134db1ffe 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -25,7 +25,7 @@ using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -using Key = osuTK.Input.Key; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -297,9 +297,12 @@ namespace osu.Game.Rulesets.Edit double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); + // we don't want to exceed the actual duration and snap to a point in the future. - if (snappedEndTime > actualDuration) - snappedEndTime -= BeatSnapProvider.GetBeatLengthAtTime(referenceTime); + // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. + if (snappedEndTime > actualDuration + 1) + snappedEndTime -= beatLength; return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } From f09f882cc77f9264ad1292bc7df4c55ca3b548b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 25 Aug 2020 22:43:23 +0200 Subject: [PATCH 4/4] Add component for displaying simple statistics on result screen --- .../Ranking/TestSceneSimpleStatisticRow.cs | 68 ++++++++++ .../Ranking/Statistics/SimpleStatisticItem.cs | 80 ++++++++++++ .../Ranking/Statistics/SimpleStatisticRow.cs | 122 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs new file mode 100644 index 0000000000..aa569e47b1 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . 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 TestSceneSimpleStatisticRow : 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 SimpleStatisticRow(2, Enumerable.Empty()))); + } + + [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($"Statistic #{i}") + { + Value = RNG.Next(100) + }); + + container.Add(new SimpleStatisticRow(columnCount, items)); + }); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs new file mode 100644 index 0000000000..e6c4ab1c4e --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . 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 +{ + /// + /// Represents a simple statistic item (one that only needs textual display). + /// Richer visualisations should be done with s. + /// + public abstract class SimpleStatisticItem : Container + { + /// + /// The text to display as the statistic's value. + /// + protected string Value + { + set => this.value.Text = value; + } + + private readonly OsuSpriteText value; + + /// + /// Creates a new simple statistic item. + /// + /// The name of the statistic. + protected SimpleStatisticItem(string name) + { + Name = name; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddRange(new[] + { + new OsuSpriteText + { + Text = Name, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + value = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.Torus.With(weight: FontWeight.Bold) + } + }); + } + } + + /// + /// Strongly-typed generic specialisation for . + /// + public class SimpleStatisticItem : SimpleStatisticItem + { + /// + /// The statistic's value to be displayed. + /// + public new TValue Value + { + set => base.Value = DisplayValue(value); + } + + /// + /// Used to convert to a text representation. + /// Defaults to using . + /// + protected virtual string DisplayValue(TValue value) => value.ToString(); + + public SimpleStatisticItem(string name) + : base(name) + { + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs new file mode 100644 index 0000000000..16501aae54 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs @@ -0,0 +1,122 @@ +// Copyright (c) ppy Pty Ltd . 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.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 +{ + /// + /// Represents a statistic row with simple statistics (ones that only need textual display). + /// Richer visualisations should be done with s and s. + /// + public class SimpleStatisticRow : CompositeDrawable + { + private readonly SimpleStatisticItem[] items; + private readonly int columnCount; + + private FillFlowContainer[] columns; + + /// + /// Creates a statistic row for the supplied s. + /// + /// The number of columns to layout the into. + /// The s to display in this row. + public SimpleStatisticRow(int columnCount, IEnumerable 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 createColumnDimensions() + { + for (int column = 0; column < columnCount; ++column) + { + if (column > 0) + yield return new Dimension(GridSizeMode.Absolute, 30); + + yield return new Dimension(); + } + } + + private IEnumerable 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") + } + }; + } + } + } +}