diff --git a/osu-framework b/osu-framework index b64322a56d..06f9c47ef5 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit b64322a56d3d55e8e5d1e6c3328024923cecd4d3 +Subproject commit 06f9c47ef5d007b39faf8169170d16ece672b981 diff --git a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs index fe270bb82e..c5f7c81585 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs @@ -158,7 +158,7 @@ namespace osu.Desktop.VisualTests.Tests AddButton(@"Alter stars", delegate { - stars.Count = RNG.NextSingle() * (stars.MaxStars + 1); + stars.Count = RNG.NextSingle() * (stars.StarCount + 1); starsLabel.Text = stars.Count.ToString("0.00"); }); diff --git a/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs b/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs index 47fe1774bd..ccc4097d59 100644 --- a/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs +++ b/osu.Game.Modes.Catch/CatchDifficultyCalculator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Modes.Catch protected override HitObjectConverter Converter => new CatchConverter(); - protected override double ComputeDifficulty(Dictionary categoryDifficulty) + protected override double CalculateInternal(Dictionary categoryDifficulty) { return 0; } diff --git a/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs index 5075c44db6..975b78c215 100644 --- a/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Modes.Mania/ManiaDifficultyCalculator.cs @@ -22,7 +22,7 @@ namespace osu.Game.Modes.Mania protected override HitObjectConverter Converter => new ManiaConverter(columns); - protected override double ComputeDifficulty(Dictionary categoryDifficulty) + protected override double CalculateInternal(Dictionary categoryDifficulty) { return 0; } diff --git a/osu.Game.Modes.Osu/Objects/SliderCurve.cs b/osu.Game.Modes.Osu/Objects/SliderCurve.cs index e60e58da9a..e582cca580 100644 --- a/osu.Game.Modes.Osu/Objects/SliderCurve.cs +++ b/osu.Game.Modes.Osu/Objects/SliderCurve.cs @@ -29,26 +29,21 @@ namespace osu.Game.Modes.Osu.Objects case CurveTypes.Linear: return subControlPoints; case CurveTypes.PerfectCurve: - // If we have a different amount than 3 control points, use bezier for perfect curves. - if (ControlPoints.Count != 3) - return new BezierApproximator(subControlPoints).CreateBezier(); - else - { - Debug.Assert(subControlPoints.Count == 3); + //we can only use CircularArc iff we have exactly three control points and no dissection. + if (ControlPoints.Count != 3 || subControlPoints.Count != 3) + break; - // Here we have exactly 3 control points. Attempt to fit a circular arc. - List subpath = new CircularArcApproximator(subControlPoints[0], subControlPoints[1], subControlPoints[2]).CreateArc(); + // Here we have exactly 3 control points. Attempt to fit a circular arc. + List subpath = new CircularArcApproximator(subControlPoints[0], subControlPoints[1], subControlPoints[2]).CreateArc(); - if (subpath.Count == 0) - // For some reason a circular arc could not be fit to the 3 given points. Fall back - // to a numerically stable bezier approximation. - subpath = new BezierApproximator(subControlPoints).CreateBezier(); + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) + break; - return subpath; - } - default: - return new BezierApproximator(subControlPoints).CreateBezier(); + return subpath; } + + return new BezierApproximator(subControlPoints).CreateBezier(); } private void calculatePath() @@ -181,7 +176,7 @@ namespace osu.Game.Modes.Osu.Objects path.Clear(); int i = 0; - for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i); + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) ; path.Add(interpolateVertices(i, d0) + Offset); diff --git a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs b/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs index 24648b1726..9e2a311021 100644 --- a/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs +++ b/osu.Game.Modes.Osu/OsuDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Modes.Osu ((Slider)h).Curve.Calculate(); } - protected override double ComputeDifficulty(Dictionary categoryDifficulty) + protected override double CalculateInternal(Dictionary categoryDifficulty) { // Fill our custom DifficultyHitObject class, that carries additional information DifficultyHitObjects.Clear(); diff --git a/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs index 69b86a86af..5067cef2b3 100644 --- a/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs +++ b/osu.Game.Modes.Taiko/TaikoDifficultyCalculator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Modes.Taiko protected override HitObjectConverter Converter => new TaikoConverter(); - protected override double ComputeDifficulty(Dictionary categoryDifficulty) + protected override double CalculateInternal(Dictionary categoryDifficulty) { return 0; } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index a55e9fa80d..d92e340e72 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -7,6 +7,7 @@ using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Modes.Objects; +using osu.Game.Modes; namespace osu.Game.Beatmaps { @@ -57,5 +58,7 @@ namespace osu.Game.Beatmaps return timingPoint ?? ControlPoint.Default; } + + public double CalculateStarDifficulty() => Ruleset.GetRuleset(BeatmapInfo.Mode).CreateDifficultyCalculator(this).Calculate(); } } diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 14b6ce3e96..8214496363 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps protected double TimeRate = 1; - protected abstract double ComputeDifficulty(Dictionary categoryDifficulty); + protected abstract double CalculateInternal(Dictionary categoryDifficulty); private void loadTiming() { @@ -23,10 +23,10 @@ namespace osu.Game.Beatmaps TimeRate = audioRate / 100.0; } - public double GetDifficulty(Dictionary categoryDifficulty = null) + public double Calculate(Dictionary categoryDifficulty = null) { loadTiming(); - double difficulty = ComputeDifficulty(categoryDifficulty); + double difficulty = CalculateInternal(categoryDifficulty); return difficulty; } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 192a383fdf..36c042ac30 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -59,16 +59,21 @@ namespace osu.Game.Beatmaps.Drawables } } - public BeatmapGroup(WorkingBeatmap beatmap) + public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapDatabase database) { + BeatmapSet = beatmapSet; + WorkingBeatmap beatmap = database.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault()); + foreach (var b in BeatmapSet.Beatmaps) + b.StarDifficulty = (float)database.GetWorkingBeatmap(b).Beatmap.CalculateStarDifficulty(); + Header = new BeatmapSetHeader(beatmap) { GainedSelection = headerGainedSelection, RelativeSizeAxes = Axes.X, }; - - BeatmapSet = beatmap.BeatmapSetInfo; - BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b) + + BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList(); + BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b) { Alpha = 0, GainedSelection = panelGainedSelection, diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index a6ad0a6457..214712af72 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -26,6 +26,7 @@ namespace osu.Game.Beatmaps.Drawables public Action GainedSelection; public Action StartRequested; private Triangles triangles; + private StarCounter starCounter; protected override void Selected() { @@ -56,6 +57,14 @@ namespace osu.Game.Beatmaps.Drawables return base.OnClick(state); } + protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) + { + base.ApplyState(last); + + if (last == PanelSelectedState.Hidden && State != last) + starCounter.ReplayAnimation(); + } + public BeatmapPanel(BeatmapInfo beatmap) { Beatmap = beatmap; @@ -92,7 +101,6 @@ namespace osu.Game.Beatmaps.Drawables new FlowContainer { Padding = new MarginPadding { Left = 5 }, - Spacing = new Vector2(0, 5), Direction = FlowDirections.Vertical, AutoSizeAxes = Axes.Both, Children = new Drawable[] @@ -130,7 +138,11 @@ namespace osu.Game.Beatmaps.Drawables }, } }, - new StarCounter { Count = beatmap.StarDifficulty, StarSize = 8 } + starCounter = new StarCounter + { + Count = beatmap.StarDifficulty, + Scale = new Vector2(0.8f), + } } } } diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Beatmaps/Drawables/Panel.cs index 2e5f5f248b..ec3c2a291f 100644 --- a/osu.Game/Beatmaps/Drawables/Panel.cs +++ b/osu.Game/Beatmaps/Drawables/Panel.cs @@ -50,10 +50,10 @@ namespace osu.Game.Beatmaps.Drawables protected override void LoadComplete() { base.LoadComplete(); - applyState(); + ApplyState(); } - private void applyState() + protected virtual void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) { switch (state) { @@ -81,9 +81,10 @@ namespace osu.Game.Beatmaps.Drawables set { if (state == value) return; - state = value; - applyState(); + var last = state; + state = value; + ApplyState(last); } } diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index f962146717..66c348279d 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -119,10 +119,6 @@ namespace osu.Game.Database using (var reader = ArchiveReader.GetReader(storage, path)) metadata = reader.ReadMetadata(); - if (metadata.OnlineBeatmapSetID.HasValue && - connection.Table().Count(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) != 0) - return; // TODO: Update this beatmap instead - if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader { using (var md5 = MD5.Create()) @@ -131,10 +127,26 @@ namespace osu.Game.Database hash = BitConverter.ToString(md5.ComputeHash(input)).Replace("-", "").ToLowerInvariant(); input.Seek(0, SeekOrigin.Begin); path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); - using (var output = storage.GetStream(path, FileAccess.Write)) - input.CopyTo(output); + if (!storage.Exists(path)) + using (var output = storage.GetStream(path, FileAccess.Write)) + input.CopyTo(output); } } + + var existing = connection.Table().FirstOrDefault(b => b.Hash == hash); + + if (existing != null) + { + if (existing.DeletePending) + { + existing.DeletePending = false; + Update(existing, false); + BeatmapSetAdded?.Invoke(existing); + } + + return; + } + var beatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index 9622b73108..c97b6653e0 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -73,7 +73,6 @@ namespace osu.Game.Database // Metadata public string Version { get; set; } - //todo: background threaded computation of this private float starDifficulty = -1; public float StarDifficulty { @@ -85,11 +84,6 @@ namespace osu.Game.Database set { starDifficulty = value; } } - internal void ComputeDifficulty(BeatmapDatabase database) - { - StarDifficulty = (float)Ruleset.GetRuleset(Mode).CreateDifficultyCalculator(database.GetWorkingBeatmap(this).Beatmap).GetDifficulty(); - } - public bool Equals(BeatmapInfo other) { return ID == other?.ID; diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 442b3a351f..3c2e9c5cbc 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -7,16 +7,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transformations; using osu.Framework.MathUtils; using System; -using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { public class StarCounter : Container { - private readonly Container starContainer; - private readonly List stars = new List(); + private readonly Container stars; - private double transformStartTime = 0; + private double transformStartTime; /// /// Maximum amount of stars displayed. @@ -24,37 +22,20 @@ namespace osu.Game.Graphics.UserInterface /// /// This does not limit the counter value, but the amount of stars displayed. /// - public int MaxStars - { - get; - protected set; - } + public int StarCount { get; } private double animationDelay => 80; - private double scalingDuration => 500; + private double scalingDuration => 1000; private EasingTypes scalingEasing => EasingTypes.OutElasticHalf; - private float minStarScale => 0.3f; + private float minStarScale => 0.4f; private double fadingDuration => 100; private float minStarAlpha => 0.5f; - public float StarSize = 20; - public float StarSpacing = 4; + private const float star_size = 20; + private float star_spacing = 4; - public float VisibleValue - { - get - { - double elapsedTime = Time.Current - transformStartTime; - double expectedElapsedTime = Math.Abs(prevCount - count) * animationDelay; - if (elapsedTime >= expectedElapsedTime) - return count; - return Interpolation.ValueAt(elapsedTime, prevCount, count, 0, expectedElapsedTime); - } - } - - private float prevCount; private float count; /// @@ -69,119 +50,121 @@ namespace osu.Game.Graphics.UserInterface set { - if (IsLoaded) - { - prevCount = VisibleValue; - transformCount(prevCount, value); - } + if (count == value) return; + if (IsLoaded) + transformCount(value); count = value; } } - /// - /// Shows a float count as stars (up to 10). Used as star difficulty display. - /// - public StarCounter() : this(10) - { - AutoSizeAxes = Axes.Both; - } - /// /// Shows a float count as stars. Used as star difficulty display. /// - /// Maximum amount of stars to display. - public StarCounter(int maxstars) + /// Maximum amount of stars to display. + public StarCounter(int starCount = 10) { - MaxStars = Math.Max(maxstars, 0); + StarCount = Math.Max(starCount, 0); + + AutoSizeAxes = Axes.Both; Children = new Drawable[] { - starContainer = new Container + stars = new FlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FlowDirections.Horizontal, + Spacing = new Vector2(star_spacing), } }; - starContainer.Width = MaxStars * StarSize + Math.Max(MaxStars - 1, 0) * StarSpacing; - starContainer.Height = StarSize; - - for (int i = 0; i < MaxStars; i++) + for (int i = 0; i < StarCount; i++) { - TextAwesome star = new TextAwesome + stars.Add(new Star { - Icon = FontAwesome.fa_star, - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - TextSize = StarSize, - Scale = new Vector2(minStarScale), Alpha = minStarAlpha, - Position = new Vector2((StarSize + StarSpacing) * i + (StarSize + StarSpacing) / 2, 0), - }; - - //todo: user Container once we have it. - stars.Add(star); - starContainer.Add(star); + }); } } protected override void LoadComplete() { base.LoadComplete(); + // Animate initial state from zero. - transformCount(0, Count); + ReplayAnimation(); } public void ResetCount() { - Count = 0; + count = 0; StopAnimation(); } + public void ReplayAnimation() + { + var t = count; + ResetCount(); + Count = t; + } + public void StopAnimation() { - prevCount = count; - transformStartTime = Time.Current; - - for (int i = 0; i < MaxStars; i++) - transformStarQuick(i, count); + int i = 0; + foreach (var star in stars.Children) + { + star.ClearTransformations(true); + star.FadeTo(i < count ? 1.0f : minStarAlpha); + star.Icon.ScaleTo(getStarScale(i, count)); + i++; + } } private float getStarScale(int i, float value) { if (value <= i) return minStarScale; - if (i + 1 <= value) - return 1.0f; - return Interpolation.ValueAt(value, minStarScale, 1.0f, i, i + 1); + + return i + 1 <= value ? 1.0f : Interpolation.ValueAt(value, minStarScale, 1.0f, i, i + 1); } - private void transformStar(int i, float value) + private void transformCount(float newValue) { - stars[i].FadeTo(i < value ? 1.0f : minStarAlpha, fadingDuration); - stars[i].ScaleTo(getStarScale(i, value), scalingDuration, scalingEasing); - } - - private void transformStarQuick(int i, float value) - { - stars[i].FadeTo(i < value ? 1.0f : minStarAlpha); - stars[i].ScaleTo(getStarScale(i, value)); - } - - private void transformCount(float currentValue, float newValue) - { - for (int i = 0; i < MaxStars; i++) + int i = 0; + foreach (var star in stars.Children) { - stars[i].ClearTransformations(); - if (currentValue <= newValue) - stars[i].Delay(Math.Max(i - currentValue, 0) * animationDelay); + star.ClearTransformations(true); + if (count <= newValue) + star.Delay(Math.Max(i - count, 0) * animationDelay, true); else - stars[i].Delay(Math.Max(currentValue - 1 - i, 0) * animationDelay); - transformStar(i, newValue); - stars[i].DelayReset(); + star.Delay(Math.Max(count - 1 - i, 0) * animationDelay, true); + + star.FadeTo(i < newValue ? 1.0f : minStarAlpha, fadingDuration); + star.Icon.ScaleTo(getStarScale(i, newValue), scalingDuration, scalingEasing); + star.DelayReset(); + + i++; + } + } + + class Star : Container + { + public TextAwesome Icon; + public Star() + { + Size = new Vector2(star_size); + + Children = new[] + { + Icon = new TextAwesome + { + TextSize = star_size, + Icon = FontAwesome.fa_star, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; } - transformStartTime = Time.Current; } } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 3911b26b00..ff8bf24ec6 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -325,11 +325,7 @@ namespace osu.Game.Screens.Select if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; }); - foreach (var b in beatmapSet.Beatmaps) - b.ComputeDifficulty(database); - beatmapSet.Beatmaps = beatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList(); - - var group = new BeatmapGroup(database.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + var group = new BeatmapGroup(beatmapSet, database) { SelectionChanged = selectionChanged, StartRequested = b => footer.StartButton.TriggerClick()