From e71a9668a537b2616b71b8f192c37671e2447553 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:32:55 +0300 Subject: [PATCH 001/123] Disallow draining in non-draining sections --- .../Scoring/DrainingHealthProcessor.cs | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index fffcbb3c9f..b36e42326c 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -47,6 +49,34 @@ namespace osu.Game.Rulesets.Scoring private double targetMinimumHealth; private double drainRate = 1; + private readonly List<(double startTime, double endTime)> nonDrainSections = new List<(double, double)>(); + private int currentNonDrainSection; + + private bool isInNonDrainSection + { + get + { + if (nonDrainSections.Count == 0) + return false; + + var time = Time.Current; + + if (time > nonDrainSections[currentNonDrainSection].endTime) + { + while (time > nonDrainSections[currentNonDrainSection].endTime && currentNonDrainSection < nonDrainSections.Count - 1) + currentNonDrainSection++; + } + else + { + while (time < nonDrainSections[currentNonDrainSection].startTime && currentNonDrainSection > 0) + currentNonDrainSection--; + } + + var closestSection = nonDrainSections[currentNonDrainSection]; + return time >= closestSection.startTime && time <= closestSection.endTime; + } + } + /// /// Creates a new . /// @@ -60,23 +90,36 @@ namespace osu.Game.Rulesets.Scoring { base.Update(); - if (!IsBreakTime.Value) - { - // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time - double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime); - double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); + if (isInNonDrainSection) + return; - Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); - } + // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time + double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime); + double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); + + Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); } public override void ApplyBeatmap(IBeatmap beatmap) { + nonDrainSections.Clear(); + this.beatmap = beatmap; if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); + // Ranges between the end of last hit object before a break + // and the start of first hit object after a break should + // not allow HP draining. (with break periods in) + foreach (BreakPeriod b in beatmap.Breaks) + { + var startTime = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < b.StartTime)?.GetEndTime() ?? double.MinValue; + var endTime = beatmap.HitObjects.FirstOrDefault(h => h.StartTime > b.EndTime)?.StartTime ?? double.MaxValue; + + nonDrainSections.Add((startTime, endTime)); + } + targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); base.ApplyBeatmap(beatmap); From 7fab07670ed8ff6725bc4b8c1c543e0101af9664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:35:09 +0300 Subject: [PATCH 002/123] Remove no longer necessary usage of IsBreakTime --- osu.Game/Rulesets/Scoring/HealthProcessor.cs | 5 ----- osu.Game/Screens/Play/Player.cs | 2 -- 2 files changed, 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 45edc0f4a3..1535fe4d00 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -26,11 +26,6 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; - /// - /// Whether gameplay is currently in a break. - /// - public readonly IBindable IsBreakTime = new Bindable(); - /// /// Whether this ScoreProcessor has already triggered the failed state. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4597ae760c..f2051790db 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -243,8 +243,6 @@ namespace osu.Game.Screens.Play Breaks = working.Beatmap.Breaks } }); - - HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime); } private void addOverlayComponents(Container target, WorkingBeatmap working) From c902ba40860b2757ea960925322bf9731b4eeefc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:46:07 +0300 Subject: [PATCH 003/123] Add test cases for HP draining not applied before a break and after it --- .../TestSceneDrainingHealthProcessor.cs | 87 ++++++++++++++----- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 885abb61b5..2f83ea4832 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -1,13 +1,15 @@ // Copyright (c) ppy Pty Ltd . 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -18,7 +20,6 @@ namespace osu.Game.Tests.Gameplay [HeadlessTest] public class TestSceneDrainingHealthProcessor : OsuTestScene { - private Bindable breakTime; private HealthProcessor processor; private ManualClock clock; @@ -41,6 +42,55 @@ namespace osu.Game.Tests.Gameplay assertHealthEqualTo(1); } + [Test] + public void TestHealthNotDrainedBeforeBreak() + { + createProcessor(createBeatmap(0, 2000, + new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); + + setTime(300); + setHealth(1); + + setTime(400); + assertHealthEqualTo(1); + + setTime(1100); + setHealth(1); + + setTime(1200); + assertHealthEqualTo(1); + } + + [Test] + public void TestHealthNotDrainedDuringBreak() + { + createProcessor(createBeatmap(0, 2000, new BreakPeriod(0, 1200))); + + setTime(700); + assertHealthEqualTo(1); + setTime(900); + assertHealthEqualTo(1); + } + + [Test] + public void TestHealthNotDrainedAfterBreak() + { + createProcessor(createBeatmap(0, 2000, + new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); + + setTime(600); + setHealth(1); + + setTime(700); + assertHealthEqualTo(1); + + setTime(1400); + setHealth(1); + + setTime(1500); + assertHealthEqualTo(1); + } + [Test] public void TestHealthNotDrainedAfterGameplayEnd() { @@ -54,18 +104,6 @@ namespace osu.Game.Tests.Gameplay assertHealthEqualTo(1); } - [Test] - public void TestHealthNotDrainedDuringBreak() - { - createProcessor(createBeatmap(0, 2000)); - setBreak(true); - - setTime(700); - assertHealthEqualTo(1); - setTime(900); - assertHealthEqualTo(1); - } - [Test] public void TestHealthDrainedDuringGameplay() { @@ -112,30 +150,39 @@ namespace osu.Game.Tests.Gameplay assertHealthNotEqualTo(1); } - private Beatmap createBeatmap(double startTime, double endTime) + private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks) { var beatmap = new Beatmap { BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } }, }; - for (double time = startTime; time <= endTime; time += 100) + double time = startTime; + + while (time <= endTime) + { beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time }); + // leave a 100ms gap between the start and end of a break period. + time += (getCurrentBreak(breaks, time)?.Duration ?? 0) + 100; + } + + beatmap.Breaks.AddRange(breaks); + + static BreakPeriod getCurrentBreak(IEnumerable breaks, double time) => + breaks?.FirstOrDefault(b => time >= b.StartTime && time <= b.EndTime); + return beatmap; } private void createProcessor(Beatmap beatmap) => AddStep("create processor", () => { - breakTime = new Bindable(); - Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d => { d.RelativeSizeAxes = Axes.Both; d.Clock = new FramedClock(clock = new ManualClock()); }); - processor.IsBreakTime.BindTo(breakTime); processor.ApplyBeatmap(beatmap); }); @@ -143,8 +190,6 @@ namespace osu.Game.Tests.Gameplay private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health); - private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled); - private void assertHealthEqualTo(double value) => AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f)); From 1d999bb634de168c5acb1f3abbc5fd12d5596e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 10 May 2020 18:32:38 +0200 Subject: [PATCH 004/123] Integrate PeriodTracker changes --- .../Scoring/DrainingHealthProcessor.cs | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index b36e42326c..1958efdd6f 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -5,9 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Utils; namespace osu.Game.Rulesets.Scoring { @@ -49,33 +49,7 @@ namespace osu.Game.Rulesets.Scoring private double targetMinimumHealth; private double drainRate = 1; - private readonly List<(double startTime, double endTime)> nonDrainSections = new List<(double, double)>(); - private int currentNonDrainSection; - - private bool isInNonDrainSection - { - get - { - if (nonDrainSections.Count == 0) - return false; - - var time = Time.Current; - - if (time > nonDrainSections[currentNonDrainSection].endTime) - { - while (time > nonDrainSections[currentNonDrainSection].endTime && currentNonDrainSection < nonDrainSections.Count - 1) - currentNonDrainSection++; - } - else - { - while (time < nonDrainSections[currentNonDrainSection].startTime && currentNonDrainSection > 0) - currentNonDrainSection--; - } - - var closestSection = nonDrainSections[currentNonDrainSection]; - return time >= closestSection.startTime && time <= closestSection.endTime; - } - } + private PeriodTracker noDrainPeriodTracker; /// /// Creates a new . @@ -90,7 +64,7 @@ namespace osu.Game.Rulesets.Scoring { base.Update(); - if (isInNonDrainSection) + if (noDrainPeriodTracker?.IsInAny(Time.Current) == true) return; // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time @@ -102,23 +76,23 @@ namespace osu.Game.Rulesets.Scoring public override void ApplyBeatmap(IBeatmap beatmap) { - nonDrainSections.Clear(); - this.beatmap = beatmap; if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); - // Ranges between the end of last hit object before a break - // and the start of first hit object after a break should - // not allow HP draining. (with break periods in) - foreach (BreakPeriod b in beatmap.Breaks) - { - var startTime = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < b.StartTime)?.GetEndTime() ?? double.MinValue; - var endTime = beatmap.HitObjects.FirstOrDefault(h => h.StartTime > b.EndTime)?.StartTime ?? double.MaxValue; - - nonDrainSections.Add((startTime, endTime)); - } + noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( + beatmap.HitObjects + .Select(hitObject => hitObject.GetEndTime()) + .Where(endTime => endTime < breakPeriod.StartTime) + .DefaultIfEmpty(double.MinValue) + .Last(), + beatmap.HitObjects + .Select(hitObject => hitObject.StartTime) + .Where(startTime => startTime > breakPeriod.EndTime) + .DefaultIfEmpty(double.MaxValue) + .First() + ))); targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); From e650b10b5e1adfb00123064e043d5768eb0e409f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 May 2020 19:03:13 +0200 Subject: [PATCH 005/123] Add test case for maximal break --- .../TestSceneDrainingHealthProcessor.cs | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 2f83ea4832..e50b2231bf 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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.Graphics; using osu.Framework.Utils; @@ -43,52 +41,61 @@ namespace osu.Game.Tests.Gameplay } [Test] - public void TestHealthNotDrainedBeforeBreak() + public void TestHealthDrainBetweenBreakAndObjects() { - createProcessor(createBeatmap(0, 2000, - new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); + createProcessor(createBeatmap(0, 2000, new BreakPeriod(325, 375))); - setTime(300); + // 275 300 325 350 375 400 425 + // hitobjects o o + // break [-------------] + // no drain [---------------------------] + + setTime(285); setHealth(1); - setTime(400); - assertHealthEqualTo(1); + setTime(295); + assertHealthNotEqualTo(1); - setTime(1100); + setTime(305); setHealth(1); - setTime(1200); + setTime(315); assertHealthEqualTo(1); + + setTime(365); + assertHealthEqualTo(1); + + setTime(395); + assertHealthEqualTo(1); + + setTime(425); + assertHealthNotEqualTo(1); } [Test] - public void TestHealthNotDrainedDuringBreak() + public void TestHealthDrainDuringMaximalBreak() { - createProcessor(createBeatmap(0, 2000, new BreakPeriod(0, 1200))); + createProcessor(createBeatmap(0, 2000, new BreakPeriod(300, 400))); - setTime(700); - assertHealthEqualTo(1); - setTime(900); - assertHealthEqualTo(1); - } + // 275 300 325 350 375 400 425 + // hitobjects o o + // break [---------------------------] + // no drain [---------------------------] - [Test] - public void TestHealthNotDrainedAfterBreak() - { - createProcessor(createBeatmap(0, 2000, - new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); - - setTime(600); + setTime(285); setHealth(1); - setTime(700); - assertHealthEqualTo(1); + setTime(295); + assertHealthNotEqualTo(1); - setTime(1400); + setTime(305); setHealth(1); - setTime(1500); + setTime(395); assertHealthEqualTo(1); + + setTime(425); + assertHealthNotEqualTo(1); } [Test] @@ -154,24 +161,16 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { - BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } }, + BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } }, }; - double time = startTime; - - while (time <= endTime) + for (double time = startTime; time <= endTime; time += 100) { beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time }); - - // leave a 100ms gap between the start and end of a break period. - time += (getCurrentBreak(breaks, time)?.Duration ?? 0) + 100; } beatmap.Breaks.AddRange(breaks); - static BreakPeriod getCurrentBreak(IEnumerable breaks, double time) => - breaks?.FirstOrDefault(b => time >= b.StartTime && time <= b.EndTime); - return beatmap; } From 848a3fb6d74ec3b443029cc048934c1df4ea728f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 May 2020 19:06:36 +0200 Subject: [PATCH 006/123] Take hitobject start/end times into account in drain --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 1958efdd6f..982f527517 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -84,12 +84,12 @@ namespace osu.Game.Rulesets.Scoring noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( beatmap.HitObjects .Select(hitObject => hitObject.GetEndTime()) - .Where(endTime => endTime < breakPeriod.StartTime) + .Where(endTime => endTime <= breakPeriod.StartTime) .DefaultIfEmpty(double.MinValue) .Last(), beatmap.HitObjects .Select(hitObject => hitObject.StartTime) - .Where(startTime => startTime > breakPeriod.EndTime) + .Where(startTime => startTime >= breakPeriod.EndTime) .DefaultIfEmpty(double.MaxValue) .First() ))); From 554be1c4222eec67b7926c2a4a802b6130aeae35 Mon Sep 17 00:00:00 2001 From: Olle Kelderman Date: Fri, 22 May 2020 19:25:05 +0200 Subject: [PATCH 007/123] add the ability to set the size of the Tournament Client to an arbitrary value instead of a fixed 1080p option --- osu.Game.Tournament/Screens/SetupScreen.cs | 79 ++++++++++++++++++---- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index c91379b2d6..f9ec29d0c6 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tournament.Screens private FillFlowContainer fillFlow; private LoginOverlay loginOverlay; - private ActionableInfo resolution; + private ActionableInfoWithNumberBox resolution; [Resolved] private MatchIPCInfo ipc { get; set; } @@ -108,18 +108,22 @@ namespace osu.Game.Tournament.Screens Items = rulesets.AvailableRulesets, Current = LadderInfo.Ruleset, }, - resolution = new ActionableInfo + resolution = new ActionableInfoWithNumberBox { Label = "Stream area resolution", - ButtonText = "Set to 1080p", - Action = () => + ButtonText = "Set size", + Action = i => { - windowSize.Value = new Size((int)(1920 / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), 1080); + i = Math.Clamp(i, 480, 2160); + windowSize.Value = new Size((int)(i * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), i); + resolution.NumberValue = i; } }, }; } + private const float aspect_ratio = 16f / 9f; + protected override void Update() { base.Update(); @@ -149,7 +153,7 @@ namespace osu.Game.Tournament.Screens private class ActionableInfo : LabelledDrawable { - private OsuButton button; + protected OsuButton Button; public ActionableInfo() : base(true) @@ -158,22 +162,22 @@ namespace osu.Game.Tournament.Screens public string ButtonText { - set => button.Text = value; + set => Button.Text = value; } public string Value { - set => valueText.Text = value; + set => ValueText.Text = value; } public bool Failing { - set => valueText.Colour = value ? Color4.Red : Color4.White; + set => ValueText.Colour = value ? Color4.Red : Color4.White; } public Action Action; - private TournamentSpriteText valueText; + protected TournamentSpriteText ValueText; protected override Drawable CreateComponent() => new Container { @@ -181,12 +185,12 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.X, Children = new Drawable[] { - valueText = new TournamentSpriteText + ValueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - button = new TriangleButton + Button = new TriangleButton { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -196,5 +200,56 @@ namespace osu.Game.Tournament.Screens } }; } + + private class ActionableInfoWithNumberBox : ActionableInfo + { + public new Action Action; + + private OsuNumberBox numberBox; + + public int NumberValue + { + get + { + int.TryParse(numberBox.Text, out var val); + return val; + } + set => numberBox.Text = value.ToString(); + } + + protected override Drawable CreateComponent() => new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + ValueText = new TournamentSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + numberBox = new OsuNumberBox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 100, + Margin = new MarginPadding + { + Right = 110 + } + }, + Button = new TriangleButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(100, 30), + Action = () => + { + if (numberBox.Text.Length > 0) Action?.Invoke(NumberValue); + } + }, + } + }; + } } } From 1062e07ec12f017da02a903f0ad1189468e8fe53 Mon Sep 17 00:00:00 2001 From: Olle Kelderman Date: Sun, 24 May 2020 22:24:46 +0200 Subject: [PATCH 008/123] refactor and implemented feedback: - button text change - renamed ActionableInfoWithNumberBox to ResolutionSelector and moved the clamping logic inside it - also removed the ugly right margin and added the FillFlowContainer --- osu.Game.Tournament/Screens/SetupScreen.cs | 99 +++++++++++----------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index f9ec29d0c6..33eefbe553 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tournament.Screens private FillFlowContainer fillFlow; private LoginOverlay loginOverlay; - private ActionableInfoWithNumberBox resolution; + private ResolutionSelector resolution; [Resolved] private MatchIPCInfo ipc { get; set; } @@ -108,15 +108,13 @@ namespace osu.Game.Tournament.Screens Items = rulesets.AvailableRulesets, Current = LadderInfo.Ruleset, }, - resolution = new ActionableInfoWithNumberBox + resolution = new ResolutionSelector { Label = "Stream area resolution", - ButtonText = "Set size", + ButtonText = "Set height", Action = i => { - i = Math.Clamp(i, 480, 2160); windowSize.Value = new Size((int)(i * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), i); - resolution.NumberValue = i; } }, }; @@ -167,17 +165,18 @@ namespace osu.Game.Tournament.Screens public string Value { - set => ValueText.Text = value; + set => valueText.Text = value; } public bool Failing { - set => ValueText.Colour = value ? Color4.Red : Color4.White; + set => valueText.Colour = value ? Color4.Red : Color4.White; } public Action Action; - protected TournamentSpriteText ValueText; + private TournamentSpriteText valueText; + protected FillFlowContainer FlowContainer; protected override Drawable CreateComponent() => new Container { @@ -185,71 +184,75 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.X, Children = new Drawable[] { - ValueText = new TournamentSpriteText + valueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - Button = new TriangleButton + FlowContainer = new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Size = new Vector2(100, 30), - Action = () => Action?.Invoke() - }, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + Button = new TriangleButton + { + Size = new Vector2(100, 30), + Action = () => Action?.Invoke() + } + } + } } }; } - private class ActionableInfoWithNumberBox : ActionableInfo + private class ResolutionSelector : ActionableInfo { + private const int height_min_allowed_value = 480; + private const int height_max_allowed_value = 2160; // 4k public new Action Action; private OsuNumberBox numberBox; - public int NumberValue + protected override Drawable CreateComponent() { - get + var drawable = base.CreateComponent(); + FlowContainer.Insert(0, numberBox = new OsuNumberBox { - int.TryParse(numberBox.Text, out var val); - return val; - } - set => numberBox.Text = value.ToString(); - } + Width = 100 + }); + FlowContainer.SetLayoutPosition(Button, 1); - protected override Drawable CreateComponent() => new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] + base.Action = () => { - ValueText = new TournamentSpriteText + if (numberBox.Text.Length > 0) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - numberBox = new OsuNumberBox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Width = 100, - Margin = new MarginPadding + // box contains text + if (!int.TryParse(numberBox.Text, out var number)) { - Right = 110 + // at this point, the only reason we can arrive here is if the input number was too big to parse into an int + // so clamp to max allowed value + number = height_max_allowed_value; } - }, - Button = new TriangleButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(100, 30), - Action = () => + else { - if (numberBox.Text.Length > 0) Action?.Invoke(NumberValue); + number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); } - }, - } - }; + + // in case number got clamped, reset number in numberBox + numberBox.Text = number.ToString(); + + Action?.Invoke(number); + } + else + { + // TODO: input box was empty, give user feedback? do nothing? + } + }; + return drawable; + } } } } From a174117880874df70d5cd07d210db4117a1d7cee Mon Sep 17 00:00:00 2001 From: Olle Kelderman Date: Mon, 25 May 2020 00:55:10 +0200 Subject: [PATCH 009/123] fix flowcontainer order properly and removed todo as its decided to do nothing there for now --- osu.Game.Tournament/Screens/SetupScreen.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 33eefbe553..bf328987fe 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tournament.Screens private class ActionableInfo : LabelledDrawable { - protected OsuButton Button; + private OsuButton button; public ActionableInfo() : base(true) @@ -160,7 +160,7 @@ namespace osu.Game.Tournament.Screens public string ButtonText { - set => Button.Text = value; + set => button.Text = value; } public string Value @@ -197,7 +197,7 @@ namespace osu.Game.Tournament.Screens Spacing = new Vector2(10, 0), Children = new Drawable[] { - Button = new TriangleButton + button = new TriangleButton { Size = new Vector2(100, 30), Action = () => Action?.Invoke() @@ -219,11 +219,10 @@ namespace osu.Game.Tournament.Screens protected override Drawable CreateComponent() { var drawable = base.CreateComponent(); - FlowContainer.Insert(0, numberBox = new OsuNumberBox + FlowContainer.Insert(-1, numberBox = new OsuNumberBox { Width = 100 }); - FlowContainer.SetLayoutPosition(Button, 1); base.Action = () => { @@ -246,10 +245,6 @@ namespace osu.Game.Tournament.Screens Action?.Invoke(number); } - else - { - // TODO: input box was empty, give user feedback? do nothing? - } }; return drawable; } From 719da489221850d009c7a87389e3c25edf9a9bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 May 2020 20:11:00 +0200 Subject: [PATCH 010/123] Rename delegate argument --- osu.Game.Tournament/Screens/SetupScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index bf328987fe..0ecab449ec 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -112,9 +112,9 @@ namespace osu.Game.Tournament.Screens { Label = "Stream area resolution", ButtonText = "Set height", - Action = i => + Action = height => { - windowSize.Value = new Size((int)(i * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), i); + windowSize.Value = new Size((int)(height * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), height); } }, }; From ca68d94cf7aa7acec4d638f61934836bd3c2b3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 May 2020 20:18:17 +0200 Subject: [PATCH 011/123] Invert if to reduce nesting --- osu.Game.Tournament/Screens/SetupScreen.cs | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 0ecab449ec..f8895f4703 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -226,25 +226,25 @@ namespace osu.Game.Tournament.Screens base.Action = () => { - if (numberBox.Text.Length > 0) + if (string.IsNullOrEmpty(numberBox.Text)) + return; + + // box contains text + if (!int.TryParse(numberBox.Text, out var number)) { - // box contains text - if (!int.TryParse(numberBox.Text, out var number)) - { - // at this point, the only reason we can arrive here is if the input number was too big to parse into an int - // so clamp to max allowed value - number = height_max_allowed_value; - } - else - { - number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); - } - - // in case number got clamped, reset number in numberBox - numberBox.Text = number.ToString(); - - Action?.Invoke(number); + // at this point, the only reason we can arrive here is if the input number was too big to parse into an int + // so clamp to max allowed value + number = height_max_allowed_value; } + else + { + number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); + } + + // in case number got clamped, reset number in numberBox + numberBox.Text = number.ToString(); + + Action?.Invoke(number); }; return drawable; } From 748f7fcd8b914ff7e84d0f29463142b18a6b244d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 May 2020 20:20:26 +0200 Subject: [PATCH 012/123] Rename constants --- osu.Game.Tournament/Screens/SetupScreen.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index f8895f4703..e1594de69e 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -210,8 +210,8 @@ namespace osu.Game.Tournament.Screens private class ResolutionSelector : ActionableInfo { - private const int height_min_allowed_value = 480; - private const int height_max_allowed_value = 2160; // 4k + private const int minimum_window_height = 480; + private const int maximum_window_height = 2160; // 4k public new Action Action; private OsuNumberBox numberBox; @@ -234,11 +234,11 @@ namespace osu.Game.Tournament.Screens { // at this point, the only reason we can arrive here is if the input number was too big to parse into an int // so clamp to max allowed value - number = height_max_allowed_value; + number = maximum_window_height; } else { - number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); + number = Math.Clamp(number, minimum_window_height, maximum_window_height); } // in case number got clamped, reset number in numberBox From 6b5b2152991f3ba617b42f274fee646f37753ab7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 17:44:47 +0900 Subject: [PATCH 013/123] Split out IHasPath from IHasCurve to better define hitobjects --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/JuiceStream.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 10 +---- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 39 +++++++++++-------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- osu.Game/Rulesets/Objects/Types/IHasPath.cs | 13 +++++++ .../{IHasCurve.cs => IHasPathWithRepeats.cs} | 20 +++++----- 13 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasPath.cs rename osu.Game/Rulesets/Objects/Types/{IHasCurve.cs => IHasPathWithRepeats.cs} (77%) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 90a6e609f0..27a9b63e9a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new JuiceStream { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d32595c2e1..24090e233a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class JuiceStream : CatchHitObject, IHasCurve + public class JuiceStream : CatchHitObject, IHasPathWithRepeats { /// /// Positional distance that results in a duration of one second, before any speed adjustments. diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index d8d5b67c0e..1bd796511b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private IList sampleInfoListAt(double time) { - if (!(HitObject is IHasCurve curveData)) + if (!(HitObject is IHasPathWithRepeats curveData)) return HitObject.Samples; double segmentTime = (EndTime - HitObject.StartTime) / spanCount; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 147d74c929..060a3919bd 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps switch (original) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new Slider { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6ba0e1c6aa..713d1a61f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasCurve + public class Slider : OsuHitObject, IHasPathWithRepeats { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d324441285..1a47be2282 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples }); + List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 7b11bce520..5f52160be1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,9 +3,7 @@ using osu.Game.Rulesets.Objects.Types; using System; -using System.Collections.Generic; using System.Threading; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -17,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasCurve + public class DrumRoll : TaikoHitObject, IHasPath { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -115,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; - int IHasRepeats.RepeatCount { get => 0; set { } } - - List> IHasRepeats.NodeSamples => new List>(); - - SliderPath IHasCurve.Path + SliderPath IHasPath.Path => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); #endregion diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index acb30a6277..dab923d75b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -365,7 +365,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - var curveData = hitObjects[0] as IHasCurve; + var curveData = hitObjects[0] as IHasPathWithRepeats; var positionData = hitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b034e66616..b4c78ce273 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); - var curveData = beatmap.HitObjects[0] as IHasCurve; + var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats; var positionData = beatmap.HitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7727f25967..d7e83fa471 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -233,9 +233,9 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); - if (hitObject is IHasCurve curveData) + if (hitObject is IHasPathWithRepeats curveData) { - addCurveData(writer, curveData, position); + addPathData(writer, curveData, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else @@ -263,7 +263,7 @@ namespace osu.Game.Beatmaps.Formats switch (hitObject) { - case IHasCurve _: + case IHasPath _: type |= LegacyHitObjectType.Slider; break; @@ -282,13 +282,13 @@ namespace osu.Game.Beatmaps.Formats return type; } - private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position) + private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position) { PathType? lastType = null; - for (int i = 0; i < curveData.Path.ControlPoints.Count; i++) + for (int i = 0; i < pathData.Path.ControlPoints.Count; i++) { - PathControlPoint point = curveData.Path.ControlPoints[i]; + PathControlPoint point = pathData.Path.ControlPoints[i]; if (point.Type.Value != null) { @@ -325,23 +325,28 @@ namespace osu.Game.Beatmaps.Formats if (i != 0) { writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}")); - writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); + writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ","); } } - writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},")); - writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},")); + var curveData = pathData as IHasPathWithRepeats; - for (int i = 0; i < curveData.NodeSamples.Count; i++) - { - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); - } + writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},")); + writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},")); - for (int i = 0; i < curveData.NodeSamples.Count; i++) + if (curveData != null) { - writer.Write(getSampleBank(curveData.NodeSamples[i], true)); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } + + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(getSampleBank(curveData.NodeSamples[i], true)); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 924182b265..73192dc42e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Types/IHasPath.cs b/osu.Game/Rulesets/Objects/Types/IHasPath.cs new file mode 100644 index 0000000000..567c24a4a2 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasPath.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects.Types +{ + public interface IHasPath : IHasDistance + { + /// + /// The curve. + /// + SliderPath Path { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs similarity index 77% rename from osu.Game/Rulesets/Objects/Types/IHasCurve.cs rename to osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index e98a888bd7..fba0fd7aff 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs @@ -1,6 +1,7 @@ // 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 osuTK; namespace osu.Game.Rulesets.Objects.Types @@ -8,15 +9,16 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// - public interface IHasCurve : IHasDistance, IHasRepeats + public interface IHasPathWithRepeats : IHasPath, IHasRepeats { - /// - /// The curve. - /// - SliderPath Path { get; } } - public static class HasCurveExtensions + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public interface IHasCurve : IHasPathWithRepeats + { + } + + public static class HasPathWithRepeatsExtensions { /// /// Computes the position on the curve relative to how much of the has been completed. @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. - public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + public static Vector2 CurvePositionAt(this IHasPathWithRepeats obj, double progress) => obj.Path.PositionAt(obj.ProgressAt(progress)); /// @@ -33,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - public static double ProgressAt(this IHasCurve obj, double progress) + public static double ProgressAt(this IHasPathWithRepeats obj, double progress) { double p = progress * obj.SpanCount() % 1; if (obj.SpanAt(progress) % 2 == 1) @@ -47,7 +49,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. /// [0, SpanCount) where 0 is the first run. - public static int SpanAt(this IHasCurve obj, double progress) + public static int SpanAt(this IHasPathWithRepeats obj, double progress) => (int)(progress * obj.SpanCount()); } } From b8e0a6f12725e464f179f03e53dd9f20625cd635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:37:44 +0900 Subject: [PATCH 014/123] Move sett from EndTime to Duration --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 10 +++++----- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++---- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 14 +++++++------- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 6 +++--- .../Objects/Legacy/Mania/ConvertHitObjectParser.cs | 8 ++++---- .../Rulesets/Objects/Legacy/Mania/ConvertHold.cs | 4 ++-- .../Objects/Legacy/Mania/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Osu/ConvertHitObjectParser.cs | 6 +++--- .../Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 4 ++-- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 6 +++--- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 17 files changed, 50 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d32595c2e1..7c8d57e689 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -115,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime + public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => this.SpanCount() * Path.Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; - - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; private readonly SliderPath path = new SliderPath(); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6ba0e1c6aa..984a9bf956 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,14 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - public double EndTime + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => EndTime - StartTime; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; - private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 32f7acadc8..7294587b10 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables case ArmedState.Miss: case ArmedState.Hit: - using (BeginAbsoluteSequence(Time.Current, true)) + using (BeginDelayedSequence(HitObject.Duration, true)) { this.FadeOut(transition_duration, Easing.Out); bodyContainer.ScaleTo(1.4f, transition_duration); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index b25b81c9af..08fd849fa6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100) + Duration = (original as IHasEndTime)?.Duration ?? 100 }; } } @@ -292,9 +292,9 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestHitObject : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } } private class DrawableTestHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 43e8d01297..c10c8dc30f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 9de311c9d7..4b0270064a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } public float X => 256; // Required for CatchBeatmapConverter diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9a60a0a75c..d8d90fddfa 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, comboOffset, endTime + Offset); + result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); } if (result == null) @@ -321,9 +321,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The spinner end time. + /// The spinner duration. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime); + protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration); /// /// Creates a legacy Hold-type hit object. @@ -331,8 +331,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The hold end time. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); + /// The hold end time. + protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 924182b265..0a3c1b40fd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } - public double EndTime + public double Duration { - get => StartTime + this.SpanCount() * Distance / Velocity; + get => this.SpanCount() * Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public double Velocity = 1; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index f94c4aaa75..bc64518f40 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -37,21 +37,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { X = position.X, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertHold { X = position.X, - EndTime = endTime + Duration = duration }; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 1d92d638dd..dcb66163e4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania { public float X { get; set; } - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 7dc13e27cd..b731f7c8d8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public float X { get; set; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index b95ec703b6..75ecab0b8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu return new ConvertSpinner { Position = position, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 8b21aab411..a231237077 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index db65a61c90..13e3e84c6a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -33,15 +33,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 8e28487f2f..0976106ec4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index bc7103c60d..5eb551e15c 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The time at which the HitObject ends. /// - [JsonIgnore] - double EndTime { get; set; } + double EndTime { get; } /// /// The duration of the HitObject. /// - double Duration { get; } + [JsonIgnore] + double Duration { get; set; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dd2f7a833e..d6fc17f358 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -296,7 +296,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (endTimeHitObject.EndTime == snappedTime) return; - endTimeHitObject.EndTime = snappedTime; + endTimeHitObject.Duration = snappedTime - hitObject.StartTime; break; } From cbd563e80b3b82aecf9b84e474197d9c12993b16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:38:39 +0900 Subject: [PATCH 015/123] Rename to IHasDuration --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/BananaShower.cs | 2 +- .../TestSceneNotes.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 6 ++--- .../Legacy/EndTimeObjectPatternGenerator.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 ++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Mania/ConvertHold.cs | 2 +- .../Objects/Legacy/Mania/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 2 +- .../Rulesets/Objects/Types/IHasDistance.cs | 2 +- .../Rulesets/Objects/Types/IHasDuration.cs | 24 +++++++++++++++++++ .../Rulesets/Objects/Types/IHasEndTime.cs | 20 ++++------------ .../Rulesets/Objects/Types/IHasRepeats.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 4 ++-- 27 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasDuration.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 90a6e609f0..0b370cf911 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 }.Yield(); - case IHasEndTime endTime: + case IHasDuration endTime: return new BananaShower { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 3a0b5ace53..04a995c77e 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class BananaShower : CatchHitObject, IHasEndTime + public class BananaShower : CatchHitObject, IHasDuration { public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index ea6a1e2e6a..dd5fd93710 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var obj in content.OfType()) { - if (!(obj.HitObject is IHasEndTime endTime)) + if (!(obj.HitObject is IHasDuration endTime)) continue; foreach (var nested in obj.NestedHitObjects) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 1c8116754f..32abf5e7f9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else { - float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; + float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) TargetColumns = 7; else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); @@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps var pattern = new Pattern(); - if (HitObject is IHasEndTime endTimeData) + if (HitObject is IHasDuration endTimeData) { pattern.Add(new HoldNote { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 907bed0d65..d5286a3779 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) { - endTime = (HitObject as IHasEndTime)?.EndTime ?? 0; + endTime = (HitObject as IHasDuration)?.EndTime ?? 0; } public override IEnumerable Generate() diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index e6f722a8a9..a100c9a58e 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// Represents a hit object which requires pressing, holding, and releasing a key. /// - public class HoldNote : ManiaHitObject, IHasEndTime + public class HoldNote : ManiaHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 147d74c929..3490d96b61 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 }.Yield(); - case IHasEndTime endTimeData: + case IHasDuration endTimeData: return new Spinner { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 7b1941b7f9..5d191119b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; // already hit or beyond the hittable end time. - if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime)) + if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime)) continue; switch (h) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 297a0fea79..3cad52faeb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods } // Keep wiggling sliders and spinners for their duration - if (!(osuObject is IHasEndTime endTime)) + if (!(osuObject is IHasDuration endTime)) return; amountWiggles = (int)(endTime.Duration / wiggle_duration); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0b8d03d118..418375c090 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Spinner : OsuHitObject, IHasEndTime + public class Spinner : OsuHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d324441285..a4ec7211ca 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 390f8d1f3b..8a63a89951 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { - public class Swell : TaikoHitObject, IHasEndTime + public class Swell : TaikoHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 08fd849fa6..bd7e894cf8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - Duration = (original as IHasEndTime)?.Duration ?? 100 + Duration = (original as IHasDuration)?.Duration ?? 100 }; } } @@ -290,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay #region HitObject - private class TestHitObject : ConvertHitObject, IHasEndTime + private class TestHitObject : ConvertHitObject, IHasDuration { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7727f25967..a8982238cf 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats } else { - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) addEndTimeData(writer, hitObject); writer.Write(getSampleBank(hitObject.Samples)); @@ -267,7 +267,7 @@ namespace osu.Game.Beatmaps.Formats type |= LegacyHitObjectType.Slider; break; - case IHasEndTime _: + case IHasDuration _: if (beatmap.BeatmapInfo.RulesetID == 3) type |= LegacyHitObjectType.Hold; else @@ -347,7 +347,7 @@ namespace osu.Game.Beatmaps.Formats private void addEndTimeData(TextWriter writer, HitObject hitObject) { - var endTimeData = (IHasEndTime)hitObject; + var endTimeData = (IHasDuration)hitObject; var type = getObjectType(hitObject); char suffix = ','; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8126311cbd..ac399e37c4 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps length = emptyLength; break; - case IHasEndTime endTime: + case IHasDuration endTime: length = endTime.EndTime + excess_length; break; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 6f9053d7cb..e2cc98813a 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -175,10 +175,10 @@ namespace osu.Game.Rulesets.Objects /// Returns the end time of this object. /// /// - /// This returns the where available, falling back to otherwise. + /// This returns the where available, falling back to otherwise. /// /// The object. /// The end time of this object. - public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; + public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 4b0270064a..014494ec54 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index dcb66163e4..2fa4766c1d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects.Legacy.Mania { - internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime + internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration { public float X { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index b731f7c8d8..c05aaceb9c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index a231237077..e9e5ca8c94 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 0976106ec4..1d5ecb1ef3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs index e7f552115e..b497ca5da3 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a positional length. /// - public interface IHasDistance : IHasEndTime + public interface IHasDistance : IHasDuration { /// /// The positional length of the HitObject. diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs new file mode 100644 index 0000000000..2433f9597e --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that ends at a different time than its start time. + /// + public interface IHasDuration + { + /// + /// The time at which the HitObject ends. + /// + double EndTime { get; } + + /// + /// The duration of the HitObject. + /// + [JsonIgnore] + double Duration { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 5eb551e15c..7395223c7e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,24 +1,12 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Newtonsoft.Json; +using System; namespace osu.Game.Rulesets.Objects.Types { - /// - /// A HitObject that ends at a different time than its start time. - /// - public interface IHasEndTime + [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 + public interface IHasEndTime : IHasDuration { - /// - /// The time at which the HitObject ends. - /// - double EndTime { get; } - - /// - /// The duration of the HitObject. - /// - [JsonIgnore] - double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 256b1f3963..7a3fb16196 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that spans some length. /// - public interface IHasRepeats : IHasEndTime + public interface IHasRepeats : IHasDuration { /// /// The amount of times the HitObject repeats. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index c817d84d5c..0dc3324559 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Cant use AddOnce() since the delegate is re-constructed every invocation private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { - if (hitObject.HitObject is IHasEndTime e) + if (hitObject.HitObject is IHasDuration e) { switch (direction.Value) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d6fc17f358..b95b3842b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline shadowComponents.Add(circle); - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) { DragBar dragBarUnderlay; Container extensionBar; @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline repeatHitObject.RepeatCount = proposedCount; break; - case IHasEndTime endTimeHitObject: + case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); if (endTimeHitObject.EndTime == snappedTime) From f5c974dd897b0a7006b944e0103e4158d3d7667a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 13:40:16 +0900 Subject: [PATCH 016/123] Hide non-alive selection blueprints by default --- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index b0e13808a5..8dd550bb96 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints { protected new T HitObject => (T)DrawableObject.HitObject; + protected override bool AlwaysShowWhenSelected => true; + protected OsuSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 8202d3a1d1..75200e3027 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -15,7 +15,12 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject DrawableObject; - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; + /// + /// Whether the blueprint should be shown even when the is not alive. + /// + protected virtual bool AlwaysShowWhenSelected => false; + + protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject.HitObject) From f989f1aa0034d6283ee3d49169316a80713c0a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 16:08:47 +0900 Subject: [PATCH 017/123] Change event flow to avoid firing store delete events on update --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- osu.Game/Database/ArchiveModelManager.cs | 8 ++++---- osu.Game/Database/IModelManager.cs | 2 +- osu.Game/Database/MutableDatabaseBackedStore.cs | 16 +++++++++++----- osu.Game/Online/DownloadTrackingComposite.cs | 8 ++++---- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Overlays/MusicController.cs | 12 ++++++------ .../Overlays/Settings/Sections/SkinSection.cs | 10 +++++----- .../Multi/Match/Components/ReadyButton.cs | 8 ++++---- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 8 ++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++---- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 6 +++--- 12 files changed, 49 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 43fab186aa..5eb11a3264 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); + manager.ItemUpdated.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); var imported = await LoadOszIntoOsu(osu); @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Beatmaps.IO imported.Hash += "-changed"; manager.Update(imported); - Assert.AreEqual(0, itemAddRemoveFireCount -= 2); + Assert.AreEqual(0, itemAddRemoveFireCount -= 1); checkBeatmapSetCount(osu, 1); checkBeatmapCount(osu, 12); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index f21f708f95..ae55a7b14a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -55,12 +55,12 @@ namespace osu.Game.Database public Action PostNotification { protected get; set; } /// - /// Fired when a new becomes available in the database. + /// Fired when a new or updated becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public IBindable> ItemAdded => itemAdded; + public IBindable> ItemUpdated => itemUpdated; - private readonly Bindable> itemAdded = new Bindable>(); + private readonly Bindable> itemUpdated = new Bindable>(); /// /// Fired when a is removed from the database. @@ -90,7 +90,7 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item)); + ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); exportStorage = storage.GetStorageForDirectory("exports"); diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 852b385798..7f7e5565f1 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -13,7 +13,7 @@ namespace osu.Game.Database public interface IModelManager where TModel : class { - IBindable> ItemAdded { get; } + IBindable> ItemUpdated { get; } IBindable> ItemRemoved { get; } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 4ca1eef989..c9d0c4bc41 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -16,7 +16,14 @@ namespace osu.Game.Database public abstract class MutableDatabaseBackedStore : DatabaseBackedStore where T : class, IHasPrimaryKey, ISoftDelete { - public event Action ItemAdded; + /// + /// Fired when an item was added or updated. + /// + public event Action ItemUpdated; + + /// + /// Fired when an item was removed. + /// public event Action ItemRemoved; protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) @@ -41,7 +48,7 @@ namespace osu.Game.Database context.Attach(item); } - ItemAdded?.Invoke(item); + ItemUpdated?.Invoke(item); } /// @@ -53,8 +60,7 @@ namespace osu.Game.Database using (var usage = ContextFactory.GetForWrite()) usage.Context.Update(item); - ItemRemoved?.Invoke(item); - ItemAdded?.Invoke(item); + ItemUpdated?.Invoke(item); } /// @@ -91,7 +97,7 @@ namespace osu.Game.Database item.DeletePending = false; } - ItemAdded?.Invoke(item); + ItemUpdated?.Invoke(item); return true; } diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 47de7d75ed..5d9cf612bb 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -34,7 +34,7 @@ namespace osu.Game.Online Model.Value = model; } - private IBindable> managerAdded; + private IBindable> managedUpdated; private IBindable> managerRemoved; private IBindable>> managerDownloadBegan; private IBindable>> managerDownloadFailed; @@ -56,8 +56,8 @@ namespace osu.Game.Online managerDownloadBegan.BindValueChanged(downloadBegan); managerDownloadFailed = manager.DownloadFailed.GetBoundCopy(); managerDownloadFailed.BindValueChanged(downloadFailed); - managerAdded = manager.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(itemAdded); + managedUpdated = manager.ItemUpdated.GetBoundCopy(); + managedUpdated.BindValueChanged(itemUpdated); managerRemoved = manager.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); } @@ -128,7 +128,7 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemAdded(ValueChangedEvent> weakItem) + private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) setDownloadStateFromManager(item, DownloadState.LocallyAvailable); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 453587df18..e7446975b9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -192,7 +192,7 @@ namespace osu.Game ScoreManager.Delete(getBeatmapScores(item), true); }); - BeatmapManager.ItemAdded.BindValueChanged(i => + BeatmapManager.ItemUpdated.BindValueChanged(i => { if (i.NewValue.TryGetTarget(out var item)) ScoreManager.Undelete(getBeatmapScores(item), true); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 35f3cb0e25..92cf490be2 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -60,14 +60,14 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } - private IBindable> managerAdded; + private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] private void load() { - managerAdded = beatmaps.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(beatmapAdded); + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(beatmapRemoved); @@ -98,14 +98,14 @@ namespace osu.Game.Overlays /// public bool IsPlaying => current?.Track.IsRunning ?? false; - private void beatmapAdded(ValueChangedEvent> weakSet) + private void beatmapUpdated(ValueChangedEvent> weakSet) { if (weakSet.NewValue.TryGetTarget(out var set)) { Schedule(() => { - if (!beatmapSets.Contains(set)) - beatmapSets.Add(set); + beatmapSets.Remove(set); + beatmapSets.Add(set); }); } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index b84b9fec37..04390a1193 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections [Resolved] private SkinManager skins { get; set; } - private IBindable> managerAdded; + private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] @@ -73,8 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections }, }; - managerAdded = skins.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(itemAdded); + managerUpdated = skins.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(itemUpdated); managerRemoved = skins.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); @@ -92,10 +92,10 @@ namespace osu.Game.Overlays.Settings.Sections dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID); } - private void itemAdded(ValueChangedEvent> weakItem) + private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(item).ToArray()); + Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => !i.Equals(item)).Append(item).ToArray()); } private void itemRemoved(ValueChangedEvent> weakItem) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index 4420b2d58a..e1f86fcc97 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -32,14 +32,14 @@ namespace osu.Game.Screens.Multi.Match.Components Text = "Start"; } - private IBindable> managerAdded; + private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] private void load(OsuColour colours) { - managerAdded = beatmaps.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(beatmapAdded); + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(beatmapRemoved); @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Multi.Match.Components hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null; } - private void beatmapAdded(ValueChangedEvent> weakSet) + private void beatmapUpdated(ValueChangedEvent> weakSet) { if (weakSet.NewValue.TryGetTarget(out var set)) { diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index caa547ac72..e1d72d9600 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Multi.Match private LeaderboardChatDisplay leaderboardChatDisplay; private MatchSettingsOverlay settingsOverlay; - private IBindable> managerAdded; + private IBindable> managerUpdated; public MatchSubScreen(Room room) { @@ -183,8 +183,8 @@ namespace osu.Game.Screens.Multi.Match SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); SelectedItem.Value = playlist.FirstOrDefault(); - managerAdded = beatmapManager.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(beatmapAdded); + managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); } public override bool OnExiting(IScreen next) @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void beatmapAdded(ValueChangedEvent> weakSet) + private void beatmapUpdated(ValueChangedEvent> weakSet) { Schedule(() => { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f23e1b1ef2..2d714d1794 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - private IBindable> itemAdded; + private IBindable> itemUpdated; private IBindable> itemRemoved; private IBindable> itemHidden; private IBindable> itemRestored; @@ -166,8 +166,8 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - itemAdded = beatmaps.ItemAdded.GetBoundCopy(); - itemAdded.BindValueChanged(beatmapAdded); + itemUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + itemUpdated.BindValueChanged(beatmapUpdated); itemRemoved = beatmaps.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(beatmapRemoved); itemHidden = beatmaps.BeatmapHidden.GetBoundCopy(); @@ -582,7 +582,7 @@ namespace osu.Game.Screens.Select RemoveBeatmapSet(item); } - private void beatmapAdded(ValueChangedEvent> weakItem) + private void beatmapUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) UpdateBeatmapSet(item); diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index aed25787b0..3ad57c1cb0 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IAPIProvider api { get; set; } - private IBindable> itemAdded; + private IBindable> itemUpdated; private IBindable> itemRemoved; public TopLocalRank(BeatmapInfo beatmap) @@ -40,8 +40,8 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load() { - itemAdded = scores.ItemAdded.GetBoundCopy(); - itemAdded.BindValueChanged(scoreChanged); + itemUpdated = scores.ItemUpdated.GetBoundCopy(); + itemUpdated.BindValueChanged(scoreChanged); itemRemoved = scores.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(scoreChanged); From 9a060cfb3acfac28816eb68f245e365153a9af46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 20:44:15 +0900 Subject: [PATCH 018/123] Allow drag selections to occur from outside the playfield --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 + osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3453bfbf63..99759bde3d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -102,6 +102,7 @@ namespace osu.Game.Rulesets.Edit { Name = "Content", RelativeSizeAxes = Axes.Both, + Masking = true, Children = new Drawable[] { // layers below playfield diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cc417bbb10..d07cffff0c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly BindableList selectedHitObjects = new BindableList(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + [Resolved(canBeNull: true)] private IPositionSnapProvider snapProvider { get; set; } From 919ff92d15775e63bbb74083a3ce05b61ac53707 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 22:56:12 +0900 Subject: [PATCH 019/123] Remove unused resolved composer --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 500b26917d..b5ec1e1a2a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -20,9 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly EditNotePiece headPiece; private readonly EditNotePiece tailPiece; - [Resolved] - private IManiaHitObjectComposer composer { get; set; } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } From 6be5917eb0c232d5ad34843736fc7c4781c36d02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 23:15:16 +0900 Subject: [PATCH 020/123] Remove necessity for custom mania interface caching --- .../ManiaPlacementBlueprintTestScene.cs | 4 +- .../ManiaSelectionBlueprintTestScene.cs | 4 +- .../TestSceneManiaBeatSnapGrid.cs | 60 ++++++++++++++++++- .../Blueprints/ManiaSelectionBlueprint.cs | 3 - .../Edit/IManiaHitObjectComposer.cs | 12 ---- .../Edit/ManiaBeatSnapGrid.cs | 6 +- .../Edit/ManiaHitObjectComposer.cs | 5 +- .../Edit/ManiaSelectionHandler.cs | 9 ++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +- 9 files changed, 76 insertions(+), 33 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index 1119a66f63..0fe4a3c669 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -19,8 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { - [Cached(Type = typeof(IManiaHitObjectComposer))] - public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer + public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene { private readonly Column column; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index 35fe596e98..149f6582ab 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -4,15 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { - [Cached(Type = typeof(IManiaHitObjectComposer))] - public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer + public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene { [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs index ce9546415f..639be0bc11 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs @@ -2,23 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - [Cached(typeof(IManiaHitObjectComposer))] - public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer + public class TestSceneManiaBeatSnapGrid : EditorClockTestScene { [Cached(typeof(IScrollingInfo))] private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo(); @@ -50,7 +54,10 @@ namespace osu.Game.Rulesets.Mania.Tests { Clock = new FramedClock(new StopwatchClock()) }, - beatSnapGrid = new ManiaBeatSnapGrid() + new TestHitObjectComposer(Playfield) + { + Child = beatSnapGrid = new ManiaBeatSnapGrid() + } }; } @@ -67,4 +74,51 @@ namespace osu.Game.Rulesets.Mania.Tests public ManiaPlayfield Playfield { get; } } + + public class TestHitObjectComposer : HitObjectComposer + { + public override Playfield Playfield { get; } + public override IEnumerable HitObjects => Enumerable.Empty(); + public override bool CursorInPlacementArea => false; + + public TestHitObjectComposer(Playfield playfield) + { + Playfield = playfield; + } + + public Drawable Child + { + set => InternalChild = value; + } + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + throw new System.NotImplementedException(); + } + + public override float GetBeatSnapDistanceAt(double referenceTime) + { + throw new System.NotImplementedException(); + } + + public override float DurationToDistance(double referenceTime, double duration) + { + throw new System.NotImplementedException(); + } + + public override double DistanceToDuration(double referenceTime, float distance) + { + throw new System.NotImplementedException(); + } + + public override double GetSnappedDurationFromDistance(double referenceTime, float distance) + { + throw new System.NotImplementedException(); + } + + public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) + { + throw new System.NotImplementedException(); + } + } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 0089a9fbee..384f49d9b2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - [Resolved] - private IManiaHitObjectComposer composer { get; set; } - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs deleted file mode 100644 index 3818d0e15d..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Mania.UI; - -namespace osu.Game.Rulesets.Mania.Edit -{ - public interface IManiaHitObjectComposer - { - ManiaPlayfield Playfield { get; } - } -} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index b5b6c08fca..2028cae9a5 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -63,9 +65,9 @@ namespace osu.Game.Rulesets.Mania.Edit private (double start, double end)? selectionTimeRange; [BackgroundDependencyLoader] - private void load(IManiaHitObjectComposer composer) + private void load(HitObjectComposer composer) { - foreach (var stage in composer.Playfield.Stages) + foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages) { foreach (var column in stage.Columns) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 73cbadc97c..10d344242c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -20,8 +20,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Edit { - [Cached(Type = typeof(IManiaHitObjectComposer))] - public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer + public class ManiaHitObjectComposer : HitObjectComposer { private DrawableManiaEditRuleset drawableRuleset; private ManiaBeatSnapGrid beatSnapGrid; @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); + public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 4ea71652bc..65f40d7d0a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit private IScrollingInfo scrollingInfo { get; set; } [Resolved] - private IManiaHitObjectComposer composer { get; set; } + private HitObjectComposer composer { get; set; } public override bool HandleMovement(MoveSelectionEvent moveEvent) { @@ -31,7 +32,9 @@ namespace osu.Game.Rulesets.Mania.Edit private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { - var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); + var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; + + var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); if (currentColumn == null) return; @@ -50,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3453bfbf63..0a2df64dde 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } + public override Playfield Playfield => drawableRulesetWrapper.Playfield; + private DrawableEditRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -260,11 +262,13 @@ namespace osu.Game.Rulesets.Edit [Cached(typeof(IPositionSnapProvider))] public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider { - internal HitObjectComposer() + protected HitObjectComposer() { RelativeSizeAxes = Axes.Both; } + public abstract Playfield Playfield { get; } + /// /// All the s. /// From e4de20f0a5b6721519d6fa1baa4b2b2daf9222e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 May 2020 11:54:27 +0900 Subject: [PATCH 021/123] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b7d08fb120..7ea1f3140b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d5017a436f..3d2a4f3081 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 19a36f1e1f..8a7f75b515 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 912c999f4051a259c58c2dc8ec265193bb8f8bd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 May 2020 19:05:35 +0900 Subject: [PATCH 022/123] Fix minor typo in OsuGameBase --- osu.Game/OsuGameBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e7446975b9..5e44562144 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -229,8 +229,8 @@ namespace osu.Game FileStore.Cleanup(); - if (API is APIAccess apiAcces) - AddInternal(apiAcces); + if (API is APIAccess apiAccess) + AddInternal(apiAccess); AddInternal(RulesetConfigCache); GlobalActionContainer globalBinding; From 46689a2fbc46281277feb6fe806b0f756e1bfddc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 11:46:08 +0900 Subject: [PATCH 023/123] Tidy up and complete xmldoc for HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 89 ++++++++++++++++----- osu.sln.DotSettings | 5 +- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 38576e02a0..c956439eb2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - public override Playfield Playfield => drawableRulesetWrapper.Playfield; - private DrawableEditRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -61,7 +59,6 @@ namespace osu.Game.Rulesets.Edit protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -137,6 +134,49 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + public override Playfield Playfield => drawableRulesetWrapper.Playfield; + + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + + /// + /// Defines all available composition tools, listed on the left side of the editor screen as button controls. + /// This should usually define one tool for each type used in the target ruleset. + /// + /// + /// A "select" tool is automatically added as the first tool. + /// + protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. + /// + protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + + /// + /// Construct a drawable ruleset for the provided ruleset. + /// + /// + /// Can be overridden to add editor-specific logical changes to a 's standard . + /// For example, hit animations or judgement logic may be changed to give a better editor user experience. + /// + /// The ruleset used to construct its drawable counterpart. + /// The loaded beatmap. + /// The mods to be applied. + /// An editor-relevant . + protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); + + #region Tool selection logic + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key >= Key.Number1 && e.Key <= Key.Number9) @@ -153,13 +193,6 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { if (EditorBeatmap.SelectedHitObjects.Any()) @@ -179,15 +212,9 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.Clear(); } - public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + #endregion - public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - - protected abstract IReadOnlyList CompositionTools { get; } - - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); - - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null); + #region IPlacementHandler public void BeginPlacement(HitObject hitObject) { @@ -209,6 +236,17 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); + #endregion + + #region IPositionSnapProvider + + /// + /// Retrieve the relevant at a specified screen-space position. + /// In cases where a ruleset doesn't require custom logic (due to nested playfields, for example) + /// this will return the ruleset's main playfield. + /// + /// The screen-space position to query. + /// The most relevant . protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) @@ -257,8 +295,14 @@ namespace osu.Game.Rulesets.Edit return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } + + #endregion } + /// + /// A non-generic definition of a HitObject composer class. + /// Generally used to access certain methods without requiring a generic type for . + /// [Cached(typeof(HitObjectComposer))] [Cached(typeof(IPositionSnapProvider))] public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider @@ -268,10 +312,13 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; } + /// + /// The target ruleset's playfield. + /// public abstract Playfield Playfield { get; } /// - /// All the s. + /// All s in currently loaded beatmap. /// public abstract IEnumerable HitObjects { get; } @@ -280,6 +327,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } + #region IPositionSnapProvider + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); @@ -291,5 +340,7 @@ namespace osu.Game.Rulesets.Edit public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); + + #endregion } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e3b64c03b9..b9fc3de734 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1,4 +1,4 @@ - + True True True @@ -905,14 +905,17 @@ private void load() True True True + True True True True True True True + True True True True + True True True From 8fa8c561e7bc438eb5c64473d70d50e6de4c4584 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 12:20:50 +0900 Subject: [PATCH 024/123] Pass hitobjects as a parameter to CreateBlueprintContainer --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 +++- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 +++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 10d344242c..7e2469a794 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -88,7 +89,8 @@ namespace osu.Game.Rulesets.Mania.Edit return drawableRuleset; } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ManiaBlueprintContainer(hitObjects); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index de5c1e54d7..37019a7a05 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new OsuBlueprintContainer(hitObjects); private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c956439eb2..8b9f531417 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(BlueprintContainer = CreateBlueprintContainer()) + .WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) } } }, @@ -159,7 +159,9 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + /// A live collection of all s in the editor beatmap. + protected virtual ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ComposeBlueprintContainer(hitObjects); /// /// Construct a drawable ruleset for the provided ruleset. From f9883373bbd86eb0513968d6dd487b95f4ce759f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:11:26 +0900 Subject: [PATCH 025/123] Flip direction to avoid breaking other usages --- osu.Game/Rulesets/Objects/Types/IHasDuration.cs | 16 +++++++++++++--- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs index 2433f9597e..185fd5977b 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -8,17 +8,27 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that ends at a different time than its start time. /// - public interface IHasDuration +#pragma warning disable 618 + public interface IHasDuration : IHasEndTime +#pragma warning restore 618 { + double IHasEndTime.EndTime + { + get => EndTime; + set => Duration = (Duration - EndTime) + value; + } + + double IHasEndTime.Duration => Duration; + /// /// The time at which the HitObject ends. /// - double EndTime { get; } + new double EndTime { get; } /// /// The duration of the HitObject. /// [JsonIgnore] - double Duration { get; set; } + new double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 7395223c7e..c3769c5909 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -2,11 +2,25 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; namespace osu.Game.Rulesets.Objects.Types { + /// + /// A HitObject that ends at a different time than its start time. + /// [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 - public interface IHasEndTime : IHasDuration + public interface IHasEndTime { + /// + /// The time at which the HitObject ends. + /// + [JsonIgnore] + double EndTime { get; set; } + + /// + /// The duration of the HitObject. + /// + double Duration { get; } } } From 7d4e60f05e01b77932ffac10ab5f5735a85a3015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 May 2020 16:06:25 +0900 Subject: [PATCH 026/123] Add basic setup for TaikoHitObjectComposer --- .../TestSceneTaikoHitObjectComposer.cs | 58 +++++++++++++++++++ .../TaikoHitObjectComposer.cs | 34 +++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 + 3 files changed, 95 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs create mode 100644 osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs new file mode 100644 index 0000000000..0fff08fab7 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoHitObjectComposer : EditorClockTestScene + { + private TestComposer composer; + + [SetUp] + public void Setup() => Schedule(() => + { + BeatDivisor.Value = 8; + Clock.Seek(0); + + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; + }); + + [Test] + public void BasicTest() + { + } + + private class TestComposer : CompositeDrawable + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + public readonly EditorBeatmap EditorBeatmap; + + public readonly TaikoHitObjectComposer Composer; + + public TestComposer() + { + InternalChildren = new Drawable[] + { + EditorBeatmap = new EditorBeatmap(new TaikoBeatmap()) + { + BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo } + }, + Composer = new TaikoHitObjectComposer(new TaikoRuleset()) + }; + + for (int i = 0; i < 10; i++) + EditorBeatmap.Add(new Hit { StartTime = 125 * i }); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs new file mode 100644 index 0000000000..069517fe46 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko +{ + public class TaikoHitObjectComposer : HitObjectComposer + { + private DrawableTaikoRuleset drawableRuleset; + + public TaikoHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => new List(); + + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + { + return drawableRuleset = new DrawableTaikoRuleset(ruleset, beatmap, mods); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 74d9e68ad3..7be16471b4 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Skinning; @@ -144,6 +145,8 @@ namespace osu.Game.Rulesets.Taiko public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; + public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); From 90acba8c36ded4f5d6c3873d46c99e27a5ff9517 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 19:23:36 +0900 Subject: [PATCH 027/123] Introduce initial placement blueprint logic --- .../TestSceneTaikoHitObjectComposer.cs | 8 +- .../TaikoHitObjectComposer.cs | 119 +++++++++++++++++- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs index 0fff08fab7..b5ee33fa8e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs @@ -15,15 +15,13 @@ namespace osu.Game.Rulesets.Taiko.Tests { public class TestSceneTaikoHitObjectComposer : EditorClockTestScene { - private TestComposer composer; - [SetUp] public void Setup() => Schedule(() => { BeatDivisor.Value = 8; Clock.Seek(0); - Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; + Child = new TestComposer { RelativeSizeAxes = Axes.Both }; }); [Test] @@ -37,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [Cached(typeof(IBeatSnapProvider))] public readonly EditorBeatmap EditorBeatmap; - public readonly TaikoHitObjectComposer Composer; - public TestComposer() { InternalChildren = new Drawable[] @@ -47,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo } }, - Composer = new TaikoHitObjectComposer(new TaikoRuleset()) + new TaikoHitObjectComposer(new TaikoRuleset()) }; for (int i = 0; i < 10; i++) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 069517fe46..dfdc91f7dc 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -2,14 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Taiko { @@ -22,13 +30,120 @@ namespace osu.Game.Rulesets.Taiko { } - protected override IReadOnlyList CompositionTools => new List(); + protected override IReadOnlyList CompositionTools => new[] + { + new HitCompositionTool() + }; - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { return drawableRuleset = new DrawableTaikoRuleset(ruleset, beatmap, mods); } } + + public class TaikoBlueprintContainer : ComposeBlueprintContainer + { + public TaikoBlueprintContainer(IEnumerable hitObjects) + : base(hitObjects) + { + } + + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => + new TaikoSelectionBlueprint(hitObject); + } + + public class TaikoSelectionBlueprint : OverlaySelectionBlueprint + { + public TaikoSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + + AddInternal(new HitPiece { RelativeSizeAxes = Axes.Both }); + } + + protected override void Update() + { + base.Update(); + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight)); + + Size = bottomRight - topLeft; + Position = topLeft; + } + } + + public class HitCompositionTool : HitObjectCompositionTool + { + public HitCompositionTool() + : base(nameof(Hit)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); + } + + public class HitPlacementBlueprint : PlacementBlueprint + + { + private readonly HitPiece piece; + + public HitPlacementBlueprint() + : base(new Hit()) + { + InternalChild = piece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + { + EndPlacement(true); + return true; + } + + return base.OnMouseDown(e); + } + + public override void UpdatePosition(SnapResult snapResult) + { + piece.Position = ToLocalSpace(snapResult.ScreenSpacePosition); + base.UpdatePosition(snapResult); + } + } + + public class HitPiece : CompositeDrawable + { + public HitPiece() + { + Origin = Anchor.Centre; + + InternalChild = new CircularContainer + { + Masking = true, + BorderThickness = 10, + BorderColour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }; + } + } } From 4b1a2b5bc2d1481120d60700270c7b48436d853c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 22:36:49 +0900 Subject: [PATCH 028/123] Fix offsets --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index dfdc91f7dc..c820b3eb92 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -61,7 +61,11 @@ namespace osu.Game.Rulesets.Taiko { RelativeSizeAxes = Axes.None; - AddInternal(new HitPiece { RelativeSizeAxes = Axes.Both }); + AddInternal(new HitPiece + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft + }); } protected override void Update() From 3487c1fd1b9e1d53df72f60cece30f890c239637 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 13:51:53 +0900 Subject: [PATCH 029/123] Add menus to mark as rim and strong --- .../TestSceneEditor.cs | 17 +++++ osu.Game.Rulesets.Taiko/Objects/Hit.cs | 10 ++- .../Objects/TaikoHitObject.cs | 9 ++- .../TaikoHitObjectComposer.cs | 70 +++++++++++++++++++ .../UserInterface/TernaryStateMenuItem.cs | 12 +--- 5 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs new file mode 100644 index 0000000000..089a7ad00b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneEditor : EditorTestScene + { + public TestSceneEditor() + : base(new TaikoRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 2aca701515..68cc8d0ead 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,13 +1,21 @@ // 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.Bindables; + namespace osu.Game.Rulesets.Taiko.Objects { public class Hit : TaikoHitObject { + public readonly Bindable TypeBindable = new Bindable(); + /// /// The that actuates this . /// - public HitType Type { get; set; } + public HitType Type + { + get => TypeBindable.Value; + set => TypeBindable.Value = value; + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 206bfcfdb2..4de762ce30 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; +using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -27,11 +28,17 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE; + public readonly Bindable IsStrongBindable = new BindableBool(); + /// /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. /// - public virtual bool IsStrong { get; set; } + public virtual bool IsStrong + { + get => IsStrongBindable.Value; + set => IsStrongBindable.Value = value; + } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index c820b3eb92..beef2b6155 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -1,12 +1,16 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -50,10 +54,76 @@ namespace osu.Game.Rulesets.Taiko { } + protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => new TaikoSelectionBlueprint(hitObject); } + public class TaikoSelectionHandler : SelectionHandler + { + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + { + if (selection.All(s => s.HitObject is Hit)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Rim", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.Type = HitType.Rim; + break; + + case TernaryState.False: + h.Type = HitType.Centre; + break; + } + } + }) + { + State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } + }; + } + + if (selection.All(s => s.HitObject is TaikoHitObject)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Strong", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.IsStrong = true; + break; + + case TernaryState.False: + h.IsStrong = false; + break; + } + } + }) + { + State = { Value = getTernaryState(hits, h => h.IsStrong) } + }; + } + } + + private TernaryState getTernaryState(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + } + public class TaikoSelectionBlueprint : OverlaySelectionBlueprint { public TaikoSelectionBlueprint(DrawableHitObject hitObject) diff --git a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index 2d9e2106d4..acf4065f49 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -11,23 +11,13 @@ namespace osu.Game.Graphics.UserInterface /// public class TernaryStateMenuItem : StatefulMenuItem { - /// - /// Creates a new . - /// - /// The text to display. - /// The type of action which this performs. - public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : this(text, type, null) - { - } - /// /// Creates a new . /// /// The text to display. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public TernaryStateMenuItem(string text, MenuItemType type, Action action) + public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) : this(text, getNextState, type, action) { } From 4e9631b54631655ee6d92742b14fb3c730d9a463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 14:43:38 +0900 Subject: [PATCH 030/123] Support HitType bindable changes --- .../Objects/Drawables/DrawableHit.cs | 24 ++++++++++++++++++- .../Drawables/DrawableTaikoHitObject.cs | 21 ++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 81b969eaf3..92ae7e0fd3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; @@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// A list of keys which can result in hits for this HitObject. /// - public TaikoAction[] HitActions { get; } + public TaikoAction[] HitActions { get; private set; } /// /// The action that caused this to be hit. @@ -34,15 +36,35 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private bool pressHandledThisFrame; + private Bindable type; + public DrawableHit(Hit hit) : base(hit) { FillMode = FillMode.Fit; + } + [BackgroundDependencyLoader] + private void load() + { + type = HitObject.TypeBindable.GetBoundCopy(); + type.BindValueChanged(_ => + { + updateType(); + RecreatePieces(); + }); + + updateType(); + } + + private void updateType() + { HitActions = HitObject.Type == HitType.Centre ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } : new[] { TaikoAction.LeftRim, TaikoAction.RightRim }; + + RecreatePieces(); } protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 3ab09d4cbe..a3dfc9acc0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -8,6 +8,7 @@ using osuTK; using System.Linq; using osu.Game.Audio; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects; @@ -115,10 +116,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public new TObject HitObject; - protected readonly Vector2 BaseSize; - protected readonly SkinnableDrawable MainPiece; + protected Vector2 BaseSize; + protected SkinnableDrawable MainPiece; - private readonly Container strongHitContainer; + private Container strongHitContainer; protected DrawableTaikoHitObject(TObject hitObject) : base(hitObject) @@ -129,11 +130,21 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Custom; RelativeSizeAxes = Axes.Both; + AddInternal(strongHitContainer = new Container()); + } + + [BackgroundDependencyLoader] + private void load() + { + RecreatePieces(); + } + + protected virtual void RecreatePieces() + { Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); + Content.Clear(); Content.Add(MainPiece = CreateMainPiece()); - - AddInternal(strongHitContainer = new Container()); } protected override void AddNestedHitObject(DrawableHitObject hitObject) From 50fcd4149f42dae335f9e0eae0507c516a2c058e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 14:54:12 +0900 Subject: [PATCH 031/123] Support Strong bindable changes --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index a3dfc9acc0..929cf8a937 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Audio; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects; @@ -119,7 +120,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected Vector2 BaseSize; protected SkinnableDrawable MainPiece; - private Container strongHitContainer; + private Bindable isStrong; + + private readonly Container strongHitContainer; protected DrawableTaikoHitObject(TObject hitObject) : base(hitObject) @@ -130,20 +133,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Custom; RelativeSizeAxes = Axes.Both; + AddInternal(strongHitContainer = new Container()); } [BackgroundDependencyLoader] private void load() { - RecreatePieces(); + isStrong = HitObject.IsStrongBindable.GetBoundCopy(); + isStrong.BindValueChanged(_ => RecreatePieces(), true); } protected virtual void RecreatePieces() { Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); - Content.Clear(); + MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); } From 910326623c8e24658565e63d95e77b707c244a4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 15:09:22 +0900 Subject: [PATCH 032/123] Place rim hits using right mosue for now --- .../TaikoHitObjectComposer.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index beef2b6155..5d7880278a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -169,8 +169,10 @@ namespace osu.Game.Rulesets.Taiko { private readonly HitPiece piece; + private static Hit hit; + public HitPlacementBlueprint() - : base(new Hit()) + : base(hit = new Hit()) { InternalChild = piece = new HitPiece { @@ -180,13 +182,20 @@ namespace osu.Game.Rulesets.Taiko protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button == MouseButton.Left) + switch (e.Button) { - EndPlacement(true); - return true; + case MouseButton.Left: + hit.Type = HitType.Centre; + EndPlacement(true); + return true; + + case MouseButton.Right: + hit.Type = HitType.Rim; + EndPlacement(true); + return true; } - return base.OnMouseDown(e); + return false; } public override void UpdatePosition(SnapResult snapResult) From a2eec5d963dad4cbf4cda878eb7fefc36d3c5d91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 16:58:28 +0900 Subject: [PATCH 033/123] Fix strong bindable changes for DrumRolls --- .../Objects/Drawables/DrawableDrumRoll.cs | 17 +++++++++++------ osu.Game.Rulesets.Taiko/Objects/Swell.cs | 6 ------ .../Objects/TaikoHitObject.cs | 2 +- .../TaikoHitObjectComposer.cs | 2 ++ .../Edit/Compose/Components/SelectionHandler.cs | 8 ++++---- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5e731e5ad6..2c1c2d2bc1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -48,12 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables colourIdle = colours.YellowDark; colourEngaged = colours.YellowDarker; - updateColour(); - - Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); - - if (MainPiece.Drawable is IHasAccentColour accentMain) - accentMain.AccentColour = colourIdle; + Content.Add(tickContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = float.MinValue + }); } protected override void LoadComplete() @@ -63,6 +62,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables OnNewResult += onNewResult; } + protected override void RecreatePieces() + { + base.RecreatePieces(); + updateColour(); + } + protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 390f8d1f3b..b4c09b884e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -1,7 +1,6 @@ // 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.Threading; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Judgements; @@ -25,11 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; - public override bool IsStrong - { - set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); - } - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 4de762ce30..2922010001 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. /// - public virtual bool IsStrong + public bool IsStrong { get => IsStrongBindable.Value; set => IsStrongBindable.Value = value; diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 5d7880278a..802bb80fa3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -107,6 +107,8 @@ namespace osu.Game.Rulesets.Taiko h.IsStrong = false; break; } + + EditorBeatmap?.UpdateHitObject(h); } }) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7ab6340e07..38893f90a8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Drawable outline; [Resolved(CanBeNull = true)] - private EditorBeatmap editorBeatmap { get; set; } + protected EditorBeatmap EditorBeatmap { get; private set; } [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void HandleSelected(SelectionBlueprint blueprint) { selectedBlueprints.Add(blueprint); - editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); UpdateVisibility(); } @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); - editorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); + EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection if (selectedBlueprints.Count == 0) @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components changeHandler?.BeginChange(); foreach (var h in selectedBlueprints.ToList()) - editorBeatmap?.Remove(h.HitObject); + EditorBeatmap?.Remove(h.HitObject); changeHandler?.EndChange(); } From 280b0adb1dc532938d12e4fd966fb7be033030d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 17:44:47 +0900 Subject: [PATCH 034/123] Split out IHasPath from IHasCurve to better define hitobjects --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/JuiceStream.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 10 +---- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 39 +++++++++++-------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- osu.Game/Rulesets/Objects/Types/IHasPath.cs | 13 +++++++ .../{IHasCurve.cs => IHasPathWithRepeats.cs} | 20 +++++----- 13 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasPath.cs rename osu.Game/Rulesets/Objects/Types/{IHasCurve.cs => IHasPathWithRepeats.cs} (77%) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 90a6e609f0..27a9b63e9a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new JuiceStream { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d32595c2e1..24090e233a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class JuiceStream : CatchHitObject, IHasCurve + public class JuiceStream : CatchHitObject, IHasPathWithRepeats { /// /// Positional distance that results in a duration of one second, before any speed adjustments. diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index d8d5b67c0e..1bd796511b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private IList sampleInfoListAt(double time) { - if (!(HitObject is IHasCurve curveData)) + if (!(HitObject is IHasPathWithRepeats curveData)) return HitObject.Samples; double segmentTime = (EndTime - HitObject.StartTime) / spanCount; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 147d74c929..060a3919bd 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps switch (original) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new Slider { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6ba0e1c6aa..713d1a61f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasCurve + public class Slider : OsuHitObject, IHasPathWithRepeats { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d324441285..1a47be2282 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples }); + List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 7b11bce520..5f52160be1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,9 +3,7 @@ using osu.Game.Rulesets.Objects.Types; using System; -using System.Collections.Generic; using System.Threading; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -17,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasCurve + public class DrumRoll : TaikoHitObject, IHasPath { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -115,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; - int IHasRepeats.RepeatCount { get => 0; set { } } - - List> IHasRepeats.NodeSamples => new List>(); - - SliderPath IHasCurve.Path + SliderPath IHasPath.Path => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); #endregion diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index acb30a6277..dab923d75b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -365,7 +365,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - var curveData = hitObjects[0] as IHasCurve; + var curveData = hitObjects[0] as IHasPathWithRepeats; var positionData = hitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b034e66616..b4c78ce273 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); - var curveData = beatmap.HitObjects[0] as IHasCurve; + var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats; var positionData = beatmap.HitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7727f25967..d7e83fa471 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -233,9 +233,9 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); - if (hitObject is IHasCurve curveData) + if (hitObject is IHasPathWithRepeats curveData) { - addCurveData(writer, curveData, position); + addPathData(writer, curveData, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else @@ -263,7 +263,7 @@ namespace osu.Game.Beatmaps.Formats switch (hitObject) { - case IHasCurve _: + case IHasPath _: type |= LegacyHitObjectType.Slider; break; @@ -282,13 +282,13 @@ namespace osu.Game.Beatmaps.Formats return type; } - private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position) + private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position) { PathType? lastType = null; - for (int i = 0; i < curveData.Path.ControlPoints.Count; i++) + for (int i = 0; i < pathData.Path.ControlPoints.Count; i++) { - PathControlPoint point = curveData.Path.ControlPoints[i]; + PathControlPoint point = pathData.Path.ControlPoints[i]; if (point.Type.Value != null) { @@ -325,23 +325,28 @@ namespace osu.Game.Beatmaps.Formats if (i != 0) { writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}")); - writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); + writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ","); } } - writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},")); - writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},")); + var curveData = pathData as IHasPathWithRepeats; - for (int i = 0; i < curveData.NodeSamples.Count; i++) - { - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); - } + writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},")); + writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},")); - for (int i = 0; i < curveData.NodeSamples.Count; i++) + if (curveData != null) { - writer.Write(getSampleBank(curveData.NodeSamples[i], true)); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } + + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(getSampleBank(curveData.NodeSamples[i], true)); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 924182b265..73192dc42e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Types/IHasPath.cs b/osu.Game/Rulesets/Objects/Types/IHasPath.cs new file mode 100644 index 0000000000..567c24a4a2 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasPath.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects.Types +{ + public interface IHasPath : IHasDistance + { + /// + /// The curve. + /// + SliderPath Path { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs similarity index 77% rename from osu.Game/Rulesets/Objects/Types/IHasCurve.cs rename to osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index e98a888bd7..fba0fd7aff 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs @@ -1,6 +1,7 @@ // 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 osuTK; namespace osu.Game.Rulesets.Objects.Types @@ -8,15 +9,16 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// - public interface IHasCurve : IHasDistance, IHasRepeats + public interface IHasPathWithRepeats : IHasPath, IHasRepeats { - /// - /// The curve. - /// - SliderPath Path { get; } } - public static class HasCurveExtensions + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public interface IHasCurve : IHasPathWithRepeats + { + } + + public static class HasPathWithRepeatsExtensions { /// /// Computes the position on the curve relative to how much of the has been completed. @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. - public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + public static Vector2 CurvePositionAt(this IHasPathWithRepeats obj, double progress) => obj.Path.PositionAt(obj.ProgressAt(progress)); /// @@ -33,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - public static double ProgressAt(this IHasCurve obj, double progress) + public static double ProgressAt(this IHasPathWithRepeats obj, double progress) { double p = progress * obj.SpanCount() % 1; if (obj.SpanAt(progress) % 2 == 1) @@ -47,7 +49,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. /// [0, SpanCount) where 0 is the first run. - public static int SpanAt(this IHasCurve obj, double progress) + public static int SpanAt(this IHasPathWithRepeats obj, double progress) => (int)(progress * obj.SpanCount()); } } From a953f9e422810198b1d431b52f4b365c912c4e84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 20:30:33 +0900 Subject: [PATCH 035/123] Add drum roll composition support --- .../TaikoHitObjectComposer.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 802bb80fa3..d233cd5e7f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -34,9 +34,10 @@ namespace osu.Game.Rulesets.Taiko { } - protected override IReadOnlyList CompositionTools => new[] + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new HitCompositionTool() + new HitCompositionTool(), + new DrumRollCompositionTool() }; protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); @@ -156,6 +157,26 @@ namespace osu.Game.Rulesets.Taiko } } + public class DrumRollCompositionTool : HitObjectCompositionTool + { + public DrumRollCompositionTool() + : base(nameof(DrumRoll)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); + } + + public class DrumRollPlacementBlueprint : PlacementBlueprint + { + private static DrumRoll drumRoll; + + public DrumRollPlacementBlueprint() + : base(drumRoll = new DrumRoll()) + { + } + } + public class HitCompositionTool : HitObjectCompositionTool { public HitCompositionTool() From 534dccc0c384a0e33a6fbe49c1b3b2acc97d56b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:37:44 +0900 Subject: [PATCH 036/123] Move sett from EndTime to Duration --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 10 +++++----- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++---- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 14 +++++++------- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 6 +++--- .../Objects/Legacy/Mania/ConvertHitObjectParser.cs | 8 ++++---- .../Rulesets/Objects/Legacy/Mania/ConvertHold.cs | 4 ++-- .../Objects/Legacy/Mania/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Osu/ConvertHitObjectParser.cs | 6 +++--- .../Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 4 ++-- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 6 +++--- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 17 files changed, 50 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 24090e233a..2c96ee2b19 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -115,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime + public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => this.SpanCount() * Path.Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; - - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; private readonly SliderPath path = new SliderPath(); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 713d1a61f8..705e88040f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,14 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats { - public double EndTime + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => EndTime - StartTime; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; - private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 32f7acadc8..7294587b10 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables case ArmedState.Miss: case ArmedState.Hit: - using (BeginAbsoluteSequence(Time.Current, true)) + using (BeginDelayedSequence(HitObject.Duration, true)) { this.FadeOut(transition_duration, Easing.Out); bodyContainer.ScaleTo(1.4f, transition_duration); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index b25b81c9af..08fd849fa6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100) + Duration = (original as IHasEndTime)?.Duration ?? 100 }; } } @@ -292,9 +292,9 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestHitObject : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } } private class DrawableTestHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 43e8d01297..c10c8dc30f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 9de311c9d7..4b0270064a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } public float X => 256; // Required for CatchBeatmapConverter diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9a60a0a75c..d8d90fddfa 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, comboOffset, endTime + Offset); + result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); } if (result == null) @@ -321,9 +321,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The spinner end time. + /// The spinner duration. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime); + protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration); /// /// Creates a legacy Hold-type hit object. @@ -331,8 +331,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The hold end time. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); + /// The hold end time. + protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 73192dc42e..cd2f9f88b8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } - public double EndTime + public double Duration { - get => StartTime + this.SpanCount() * Distance / Velocity; + get => this.SpanCount() * Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public double Velocity = 1; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index f94c4aaa75..bc64518f40 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -37,21 +37,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { X = position.X, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertHold { X = position.X, - EndTime = endTime + Duration = duration }; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 1d92d638dd..dcb66163e4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania { public float X { get; set; } - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 7dc13e27cd..b731f7c8d8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public float X { get; set; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index b95ec703b6..75ecab0b8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu return new ConvertSpinner { Position = position, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 8b21aab411..a231237077 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index db65a61c90..13e3e84c6a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -33,15 +33,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 8e28487f2f..0976106ec4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index bc7103c60d..5eb551e15c 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The time at which the HitObject ends. /// - [JsonIgnore] - double EndTime { get; set; } + double EndTime { get; } /// /// The duration of the HitObject. /// - double Duration { get; } + [JsonIgnore] + double Duration { get; set; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dd2f7a833e..d6fc17f358 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -296,7 +296,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (endTimeHitObject.EndTime == snappedTime) return; - endTimeHitObject.EndTime = snappedTime; + endTimeHitObject.Duration = snappedTime - hitObject.StartTime; break; } From dd7dbfd5488efddef6d56f897876ce6cba4eb32d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:38:39 +0900 Subject: [PATCH 037/123] Rename to IHasDuration --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/BananaShower.cs | 2 +- .../TestSceneNotes.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 6 ++--- .../Legacy/EndTimeObjectPatternGenerator.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 ++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Mania/ConvertHold.cs | 2 +- .../Objects/Legacy/Mania/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 2 +- .../Rulesets/Objects/Types/IHasDistance.cs | 2 +- .../Rulesets/Objects/Types/IHasDuration.cs | 24 +++++++++++++++++++ .../Rulesets/Objects/Types/IHasEndTime.cs | 20 ++++------------ .../Rulesets/Objects/Types/IHasRepeats.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 4 ++-- 27 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasDuration.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 27a9b63e9a..0de2060e2d 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 }.Yield(); - case IHasEndTime endTime: + case IHasDuration endTime: return new BananaShower { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 3a0b5ace53..04a995c77e 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class BananaShower : CatchHitObject, IHasEndTime + public class BananaShower : CatchHitObject, IHasDuration { public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index ea6a1e2e6a..dd5fd93710 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var obj in content.OfType()) { - if (!(obj.HitObject is IHasEndTime endTime)) + if (!(obj.HitObject is IHasDuration endTime)) continue; foreach (var nested in obj.NestedHitObjects) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 1c8116754f..32abf5e7f9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else { - float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; + float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) TargetColumns = 7; else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); @@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps var pattern = new Pattern(); - if (HitObject is IHasEndTime endTimeData) + if (HitObject is IHasDuration endTimeData) { pattern.Add(new HoldNote { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 907bed0d65..d5286a3779 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) { - endTime = (HitObject as IHasEndTime)?.EndTime ?? 0; + endTime = (HitObject as IHasDuration)?.EndTime ?? 0; } public override IEnumerable Generate() diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index e6f722a8a9..a100c9a58e 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// Represents a hit object which requires pressing, holding, and releasing a key. /// - public class HoldNote : ManiaHitObject, IHasEndTime + public class HoldNote : ManiaHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 060a3919bd..fcad356a1c 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 }.Yield(); - case IHasEndTime endTimeData: + case IHasDuration endTimeData: return new Spinner { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 7b1941b7f9..5d191119b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; // already hit or beyond the hittable end time. - if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime)) + if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime)) continue; switch (h) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 297a0fea79..3cad52faeb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods } // Keep wiggling sliders and spinners for their duration - if (!(osuObject is IHasEndTime endTime)) + if (!(osuObject is IHasDuration endTime)) return; amountWiggles = (int)(endTime.Duration / wiggle_duration); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0b8d03d118..418375c090 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Spinner : OsuHitObject, IHasEndTime + public class Spinner : OsuHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 1a47be2282..78550ed270 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index b4c09b884e..eeae6e79f8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { - public class Swell : TaikoHitObject, IHasEndTime + public class Swell : TaikoHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 08fd849fa6..bd7e894cf8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - Duration = (original as IHasEndTime)?.Duration ?? 100 + Duration = (original as IHasDuration)?.Duration ?? 100 }; } } @@ -290,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay #region HitObject - private class TestHitObject : ConvertHitObject, IHasEndTime + private class TestHitObject : ConvertHitObject, IHasDuration { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d7e83fa471..8c63ce6fcb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats } else { - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) addEndTimeData(writer, hitObject); writer.Write(getSampleBank(hitObject.Samples)); @@ -267,7 +267,7 @@ namespace osu.Game.Beatmaps.Formats type |= LegacyHitObjectType.Slider; break; - case IHasEndTime _: + case IHasDuration _: if (beatmap.BeatmapInfo.RulesetID == 3) type |= LegacyHitObjectType.Hold; else @@ -352,7 +352,7 @@ namespace osu.Game.Beatmaps.Formats private void addEndTimeData(TextWriter writer, HitObject hitObject) { - var endTimeData = (IHasEndTime)hitObject; + var endTimeData = (IHasDuration)hitObject; var type = getObjectType(hitObject); char suffix = ','; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8126311cbd..ac399e37c4 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps length = emptyLength; break; - case IHasEndTime endTime: + case IHasDuration endTime: length = endTime.EndTime + excess_length; break; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 6f9053d7cb..e2cc98813a 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -175,10 +175,10 @@ namespace osu.Game.Rulesets.Objects /// Returns the end time of this object. /// /// - /// This returns the where available, falling back to otherwise. + /// This returns the where available, falling back to otherwise. /// /// The object. /// The end time of this object. - public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; + public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 4b0270064a..014494ec54 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index dcb66163e4..2fa4766c1d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects.Legacy.Mania { - internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime + internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration { public float X { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index b731f7c8d8..c05aaceb9c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index a231237077..e9e5ca8c94 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 0976106ec4..1d5ecb1ef3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs index e7f552115e..b497ca5da3 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a positional length. /// - public interface IHasDistance : IHasEndTime + public interface IHasDistance : IHasDuration { /// /// The positional length of the HitObject. diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs new file mode 100644 index 0000000000..2433f9597e --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that ends at a different time than its start time. + /// + public interface IHasDuration + { + /// + /// The time at which the HitObject ends. + /// + double EndTime { get; } + + /// + /// The duration of the HitObject. + /// + [JsonIgnore] + double Duration { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 5eb551e15c..7395223c7e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,24 +1,12 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Newtonsoft.Json; +using System; namespace osu.Game.Rulesets.Objects.Types { - /// - /// A HitObject that ends at a different time than its start time. - /// - public interface IHasEndTime + [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 + public interface IHasEndTime : IHasDuration { - /// - /// The time at which the HitObject ends. - /// - double EndTime { get; } - - /// - /// The duration of the HitObject. - /// - [JsonIgnore] - double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 256b1f3963..7a3fb16196 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that spans some length. /// - public interface IHasRepeats : IHasEndTime + public interface IHasRepeats : IHasDuration { /// /// The amount of times the HitObject repeats. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index c817d84d5c..0dc3324559 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Cant use AddOnce() since the delegate is re-constructed every invocation private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { - if (hitObject.HitObject is IHasEndTime e) + if (hitObject.HitObject is IHasDuration e) { switch (direction.Value) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d6fc17f358..b95b3842b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline shadowComponents.Add(circle); - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) { DragBar dragBarUnderlay; Container extensionBar; @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline repeatHitObject.RepeatCount = proposedCount; break; - case IHasEndTime endTimeHitObject: + case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); if (endTimeHitObject.EndTime == snappedTime) From b2fad915898c445cfe8ef13888de52dc08d232d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 May 2020 20:33:12 +0900 Subject: [PATCH 038/123] Add swell and drumroll blueprints --- .../TaikoHitObjectComposer.cs | 155 +++++++++++++++++- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 6 +- 2 files changed, 149 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index d233cd5e7f..2bb037815e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -14,7 +14,9 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; @@ -37,7 +39,8 @@ namespace osu.Game.Rulesets.Taiko protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new HitCompositionTool(), - new DrumRollCompositionTool() + new DrumRollCompositionTool(), + new SwellCompositionTool() }; protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); @@ -157,6 +160,16 @@ namespace osu.Game.Rulesets.Taiko } } + public class SwellCompositionTool : HitObjectCompositionTool + { + public SwellCompositionTool() + : base(nameof(Swell)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); + } + public class DrumRollCompositionTool : HitObjectCompositionTool { public DrumRollCompositionTool() @@ -167,16 +180,110 @@ namespace osu.Game.Rulesets.Taiko public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } - public class DrumRollPlacementBlueprint : PlacementBlueprint + public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint { - private static DrumRoll drumRoll; - - public DrumRollPlacementBlueprint() - : base(drumRoll = new DrumRoll()) + public SwellPlacementBlueprint() + : base(new Swell()) { } } + public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint + { + public DrumRollPlacementBlueprint() + : base(new DrumRoll()) + { + } + } + + public class TaikoSpanPlacementBlueprint : PlacementBlueprint + { + private readonly HitPiece headPiece; + private readonly HitPiece tailPiece; + + private readonly LengthPiece lengthPiece; + + private readonly IHasDuration spanPlacementObject; + + public TaikoSpanPlacementBlueprint(HitObject hitObject) + : base(hitObject) + + { + spanPlacementObject = hitObject as IHasDuration; + + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + headPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }, + lengthPiece = new LengthPiece + { + Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT + }, + tailPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + } + }; + } + + private double originalStartTime; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button != MouseButton.Left) + return false; + + BeginPlacement(true); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (e.Button != MouseButton.Left) + return; + + base.OnMouseUp(e); + EndPlacement(true); + } + + public override void UpdatePosition(SnapResult result) + { + base.UpdatePosition(result); + + if (PlacementActive) + { + if (result.Time is double endTime) + { + if (endTime < originalStartTime) + { + HitObject.StartTime = endTime; + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.X = headPiece.X; + lengthPiece.Width = tailPiece.X - headPiece.X; + } + else + { + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.Width = tailPiece.X - headPiece.X; + } + } + } + else + { + lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + + if (result.Time is double startTime) + originalStartTime = HitObject.StartTime = startTime; + } + } + } + public class HitCompositionTool : HitObjectCompositionTool { public HitCompositionTool() @@ -221,10 +328,40 @@ namespace osu.Game.Rulesets.Taiko return false; } - public override void UpdatePosition(SnapResult snapResult) + public override void UpdatePosition(SnapResult result) { - piece.Position = ToLocalSpace(snapResult.ScreenSpacePosition); - base.UpdatePosition(snapResult); + piece.Position = ToLocalSpace(result.ScreenSpacePosition); + base.UpdatePosition(result); + } + } + + public class LengthPiece : CompositeDrawable + { + public LengthPiece() + { + Origin = Anchor.CentreLeft; + + InternalChild = new Container + { + Masking = true, + Colour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.X, + Height = 8, + }, + new Box + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 8, + } + } + }; } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index bb89ba8311..02d5955ae6 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -87,11 +87,11 @@ namespace osu.Game.Rulesets.Edit /// /// Updates the position of this to a new screen-space position. /// - /// The snap result information. - public virtual void UpdatePosition(SnapResult snapResult) + /// The snap result information. + public virtual void UpdatePosition(SnapResult result) { if (!PlacementActive) - HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current; + HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; } /// From 597f2848054977d21c2af3a68dff5df82a18c516 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 11:46:08 +0900 Subject: [PATCH 039/123] Tidy up and complete xmldoc for HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 89 ++++++++++++++++----- osu.sln.DotSettings | 5 +- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 38576e02a0..c956439eb2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - public override Playfield Playfield => drawableRulesetWrapper.Playfield; - private DrawableEditRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -61,7 +59,6 @@ namespace osu.Game.Rulesets.Edit protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -137,6 +134,49 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + public override Playfield Playfield => drawableRulesetWrapper.Playfield; + + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + + /// + /// Defines all available composition tools, listed on the left side of the editor screen as button controls. + /// This should usually define one tool for each type used in the target ruleset. + /// + /// + /// A "select" tool is automatically added as the first tool. + /// + protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. + /// + protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + + /// + /// Construct a drawable ruleset for the provided ruleset. + /// + /// + /// Can be overridden to add editor-specific logical changes to a 's standard . + /// For example, hit animations or judgement logic may be changed to give a better editor user experience. + /// + /// The ruleset used to construct its drawable counterpart. + /// The loaded beatmap. + /// The mods to be applied. + /// An editor-relevant . + protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); + + #region Tool selection logic + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key >= Key.Number1 && e.Key <= Key.Number9) @@ -153,13 +193,6 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { if (EditorBeatmap.SelectedHitObjects.Any()) @@ -179,15 +212,9 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.Clear(); } - public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + #endregion - public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - - protected abstract IReadOnlyList CompositionTools { get; } - - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); - - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null); + #region IPlacementHandler public void BeginPlacement(HitObject hitObject) { @@ -209,6 +236,17 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); + #endregion + + #region IPositionSnapProvider + + /// + /// Retrieve the relevant at a specified screen-space position. + /// In cases where a ruleset doesn't require custom logic (due to nested playfields, for example) + /// this will return the ruleset's main playfield. + /// + /// The screen-space position to query. + /// The most relevant . protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) @@ -257,8 +295,14 @@ namespace osu.Game.Rulesets.Edit return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } + + #endregion } + /// + /// A non-generic definition of a HitObject composer class. + /// Generally used to access certain methods without requiring a generic type for . + /// [Cached(typeof(HitObjectComposer))] [Cached(typeof(IPositionSnapProvider))] public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider @@ -268,10 +312,13 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; } + /// + /// The target ruleset's playfield. + /// public abstract Playfield Playfield { get; } /// - /// All the s. + /// All s in currently loaded beatmap. /// public abstract IEnumerable HitObjects { get; } @@ -280,6 +327,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } + #region IPositionSnapProvider + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); @@ -291,5 +340,7 @@ namespace osu.Game.Rulesets.Edit public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); + + #endregion } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e3b64c03b9..b9fc3de734 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1,4 +1,4 @@ - + True True True @@ -905,14 +905,17 @@ private void load() True True True + True True True True True True True + True True True True + True True True From 3e973c176f0ccf506b2b07c94c5bf317a94f0b34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 11:59:21 +0900 Subject: [PATCH 040/123] Remove unnecessary overrides --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 2bb037815e..16111f4abf 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -9,17 +9,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Graphics; @@ -29,8 +26,6 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoHitObjectComposer : HitObjectComposer { - private DrawableTaikoRuleset drawableRuleset; - public TaikoHitObjectComposer(Ruleset ruleset) : base(ruleset) { @@ -43,12 +38,7 @@ namespace osu.Game.Rulesets.Taiko new SwellCompositionTool() }; - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); - - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) - { - return drawableRuleset = new DrawableTaikoRuleset(ruleset, beatmap, mods); - } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(Playfield.AllHitObjects); } public class TaikoBlueprintContainer : ComposeBlueprintContainer From 590931b17cfaccafd3bf8f0db168f4082a1dd8be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 12:20:50 +0900 Subject: [PATCH 041/123] Pass hitobjects as a parameter to CreateBlueprintContainer --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 +++- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 +++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 10d344242c..7e2469a794 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -88,7 +89,8 @@ namespace osu.Game.Rulesets.Mania.Edit return drawableRuleset; } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ManiaBlueprintContainer(hitObjects); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index de5c1e54d7..37019a7a05 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new OsuBlueprintContainer(hitObjects); private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c956439eb2..8b9f531417 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(BlueprintContainer = CreateBlueprintContainer()) + .WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) } } }, @@ -159,7 +159,9 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + /// A live collection of all s in the editor beatmap. + protected virtual ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ComposeBlueprintContainer(hitObjects); /// /// Construct a drawable ruleset for the provided ruleset. From 7b52faa76d086ce98dd455e1475448e09c825870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 12:45:09 +0900 Subject: [PATCH 042/123] Update override --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 16111f4abf..29e0e3a3c0 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -38,7 +38,8 @@ namespace osu.Game.Rulesets.Taiko new SwellCompositionTool() }; - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new TaikoBlueprintContainer(hitObjects); } public class TaikoBlueprintContainer : ComposeBlueprintContainer From 7f8f41715d31f74234e019b4de049c9e0acacc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 13:15:43 +0900 Subject: [PATCH 043/123] Remove stray whitespace --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 29e0e3a3c0..b34a8e75cc 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -286,7 +286,6 @@ namespace osu.Game.Rulesets.Taiko } public class HitPlacementBlueprint : PlacementBlueprint - { private readonly HitPiece piece; From 3b6619a3608e7d60c7c22b541478ec62c4442335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:11:26 +0900 Subject: [PATCH 044/123] Flip direction to avoid breaking other usages --- osu.Game/Rulesets/Objects/Types/IHasDuration.cs | 16 +++++++++++++--- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs index 2433f9597e..185fd5977b 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -8,17 +8,27 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that ends at a different time than its start time. /// - public interface IHasDuration +#pragma warning disable 618 + public interface IHasDuration : IHasEndTime +#pragma warning restore 618 { + double IHasEndTime.EndTime + { + get => EndTime; + set => Duration = (Duration - EndTime) + value; + } + + double IHasEndTime.Duration => Duration; + /// /// The time at which the HitObject ends. /// - double EndTime { get; } + new double EndTime { get; } /// /// The duration of the HitObject. /// [JsonIgnore] - double Duration { get; set; } + new double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 7395223c7e..c3769c5909 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -2,11 +2,25 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; namespace osu.Game.Rulesets.Objects.Types { + /// + /// A HitObject that ends at a different time than its start time. + /// [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 - public interface IHasEndTime : IHasDuration + public interface IHasEndTime { + /// + /// The time at which the HitObject ends. + /// + [JsonIgnore] + double EndTime { get; set; } + + /// + /// The duration of the HitObject. + /// + double Duration { get; } } } From da289c474e2ceba450437a20572fa7d7d2b902f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:40:10 +0900 Subject: [PATCH 045/123] Split files out --- .../TestSceneTaikoHitObjectComposer.cs | 1 + .../Blueprints/DrumRollPlacementBlueprint.cs | 12 + .../Edit/Blueprints/HitPiece.cs | 32 ++ .../Edit/Blueprints/HitPlacementBlueprint.cs | 49 +++ .../Edit/Blueprints/LengthPiece.cs | 37 ++ .../Blueprints/SwellPlacementBlueprint.cs | 12 + .../Blueprints/TaikoSpanPlacementBlueprint.cs | 101 +++++ .../Edit/DrumRollCompositionTool.cs | 17 + .../Edit/HitCompositionTool.cs | 17 + .../Edit/SwellCompositionTool.cs | 17 + .../Edit/TaikoBlueprintContainer.cs | 20 + .../Edit/TaikoHitObjectComposer.cs | 30 ++ .../Edit/TaikoSelectionBlueprint.cs | 41 ++ .../Edit/TaikoSelectionHandler.cs | 80 ++++ .../TaikoHitObjectComposer.cs | 382 ------------------ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 16 files changed, 467 insertions(+), 382 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs delete mode 100644 osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs index b5ee33fa8e..34d5fdf857 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs new file mode 100644 index 0000000000..2f086891a9 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs @@ -0,0 +1,12 @@ +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint + { + public DrumRollPlacementBlueprint() + : base(new DrumRoll()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs new file mode 100644 index 0000000000..992fba1e29 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs @@ -0,0 +1,32 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class HitPiece : CompositeDrawable + { + public HitPiece() + { + Origin = Anchor.Centre; + + InternalChild = new CircularContainer + { + Masking = true, + BorderThickness = 10, + BorderColour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs new file mode 100644 index 0000000000..c21aed32e8 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -0,0 +1,49 @@ +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class HitPlacementBlueprint : PlacementBlueprint + { + private readonly HitPiece piece; + + private static Hit hit; + + public HitPlacementBlueprint() + : base(hit = new Hit()) + { + InternalChild = piece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + switch (e.Button) + { + case MouseButton.Left: + hit.Type = HitType.Centre; + EndPlacement(true); + return true; + + case MouseButton.Right: + hit.Type = HitType.Rim; + EndPlacement(true); + return true; + } + + return false; + } + + public override void UpdatePosition(SnapResult result) + { + piece.Position = ToLocalSpace(result.ScreenSpacePosition); + base.UpdatePosition(result); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs new file mode 100644 index 0000000000..2139a852b2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs @@ -0,0 +1,37 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class LengthPiece : CompositeDrawable + { + public LengthPiece() + { + Origin = Anchor.CentreLeft; + + InternalChild = new Container + { + Masking = true, + Colour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.X, + Height = 8, + }, + new Box + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 8, + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs new file mode 100644 index 0000000000..180bf26f34 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs @@ -0,0 +1,12 @@ +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint + { + public SwellPlacementBlueprint() + : base(new Swell()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs new file mode 100644 index 0000000000..b08cc68225 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -0,0 +1,101 @@ +using System; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class TaikoSpanPlacementBlueprint : PlacementBlueprint + { + private readonly HitPiece headPiece; + private readonly HitPiece tailPiece; + + private readonly LengthPiece lengthPiece; + + private readonly IHasDuration spanPlacementObject; + + public TaikoSpanPlacementBlueprint(HitObject hitObject) + : base(hitObject) + + { + spanPlacementObject = hitObject as IHasDuration; + + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + headPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }, + lengthPiece = new LengthPiece + { + Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT + }, + tailPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + } + }; + } + + private double originalStartTime; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button != MouseButton.Left) + return false; + + BeginPlacement(true); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (e.Button != MouseButton.Left) + return; + + base.OnMouseUp(e); + EndPlacement(true); + } + + public override void UpdatePosition(SnapResult result) + { + base.UpdatePosition(result); + + if (PlacementActive) + { + if (result.Time is double endTime) + { + if (endTime < originalStartTime) + { + HitObject.StartTime = endTime; + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.X = headPiece.X; + lengthPiece.Width = tailPiece.X - headPiece.X; + } + else + { + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.Width = tailPiece.X - headPiece.X; + } + } + } + else + { + lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + + if (result.Time is double startTime) + originalStartTime = HitObject.StartTime = startTime; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs new file mode 100644 index 0000000000..c17e22180a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -0,0 +1,17 @@ +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class DrumRollCompositionTool : HitObjectCompositionTool + { + public DrumRollCompositionTool() + : base(nameof(DrumRoll)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs new file mode 100644 index 0000000000..7e8f245613 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -0,0 +1,17 @@ +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class HitCompositionTool : HitObjectCompositionTool + { + public HitCompositionTool() + : base(nameof(Hit)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs new file mode 100644 index 0000000000..aa96a397d0 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -0,0 +1,17 @@ +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class SwellCompositionTool : HitObjectCompositionTool + { + public SwellCompositionTool() + : base(nameof(Swell)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs new file mode 100644 index 0000000000..b7cda04705 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoBlueprintContainer : ComposeBlueprintContainer + { + public TaikoBlueprintContainer(IEnumerable hitObjects) + : base(hitObjects) + { + } + + protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); + + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => + new TaikoSelectionBlueprint(hitObject); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs new file mode 100644 index 0000000000..7ad40903d2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoHitObjectComposer : HitObjectComposer + { + public TaikoHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + { + new HitCompositionTool(), + new DrumRollCompositionTool(), + new SwellCompositionTool() + }; + + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new TaikoBlueprintContainer(hitObjects); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs new file mode 100644 index 0000000000..e3797a5fa6 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs @@ -0,0 +1,41 @@ +// 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.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoSelectionBlueprint : OverlaySelectionBlueprint + { + public TaikoSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + + AddInternal(new HitPiece + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft + }); + } + + protected override void Update() + { + base.Update(); + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight)); + + Size = bottomRight - topLeft; + Position = topLeft; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs new file mode 100644 index 0000000000..eebf6980fe --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.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 System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoSelectionHandler : SelectionHandler + { + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + { + if (selection.All(s => s.HitObject is Hit)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Rim", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.Type = HitType.Rim; + break; + + case TernaryState.False: + h.Type = HitType.Centre; + break; + } + } + }) + { + State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } + }; + } + + if (selection.All(s => s.HitObject is TaikoHitObject)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Strong", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.IsStrong = true; + break; + + case TernaryState.False: + h.IsStrong = false; + break; + } + + EditorBeatmap?.UpdateHitObject(h); + } + }) + { + State = { Value = getTernaryState(hits, h => h.IsStrong) } + }; + } + } + + private TernaryState getTernaryState(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs deleted file mode 100644 index b34a8e75cc..0000000000 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ /dev/null @@ -1,382 +0,0 @@ -// 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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Rulesets.Taiko -{ - public class TaikoHitObjectComposer : HitObjectComposer - { - public TaikoHitObjectComposer(Ruleset ruleset) - : base(ruleset) - { - } - - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] - { - new HitCompositionTool(), - new DrumRollCompositionTool(), - new SwellCompositionTool() - }; - - protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) - => new TaikoBlueprintContainer(hitObjects); - } - - public class TaikoBlueprintContainer : ComposeBlueprintContainer - { - public TaikoBlueprintContainer(IEnumerable hitObjects) - : base(hitObjects) - { - } - - protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); - - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => - new TaikoSelectionBlueprint(hitObject); - } - - public class TaikoSelectionHandler : SelectionHandler - { - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) - { - if (selection.All(s => s.HitObject is Hit)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Rim", action: state => - { - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.Type = HitType.Rim; - break; - - case TernaryState.False: - h.Type = HitType.Centre; - break; - } - } - }) - { - State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } - }; - } - - if (selection.All(s => s.HitObject is TaikoHitObject)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Strong", action: state => - { - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.IsStrong = true; - break; - - case TernaryState.False: - h.IsStrong = false; - break; - } - - EditorBeatmap?.UpdateHitObject(h); - } - }) - { - State = { Value = getTernaryState(hits, h => h.IsStrong) } - }; - } - } - - private TernaryState getTernaryState(IEnumerable selection, Func func) - { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - - return TernaryState.False; - } - } - - public class TaikoSelectionBlueprint : OverlaySelectionBlueprint - { - public TaikoSelectionBlueprint(DrawableHitObject hitObject) - : base(hitObject) - { - RelativeSizeAxes = Axes.None; - - AddInternal(new HitPiece - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopLeft - }); - } - - protected override void Update() - { - base.Update(); - - // Move the rectangle to cover the hitobjects - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); - - topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight)); - - Size = bottomRight - topLeft; - Position = topLeft; - } - } - - public class SwellCompositionTool : HitObjectCompositionTool - { - public SwellCompositionTool() - : base(nameof(Swell)) - { - } - - public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); - } - - public class DrumRollCompositionTool : HitObjectCompositionTool - { - public DrumRollCompositionTool() - : base(nameof(DrumRoll)) - { - } - - public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); - } - - public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint - { - public SwellPlacementBlueprint() - : base(new Swell()) - { - } - } - - public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint - { - public DrumRollPlacementBlueprint() - : base(new DrumRoll()) - { - } - } - - public class TaikoSpanPlacementBlueprint : PlacementBlueprint - { - private readonly HitPiece headPiece; - private readonly HitPiece tailPiece; - - private readonly LengthPiece lengthPiece; - - private readonly IHasDuration spanPlacementObject; - - public TaikoSpanPlacementBlueprint(HitObject hitObject) - : base(hitObject) - - { - spanPlacementObject = hitObject as IHasDuration; - - RelativeSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - headPiece = new HitPiece - { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) - }, - lengthPiece = new LengthPiece - { - Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT - }, - tailPiece = new HitPiece - { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) - } - }; - } - - private double originalStartTime; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button != MouseButton.Left) - return false; - - BeginPlacement(true); - return true; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - if (e.Button != MouseButton.Left) - return; - - base.OnMouseUp(e); - EndPlacement(true); - } - - public override void UpdatePosition(SnapResult result) - { - base.UpdatePosition(result); - - if (PlacementActive) - { - if (result.Time is double endTime) - { - if (endTime < originalStartTime) - { - HitObject.StartTime = endTime; - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); - headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.X = headPiece.X; - lengthPiece.Width = tailPiece.X - headPiece.X; - } - else - { - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); - tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.Width = tailPiece.X - headPiece.X; - } - } - } - else - { - lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - - if (result.Time is double startTime) - originalStartTime = HitObject.StartTime = startTime; - } - } - } - - public class HitCompositionTool : HitObjectCompositionTool - { - public HitCompositionTool() - : base(nameof(Hit)) - { - } - - public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); - } - - public class HitPlacementBlueprint : PlacementBlueprint - { - private readonly HitPiece piece; - - private static Hit hit; - - public HitPlacementBlueprint() - : base(hit = new Hit()) - { - InternalChild = piece = new HitPiece - { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - switch (e.Button) - { - case MouseButton.Left: - hit.Type = HitType.Centre; - EndPlacement(true); - return true; - - case MouseButton.Right: - hit.Type = HitType.Rim; - EndPlacement(true); - return true; - } - - return false; - } - - public override void UpdatePosition(SnapResult result) - { - piece.Position = ToLocalSpace(result.ScreenSpacePosition); - base.UpdatePosition(result); - } - } - - public class LengthPiece : CompositeDrawable - { - public LengthPiece() - { - Origin = Anchor.CentreLeft; - - InternalChild = new Container - { - Masking = true, - Colour = Color4.Yellow, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.X, - Height = 8, - }, - new Box - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 8, - } - } - }; - } - } - - public class HitPiece : CompositeDrawable - { - public HitPiece() - { - Origin = Anchor.Centre; - - InternalChild = new CircularContainer - { - Masking = true, - BorderThickness = 10, - BorderColour = Color4.Yellow, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } - }; - } - } -} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7be16471b4..4cdd1fbc24 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Skinning; From e0aae15c0a313549f29a610b0bc697a1fd2c9435 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:40:23 +0900 Subject: [PATCH 046/123] Hard type incoming ruleset --- osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 7ad40903d2..cdc9672a8e 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoHitObjectComposer : HitObjectComposer { - public TaikoHitObjectComposer(Ruleset ruleset) + public TaikoHitObjectComposer(TaikoRuleset ruleset) : base(ruleset) { } From b068992a15f43237bfb3693e0fca4daa63922fd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 18:58:34 +0900 Subject: [PATCH 047/123] Add missing licence headers --- .../Edit/Blueprints/DrumRollPlacementBlueprint.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs | 3 +++ .../Edit/Blueprints/HitPlacementBlueprint.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs | 3 +++ .../Edit/Blueprints/SwellPlacementBlueprint.cs | 3 +++ .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs | 3 +++ 10 files changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs index 2f086891a9..eb07ce7635 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs index 992fba1e29..b02e3aa9ba 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs @@ -1,3 +1,6 @@ +// 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.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index c21aed32e8..c5191ab241 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -1,3 +1,6 @@ +// 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.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs index 2139a852b2..6b651fd739 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs @@ -1,3 +1,6 @@ +// 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.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs index 180bf26f34..95fa82a0f2 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index b08cc68225..7f96b5a46e 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -1,3 +1,6 @@ +// 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 osu.Framework.Graphics; using osu.Framework.Input.Events; diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index c17e22180a..bf77c76670 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index 7e8f245613..e877cf6240 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index aa96a397d0..a6191fcedc 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index b7cda04705..36227b0798 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System.Collections.Generic; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; From affad4724867f9cda7a5d707a1edca3943e0c0a0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 29 May 2020 19:44:53 +0300 Subject: [PATCH 048/123] Fix genre/language search doesn't work --- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 13 ++++++++++--- .../BeatmapListing/BeatmapListingFilterControl.cs | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 0c3272c7de..ce8b40aa30 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,7 +27,14 @@ namespace osu.Game.Online.API.Requests private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending) + public SearchBeatmapSetsRequest(string query, + RulesetInfo ruleset, + Cursor cursor = null, + SearchCategory searchCategory = SearchCategory.Any, + SortCriteria sortCriteria = SortCriteria.Ranked, + SortDirection sortDirection = SortDirection.Descending, + SearchGenre genre = SearchGenre.Any, + SearchLanguage language = SearchLanguage.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -36,8 +43,8 @@ namespace osu.Game.Online.API.Requests SearchCategory = searchCategory; SortCriteria = sortCriteria; SortDirection = sortDirection; - Genre = SearchGenre.Any; - Language = SearchLanguage.Any; + Genre = genre; + Language = language; } protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 41c99d5d03..0ead5cc226 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -177,7 +177,9 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse?.Cursor, searchControl.Category.Value, sortControl.Current.Value, - sortControl.SortDirection.Value); + sortControl.SortDirection.Value, + searchControl.Genre.Value, + searchControl.Language.Value); getSetsRequest.Success += response => { From 9aa54ed89e059349ca257fbb5b27e8ceee5f835e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 29 May 2020 19:53:32 +0300 Subject: [PATCH 049/123] Fix serach control background never being updated --- .../Overlays/BeatmapListing/BeatmapListingFilterControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 0ead5cc226..494a0df8f8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -188,6 +188,9 @@ namespace osu.Game.Overlays.BeatmapListing if (sets.Count == 0) noMoreResults = true; + if (CurrentPage == 0) + searchControl.BeatmapSet = sets.FirstOrDefault(); + lastResponse = response; getSetsRequest = null; From 11057cd6a83859744ee3057112eef5a8b3daefca Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 29 May 2020 21:43:31 +0300 Subject: [PATCH 050/123] CI fix --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index ce8b40aa30..dde45b5aeb 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,7 +27,8 @@ namespace osu.Game.Online.API.Requests private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, + public SearchBeatmapSetsRequest( + string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, From 816f721f3dffb36ed0d6279237e9137bd791dba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 May 2020 15:24:37 +0900 Subject: [PATCH 051/123] Move selection blueprint to correct namespace --- .../Edit/{ => Blueprints}/TaikoSelectionBlueprint.cs | 3 +-- osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Taiko/Edit/{ => Blueprints}/TaikoSelectionBlueprint.cs (93%) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs similarity index 93% rename from osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs rename to osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs index e3797a5fa6..62f69122cc 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs @@ -4,10 +4,9 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osuTK; -namespace osu.Game.Rulesets.Taiko.Edit +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { public class TaikoSelectionBlueprint : OverlaySelectionBlueprint { diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index 36227b0798..35227b3c64 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Taiko.Edit From 82fe99cf4a54b90a473d43855238f2aa5ae58d65 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 May 2020 02:18:07 +0300 Subject: [PATCH 052/123] Replace any potential usage of Environment.CurrentDirectory with a new RuntimeInfo.StartupDirectory Using `Environment.CurrentDirectory` for storing / reading files is dangerous as the current directory is mutable and can be changed when performing a certain operation (like opening solutions in roslyn type reference builder for example). --- osu.Desktop/Program.cs | 4 +--- .../NonVisual/CustomDataDirectoryTest.cs | 16 ++++++++-------- osu.Game.Tests/Resources/TestResources.cs | 5 +++-- osu.Game/Rulesets/RulesetStore.cs | 5 +++-- osu.Game/Tests/Visual/OsuTestScene.cs | 3 ++- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index bd91bcc933..285a813d97 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -33,13 +33,11 @@ namespace osu.Desktop if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args { var importer = new ArchiveImportIPCChannel(host); - // Restore the cwd so relative paths given at the command line work correctly - Directory.SetCurrentDirectory(cwd); foreach (var file in args) { Console.WriteLine(@"Importing {0}", file); - if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) + if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) throw new TimeoutException(@"IPC took too long to send"); } diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 743c924bbd..1f6e92a535 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Platform; @@ -35,8 +36,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestDefaultDirectory)); - + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestDefaultDirectory))); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); } finally @@ -46,17 +46,17 @@ namespace osu.Game.Tests.NonVisual } } - private string customPath => Path.Combine(Environment.CurrentDirectory, "custom-path"); + private string customPath { get; } = RuntimeInfo.StartupStorage.GetFullPath("custom-path"); [Test] public void TestCustomDirectory() { using (var host = new HeadlessGameHost(nameof(TestCustomDirectory))) { - string headlessPrefix = Path.Combine("headless", nameof(TestCustomDirectory)); + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestCustomDirectory))); // need access before the game has constructed its own storage yet. - Storage storage = new DesktopStorage(headlessPrefix, host); + Storage storage = new DesktopStorage(defaultStorageLocation, host); // manual cleaning so we can prepare a config file. storage.DeleteDirectory(string.Empty); @@ -84,10 +84,10 @@ namespace osu.Game.Tests.NonVisual { using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup))) { - string headlessPrefix = Path.Combine("headless", nameof(TestSubDirectoryLookup)); + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestSubDirectoryLookup))); // need access before the game has constructed its own storage yet. - Storage storage = new DesktopStorage(headlessPrefix, host); + Storage storage = new DesktopStorage(defaultStorageLocation, host); // manual cleaning so we can prepare a config file. storage.DeleteDirectory(string.Empty); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.NonVisual // for testing nested files are not ignored (only top level) host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache"); - string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestMigration)); + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestMigration))); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 8b892fbb2f..33b580f68f 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -3,6 +3,7 @@ using System.IO; using NUnit.Framework; +using osu.Framework; using osu.Framework.IO.Stores; namespace osu.Game.Tests.Resources @@ -20,10 +21,10 @@ namespace osu.Game.Tests.Resources var temp = Path.GetTempFileName() + ".osz"; using (var stream = GetTestBeatmapStream(virtualTrack)) - using (var newFile = File.Create(temp)) + using (var newFile = RuntimeInfo.StartupStorage.GetStream(temp, FileAccess.Write)) stream.CopyTo(newFile); - Assert.IsTrue(File.Exists(temp)); + Assert.IsTrue(RuntimeInfo.StartupStorage.Exists(temp)); return temp; } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index b3026bf2b7..5c49141064 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using osu.Framework; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; @@ -153,14 +154,14 @@ namespace osu.Game.Rulesets { try { - string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll"); + var files = RuntimeInfo.StartupStorage.GetFiles($"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); } catch (Exception e) { - Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}"); + Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}"); } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 5dc8714c07..2672022720 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -118,7 +119,7 @@ namespace osu.Game.Tests.Visual } } - localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); + localStorage = new Lazy(() => RuntimeInfo.StartupStorage.GetStorageForDirectory($"{GetType().Name}-{Guid.NewGuid()}")); } [Resolved] From b06017dbf16a868f353226c1bed6261bed79308c Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 31 May 2020 11:28:54 +0800 Subject: [PATCH 053/123] supress horizontal scaling of left-and-right stages --- osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 7680526ac4..f177284399 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mania.Skinning base.Update(); if (leftSprite?.Height > 0) - leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height); + leftSprite.Scale = new Vector2(1, DrawHeight / leftSprite.Height); if (rightSprite?.Height > 0) - rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height); + rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height); } } } From f2dadeeeb5bbd96c2ff9e62f3b09464eb1c7ffc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 13:50:42 +0900 Subject: [PATCH 054/123] Allow horizontal scroll on results screen when not hovering expanded panel --- osu.Game/Screens/Ranking/ScorePanelList.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 18db3f2af4..1142297274 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -1,6 +1,7 @@ // 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.Bindables; @@ -107,6 +108,9 @@ namespace osu.Game.Screens.Ranking // Find the panel corresponding to the new score. expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); + // handle horizontal scroll only when not hovering the expanded panel. + scroll.HandleScroll = () => expandedPanel?.IsHovered != true; + if (expandedPanel == null) return; @@ -166,6 +170,11 @@ namespace osu.Game.Screens.Ranking /// public float? InstantScrollTarget; + /// + /// Whether this container should handle scroll trigger events. + /// + public Func HandleScroll; + protected override void UpdateAfterChildren() { if (InstantScrollTarget != null) @@ -177,9 +186,9 @@ namespace osu.Game.Screens.Ranking base.UpdateAfterChildren(); } - public override bool HandlePositionalInput => false; + public override bool HandlePositionalInput => HandleScroll(); - public override bool HandleNonPositionalInput => false; + public override bool HandleNonPositionalInput => HandleScroll(); } } } From e43217f5797c4699511078fbe3e3550a85b5a7b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 20:01:13 +0900 Subject: [PATCH 055/123] Add test resources --- .../Resources/special-skin/mania-stage-left.png | Bin 0 -> 165 bytes .../Resources/special-skin/mania-stage-right.png | Bin 0 -> 899 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png new file mode 100644 index 0000000000000000000000000000000000000000..03ca371c4e26972982544c5b8f7b02a85da9a823 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#S9F3${@^GvDCf{D9B#o z>Fdh=gqe+pPssgM@hqT_WQl7;iF1B#Zfaf$gL6@8Vo7R>LV0FMhJw4NZ$Nk>pEyvF zyr+v}h{pNkHS5=>EAW|^m>2+o;RFsh)1E8|D=7xCTxMCBnie0RQU*^~KbLh*2~7Y- CL?;CR literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png new file mode 100644 index 0000000000000000000000000000000000000000..45b7be025591b85e6849824f181672905d129933 GIT binary patch literal 899 zcmZ`%OK;Oa5FY1IO4N$d6BHyYw?YLq-XwrTEITw!ATg>-6jCWI9B`9OWAG!gH^fMs zI20jqK%6;5NL)DdA0UCaqe%P;#E~l!?AobQT54-|W@oDM2f3eKxn)?okJ#UJ$W5jWM2romOJPeYQMhd6`KAGifb20Fl9?n0#3 zx#ck?2Jq5=#2B+pB~w?}7RkihJvc=z=jnPg0!6zSQfU)AsPv0_c*2zg-rmt793JK;hyD?z&F8uXqz z>tP`)qhq#lFTr$&yxmMdzx*tmJN_4`a>W_2_g!Taje r`qIzU@0rIC|JptGdXHOvF!Njj=r1MTeKD0y_QurP8|D4AM=$;YKmXm5 literal 0 HcmV?d00001 From 81b8898272fa1bf325afb85236bb0020be65667c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 22:30:55 +0900 Subject: [PATCH 056/123] Fix incorrect type cast in encoder --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d7e83fa471..ab1b8aecfd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -233,9 +233,9 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); - if (hitObject is IHasPathWithRepeats curveData) + if (hitObject is IHasPath path) { - addPathData(writer, curveData, position); + addPathData(writer, path, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else From 19be111da098a03227d645ee13f75b3b42b74814 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 22:33:10 +0900 Subject: [PATCH 057/123] Move incorrect placed full stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c956439eb2..6dd6e6815b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Edit /// The ruleset used to construct its drawable counterpart. /// The loaded beatmap. /// The mods to be applied. - /// An editor-relevant . + /// An editor-relevant . protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); From e688033967bbd5000f0f515d8e63ccafa1ded00d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 22:39:03 +0900 Subject: [PATCH 058/123] Fix incorrect xmldoc --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d8d90fddfa..9e936c7717 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The hold end time. + /// The hold duration. protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) From 2c6887e610dec2babe5f1436b2406b31547b7a15 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 May 2020 19:49:03 +0300 Subject: [PATCH 059/123] Remove unnecessary use of and remove StartupStorage --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 10 +++++----- osu.Game/Rulesets/RulesetStore.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 1f6e92a535..f3d54d876a 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestDefaultDirectory))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory)); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); } finally @@ -46,14 +46,14 @@ namespace osu.Game.Tests.NonVisual } } - private string customPath { get; } = RuntimeInfo.StartupStorage.GetFullPath("custom-path"); + private string customPath => Path.Combine(RuntimeInfo.StartupDirectory, "custom-path"); [Test] public void TestCustomDirectory() { using (var host = new HeadlessGameHost(nameof(TestCustomDirectory))) { - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestCustomDirectory))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); // need access before the game has constructed its own storage yet. Storage storage = new DesktopStorage(defaultStorageLocation, host); @@ -84,7 +84,7 @@ namespace osu.Game.Tests.NonVisual { using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup))) { - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestSubDirectoryLookup))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestSubDirectoryLookup)); // need access before the game has constructed its own storage yet. Storage storage = new DesktopStorage(defaultStorageLocation, host); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.NonVisual // for testing nested files are not ignored (only top level) host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache"); - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestMigration))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration)); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5c49141064..10b6edca8c 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets { try { - var files = RuntimeInfo.StartupStorage.GetFiles($"{ruleset_library_prefix}.*.dll"); + var files = Directory.GetFiles(Path.Combine(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll")); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 2672022720..632d668a01 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual } } - localStorage = new Lazy(() => RuntimeInfo.StartupStorage.GetStorageForDirectory($"{GetType().Name}-{Guid.NewGuid()}")); + localStorage = new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } [Resolved] From 53b58910c3d0faacf969685c45ba5620e90d5917 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Jun 2020 14:27:39 +0900 Subject: [PATCH 060/123] Invert interface definition --- osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index fba0fd7aff..342b0ec1f6 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs @@ -9,12 +9,14 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// - public interface IHasPathWithRepeats : IHasPath, IHasRepeats +#pragma warning disable 618 + public interface IHasPathWithRepeats : IHasCurve +#pragma warning restore 618 { } [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 - public interface IHasCurve : IHasPathWithRepeats + public interface IHasCurve : IHasPath, IHasRepeats { } From cac6e93575890d31ea6e4276d93b3b4698ea440c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Jun 2020 15:10:22 +0900 Subject: [PATCH 061/123] Restore original IHasCurve implementation --- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 5 +- osu.Game/Rulesets/Objects/Types/IHasCurve.cs | 55 +++++++++++++++++++ .../Objects/Types/IHasPathWithRepeats.cs | 11 +--- 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasCurve.cs diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 73192dc42e..c946e43df3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,10 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, +#pragma warning disable 618 + IHasCurve +#pragma warning restore 618 { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs new file mode 100644 index 0000000000..26f50ffa31 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -0,0 +1,55 @@ +// 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 osuTK; + +namespace osu.Game.Rulesets.Objects.Types +{ + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public interface IHasCurve : IHasDistance, IHasRepeats + { + /// + /// The curve. + /// + SliderPath Path { get; } + } + +#pragma warning disable 618 + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public static class HasCurveExtensions + { + /// + /// Computes the position on the curve relative to how much of the has been completed. + /// + /// The curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// The position on the curve. + public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + => obj.Path.PositionAt(obj.ProgressAt(progress)); + + /// + /// Computes the progress along the curve relative to how much of the has been completed. + /// + /// The curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + public static double ProgressAt(this IHasCurve obj, double progress) + { + double p = progress * obj.SpanCount() % 1; + if (obj.SpanAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + /// + /// Determines which span of the curve the progress point is on. + /// + /// The curve. + /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + /// [0, SpanCount) where 0 is the first run. + public static int SpanAt(this IHasCurve obj, double progress) + => (int)(progress * obj.SpanCount()); + } +#pragma warning restore 618 +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index 342b0ec1f6..279946b44e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs @@ -1,7 +1,6 @@ // 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 osuTK; namespace osu.Game.Rulesets.Objects.Types @@ -9,14 +8,8 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// -#pragma warning disable 618 - public interface IHasPathWithRepeats : IHasCurve -#pragma warning restore 618 - { - } - - [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 - public interface IHasCurve : IHasPath, IHasRepeats + // ReSharper disable once RedundantExtendsListEntry + public interface IHasPathWithRepeats : IHasPath, IHasRepeats { } From 7a9ed78527d1f9c02d7e3509011cc6be5a8ec60b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Jun 2020 11:57:32 +0300 Subject: [PATCH 062/123] Remove missed leftover usages --- osu.Game.Tests/Resources/TestResources.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 33b580f68f..1c264f66e0 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -18,14 +18,14 @@ namespace osu.Game.Tests.Resources public static string GetTestBeatmapForImport(bool virtualTrack = false) { - var temp = Path.GetTempFileName() + ".osz"; + var tempPath = Path.Combine(RuntimeInfo.StartupDirectory, Path.GetTempFileName() + ".osz"); using (var stream = GetTestBeatmapStream(virtualTrack)) - using (var newFile = RuntimeInfo.StartupStorage.GetStream(temp, FileAccess.Write)) + using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); - Assert.IsTrue(RuntimeInfo.StartupStorage.Exists(temp)); - return temp; + Assert.IsTrue(File.Exists(tempPath)); + return tempPath; } } } From fbd9ad411f85ceb5b8894701b830d5da8b6bac11 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:05:46 +0000 Subject: [PATCH 063/123] Bump DiffPlex from 1.6.2 to 1.6.3 Bumps [DiffPlex](https://github.com/mmanela/diffplex) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/mmanela/diffplex/releases) - [Commits](https://github.com/mmanela/diffplex/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3d2a4f3081..305e4e0a92 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8a7f75b515..016f2ba35d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,7 +75,7 @@ - + From e9b09373e784e50a9bd6c718656df9dc2dd371b1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 1 Jun 2020 17:41:04 +0200 Subject: [PATCH 064/123] Fix crashing if selected ruleset doesn't have an autoplay mod. --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index bee11accca..8f41e421a3 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets return value; } - public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); + public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 0a4c0e2085..71ab3715e0 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -49,8 +49,11 @@ namespace osu.Game.Screens.Select if (removeAutoModOnResume) { - var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType(); - ModSelect.DeselectTypes(new[] { autoType }, true); + var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod()?.GetType(); + + if (autoType != null) + ModSelect.DeselectTypes(new[] { autoType }, true); + removeAutoModOnResume = false; } } @@ -78,14 +81,17 @@ namespace osu.Game.Screens.Select if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); - var autoType = auto.GetType(); + var autoType = auto?.GetType(); - var mods = Mods.Value; - - if (mods.All(m => m.GetType() != autoType)) + if (autoType != null) { - Mods.Value = mods.Append(auto).ToArray(); - removeAutoModOnResume = true; + var mods = Mods.Value; + + if (mods.All(m => m.GetType() != autoType)) + { + Mods.Value = mods.Append(auto).ToArray(); + removeAutoModOnResume = true; + } } } From 1ccdfd736429a43f913491a6d562086c97e5133d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:03:13 +0900 Subject: [PATCH 065/123] Pull playlist beatmap checksum from api --- osu.Game/Online/API/APIPlaylistBeatmap.cs | 23 +++++++++++++++++++ .../API/Requests/Responses/APIBeatmap.cs | 2 +- osu.Game/Online/Multiplayer/PlaylistItem.cs | 3 +-- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Online/API/APIPlaylistBeatmap.cs diff --git a/osu.Game/Online/API/APIPlaylistBeatmap.cs b/osu.Game/Online/API/APIPlaylistBeatmap.cs new file mode 100644 index 0000000000..4f7786e880 --- /dev/null +++ b/osu.Game/Online/API/APIPlaylistBeatmap.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API +{ + public class APIPlaylistBeatmap : APIBeatmap + { + [JsonProperty("checksum")] + public string Checksum { get; set; } + + public override BeatmapInfo ToBeatmap(RulesetStore rulesets) + { + var b = base.ToBeatmap(rulesets); + b.MD5Hash = Checksum; + return b; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index e023a2502f..ae65ac09b2 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"max_combo")] private int? maxCombo { get; set; } - public BeatmapInfo ToBeatmap(RulesetStore rulesets) + public virtual BeatmapInfo ToBeatmap(RulesetStore rulesets) { var set = BeatmapSet?.ToBeatmapSet(rulesets); diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index 9d6e8eb8e3..416091a1aa 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -7,7 +7,6 @@ using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -37,7 +36,7 @@ namespace osu.Game.Online.Multiplayer public readonly BindableList RequiredMods = new BindableList(); [JsonProperty("beatmap")] - private APIBeatmap apiBeatmap { get; set; } + private APIPlaylistBeatmap apiBeatmap { get; set; } private APIMod[] allowedModsBacking; From 68fbe9f4c144ad8be6be89286053fb08ccd5f50d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:03:50 +0900 Subject: [PATCH 066/123] Add checksum validation to the ready/start button --- .../Multi/Match/Components/ReadyButton.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index e1f86fcc97..a64f24dd7e 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Linq.Expressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -52,24 +53,14 @@ namespace osu.Game.Screens.Multi.Match.Components private void updateSelectedItem(PlaylistItem item) { - hasBeatmap = false; - - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; - - hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null; + hasBeatmap = findBeatmap(expr => beatmaps.QueryBeatmap(expr)); } private void beatmapUpdated(ValueChangedEvent> weakSet) { if (weakSet.NewValue.TryGetTarget(out var set)) { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; - - if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) + if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr))) Schedule(() => hasBeatmap = true); } } @@ -78,15 +69,22 @@ namespace osu.Game.Screens.Multi.Match.Components { if (weakSet.NewValue.TryGetTarget(out var set)) { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; - - if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) + if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr))) Schedule(() => hasBeatmap = false); } } + private bool findBeatmap(Func>, BeatmapInfo> expression) + { + int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; + string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash; + + if (beatmapId == null || checksum == null) + return false; + + return expression(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum) != null; + } + protected override void Update() { base.Update(); From b41bb5a6824d8f4467b4178708cce88ace77011c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:04:00 +0900 Subject: [PATCH 067/123] Update databased MD5 hash on save --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f626b45e42..e5907809f3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -201,7 +201,9 @@ namespace osu.Game.Beatmaps using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent).Encode(sw); - stream.Seek(0, SeekOrigin.Begin); + var attachedInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + var md5Hash = stream.ComputeMD5Hash(); + attachedInfo.MD5Hash = md5Hash; UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } From 17e91695e0cf982b76b34db1184aca8be4a7020b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:04:51 +0900 Subject: [PATCH 068/123] Add checksum validation to the panel download buttons --- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index c024304856..414c1f5748 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Multi X = -18, Children = new Drawable[] { - new PlaylistDownloadButton(item.Beatmap.Value.BeatmapSet) + new PlaylistDownloadButton(item) { Size = new Vector2(50, 30) }, @@ -212,9 +212,15 @@ namespace osu.Game.Screens.Multi private class PlaylistDownloadButton : BeatmapPanelDownloadButton { - public PlaylistDownloadButton(BeatmapSetInfo beatmapSet) - : base(beatmapSet) + private readonly PlaylistItem playlistItem; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + public PlaylistDownloadButton(PlaylistItem playlistItem) + : base(playlistItem.Beatmap.Value.BeatmapSet) { + this.playlistItem = playlistItem; Alpha = 0; } @@ -223,11 +229,26 @@ namespace osu.Game.Screens.Multi base.LoadComplete(); State.BindValueChanged(stateChanged, true); + FinishTransforms(true); } private void stateChanged(ValueChangedEvent state) { - this.FadeTo(state.NewValue == DownloadState.LocallyAvailable ? 0 : 1, 500); + switch (state.NewValue) + { + case DownloadState.LocallyAvailable: + // Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching. + if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null) + State.Value = DownloadState.NotDownloaded; + else + this.FadeTo(0, 500); + + break; + + default: + this.FadeTo(1, 500); + break; + } } } From 46a209540e65ee40bbbd4020128e4e01fbb91ce8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:28:48 +0900 Subject: [PATCH 069/123] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7ea1f3140b..8dcd2f1c6f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 305e4e0a92..a70a263cdc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 016f2ba35d..9652f967f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 90f9905ed0f142121d8e295f8df4873eee3682e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:29:17 +0900 Subject: [PATCH 070/123] Update resourcse --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8dcd2f1c6f..07be3ab0d2 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a70a263cdc..4d6358575b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9652f967f2..6b55fa51ff 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 3c85561cdce09b7531aac9ea14bd8a681c159d8c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:31:43 +0900 Subject: [PATCH 071/123] Add tests --- .../TestSceneDrawableRoomPlaylist.cs | 73 +++++++++++++++++++ osu.Game/Tests/Beatmaps/TestBeatmap.cs | 21 +++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 5ef4dd6773..55b026eff6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -4,12 +4,18 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Multi; @@ -23,6 +29,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { private TestPlaylist playlist; + private BeatmapManager manager; + private RulesetStore rulesets; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); + } + [Test] public void TestNonEditableNonSelectable() { @@ -182,6 +200,28 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); } + [Test] + public void TestDownloadButtonHiddenInitiallyWhenBeatmapExists() + { + createPlaylist(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo); + + AddAssert("download button hidden", () => !playlist.ChildrenOfType().Single().IsPresent); + } + + [Test] + public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist() + { + var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally. + + var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally. + + createPlaylist(byOnlineId, byChecksum); + + AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent)); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType>().ElementAt(index), offset)); @@ -235,6 +275,39 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } + private void createPlaylist(params BeatmapInfo[] beatmaps) + { + AddStep("create playlist", () => + { + Child = playlist = new TestPlaylist(false, false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + }; + + int index = 0; + + foreach (var b in beatmaps) + { + playlist.Items.Add(new PlaylistItem + { + ID = index++, + Beatmap = { Value = b }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }); + } + }); + + AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); + } + private class TestPlaylist : DrawableRoomPlaylist { public new IReadOnlyDictionary> ItemMap => base.ItemMap; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index a7c84bf692..9fc20fd0f2 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -1,9 +1,11 @@ // 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.IO; using System.Text; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO; using osu.Game.Rulesets; @@ -43,10 +45,25 @@ namespace osu.Game.Tests.Beatmaps private static Beatmap createTestBeatmap() { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) - using (var reader = new LineBufferedReader(stream)) - return Decoder.GetDecoder(reader).Decode(reader); + { + using (var reader = new LineBufferedReader(stream)) + { + var b = Decoder.GetDecoder(reader).Decode(reader); + + b.BeatmapInfo.MD5Hash = test_beatmap_hash.Value.md5; + b.BeatmapInfo.Hash = test_beatmap_hash.Value.sha2; + + return b; + } + } } + private static readonly Lazy<(string md5, string sha2)> test_beatmap_hash = new Lazy<(string md5, string sha2)>(() => + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) + return (stream.ComputeMD5Hash(), stream.ComputeSHA2Hash()); + }); + private const string test_beatmap_data = @"osu file format v14 [General] From 800c46f7524277b9aa8e9af5732bcfb06487a863 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:39:15 +0900 Subject: [PATCH 072/123] Fix test function override --- .../NonVisual/Skinning/LegacySkinTextureFallbackTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index 867af9c1b8..69e66942ab 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Skinning; @@ -103,7 +104,7 @@ namespace osu.Game.Tests.NonVisual.Skinning Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1)); } - public override Texture Get(string name) => Textures.GetValueOrDefault(name); + public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => Textures.GetValueOrDefault(name); } } } From 6c8c95677f65d707cc9ac657138f1274a393b1b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:54:55 +0900 Subject: [PATCH 073/123] Fix incorrect usage of Directory.GetFiles --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 10b6edca8c..58a2ba056e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets { try { - var files = Directory.GetFiles(Path.Combine(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll")); + var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); From 70c84811ed00a37b438cea3941000bba12ed418d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 15:49:51 +0900 Subject: [PATCH 074/123] Revert incorrect change --- osu.Game.Tests/Resources/TestResources.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 1c264f66e0..e882229570 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -3,7 +3,6 @@ using System.IO; using NUnit.Framework; -using osu.Framework; using osu.Framework.IO.Stores; namespace osu.Game.Tests.Resources @@ -18,7 +17,7 @@ namespace osu.Game.Tests.Resources public static string GetTestBeatmapForImport(bool virtualTrack = false) { - var tempPath = Path.Combine(RuntimeInfo.StartupDirectory, Path.GetTempFileName() + ".osz"); + var tempPath = Path.GetTempFileName() + ".osz"; using (var stream = GetTestBeatmapStream(virtualTrack)) using (var newFile = File.Create(tempPath)) From fac96f6dddf9dba724fa0b298444694310c508af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 17:02:01 +0900 Subject: [PATCH 075/123] Fix match beatmap not updating after re-download --- .../Multiplayer/TestSceneMatchSubScreen.cs | 57 +++++++++++++++++-- .../Match/Components/MatchSettingsOverlay.cs | 2 +- .../Screens/Multi/Match/MatchSubScreen.cs | 13 +---- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index d678d5a814..6154e646f8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -5,7 +5,9 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -29,14 +31,20 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(typeof(IRoomManager))] private readonly TestRoomManager roomManager = new TestRoomManager(); - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [Resolved] - private RulesetStore rulesets { get; set; } + private BeatmapManager manager; + private RulesetStore rulesets; private TestMatchSubScreen match; + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); + } + [SetUp] public void Setup() => Schedule(() => { @@ -75,10 +83,49 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]); } + [Test] + public void TestBeatmapUpdatedOnReImport() + { + BeatmapSetInfo importedSet = null; + + AddStep("import altered beatmap", () => + { + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); + beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; + + importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; + }); + + AddStep("load room", () => + { + Room.Name.Value = "my awesome room"; + Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = importedSet.Beatmaps[0] }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + }); + }); + + AddStep("create room", () => + { + InputManager.MoveMouseTo(match.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize == 1); + + AddStep("re-import original beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); + + AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize != 1); + } + private class TestMatchSubScreen : MatchSubScreen { public new Bindable SelectedItem => base.SelectedItem; + public new Bindable Beatmap => base.Beatmap; + public TestMatchSubScreen(Room room) : base(room) { diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 54c4f8f7c7..49a0fc434b 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -433,7 +433,7 @@ namespace osu.Game.Screens.Multi.Match.Components } } - private class CreateRoomButton : TriangleButton + public class CreateRoomButton : TriangleButton { public CreateRoomButton() { diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index e1d72d9600..bbfbaf81af 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -207,6 +207,8 @@ namespace osu.Game.Screens.Multi.Match Ruleset.Value = item.Ruleset.Value; } + private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); + private void updateWorkingBeatmap() { var beatmap = SelectedItem.Value?.Beatmap.Value; @@ -217,17 +219,6 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void beatmapUpdated(ValueChangedEvent> weakSet) - { - Schedule(() => - { - if (Beatmap.Value != beatmapManager.DefaultBeatmap) - return; - - updateWorkingBeatmap(); - }); - } - private void onStart() { switch (type.Value) From dfb9687fb5bfc47670ea6b017410e46c76c5905c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 17:22:09 +0900 Subject: [PATCH 076/123] Extract update into PreUpdate(), add test --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 +++ osu.Game/Beatmaps/BeatmapManager.cs | 17 +++++++++++++---- osu.Game/Database/ArchiveModelManager.cs | 10 ++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5eb11a3264..88bb39a521 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -602,6 +602,8 @@ namespace osu.Game.Tests.Beatmaps.IO Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); + string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; + using (var stream = new MemoryStream()) { using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -624,6 +626,7 @@ namespace osu.Game.Tests.Beatmaps.IO Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap; Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1)); Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000)); + Assert.That(updatedBeatmap.BeatmapInfo.MD5Hash, Is.Not.EqualTo(oldMd5Hash)); } finally { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e5907809f3..668ac6ee10 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -201,10 +201,6 @@ namespace osu.Game.Beatmaps using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent).Encode(sw); - var attachedInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); - var md5Hash = stream.ComputeMD5Hash(); - attachedInfo.MD5Hash = md5Hash; - UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } @@ -213,6 +209,19 @@ namespace osu.Game.Beatmaps workingCache.Remove(working); } + protected override void PreUpdate(BeatmapSetInfo item) + { + base.PreUpdate(item); + + foreach (var info in item.Beatmaps) + { + var file = item.Files.FirstOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + using (var stream = Files.Store.GetStream(file)) + info.MD5Hash = stream.ComputeMD5Hash(); + } + } + private readonly WeakList workingCache = new WeakList(); /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ae55a7b14a..f7e81ae4bc 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -430,10 +430,20 @@ namespace osu.Game.Database { item.Hash = computeHash(item); + PreUpdate(item); + ModelStore.Update(item); } } + /// + /// Perform any final actions before the update to database executes. + /// + /// The that is being updated. + protected virtual void PreUpdate(TModel item) + { + } + /// /// Delete an item from the manager. /// Is a no-op for already deleted items. From 665530f1c3a9b43ac2925220ee41efc767919974 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 17:22:59 +0900 Subject: [PATCH 077/123] Remove excess newline --- .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index 7f96b5a46e..a88eff13a1 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints public TaikoSpanPlacementBlueprint(HitObject hitObject) : base(hitObject) - { spanPlacementObject = hitObject as IHasDuration; From 828180ad9b3e6006e70c811e107d340ae18b603e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 19:29:22 +0900 Subject: [PATCH 078/123] Add default --- osu.Game.Tournament/Screens/SetupScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index e1594de69e..2bd0bcf2f2 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -211,7 +211,8 @@ namespace osu.Game.Tournament.Screens private class ResolutionSelector : ActionableInfo { private const int minimum_window_height = 480; - private const int maximum_window_height = 2160; // 4k + private const int maximum_window_height = 2160; + public new Action Action; private OsuNumberBox numberBox; @@ -221,6 +222,7 @@ namespace osu.Game.Tournament.Screens var drawable = base.CreateComponent(); FlowContainer.Insert(-1, numberBox = new OsuNumberBox { + Text = "1080", Width = 100 }); From 78fddc895738ec770447f025bdc8a487bdd5c988 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 19:29:59 +0900 Subject: [PATCH 079/123] Make button match height --- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 2bd0bcf2f2..cf8eb8bd6c 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -199,7 +199,7 @@ namespace osu.Game.Tournament.Screens { button = new TriangleButton { - Size = new Vector2(100, 30), + Size = new Vector2(100, 40), Action = () => Action?.Invoke() } } From f63c66396f7a2f1f462377ae4a3b6c4c9e4dce40 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 2 Jun 2020 13:32:52 +0200 Subject: [PATCH 080/123] Apply review suggestions. --- .../Visual/Gameplay/TestSceneReplay.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 ++ osu.Game/Screens/Select/PlaySongSelect.cs | 25 +++++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index 1908988739..3a71d4ca54 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); - return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); + return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap)); } protected override void AddCheckSteps() diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 8f41e421a3..4f28607733 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; +using JetBrains.Annotations; namespace osu.Game.Rulesets { @@ -100,6 +101,7 @@ namespace osu.Game.Rulesets return value; } + [CanBeNull] public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 71ab3715e0..a0201e696f 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -20,6 +22,9 @@ namespace osu.Game.Screens.Select private bool removeAutoModOnResume; private OsuScreen player; + [Resolved] + private NotificationOverlay notifications { get; set; } + public override bool AllowExternalScreenChange => true; protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); @@ -83,15 +88,21 @@ namespace osu.Game.Screens.Select var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto?.GetType(); - if (autoType != null) - { - var mods = Mods.Value; + var mods = Mods.Value; - if (mods.All(m => m.GetType() != autoType)) + if (autoType == null) + { + notifications.Post(new SimpleNotification { - Mods.Value = mods.Append(auto).ToArray(); - removeAutoModOnResume = true; - } + Text = "The current ruleset doesn't have an autoplay mod avalaible!" + }); + return false; + } + + if (mods.All(m => m.GetType() != autoType)) + { + Mods.Value = mods.Append(auto).ToArray(); + removeAutoModOnResume = true; } } From 61f906d9c462279b89881f1083758f493eb34450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 21:02:09 +0900 Subject: [PATCH 081/123] Fix span piece being incorrect in some drag scenarios --- .../Blueprints/TaikoSpanPlacementBlueprint.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index a88eff13a1..468d980b23 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints } private double originalStartTime; + private Vector2 originalPosition; protected override bool OnMouseDown(MouseDownEvent e) { @@ -73,22 +74,25 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints if (PlacementActive) { - if (result.Time is double endTime) + if (result.Time is double dragTime) { - if (endTime < originalStartTime) + if (dragTime < originalStartTime) { - HitObject.StartTime = endTime; - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + HitObject.StartTime = dragTime; + spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime); headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.X = headPiece.X; - lengthPiece.Width = tailPiece.X - headPiece.X; + tailPiece.Position = originalPosition; } else { - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + HitObject.StartTime = originalStartTime; + spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime); tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.Width = tailPiece.X - headPiece.X; + headPiece.Position = originalPosition; } + + lengthPiece.X = headPiece.X; + lengthPiece.Width = tailPiece.X - headPiece.X; } } else @@ -96,7 +100,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); if (result.Time is double startTime) + { originalStartTime = HitObject.StartTime = startTime; + originalPosition = ToLocalSpace(result.ScreenSpacePosition); + } } } } From 275d95082a88f63e5bc0ba7be8d6efd4950fc30e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 2 Jun 2020 16:01:01 +0200 Subject: [PATCH 082/123] Fix crash in testing environment. --- osu.Game/Screens/Select/PlaySongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index a0201e696f..2236aa4d72 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select private bool removeAutoModOnResume; private OsuScreen player; - [Resolved] + [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } public override bool AllowExternalScreenChange => true; @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Select if (autoType == null) { - notifications.Post(new SimpleNotification + notifications?.Post(new SimpleNotification { Text = "The current ruleset doesn't have an autoplay mod avalaible!" }); From dc41e74e1912ea9a49a641ab091ed536f6f5e38a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:47:18 +0900 Subject: [PATCH 083/123] Fix cursor trail not displaying --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 37df5ec540..9bcb3abc63 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2), TexturePosition = textureRect.BottomLeft, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, Time = part.Time }); @@ -245,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2), TexturePosition = textureRect.BottomRight, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, Time = part.Time }); @@ -253,6 +255,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2), TexturePosition = textureRect.TopRight, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, Time = part.Time }); @@ -261,6 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2), TexturePosition = textureRect.TopLeft, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, Time = part.Time }); @@ -290,6 +294,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [VertexMember(2, VertexAttribPointerType.Float)] public Vector2 TexturePosition; + [VertexMember(4, VertexAttribPointerType.Float)] + public Vector4 TextureRect; + [VertexMember(1, VertexAttribPointerType.Float)] public float Time; From 40e64eed475b7c09de60643671035e5cd0ec9967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jun 2020 23:15:14 +0200 Subject: [PATCH 084/123] Add contributing guidelines --- CONTRIBUTING.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..441521f9ef --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,121 @@ +# Contributing Guidelines + +Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience. + +These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner. + +## Table of contents + +1. [I would like to submit an issue!](#i-would-like-to-submit-an-issue) +2. [I would like to submit a pull request!](#i-would-like-to-submit-a-pull-request) + +## I would like to submit an issue! + +When it comes to issues, bug reports and feature suggestions are welcomed, although please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most. + +* **Before submitting an issue, try searching existing issues first.** + + For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before. + +* **When submitting a bug report, please try to include as much detail as possible.** + + Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information contains of: + + * the in-game logs, which are located at: + * `%AppData%/osu/logs` (on Windows), + * `~/.local/share/osu/logs` (on Linux and macOS), + * `Android/Data/sh.ppy.osulazer/logs` (on Android), + * on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes, + * your system specifications (including the operating system and platform you are playing on), + * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug), + * a video or picture of the bug, if at all possible. + +* **Provide more information when asked to do so.** + + Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is! + +* **When submitting a feature proposal, please describe it in the most understandable way you can.** + + Communicating your idea for a feature can often be hard, and we would like to avoid any misunderstandings. As such, please try to explain your idea in a short, but understandable manner - it's best to avoid jargon or terms and references that could be considered obscure. A mock-up picture (doesn't have to be good!) of the feature can also go a long way in explaining. + +* **Refrain from posting "+1" comments.** + + If an issue has already been created, saying that you also experience it without providing any additional details doesn't really help us in any way. To express support for a proposal or indicate that you are also affected by a particular bug, you can use comment reactions instead. + +* **Refrain from asking if an issue has been resolved yet.** + + As mentioned above, the issue tracker has hundreds of issues open at any given time. Currently the game is being worked on by two members of the core team, and a handful of outside contributors who offer their free time to help out. As such, it can happen that an issue gets placed on the backburner due to being less important; generally posting a comment demanding its resolution some months or years after it is reported is not very likely to increase its priority. + +* **Avoid long discussions about non-development topics.** + + GitHub is mostly a developer space, and as such isn't really fit for lengthened discussions about gameplay mechanics (which might not even be in any way confirmed for the final release) and similar non-technical matters. Such matters are probably best addressed at the osu! forums. + +## I would like to submit a pull request! + +We also welcome pull requests from unaffiliated contributors. The issue tracker should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. + +However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management). + +Here are some key things to note before jumping in: + +* **Make sure you are comfortable with C\# and your development environment.** + + While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first. + +* **Make sure you are familiar with git and the pull request workflow.** + + [git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow. + + To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests). + +* **Make sure to submit pull requests off of a topic branch.** + + As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident. + +* **Refrain from making changes through the GitHub web interface.** + + Even though GitHub provides an option to edit code or replace files in the repository using the web interface, we strongly discourage using it in most scenarios. Editing files this way is inefficient and likely to introduce whitespace or file encoding changes that make it more difficult to review the code. + + Code written through the web interface will also very likely be questioned outright by the reviewers, as it is likely that it has not been properly tested or that it will fail continuous integration checks. We strongly encourage using an IDE like [Visual Studio](https://visualstudio.microsoft.com/), [Visual Studio Code](https://code.visualstudio.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/) instead. + +* **Add tests for your code whenever possible.** + + Automated tests are an essential part of a quality and reliable codebase. They help to make the code more maintainable by ensuring it is safe to reorganise (or refactor) the code in various ways, and also prevent regressions - bugs that resurface after having been fixed at some point in the past. If it is viable, please put in the time to add tests, so that the changes you make can last for a (hopefully) very long time. + +* **Run tests before opening a pull request.** + + Tying into the previous point, sometimes changes in one part of the codebase can result in unpredictable changes in behaviour in other pieces of the code. This is why it is best to always try to run tests before opening a PR. + + Continuous integration will always run the tests for you (and us), too, but it is best not to rely on it, as there might be many builds queued at any time. Running tests on your own will help you be more certain that at the point of clicking the "Create pull request" button, your changes are as ready as can be. + +* **Run code style analysis before opening a pull request.** + + As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation. + +* **Make sure to keep the *Allow edits from maintainers* check box checked.** + + To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them. + +* **Refrain from continually merging the master branch back to the PR.** + + Unless there are merge conflicts that need resolution, there is no need to keep merging `master` back to a branch over and over again. One of the maintainers will merge `master` themselves before merging the PR itself anyway, and continual merge commits can cause CI to get overwhelmed due to queueing up too many builds. + +* **Refrain from force-pushing to the PR branch.** + + Force-pushing should be avoided, as it can lead to accidentally overwriting a maintainer's changes or CI building wrong commits. We value all history in the project, so there is no need to squash or amend commits in most cases. + + The cases in which force-pushing is warranted are very rare (such as accidentally leaking sensitive info in one of the files committed, adding unrelated files, or mis-merging a dependent PR). + +* **Be patient when waiting for the code to be reviewed and merged.** + + As much as we'd like to review all contributions as fast as possible, our time is limited, as team members have to work on their own tasks in addition to reviewing code. As such, work needs to be prioritised, and it can unfortunately take weeks or months for your PR to be merged, depending on how important it is deemed to be. + +* **Don't mistake criticism of code for criticism of your person.** + + As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack. + +* **Feel free to reach out for help.** + + If you're uncertain about some part of the codebase or some inner workings of the game and framework, please reach out either by leaving a comment in the relevant issue or PR thread, or by posting a message in the [development Discord server](https://discord.gg/ppy). We will try to help you as much as we can. + + When it comes to which form of communication is best, GitHub generally lends better to longer-form discussions, while Discord is better for snappy call-and-response answers. Use your best discretion when deciding, and try to keep a single discussion in one place instead of moving back and forth. From f4f84ede6a2ff88b3f0cf7517c89a59ef309ed20 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 10:43:16 +0930 Subject: [PATCH 085/123] Fix results screen crashing for beatmaps with no online ID --- osu.Game/Screens/Ranking/ResultsScreen.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index fbb9b95478..145ba93573 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -140,14 +140,17 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - var req = FetchScores(scores => Schedule(() => + if (Score.Beatmap.OnlineBeatmapID != null) { - foreach (var s in scores) - panels.AddScore(s); - })); + var req = FetchScores(scores => Schedule(() => + { + foreach (var s in scores) + panels.AddScore(s); + })); - if (req != null) - api.Queue(req); + if (req != null) + api.Queue(req); + } } /// From 90213d079d33713d8729739675910ebe1cfdda24 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 10:48:27 +0930 Subject: [PATCH 086/123] Include submission status in check --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 145ba93573..25c8205c30 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -140,7 +141,7 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - if (Score.Beatmap.OnlineBeatmapID != null) + if (Score.Beatmap.OnlineBeatmapID != null && Score.Beatmap.Status > BeatmapSetOnlineStatus.Pending) { var req = FetchScores(scores => Schedule(() => { From 96e3c6e8e888d47069cc636c3049178cae09733b Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 11:36:47 +0930 Subject: [PATCH 087/123] Move check to SoloResultsScreen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 15 ++++++--------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 25c8205c30..aff0540652 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -141,17 +141,14 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - if (Score.Beatmap.OnlineBeatmapID != null && Score.Beatmap.Status > BeatmapSetOnlineStatus.Pending) + var req = FetchScores(scores => Schedule(() => { - var req = FetchScores(scores => Schedule(() => - { - foreach (var s in scores) - panels.AddScore(s); - })); + foreach (var s in scores) + panels.AddScore(s); + })); - if (req != null) - api.Queue(req); - } + if (req != null) + api.Queue(req); } /// diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 3ae723683a..9cf2e6757a 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; @@ -24,6 +25,9 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { + if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending) + return null; + var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return req; From 0d5a2cf96d089038615d8f30572ae4b7e9b7c7ed Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 11:36:59 +0930 Subject: [PATCH 088/123] Add unit tests --- .../Visual/Ranking/TestSceneResultsScreen.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 242766ad4b..125aa0a1e7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -36,12 +36,14 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); } - private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); [Test] public void ResultsWithoutPlayer() { - TestSoloResults screen = null; + TestResultsScreen screen = null; OsuScreenStack stack; AddStep("load results", () => @@ -60,13 +62,23 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void ResultsWithPlayer() { - TestSoloResults screen = null; + TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void ResultsForUnranked() + { + UnrankedSoloResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createUnrankedSoloResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + } + private class TestResultsContainer : Container { [Cached(typeof(Player))] @@ -86,11 +98,11 @@ namespace osu.Game.Tests.Visual.Ranking } } - private class TestSoloResults : ResultsScreen + private class TestResultsScreen : ResultsScreen { public HotkeyRetryOverlay RetryOverlay; - public TestSoloResults(ScoreInfo score) + public TestResultsScreen(ScoreInfo score) : base(score) { } @@ -102,5 +114,24 @@ namespace osu.Game.Tests.Visual.Ranking RetryOverlay = InternalChildren.OfType().SingleOrDefault(); } } + + private class UnrankedSoloResultsScreen : SoloResultsScreen + { + public HotkeyRetryOverlay RetryOverlay; + + public UnrankedSoloResultsScreen(ScoreInfo score) + : base(score) + { + Score.Beatmap.OnlineBeatmapID = 0; + Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + RetryOverlay = InternalChildren.OfType().SingleOrDefault(); + } + } } } From b174daa94a5f7968ac9ef31b50257ecf6a96698d Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 11:58:56 +0930 Subject: [PATCH 089/123] Remove unused using --- osu.Game/Screens/Ranking/ResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index aff0540652..fbb9b95478 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; From 74875f9b629715a1e5a13e65704af277fb2c8c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 06:47:10 +0200 Subject: [PATCH 090/123] Apply review suggestions & other cleanups --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 441521f9ef..331534ad73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,21 +11,21 @@ These are not "official rules" *per se*, but following them will help everyone d ## I would like to submit an issue! -When it comes to issues, bug reports and feature suggestions are welcomed, although please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most. +Issues, bug reports and feature suggestions are welcomed, though please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most. * **Before submitting an issue, try searching existing issues first.** - For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before. + For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not to have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before. * **When submitting a bug report, please try to include as much detail as possible.** - Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information contains of: + Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information consists of: * the in-game logs, which are located at: * `%AppData%/osu/logs` (on Windows), * `~/.local/share/osu/logs` (on Linux and macOS), * `Android/Data/sh.ppy.osulazer/logs` (on Android), - * on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes, + * on iOS they can be obtained by connecting your device to your desktop and [copying the `logs` directory from the app's own document storage using iTunes](https://support.apple.com/en-us/HT201301#copy-to-computer), * your system specifications (including the operating system and platform you are playing on), * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug), * a video or picture of the bug, if at all possible. From 1992a3db546126f536ddc9974cd5074bff5f4876 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 15:50:00 +0900 Subject: [PATCH 091/123] Fix redundant override showing up in build warnings --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 6df0041e7a..d8c6da86f9 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Objects public static class SliderEventGenerator { [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 + // ReSharper disable once RedundantOverload.Global public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) { From 1ba3f0ac14dd2d293bf453972f26ebc3db98c544 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 17:31:55 +0900 Subject: [PATCH 092/123] Fix chat history not being loaded for multiplayer matches --- osu.Game/Online/Chat/ChannelManager.cs | 51 ++++++++++++++------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 53872ddcba..6812052eeb 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -93,12 +93,6 @@ namespace osu.Game.Online.Chat { if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) JoinChannel(e.NewValue); - - if (e.NewValue?.MessagesLoaded == false) - { - // let's fetch a small number of messages to bring us up-to-date with the backlog. - fetchInitalMessages(e.NewValue); - } } /// @@ -240,7 +234,6 @@ namespace osu.Game.Online.Chat } JoinChannel(channel); - CurrentChannel.Value = channel; break; case "help": @@ -275,7 +268,7 @@ namespace osu.Game.Online.Chat // join any channels classified as "defaults" if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase))) - JoinChannel(ch); + joinChannel(ch); } }; req.Failure += error => @@ -296,7 +289,7 @@ namespace osu.Game.Online.Chat /// The channel private void fetchInitalMessages(Channel channel) { - if (channel.Id <= 0) return; + if (channel.Id <= 0 || channel.MessagesLoaded) return; var fetchInitialMsgReq = new GetMessagesRequest(channel); fetchInitialMsgReq.Success += messages => @@ -351,9 +344,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. /// /// The channel to join. - /// Whether the channel has already been joined server-side. Will skip a join request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool alreadyJoined = false) + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); + + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -362,21 +356,29 @@ namespace osu.Game.Online.Chat // ensure we are joined to the channel if (!channel.Joined.Value) { - if (alreadyJoined) - channel.Joined.Value = true; - else + switch (channel.Type) { - switch (channel.Type) - { - case ChannelType.Public: - var req = new JoinChannelRequest(channel, api.LocalUser.Value); - req.Success += () => JoinChannel(channel, true); - req.Failure += ex => LeaveChannel(channel); - api.Queue(req); - return channel; - } + case ChannelType.Private: + // can't do this yet. + break; + + default: + var req = new JoinChannelRequest(channel, api.LocalUser.Value); + req.Success += () => + { + channel.Joined.Value = true; + joinChannel(channel, fetchInitialMessages); + }; + req.Failure += ex => LeaveChannel(channel); + api.Queue(req); + return channel; } } + else + { + if (fetchInitialMessages) + fetchInitalMessages(channel); + } if (CurrentChannel.Value == null) CurrentChannel.Value = channel; @@ -420,7 +422,8 @@ namespace osu.Game.Online.Chat foreach (var channel in updates.Presence) { // we received this from the server so should mark the channel already joined. - JoinChannel(channel, true); + channel.Joined.Value = true; + joinChannel(channel); } //todo: handle left channels From 3c7e5a5b42832d2d3b801b6baf09481b47a583e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 18:00:31 +0900 Subject: [PATCH 093/123] Fix ChannelManager not being loaded in tests --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 05b33e4386..0025a26baf 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -246,7 +246,12 @@ namespace osu.Game.Tests.Visual.Online { ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); - Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }; + InternalChildren = new Drawable[] + { + ChannelManager, + ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }, + }; + ChatOverlay.Show(); } } From c155ab83399a51bdab44a48d5805463c8520318a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 18:03:10 +0900 Subject: [PATCH 094/123] Check filenames and timestamps before reusing an already imported model --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/ArchiveModelManager.cs | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f626b45e42..e7cef13c68 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -258,9 +258,9 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import) + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) { - if (!base.CanUndelete(existing, import)) + if (!base.CanReuseExisting(existing, import)) return false; var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ae55a7b14a..4d7d3e96e6 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -332,7 +332,7 @@ namespace osu.Game.Database if (existing != null) { - if (CanUndelete(existing, item)) + if (CanReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -660,13 +660,29 @@ namespace osu.Game.Database protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); /// - /// After an existing is found during an import process, the default behaviour is to restore the existing + /// After an existing is found during an import process, the default behaviour is to use/restore the existing /// item and skip the import. This method allows changing that behaviour. /// /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - protected virtual bool CanUndelete(TModel existing, TModel import) => true; + protected virtual bool CanReuseExisting(TModel existing, TModel import) => + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && + // poor-man's (cheap) equality comparison, avoiding hashing unnecessarily. + // can switch to full hash checks on a per-case basis (or for all) if we decide this is not a performance issue. + getTimestamps(existing.Files).SequenceEqual(getTimestamps(import.Files)); + + private IEnumerable getFilenames(List files) + { + foreach (var f in files.OrderBy(f => f.Filename)) + yield return f.Filename; + } + + private IEnumerable getTimestamps(List files) + { + foreach (var f in files.OrderBy(f => f.Filename)) + yield return File.GetLastWriteTimeUtc(Files.Storage.GetFullPath(f.FileInfo.StoragePath)).ToFileTime(); + } private DbSet queryModel() => ContextFactory.Get().Set(); From 012933545eccd4510dc3c0a70b040610dad0bf8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 18:30:27 +0900 Subject: [PATCH 095/123] Add test coverage --- .../Beatmaps/IO/ImportBeatmapTest.cs | 161 ++++++++++++++++++ osu.Game/Database/ArchiveModelManager.cs | 2 +- 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5eb11a3264..12c9c92e90 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -12,6 +12,7 @@ using NUnit.Framework; using osu.Framework.Platform; using osu.Game.IPC; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -22,6 +23,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.Writers.Zip; +using FileInfo = System.IO.FileInfo; namespace osu.Game.Tests.Beatmaps.IO { @@ -93,6 +95,165 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestImportThenImportWithReZip() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + string hashBefore = hashFile(temp); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + // zip files differ because different compression or encoder. + Assert.AreNotEqual(hashBefore, hashFile(temp)); + + var importedSecondTime = await osu.Dependencies.Get().Import(temp); + + ensureLoaded(osu); + + // but contents doesn't, so existing should still be used. + Assert.IsTrue(imported.ID == importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + + private string hashFile(string filename) + { + using (var s = File.OpenRead(filename)) + return s.ComputeMD5Hash(); + } + + [Test] + public async Task TestImportThenImportWithNewerTimestamp() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // change timestamp + new FileInfo(Directory.GetFiles(extractedFolder).First()).LastWriteTime = DateTime.Now; + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(temp); + + ensureLoaded(osu); + + // check the newly "imported" beatmap is not the original. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportThenImportWithDifferentFilename() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // change filename + var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First()); + firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}")); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(temp); + + ensureLoaded(osu); + + // check the newly "imported" beatmap is not the original. + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportCorruptThenImport() { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 4d7d3e96e6..5ca9423de2 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -276,7 +276,7 @@ namespace osu.Game.Database // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); - foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith))) + foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)).OrderBy(f => f.Filename)) { using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) s.CopyTo(hashable); From 25160dc220d9f2f0bde4125f0bafd7446e3ad354 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 19:15:52 +0900 Subject: [PATCH 096/123] Fix test name --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 12c9c92e90..12f06059f7 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip))) { try { From f6d9f0597b970c9411c623392ffea35a8bcc0fe4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 21:28:29 +0900 Subject: [PATCH 097/123] Add implicit join logic for multiplayer rooms --- osu.Game/Online/Chat/ChannelManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 6812052eeb..b17e0812da 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -358,6 +358,13 @@ namespace osu.Game.Online.Chat { switch (channel.Type) { + case ChannelType.Multiplayer: + // join is implicit. happens when you join a multiplayer game. + // this will probably change in the future. + channel.Joined.Value = true; + joinChannel(channel, fetchInitialMessages); + return channel; + case ChannelType.Private: // can't do this yet. break; From 5ed3cd205f068d572caddc0ae01aa7ab8a580a6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 22:35:01 +0900 Subject: [PATCH 098/123] Simplify reuse check using FileInfo IDs --- .../Beatmaps/IO/ImportBeatmapTest.cs | 9 +++++---- osu.Game/Database/ArchiveModelManager.cs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 12f06059f7..9b34eece5f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -154,9 +154,9 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public async Task TestImportThenImportWithNewerTimestamp() + public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile))) { try { @@ -174,8 +174,9 @@ namespace osu.Game.Tests.Beatmaps.IO using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); - // change timestamp - new FileInfo(Directory.GetFiles(extractedFolder).First()).LastWriteTime = DateTime.Now; + // arbitrary write to non-hashed file + using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText()) + sw.WriteLine("text"); using (var zip = ZipArchive.Create()) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5ca9423de2..0fe8dd1268 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -667,10 +667,16 @@ namespace osu.Game.Database /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. protected virtual bool CanReuseExisting(TModel existing, TModel import) => - getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && - // poor-man's (cheap) equality comparison, avoiding hashing unnecessarily. - // can switch to full hash checks on a per-case basis (or for all) if we decide this is not a performance issue. - getTimestamps(existing.Files).SequenceEqual(getTimestamps(import.Files)); + // for the best or worst, we copy and import files of a new import before checking whether + // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. + getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)); + + private IEnumerable getIDs(List files) + { + foreach (var f in files.OrderBy(f => f.Filename)) + yield return f.FileInfo.ID; + } private IEnumerable getFilenames(List files) { @@ -678,12 +684,6 @@ namespace osu.Game.Database yield return f.Filename; } - private IEnumerable getTimestamps(List files) - { - foreach (var f in files.OrderBy(f => f.Filename)) - yield return File.GetLastWriteTimeUtc(Files.Storage.GetFullPath(f.FileInfo.StoragePath)).ToFileTime(); - } - private DbSet queryModel() => ContextFactory.Get().Set(); protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; From 66ec2afe5cdcd9eb77adf5015966e5dcb652c1d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 23:38:40 +0900 Subject: [PATCH 099/123] Remove broken import test --- .../Beatmaps/IO/ImportBeatmapTest.cs | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 9b34eece5f..546bf758c1 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -374,37 +374,6 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [Test] - public async Task TestImportThenImportDifferentHash() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash))) - { - try - { - var osu = loadOsu(host); - var manager = osu.Dependencies.Get(); - - var imported = await LoadOszIntoOsu(osu); - - imported.Hash += "-changed"; - manager.Update(imported); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); - - // only one beatmap will exist as the online set ID matched, causing purging of the first import. - checkBeatmapSetCount(osu, 1); - } - finally - { - host.Exit(); - } - } - } - [Test] public async Task TestImportThenDeleteThenImport() { From c2fd2b861642a6fcac8412891d0365104c6ed6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 23:20:43 +0200 Subject: [PATCH 100/123] Add notes about draft PRs & pushing --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 331534ad73..9666f249e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,6 +92,16 @@ Here are some key things to note before jumping in: As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation. +* **Make sure that the pull request is complete before opening it.** + + Whether it's fixing a bug or implementing new functionality, it's best that you make sure that the change you want to submit as a pull request is as complete as it can be before clicking the *Create pull request* button. Having to track if a pull request is ready for review or not places additional burden on reviewers. + + Draft pull requests are an option, but use them sparingly and within reason. They are best suited to discuss code changes that cannot be easily described in natural language or have a potential large impact on the future direction of the project. When in doubt, don't open drafts unless a maintainer asks you to do so. + +* **Only push code when it's ready.** + + As an extension of the above, when making changes to an already-open PR, please try to only push changes you are reasonably certain of. Pushing after every commit causes the continuous integration build queue to grow in size, slowing down work and taking up time that could be spent verifying other changes. + * **Make sure to keep the *Allow edits from maintainers* check box checked.** To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them. From ddf5282d0e24d798a157d2c9704f8b30a7944731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 23:33:49 +0200 Subject: [PATCH 101/123] Move items from README.md to contributing guidelines --- CONTRIBUTING.md | 8 +++++++- README.md | 6 ------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9666f249e2..6c327f01b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in ## I would like to submit a pull request! -We also welcome pull requests from unaffiliated contributors. The issue tracker should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. +We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management). @@ -62,12 +62,18 @@ Here are some key things to note before jumping in: While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first. + In addition, please take the time to take a look at and get acquainted with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. + * **Make sure you are familiar with git and the pull request workflow.** [git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow. To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests). +* **Double-check designs before starting work on new functionality.** + + When implementing new features, keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted. + * **Make sure to submit pull requests off of a topic branch.** As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident. diff --git a/README.md b/README.md index 336bf33f7e..9e1cc20c8b 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,6 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it ## Contributing -We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted. - -If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label). - -Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**. - Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible. For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. From af3daaaeafd294a740d9586ee56e13858b75b5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 23:39:29 +0200 Subject: [PATCH 102/123] Add reference to contributing guidelines in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9e1cc20c8b..dc3ee63844 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it ## Contributing +When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Based on past experiences, we have prepared a [list of contributing guidelines](CONTRIBUTING.md) that should hopefully ease you into our collaboration process and answer the most frequently-asked questions. + Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible. For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. From 5d7bb8cb4e9e44eeb8a504f7d8e43f9046003aca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Jun 2020 21:33:38 +0900 Subject: [PATCH 103/123] Change format of date on score panel --- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index fd8ac33aef..81d5d113ae 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Text = $"Played on {score.Date.ToLocalTime():g}" + Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}" } } }; From afcefe01bf74177240c59e70b3ea2b87745a4223 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Jun 2020 21:48:55 +0900 Subject: [PATCH 104/123] Fix score panel not receiving input in some places --- osu.Game/Screens/Ranking/ScorePanel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index a99b48e8f0..65fb901c89 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -243,5 +243,10 @@ namespace osu.Game.Screens.Ranking return true; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + => base.ReceivePositionalInputAt(screenSpacePos) + || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) + || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); } } From 9c1542f8979637fcea83b327b5252f2fae8933a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Jun 2020 22:17:00 +0900 Subject: [PATCH 105/123] Fix crash when pressing clear button twice --- .../Settings/TestSceneKeyBindingPanel.cs | 45 ++++++++++++++++++- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 5 ++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 745820696a..3d335995ac 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -1,13 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; +using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Overlays; +using osu.Game.Overlays.KeyBinding; +using osuTK.Input; namespace osu.Game.Tests.Visual.Settings { [TestFixture] - public class TestSceneKeyBindingPanel : OsuTestScene + public class TestSceneKeyBindingPanel : OsuManualInputManagerTestScene { private readonly KeyBindingPanel panel; @@ -21,5 +27,42 @@ namespace osu.Game.Tests.Visual.Settings base.LoadComplete(); panel.Show(); } + + [Test] + public void TestClickTwiceOnClearButton() + { + KeyBindingRow firstRow = null; + + AddStep("click first row", () => + { + firstRow = panel.ChildrenOfType().First(); + InputManager.MoveMouseTo(firstRow); + InputManager.Click(MouseButton.Left); + }); + + AddStep("schedule button clicks", () => + { + var clearButton = firstRow.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(clearButton); + + int buttonClicks = 0; + ScheduledDelegate clickDelegate = null; + + clickDelegate = Scheduler.AddDelayed(() => + { + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + + if (++buttonClicks == 2) + { + // ReSharper disable once AccessToModifiedClosure + Debug.Assert(clickDelegate != null); + // ReSharper disable once AccessToModifiedClosure + clickDelegate.Cancel(); + } + }, 0, true); + }); + } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 01d5991d3e..eafb4572ca 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -274,6 +274,9 @@ namespace osu.Game.Overlays.KeyBinding private void clear() { + if (bindTarget == null) + return; + bindTarget.UpdateKeyCombination(InputKey.None); finalise(); } @@ -333,7 +336,7 @@ namespace osu.Game.Overlays.KeyBinding } } - private class ClearButton : TriangleButton + public class ClearButton : TriangleButton { public ClearButton() { From 6b88141e58b6d3863b1aeb9db41d39225cd00bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 21:30:59 +0200 Subject: [PATCH 106/123] Add mania sample conversion test --- .../ManiaBeatmapSampleConversionTest.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs new file mode 100644 index 0000000000..dbf1cf5f72 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -0,0 +1,72 @@ +// 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 NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest, SampleConvertValue> + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + + public void Test(string name) => base.Test(name); + + protected override IEnumerable CreateConvertValue(HitObject hitObject) + { + yield return new SampleConvertValue + { + StartTime = hitObject.StartTime, + EndTime = hitObject.GetEndTime(), + Column = ((ManiaHitObject)hitObject).Column, + NodeSamples = getSampleNames((hitObject as HoldNote)?.NodeSamples) + }; + } + + private IList> getSampleNames(List> hitSampleInfo) + => hitSampleInfo?.Select(samples => + (IList)samples.Select(sample => sample.LookupNames.First()).ToList()) + .ToList(); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + } + + public struct SampleConvertValue : IEquatable + { + /// + /// A sane value to account for osu!stable using ints everywhere. + /// + private const float conversion_lenience = 2; + + public double StartTime; + public double EndTime; + public int Column; + public IList> NodeSamples; + + public bool Equals(SampleConvertValue other) + => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) + && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) + && samplesEqual(NodeSamples, other.NodeSamples); + + private static bool samplesEqual(ICollection> first, ICollection> second) + { + if (first == null && second == null) + return true; + + // both items can't be null now, so if any single one is, then they're not equal + if (first == null || second == null) + return false; + + return first.Count == second.Count + && first.Zip(second).All(samples => samples.First.SequenceEqual(samples.Second)); + } + } +} From 35544ede50069851ff7cfa0fecdf141fe94345db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 21:54:19 +0200 Subject: [PATCH 107/123] Add failing test cases --- .../ManiaBeatmapSampleConversionTest.cs | 2 ++ .../convert-samples-expected-conversion.json | 30 +++++++++++++++++++ .../Testing/Beatmaps/convert-samples.osu | 16 ++++++++++ .../mania-samples-expected-conversion.json | 25 ++++++++++++++++ .../Testing/Beatmaps/mania-samples.osu | 19 ++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index dbf1cf5f72..2f6918d263 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + [TestCase("convert-samples")] + [TestCase("mania-samples")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json new file mode 100644 index 0000000000..b8ce85eef5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -0,0 +1,30 @@ +{ + "Mappings": [{ + "StartTime": 1000.0, + "Objects": [{ + "StartTime": 1000.0, + "EndTime": 2750.0, + "Column": 1, + "NodeSamples": [ + ["normal-hitnormal"], + ["soft-hitnormal"], + ["drum-hitnormal"] + ] + }, { + "StartTime": 1875.0, + "EndTime": 2750.0, + "Column": 0, + "NodeSamples": [ + ["soft-hitnormal"], + ["drum-hitnormal"] + ] + }] + }, { + "StartTime": 3750.0, + "Objects": [{ + "StartTime": 3750.0, + "EndTime": 3750.0, + "Column": 3 + }] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu new file mode 100644 index 0000000000..16b73992d2 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu @@ -0,0 +1,16 @@ +osu file format v14 + +[Difficulty] +HPDrainRate:5 +CircleSize:5 +OverallDifficulty:5 +ApproachRate:5 +SliderMultiplier:1.4 +SliderTickRate:1 + +[TimingPoints] +0,500,4,1,0,100,1,0 + +[HitObjects] +88,99,1000,6,0,L|306:259,2,245,0|0|0,1:0|2:0|3:0,0:0:0:0: +259,118,3750,1,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json new file mode 100644 index 0000000000..e22540614d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json @@ -0,0 +1,25 @@ +{ + "Mappings": [{ + "StartTime": 500.0, + "Objects": [{ + "StartTime": 500.0, + "EndTime": 1500.0, + "Column": 0, + "NodeSamples": [ + ["normal-hitnormal"], + [] + ] + }] + }, { + "StartTime": 2000.0, + "Objects": [{ + "StartTime": 2000.0, + "EndTime": 3000.0, + "Column": 2, + "NodeSamples": [ + ["drum-hitnormal"], + [] + ] + }] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu new file mode 100644 index 0000000000..7c75b45e5f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[General] +Mode: 3 + +[Difficulty] +HPDrainRate:5 +CircleSize:5 +OverallDifficulty:5 +ApproachRate:5 +SliderMultiplier:1.4 +SliderTickRate:1 + +[TimingPoints] +0,500,4,1,0,100,1,0 + +[HitObjects] +51,192,500,128,0,1500:1:0:0:0: +256,192,2000,128,0,3000:3:0:0:0: From ac019bddd61b798f86c0f9e545a5d1e45de5a746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 22:28:55 +0200 Subject: [PATCH 108/123] Only play samples at start of hold note in mania maps --- .../Beatmaps/ManiaBeatmapConverter.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 32abf5e7f9..b025ac7992 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using System.Collections.Generic; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps Duration = endTimeData.Duration, Column = column, Samples = HitObject.Samples, - NodeSamples = (HitObject as IHasRepeats)?.NodeSamples + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples }); } else if (HitObject is IHasXPosition) @@ -254,6 +255,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return pattern; } + + /// + /// osu!mania-specific beatmaps in stable only play samples at the start of the hold note. + /// + private List> defaultNodeSamples + => new List> + { + HitObject.Samples, + new List() + }; } } } From c4cae006aa800e78f29b46dfcdccda0220838a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 22:47:14 +0200 Subject: [PATCH 109/123] Correctly slice node sample list when converting --- .../Legacy/DistanceObjectPatternGenerator.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 1bd796511b..b49b881656 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -472,15 +472,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private IList sampleInfoListAt(double time) + private IList sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; + + /// + /// Retrieves the list of node samples that occur at time greater than or equal to . + /// + /// The time to retrieve node samples at. + private IEnumerable> nodeSamplesAt(double time) { if (!(HitObject is IHasPathWithRepeats curveData)) - return HitObject.Samples; + return null; double segmentTime = (EndTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; + return curveData.NodeSamples.Skip(index); } /// @@ -511,7 +517,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Duration = endTime - startTime, Column = column, Samples = HitObject.Samples, - NodeSamples = (HitObject as IHasRepeats)?.NodeSamples + NodeSamples = nodeSamplesAt(startTime)?.ToList() }; } From 4c6116e6e7c9aa1300430954ef4e6cbcc793f39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 23:50:58 +0200 Subject: [PATCH 110/123] Fix compilation failure in Android test project --- .../ManiaBeatmapSampleConversionTest.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index 2f6918d263..d8f87195d1 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -58,17 +58,19 @@ namespace osu.Game.Rulesets.Mania.Tests && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && samplesEqual(NodeSamples, other.NodeSamples); - private static bool samplesEqual(ICollection> first, ICollection> second) + private static bool samplesEqual(ICollection> firstSampleList, ICollection> secondSampleList) { - if (first == null && second == null) + if (firstSampleList == null && secondSampleList == null) return true; // both items can't be null now, so if any single one is, then they're not equal - if (first == null || second == null) + if (firstSampleList == null || secondSampleList == null) return false; - return first.Count == second.Count - && first.Zip(second).All(samples => samples.First.SequenceEqual(samples.Second)); + return firstSampleList.Count == secondSampleList.Count + // cannot use .Zip() without the selector function as it doesn't compile in android test project + && firstSampleList.Zip(secondSampleList, (first, second) => (first, second)) + .All(samples => samples.first.SequenceEqual(samples.second)); } } } From 896177801a57e0bd2161309d05775be4d0d087bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 5 Jun 2020 00:07:27 +0200 Subject: [PATCH 111/123] Avoid creating copies of node samples every time --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index b49b881656..9fbdf58e21 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Retrieves the list of node samples that occur at time greater than or equal to . /// /// The time to retrieve node samples at. - private IEnumerable> nodeSamplesAt(double time) + private List> nodeSamplesAt(double time) { if (!(HitObject is IHasPathWithRepeats curveData)) return null; @@ -486,7 +486,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy double segmentTime = (EndTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples.Skip(index); + + // avoid slicing the list & creating copies, if at all possible. + return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); } /// @@ -517,7 +519,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Duration = endTime - startTime, Column = column, Samples = HitObject.Samples, - NodeSamples = nodeSamplesAt(startTime)?.ToList() + NodeSamples = nodeSamplesAt(startTime) }; } From 0107e9ba16deb94cd8f04c5dcf36b7ca2a781adc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Jun 2020 19:18:00 +0900 Subject: [PATCH 112/123] Change lookups to use SingleOrDefault() --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 668ac6ee10..1f92d5461f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps foreach (var info in item.Beatmaps) { - var file = item.Files.FirstOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + var file = item.Files.SingleOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; using (var stream = Files.Store.GetStream(file)) info.MD5Hash = stream.ComputeMD5Hash(); diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index e62a9bb39d..39c5ccab27 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; private TextureStore textureStore; From bb89114b70eb820096ae3dc1b371c0d3ce8c05c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Jun 2020 20:52:27 +0900 Subject: [PATCH 113/123] Show a loading spinner on multiplayer lounge loads --- .../TestSceneLoungeRoomsContainer.cs | 3 ++ .../TestSceneMatchSettingsOverlay.cs | 2 ++ .../Multiplayer/TestSceneMatchSubScreen.cs | 2 ++ osu.Game/Screens/Multi/IRoomManager.cs | 5 +++ .../Screens/Multi/Lounge/LoungeSubScreen.cs | 33 +++++++++++++++++-- osu.Game/Screens/Multi/RoomManager.cs | 14 +++++++- 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 77b41c89b0..83f2297bd2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -141,6 +141,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } public readonly BindableList Rooms = new BindableList(); + + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + IBindableList IRoomManager.Rooms => Rooms; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs index 34c6940552..fdc20dc477 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -133,6 +133,8 @@ namespace osu.Game.Tests.Visual.Multiplayer remove { } } + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + public IBindableList Rooms { get; } = null; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index d678d5a814..9d0c159549 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -93,6 +93,8 @@ namespace osu.Game.Tests.Visual.Multiplayer remove => throw new NotImplementedException(); } + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + public IBindableList Rooms { get; } = new BindableList(); public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs index f6c979851e..bf75843c3e 100644 --- a/osu.Game/Screens/Multi/IRoomManager.cs +++ b/osu.Game/Screens/Multi/IRoomManager.cs @@ -14,6 +14,11 @@ namespace osu.Game.Screens.Multi /// event Action RoomsUpdated; + /// + /// Whether an initial listing of rooms has been received. + /// + Bindable InitialRoomsReceived { get; } + /// /// All the active s. /// diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 7c10f0f975..d4b6a3b79f 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -22,12 +22,16 @@ namespace osu.Game.Screens.Multi.Lounge protected readonly FilterControl Filter; + private readonly Bindable initialRoomsReceived = new Bindable(); + private readonly Container content; private readonly LoadingLayer loadingLayer; [Resolved] private Bindable selectedRoom { get; set; } + private bool joiningRoom; + public LoungeSubScreen() { SearchContainer searchContainer; @@ -73,6 +77,14 @@ namespace osu.Game.Screens.Multi.Lounge }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived); + initialRoomsReceived.BindValueChanged(onInitialRoomsReceivedChanged, true); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -126,12 +138,29 @@ namespace osu.Game.Screens.Multi.Lounge private void joinRequested(Room room) { - loadingLayer.Show(); + joiningRoom = true; + updateLoadingLayer(); + RoomManager?.JoinRoom(room, r => { Open(room); + joiningRoom = false; + updateLoadingLayer(); + }, _ => + { + joiningRoom = false; + updateLoadingLayer(); + }); + } + + private void onInitialRoomsReceivedChanged(ValueChangedEvent received) => updateLoadingLayer(); + + private void updateLoadingLayer() + { + if (joiningRoom || !initialRoomsReceived.Value) + loadingLayer.Show(); + else loadingLayer.Hide(); - }, _ => loadingLayer.Hide()); } /// diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index ad461af57f..4d6ac46c84 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -25,6 +25,9 @@ namespace osu.Game.Screens.Multi public event Action RoomsUpdated; private readonly BindableList rooms = new BindableList(); + + public Bindable InitialRoomsReceived { get; } = new Bindable(); + public IBindableList Rooms => rooms; public double TimeBetweenListingPolls @@ -62,7 +65,11 @@ namespace osu.Game.Screens.Multi InternalChildren = new Drawable[] { - listingPollingComponent = new ListingPollingComponent { RoomsReceived = onListingReceived }, + listingPollingComponent = new ListingPollingComponent + { + InitialRoomsReceived = { BindTarget = InitialRoomsReceived }, + RoomsReceived = onListingReceived + }, selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onSelectedRoomReceived } }; } @@ -262,6 +269,8 @@ namespace osu.Game.Screens.Multi { public Action> RoomsReceived; + public readonly Bindable InitialRoomsReceived = new Bindable(); + [Resolved] private IAPIProvider api { get; set; } @@ -273,6 +282,8 @@ namespace osu.Game.Screens.Multi { currentFilter.BindValueChanged(_ => { + InitialRoomsReceived.Value = false; + if (IsLoaded) PollImmediately(); }); @@ -292,6 +303,7 @@ namespace osu.Game.Screens.Multi pollReq.Success += result => { + InitialRoomsReceived.Value = true; RoomsReceived?.Invoke(result); tcs.SetResult(true); }; From 72ada020a2515a1ed839fecbeb0af52c3ce86abc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 13:42:16 +0900 Subject: [PATCH 114/123] Don't attempt to use virtual track for intro sequence clock --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 188a49c147..cb05dcc932 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -7,6 +7,7 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -61,7 +62,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(MenuMusic.Value ? Track : null), + Clock = new FramedClock(MenuMusic.Value && !(Track is TrackVirtual) ? Track : null), LoadMenu = LoadMenu }, t => { From dfed27bd4633e5b2d1268f8851fec97a698d61e6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 14:24:21 +0900 Subject: [PATCH 115/123] Add back stream seeking for sanity --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f11e94e63d..4e3714a582 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -201,6 +201,8 @@ namespace osu.Game.Beatmaps using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent).Encode(sw); + stream.Seek(0, SeekOrigin.Begin); + UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } From 443977aa8d71071a7566a4be643ffea72b77fee1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 14:40:17 +0900 Subject: [PATCH 116/123] Remove PreUpdate, update hash in Save() --- osu.Game/Beatmaps/BeatmapManager.cs | 22 ++++++++-------------- osu.Game/Database/ArchiveModelManager.cs | 11 ----------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4e3714a582..cbcdf51551 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -203,7 +203,14 @@ namespace osu.Game.Beatmaps stream.Seek(0, SeekOrigin.Begin); - UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); + using (ContextFactory.GetForWrite()) + { + var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + + stream.Seek(0, SeekOrigin.Begin); + UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); + } } var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); @@ -211,19 +218,6 @@ namespace osu.Game.Beatmaps workingCache.Remove(working); } - protected override void PreUpdate(BeatmapSetInfo item) - { - base.PreUpdate(item); - - foreach (var info in item.Beatmaps) - { - var file = item.Files.SingleOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - - using (var stream = Files.Store.GetStream(file)) - info.MD5Hash = stream.ComputeMD5Hash(); - } - } - private readonly WeakList workingCache = new WeakList(); /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index b9479af623..915d980d24 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -429,21 +429,10 @@ namespace osu.Game.Database using (ContextFactory.GetForWrite()) { item.Hash = computeHash(item); - - PreUpdate(item); - ModelStore.Update(item); } } - /// - /// Perform any final actions before the update to database executes. - /// - /// The that is being updated. - protected virtual void PreUpdate(TModel item) - { - } - /// /// Delete an item from the manager. /// Is a no-op for already deleted items. From 63003757c4fee77ca055861cb0538019509138a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 14:48:26 +0900 Subject: [PATCH 117/123] Remove WorkingBeatmap cache when deleting or updating a beatmap --- osu.Game/Beatmaps/BeatmapManager.cs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e7cef13c68..73e4c119e4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -79,6 +79,8 @@ namespace osu.Game.Beatmaps beatmaps = (BeatmapStore)ModelStore; beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference(b); beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b); + beatmaps.ItemRemoved += removeWorkingCache; + beatmaps.ItemUpdated += removeWorkingCache; onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } @@ -206,9 +208,7 @@ namespace osu.Game.Beatmaps UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } - var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); - if (working != null) - workingCache.Remove(working); + removeWorkingCache(info); } private readonly WeakList workingCache = new WeakList(); @@ -410,6 +410,24 @@ namespace osu.Game.Beatmaps return endTime - startTime; } + private void removeWorkingCache(BeatmapSetInfo info) + { + if (info.Beatmaps == null) return; + + foreach (var b in info.Beatmaps) + removeWorkingCache(b); + } + + private void removeWorkingCache(BeatmapInfo info) + { + lock (workingCache) + { + var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); + if (working != null) + workingCache.Remove(working); + } + } + public void Dispose() { onlineLookupQueue?.Dispose(); From dd61d6ed04f47aa77739e974b29949a898d79c74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 14:48:42 +0900 Subject: [PATCH 118/123] Attempt to reimport intro if a bad state is detected --- osu.Game/Screens/Menu/IntroScreen.cs | 62 ++++++++++++++++--------- osu.Game/Screens/Menu/IntroTriangles.cs | 5 +- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 0d5f3d1142..b99d8ae9d1 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -41,9 +41,9 @@ namespace osu.Game.Screens.Menu protected IBindable MenuMusic { get; private set; } - private WorkingBeatmap introBeatmap; + private WorkingBeatmap initialBeatmap; - protected Track Track { get; private set; } + protected Track Track => initialBeatmap?.Track; private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); @@ -58,6 +58,11 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } + /// + /// Whether the is provided by osu! resources, rather than a user beatmap. + /// + protected bool UsingThemedIntro { get; private set; } + [BackgroundDependencyLoader] private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { @@ -71,29 +76,45 @@ namespace osu.Game.Screens.Menu BeatmapSetInfo setInfo = null; + // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { var sets = beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal); + if (sets.Count > 0) - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - } - - if (setInfo == null) - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); - - if (setInfo == null) { - // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; - - setInfo.Protected = true; - beatmaps.Update(setInfo); + setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); } } - introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - Track = introBeatmap.Track; + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (setInfo == null) + { + if (!loadThemedIntro()) + { + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; + import.Protected = true; + beatmaps.Update(import); + + loadThemedIntro(); + } + } + + bool loadThemedIntro() + { + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo != null) + { + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + UsingThemedIntro = !(Track is TrackVirtual); + } + + return UsingThemedIntro; + } } public override void OnResuming(IScreen last) @@ -119,7 +140,7 @@ namespace osu.Game.Screens.Menu public override void OnSuspending(IScreen next) { base.OnSuspending(next); - Track = null; + initialBeatmap = null; } protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); @@ -127,7 +148,7 @@ namespace osu.Game.Screens.Menu protected void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (MenuMusic.Value) + if (UsingThemedIntro) Track.Restart(); } @@ -141,8 +162,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { - beatmap.Value = introBeatmap; - introBeatmap = null; + beatmap.Value = initialBeatmap; logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index cb05dcc932..225ad02ec4 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -7,7 +7,6 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -47,7 +46,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - if (MenuVoice.Value && !MenuMusic.Value) + if (MenuVoice.Value && !UsingThemedIntro) welcome = audio.Samples.Get(@"welcome"); } @@ -62,7 +61,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(MenuMusic.Value && !(Track is TrackVirtual) ? Track : null), + Clock = new FramedClock(UsingThemedIntro ? Track : null), LoadMenu = LoadMenu }, t => { From 712fd6a944cc957bcff10721451ffe613c7180c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 17:49:45 +0900 Subject: [PATCH 119/123] Fetch existing private message channels on re-joining --- .../API/Requests/CreateChannelRequest.cs | 34 +++++++++++++++++++ .../API/Requests/Responses/APIChatChannel.cs | 18 ++++++++++ osu.Game/Online/Chat/Channel.cs | 3 +- osu.Game/Online/Chat/ChannelManager.cs | 29 ++++++++++------ 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Online/API/Requests/CreateChannelRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIChatChannel.cs diff --git a/osu.Game/Online/API/Requests/CreateChannelRequest.cs b/osu.Game/Online/API/Requests/CreateChannelRequest.cs new file mode 100644 index 0000000000..42cb201969 --- /dev/null +++ b/osu.Game/Online/API/Requests/CreateChannelRequest.cs @@ -0,0 +1,34 @@ +// 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 System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class CreateChannelRequest : APIRequest + { + private readonly Channel channel; + + public CreateChannelRequest(Channel channel) + { + this.channel = channel; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + + req.AddParameter("type", $"{ChannelType.PM}"); + req.AddParameter("target_id", $"{channel.Users.First().Id}"); + + return req; + } + + protected override string Target => @"chat/channels"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChatChannel.cs b/osu.Game/Online/API/Requests/Responses/APIChatChannel.cs new file mode 100644 index 0000000000..fc3b2a8e31 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChatChannel.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChatChannel + { + [JsonProperty(@"channel_id")] + public int? ChannelID { get; set; } + + [JsonProperty(@"recent_messages")] + public List RecentMessages { get; set; } + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index dbb2da5c03..8c1e1ad128 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -84,7 +84,8 @@ namespace osu.Game.Online.Chat public long? LastReadId; /// - /// Signalles if the current user joined this channel or not. Defaults to false. + /// Signals if the current user joined this channel or not. Defaults to false. + /// Note that this does not guarantee a join has completed. Check Id > 0 for confirmation. /// public Bindable Joined = new Bindable(); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index b17e0812da..9350887feb 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.Chat return; CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id)) - ?? new Channel(user); + ?? JoinChannel(new Channel(user)); } private void currentChannelChanged(ValueChangedEvent e) @@ -140,7 +140,7 @@ namespace osu.Game.Online.Chat target.AddLocalEcho(message); // if this is a PM and the first message, we need to do a special request to create the PM channel - if (target.Type == ChannelType.PM && !target.Joined.Value) + if (target.Type == ChannelType.PM && target.Id == 0) { var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message); @@ -356,26 +356,35 @@ namespace osu.Game.Online.Chat // ensure we are joined to the channel if (!channel.Joined.Value) { + channel.Joined.Value = true; + switch (channel.Type) { case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - channel.Joined.Value = true; joinChannel(channel, fetchInitialMessages); return channel; - case ChannelType.Private: - // can't do this yet. + case ChannelType.PM: + var createRequest = new CreateChannelRequest(channel); + createRequest.Success += resChannel => + { + if (resChannel.ChannelID.HasValue) + { + channel.Id = resChannel.ChannelID.Value; + + handleChannelMessages(resChannel.RecentMessages); + channel.MessagesLoaded = true; // this will mark the channel as having received messages even if there were none. + } + }; + + api.Queue(createRequest); break; default: var req = new JoinChannelRequest(channel, api.LocalUser.Value); - req.Success += () => - { - channel.Joined.Value = true; - joinChannel(channel, fetchInitialMessages); - }; + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; From ff555c41c6b667ebd91ba46f166cbd247ffeece3 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 08:57:44 +0000 Subject: [PATCH 120/123] Bump Sentry from 2.1.1 to 2.1.3 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.1 to 2.1.3. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.1...2.1.3) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4d6358575b..c41d0a0cf6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From bbf8864f1478d609fe2eb7184cbd303e0cc9a14b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:45:31 +0000 Subject: [PATCH 121/123] Bump Microsoft.Build.Traversal from 2.0.34 to 2.0.48 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.34 to 2.0.48. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.0.34...Microsoft.Build.Traversal.2.0.48) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 6c793a3f1d..bdb90eb0e9 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.34" + "Microsoft.Build.Traversal": "2.0.48" } } \ No newline at end of file From e0c94304c79637c86e9304e0471ce37bb139f223 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:45:31 +0000 Subject: [PATCH 122/123] Bump Humanizer from 2.8.11 to 2.8.26 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.8.11 to 2.8.26. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/master/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.8.11...v2.8.26) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c41d0a0cf6..8213719c01 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6b55fa51ff..fd13455c63 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -76,7 +76,7 @@ - + From 8a021e0beb39a897816d8da99983eb9de6e4b419 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 22:35:01 +0900 Subject: [PATCH 123/123] Use save method in test --- .../Beatmaps/IO/ImportBeatmapTest.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 55368f6676..249a8caba9 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1,11 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.IO; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -15,7 +14,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Resources; @@ -730,25 +728,17 @@ namespace osu.Game.Tests.Beatmaps.IO await osu.Dependencies.Get().Import(temp); BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; + + var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0); Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; - using (var stream = new MemoryStream()) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - beatmapToUpdate.HitObjects.Clear(); - beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); + beatmapToUpdate.HitObjects.Clear(); + beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); - new LegacyBeatmapEncoder(beatmapToUpdate).Encode(writer); - } - - stream.Seek(0, SeekOrigin.Begin); - - manager.UpdateFile(setToUpdate, fileToUpdate, stream); - } + manager.Save(beatmapInfo, beatmapToUpdate); // Check that the old file reference has been removed Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID));