diff --git a/osu.Android.props b/osu.Android.props index 78ceaa8616..d7817cf4cf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 9839d16030..db73bb7e7f 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -9,7 +9,7 @@ using osu.Framework.Android; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuGameAndroid(); diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index cc01009dd9..75feb21298 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Type.AffectsCombo() || !result.HasResult) return; - if (result.Type == HitResult.Miss) + if (!result.IsHit) { updateCombo(0, null); return; diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index 95e86de884..9c4c2b3d5b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { public class TestSceneHoldNote : ManiaHitObjectTestScene { - public TestSceneHoldNote() + [Test] + public void TestHoldNote() { AddToggleStep("toggle hitting", v => { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index dd5fd93710..6b8f5d5d9d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -28,25 +28,33 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestVariousNotes() { - Child = new FillFlowContainer + DrawableNote note1 = null; + DrawableNote note2 = null; + DrawableHoldNote holdNote1 = null; + DrawableHoldNote holdNote2 = null; + + AddStep("create notes", () => { - Clock = new FramedClock(new ManualClock()), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20), - Children = new[] + Child = new FillFlowContainer { - createNoteDisplay(ScrollingDirection.Down, 1, out var note1), - createNoteDisplay(ScrollingDirection.Up, 2, out var note2), - createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1), - createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2), - } - }; + Clock = new FramedClock(new ManualClock()), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20), + Children = new[] + { + createNoteDisplay(ScrollingDirection.Down, 1, out note1), + createNoteDisplay(ScrollingDirection.Up, 2, out note2), + createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1), + createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2), + } + }; + }); AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2)); AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0)); diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 220dedc4a4..d28b7bdf58 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -2,10 +2,40 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.Meh: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + + case HitResult.Ok: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; + + case HitResult.Good: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.Great: + return DEFAULT_MAX_HEALTH_INCREASE * 0.8; + + case HitResult.Perfect: + return DEFAULT_MAX_HEALTH_INCREASE; + + default: + return base.HealthIncreaseFor(result); + } + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index ba6cad978d..f6d539c91b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables endHold(); } - if (Tail.Result.Type == HitResult.Miss) + if (Tail.Judged && !Tail.IsHit) HasBroken = true; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 31e43d3ee2..c780c0836e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 08c41b0d75..27960b3f3a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); } public abstract class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 973dc06e05..b3402d13e4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 37df0d6e37..596bc06c68 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneHitCircle() + [Test] + public void TestVariousHitCircles() { AddStep("Miss Big Single", () => SetContents(() => testSingle(2))); AddStep("Miss Medium Single", () => SetContents(() => testSingle(5))); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index f3221ffe32..39deba2f57 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, - PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss + PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Beatmap = beatmap, - PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss + PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c79cae2fe5..c9e112f76d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneSlider() + [Test] + public void TestVariousSliders() { AddStep("Big Single", () => SetContents(() => testSimpleBig())); AddStep("Medium Single", () => SetContents(() => testSimpleMedium())); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index d5c3538c81..0164fb8bf4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && !judgementResults.First().IsHit; - private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit; - private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss; private ScoreAccessibleReplayPlayer currentPlayer; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a438dc8be4..b5ac26c824 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var circleResult = (OsuHitCircleJudgementResult)r; // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (result != HitResult.Miss) + if (result.IsHit()) { var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 2946331bc6..45c664ba3b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 012d9f8878..49535e7fff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -8,7 +8,6 @@ using osu.Game.Configuration; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK.Graphics; @@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + lightingColour.BindValueChanged(colour => Lighting.Colour = Result.IsHit ? colour.NewValue : Color4.Transparent, true); } else { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9abcef83c4..280ca33234 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -250,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // rather than doing it this way, we should probably attach the sample to the tail circle. // this can only be done after we stop using LegacyLastTick. - if (TailCircle.Result.Type != HitResult.Miss) + if (TailCircle.IsHit) base.PlaySamples(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fe7cb278b0..130b4e6e53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else if (Progress > .75) r.Type = HitResult.Meh; else if (Time.Current >= Spinner.EndTime) - r.Type = HitResult.Miss; + r.Type = r.Judgement.MinResult; }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 587bd415ee..e855317544 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private Spinner spinner; + private const float initial_scale = 1.3f; private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // we are slightly bigger than our parent, to clip the top and bottom of the circle // this should probably be revisited when scaled spinners are a thing. - Scale = new Vector2(1.3f); + Scale = new Vector2(initial_scale); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -117,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime)); } - const float initial_scale = 0.2f; - float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; + const float initial_fill_scale = 0.2f; + float targetScale = initial_fill_scale + (1 - initial_fill_scale) * drawableSpinner.Progress; fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; @@ -129,41 +130,54 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (!(drawableHitObject is DrawableSpinner)) return; - centre.ScaleTo(0); - mainContainer.ScaleTo(0); - - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) { - // constant ambient rotation to give the spinner "spinning" character. - this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); - - centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); - mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + this.ScaleTo(initial_scale); + this.RotateTo(0); using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) { - centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); - mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + // constant ambient rotation to give the spinner "spinning" character. + this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); + } + + using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true)) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out); + this.RotateTo(mainContainer.Rotation + 180, 320); + break; + + case ArmedState.Miss: + this.ScaleTo(initial_scale * 0.8f, 320, Easing.In); + break; + } + } + } + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + { + centre.ScaleTo(0); + mainContainer.ScaleTo(0); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); + mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); + mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + } } } // transforms we have from completing the spinner will be rolled back, so reapply immediately. - updateComplete(state == ArmedState.Hit, 0); - - using (BeginDelayedSequence(spinner.Duration, true)) - { - switch (state) - { - case ArmedState.Hit: - this.ScaleTo(Scale * 1.2f, 320, Easing.Out); - this.RotateTo(mainContainer.Rotation + 180, 320); - break; - - case ArmedState.Miss: - this.ScaleTo(Scale * 0.8f, 320, Easing.In); - break; - } - } + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + updateComplete(state == ArmedState.Hit, 0); } private void updateComplete(bool complete, double duration) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index aff3f38e17..3afd36669f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTailJudgement : OsuJudgement { - public override HitResult MaxResult => HitResult.IgnoreHit; + public override HitResult MaxResult => HitResult.SmallTickHit; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 1dfc9c0772..56b5571ce1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -70,9 +70,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } @@ -83,12 +81,19 @@ namespace osu.Game.Rulesets.Osu.Skinning var spinner = (Spinner)drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + this.FadeOut(); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) this.FadeInFromZero(spinner.TimeFadeIn / 2); - fixedMiddle.FadeColour(Color4.White); - using (BeginAbsoluteSequence(spinner.StartTime, true)) - fixedMiddle.FadeColour(Color4.Red, spinner.Duration); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + { + fixedMiddle.FadeColour(Color4.White); + + using (BeginDelayedSequence(spinner.TimePreempt, true)) + fixedMiddle.FadeColour(Color4.Red, spinner.Duration); + } } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index eba9abda0b..7b0d7acbbc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -88,9 +88,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } @@ -101,6 +99,9 @@ namespace osu.Game.Rulesets.Osu.Skinning var spinner = drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + this.FadeOut(); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) this.FadeInFromZero(spinner.TimeFadeIn / 2); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 0f605be8f9..e4c0766844 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests private readonly Random rng = new Random(1337); - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestVariousHits() { AddStep("Hit", () => addHitJudgement(false)); AddStep("Strong hit", () => addStrongHitJudgement(false)); diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 0551df3211..647ad7853d 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { switch (result) { - case HitResult.Great: + case HitResult.SmallTickHit: return 0.15; default: diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 286feac5ba..8f268dc1c7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!(obj is DrawableDrumRollTick)) return; - if (result.Type > HitResult.Miss) + if (result.IsHit) rollingHits++; else rollingHits--; @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 03df28f850..bb42240f25 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; if (!validActionPressed) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); else ApplyResult(r => r.Type = result); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 11ff0729e2..8ee4a5db71 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,9 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; - - ApplyResult(r => r.Type = hitResult); + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index dd3c2289ea..f7a1d130eb 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring private double hpMultiplier; /// - /// HP multiplier for a . + /// HP multiplier for a that does not satisfy . /// private double hpMissMultiplier; @@ -45,6 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring } protected override double GetHealthIncreaseFor(JudgementResult result) - => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier); + => base.GetHealthIncreaseFor(result) * (result.IsHit ? hpMultiplier : hpMissMultiplier); } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs index 928072c491..e029040ef3 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (r?.Type.AffectsCombo() == false) return; - passing = r == null || r.Type > HitResult.Miss; + passing = r == null || r.IsHit; foreach (var sprite in InternalChildren.OfType()) sprite.Passing = passing; diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 7b8ab89233..3bd20e4bb4 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI Alpha = 0.15f; Masking = true; - if (result == HitResult.Miss) + if (!result.IsHit()) return; bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index ff2c9fb1a9..b7a41ffd1c 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -12,6 +12,14 @@ namespace osu.Game.Tests.Editing [TestFixture] public class EditorChangeHandlerTest { + private int stateChangedFired; + + [SetUp] + public void SetUp() + { + stateChangedFired = 0; + } + [Test] public void TestSaveRestoreState() { @@ -23,6 +31,8 @@ namespace osu.Game.Tests.Editing addArbitraryChange(beatmap); handler.SaveState(); + Assert.That(stateChangedFired, Is.EqualTo(1)); + Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.False); @@ -30,6 +40,8 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.True); + + Assert.That(stateChangedFired, Is.EqualTo(2)); } [Test] @@ -45,6 +57,7 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.False); + Assert.That(stateChangedFired, Is.EqualTo(1)); string hash = handler.CurrentStateHash; @@ -52,6 +65,7 @@ namespace osu.Game.Tests.Editing handler.SaveState(); Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); + Assert.That(stateChangedFired, Is.EqualTo(1)); handler.RestoreState(-1); @@ -60,6 +74,7 @@ namespace osu.Game.Tests.Editing // we should only be able to restore once even though we saved twice. Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.True); + Assert.That(stateChangedFired, Is.EqualTo(2)); } [Test] @@ -71,6 +86,8 @@ namespace osu.Game.Tests.Editing for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) { + Assert.That(stateChangedFired, Is.EqualTo(i)); + addArbitraryChange(beatmap); handler.SaveState(); } @@ -114,7 +131,10 @@ namespace osu.Game.Tests.Editing { var beatmap = new EditorBeatmap(new Beatmap()); - return (new EditorChangeHandler(beatmap), beatmap); + var changeHandler = new EditorChangeHandler(beatmap); + + changeHandler.OnStateChange += () => stateChangedFired++; + return (changeHandler, beatmap); } private void addArbitraryChange(EditorBeatmap beatmap) diff --git a/osu.Game.Tests/NonVisual/GameplayClockTest.cs b/osu.Game.Tests/NonVisual/GameplayClockTest.cs new file mode 100644 index 0000000000..3fd7c364b7 --- /dev/null +++ b/osu.Game.Tests/NonVisual/GameplayClockTest.cs @@ -0,0 +1,39 @@ +// 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 NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Timing; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class GameplayClockTest + { + [TestCase(0)] + [TestCase(1)] + public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate) + { + var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); + var gameplayClock = new TestGameplayClock(framedClock); + + gameplayClock.MutableNonGameplayAdjustments.Add(new BindableDouble()); + + Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0)); + } + + private class TestGameplayClock : GameplayClock + { + public List> MutableNonGameplayAdjustments { get; } = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public TestGameplayClock(IFrameBasedClock underlyingClock) + : base(underlyingClock) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 720cf51f2c..13a3195824 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public override void SetUpSteps() { AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); @@ -38,6 +43,15 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false); + } + + [Test] + public void TestExitWithoutSave() + { + AddStep("exit without save", () => Editor.Exit()); + AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } [Test] diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 1070b8cbd2..8ed7885101 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -40,17 +40,28 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } + [Resolved(canBeNull: true)] + private IEditorChangeHandler changeHandler { get; set; } + protected override void LoadComplete() { base.LoadComplete(); beatmap.HitObjectAdded += addHitObject; - beatmap.HitObjectUpdated += updateReplay; beatmap.HitObjectRemoved += removeHitObject; + + if (changeHandler != null) + { + // for now only regenerate replay on a finalised state change, not HitObjectUpdated. + changeHandler.OnStateChange += updateReplay; + } + else + { + beatmap.HitObjectUpdated += _ => updateReplay(); + } } - private void updateReplay(HitObject obj = null) => - drawableRuleset.RegenerateAutoplay(); + private void updateReplay() => drawableRuleset.RegenerateAutoplay(); private void addHitObject(HitObject hitObject) { @@ -58,8 +69,6 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); - - updateReplay(); } private void removeHitObject(HitObject hitObject) @@ -68,8 +77,6 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - - drawableRuleset.RegenerateAutoplay(); } public override bool PropagatePositionalInputSubTree => false; diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 4ee0ce437c..89a3a2b855 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -109,40 +109,40 @@ namespace osu.Game.Rulesets.Judgements return 0; case HitResult.SmallTickHit: - return DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.SmallTickMiss: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.LargeTickHit: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.LargeTickMiss: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Miss: return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; case HitResult.Ok: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.Good: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE * 0.75; case HitResult.Great: - return DEFAULT_MAX_HEALTH_INCREASE * 0.8; - - case HitResult.Perfect: return DEFAULT_MAX_HEALTH_INCREASE; + case HitResult.Perfect: + return DEFAULT_MAX_HEALTH_INCREASE * 1.05; + case HitResult.SmallBonus: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.LargeBonus: - return DEFAULT_MAX_HEALTH_INCREASE * 0.2; + return DEFAULT_MAX_HEALTH_INCREASE; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 29c25f20a4..66fc61720a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -506,19 +506,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime); - switch (Result.Type) - { - case HitResult.None: - break; - - case HitResult.Miss: - updateState(ArmedState.Miss); - break; - - default: - updateState(ArmedState.Hit); - break; - } + if (Result.HasResult) + updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); OnNewResult?.Invoke(this, Result); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a0692d94e6..875ab25003 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -84,6 +84,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; + private bool isNewBeatmap; + protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -113,8 +115,6 @@ namespace osu.Game.Screens.Edit // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); - bool isNewBeatmap = false; - if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; @@ -287,6 +287,9 @@ namespace osu.Game.Screens.Edit protected void Save() { + // no longer new after first user-triggered save. + isNewBeatmap = false; + // apply any set-level metadata changes. beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); @@ -435,10 +438,20 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) + if (!exitConfirmed) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); - return true; + // if the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save. + if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) + { + confirmExit(); + return true; + } + + if (isNewBeatmap || HasUnsavedChanges) + { + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + return true; + } } Background.FadeColour(Color4.White, 500); @@ -456,6 +469,12 @@ namespace osu.Game.Screens.Edit private void confirmExit() { + if (isNewBeatmap) + { + // confirming exit without save means we should delete the new beatmap completely. + beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); + } + exitConfirmed = true; this.Exit(); } diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 66331d54c0..b69e9c4c51 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit public readonly Bindable CanUndo = new Bindable(); public readonly Bindable CanRedo = new Bindable(); + public event Action OnStateChange; + private readonly LegacyEditorBeatmapPatcher patcher; private readonly List savedStates = new List(); @@ -106,6 +108,8 @@ namespace osu.Game.Screens.Edit savedStates.Add(newState); currentState = savedStates.Count - 1; + + OnStateChange?.Invoke(); updateBindables(); } } @@ -133,6 +137,7 @@ namespace osu.Game.Screens.Edit isRestoring = false; + OnStateChange?.Invoke(); updateBindables(); } diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index f95df76907..0283421b8c 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.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 osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit @@ -10,6 +11,11 @@ namespace osu.Game.Screens.Edit /// public interface IEditorChangeHandler { + /// + /// Fired whenever a state change occurs. + /// + event Action OnStateChange; + /// /// Begins a bulk state change event. should be invoked soon after. /// diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index ee19aaface..c77d48ef0a 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Edit.Timing { private LabelledTextBox textBox; + private TriangleButton button; + [Resolved] protected Bindable SelectedGroup { get; private set; } @@ -52,7 +54,7 @@ namespace osu.Game.Screens.Edit.Timing { Label = "Time" }, - new TriangleButton + button = new TriangleButton { Text = "Use current time", RelativeSizeAxes = Axes.X, @@ -82,18 +84,22 @@ namespace osu.Game.Screens.Edit.Timing if (group.NewValue == null) { textBox.Text = string.Empty; + textBox.Current.Disabled = true; + button.Enabled.Value = false; return; } textBox.Current.Disabled = false; + button.Enabled.Value = true; + textBox.Text = $"{group.NewValue.Time:n0}"; }, true); } private void changeSelectedGroupTime(in double time) { - if (time == SelectedGroup.Value.Time) + if (SelectedGroup.Value == null || time == SelectedGroup.Value.Time) return; changeHandler?.BeginChange(); diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 9d04722c12..9f2868573e 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; +using osu.Framework.Utils; namespace osu.Game.Screens.Play { @@ -47,7 +48,12 @@ namespace osu.Game.Screens.Play double baseRate = Rate; foreach (var adjustment in NonGameplayAdjustments) + { + if (Precision.AlmostEquals(adjustment.Value, 0)) + return 0; + baseRate /= adjustment.Value; + } return baseRate; } diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 7736541c92..fc4a1a5d83 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -13,7 +13,6 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { @@ -106,7 +105,7 @@ namespace osu.Game.Screens.Play.HUD public void Flash(JudgementResult result) { - if (result.Type == HitResult.Miss) + if (!result.IsHit) return; Scheduler.AddOnce(flash); diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 45fdc3ff33..aa2a83774e 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss).ToList(); + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 18a2238784..055db143d1 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss) + var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) .Select(ev => ev.TimeOffset).ToArray(); Value = 10 * standardDeviation(timeOffsets); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3a839ac1a4..fa2135580d 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 31f1af135d..20a51e5feb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - +