diff --git a/.vscode/launch.json b/.vscode/launch.json index ed67fa92cc..12bb5b7734 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,11 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Debug)", - "env": {}, + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}" + } + }, "console": "internalConsole" }, { @@ -24,7 +28,11 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Release)", - "env": {}, + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}" + } + }, "console": "internalConsole" }, { @@ -37,7 +45,11 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", - "env": {}, + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}" + } + }, "console": "internalConsole" }, { @@ -50,7 +62,11 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", - "env": {}, + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}" + } + }, "console": "internalConsole" } ] diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs index e77dd76353..5c41e4136c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests private DrawableFruit createDrawable(int index) { Fruit fruit = index == 5 - ? new BananaShower.Banana + ? new Banana { StartTime = 1000000000000, IndexInBeatmap = index, diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 8473f5a36c..ab0afb08d7 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { public class CatchBeatmapProcessor : BeatmapProcessor { + public const int RNG_SEED = 1337; + public CatchBeatmapProcessor(IBeatmap beatmap) : base(beatmap) { @@ -22,12 +24,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps public override void PostProcess() { + base.PostProcess(); + applyPositionOffsets(); initialiseHyperDash((List)Beatmap.HitObjects); - base.PostProcess(); - int index = 0; foreach (var obj in Beatmap.HitObjects.OfType()) { @@ -37,8 +39,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } } - public const int RNG_SEED = 1337; - private void applyPositionOffsets() { var rng = new FastRandom(RNG_SEED); @@ -49,9 +49,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { case BananaShower bananaShower: - foreach (var nested in bananaShower.NestedHitObjects) + foreach (var banana in bananaShower.NestedHitObjects.OfType()) { - ((BananaShower.Banana)nested).X = (float)rng.NextDouble(); + banana.X = (float)rng.NextDouble(); rng.Next(); // osu!stable retrieved a random banana type rng.Next(); // osu!stable retrieved a random banana rotation rng.Next(); // osu!stable retrieved a random banana colour diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs new file mode 100644 index 0000000000..c39e663d75 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Judgements +{ + public class CatchBananaJudgement : CatchJudgement + { + public override bool AffectsCombo => false; + + public override bool ShouldExplode => true; + + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 1100; + } + } + + protected override float HealthIncreaseFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 8; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs new file mode 100644 index 0000000000..0df2305339 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Judgements +{ + public class CatchDropletJudgement : CatchJudgement + { + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 30; + } + } + + protected override float HealthIncreaseFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 7; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index bb2786f14f..51d7d3b5cd 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -2,11 +2,51 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - // todo: wangs + public override HitResult MaxResult => HitResult.Perfect; + + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 300; + } + } + + /// + /// The base health increase for the result achieved. + /// + public float HealthIncrease => HealthIncreaseFor(Result); + + /// + /// Whether fruit on the platter should explode or drop. + /// Note that this is only checked if the owning object is also + /// + public virtual bool ShouldExplode => IsHit; + + /// + /// Convert a to a base health increase. + /// + /// The value to convert. + /// The base health increase. + protected virtual float HealthIncreaseFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 10.2f; + } + } } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs new file mode 100644 index 0000000000..8b77351027 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Judgements +{ + public class CatchTinyDropletJudgement : CatchJudgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 10; + } + } + + protected override float HealthIncreaseFor(HitResult result) + { + switch (result) + { + default: + return 0; + case HitResult.Perfect: + return 4; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs new file mode 100644 index 0000000000..f7c60a7a47 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class Banana : Fruit + { + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 4dd491966c..25af7e4bdf 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -37,10 +37,5 @@ namespace osu.Game.Rulesets.Catch.Objects public double EndTime => StartTime + Duration; public double Duration { get; set; } - - public class Banana : Fruit - { - public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; - } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs new file mode 100644 index 0000000000..dd027abbe0 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Catch.Judgements; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableBanana : DrawableFruit + { + public DrawableBanana(Banana h) + : base(h) + { + } + + protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement(); + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index 739cc6a59b..f039504600 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -5,9 +5,7 @@ using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; - foreach (var b in s.NestedHitObjects.Cast()) + foreach (var b in s.NestedHitObjects.Cast()) AddNested(getVisualRepresentation?.Invoke(b)); } - protected override void CheckForJudgements(bool userTriggered, double timeOffset) - { - if (timeOffset >= 0) - AddJudgement(new Judgement { Result = NestedHitObjects.Cast().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss }); - } + protected override bool ProvidesJudgement => false; protected override void AddNested(DrawableHitObject h) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index e3564b5967..6ce2e6a2ae 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -2,14 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using OpenTK; +using OpenTK.Graphics; using osu.Framework.Graphics; -using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using OpenTK; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -58,9 +58,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable if (CheckPosition == null) return; if (timeOffset >= 0) - AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); + { + var judgement = CreateJudgement(); + judgement.Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss; + AddJudgement(judgement); + } } + protected virtual CatchJudgement CreateJudgement() => new CatchJudgement(); + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 5c8a7c4a7c..11d5ed1f92 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using OpenTK; using OpenTK.Graphics; +using osu.Game.Rulesets.Catch.Judgements; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable Masking = false; } + protected override CatchJudgement CreateJudgement() => new CatchDropletJudgement(); + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs new file mode 100644 index 0000000000..2232bb81a7 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Game.Rulesets.Catch.Judgements; + +namespace osu.Game.Rulesets.Catch.Objects.Drawable +{ + public class DrawableTinyDroplet : DrawableDroplet + { + public DrawableTinyDroplet(Droplet h) + : base(h) + { + Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8; + } + + protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement(); + } +} diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 936ab6a9d3..23b620248f 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays return; } - if (h is BananaShower.Banana) + if (h is Banana) { // auto bananas unrealistically warp to catch 100% combo. Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Replays { switch (nestedObj) { - case BananaShower.Banana _: + case Banana _: case TinyDroplet _: case Droplet _: case Fruit _: diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index ce1aee5c34..5b69d836a3 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -1,10 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -17,28 +19,57 @@ namespace osu.Game.Rulesets.Catch.Scoring { } + private float hpDrainRate; + protected override void SimulateAutoplay(Beatmap beatmap) { + hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; + foreach (var obj in beatmap.HitObjects) { switch (obj) { case JuiceStream stream: - foreach (var _ in stream.NestedHitObjects.Cast()) - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + foreach (var nestedObject in stream.NestedHitObjects) + switch (nestedObject) + { + case TinyDroplet _: + AddJudgement(new CatchTinyDropletJudgement { Result = HitResult.Perfect }); + break; + case Droplet _: + AddJudgement(new CatchDropletJudgement { Result = HitResult.Perfect }); + break; + case Fruit _: + AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + break; + } break; case BananaShower shower: foreach (var _ in shower.NestedHitObjects.Cast()) - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); + AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect }); break; case Fruit _: AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); break; } } + } - base.SimulateAutoplay(beatmap); + private const double harshness = 0.01; + + protected override void OnNewJudgement(Judgement judgement) + { + base.OnNewJudgement(judgement); + + if (judgement.Result == HitResult.Miss) + { + if (!judgement.IsBonus) + Health.Value -= hpDrainRate * (harshness * 2); + return; + } + + if (judgement is CatchJudgement catchJudgement) + Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness; } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 52763e09af..1ac052de4d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -38,14 +38,16 @@ namespace osu.Game.Rulesets.Catch.UI { switch (h) { + case Banana banana: + return new DrawableBanana(banana); case Fruit fruit: return new DrawableFruit(fruit); case JuiceStream stream: return new DrawableJuiceStream(stream, GetVisualRepresentation); - case BananaShower banana: - return new DrawableBananaShower(banana, GetVisualRepresentation); + case BananaShower shower: + return new DrawableBananaShower(shower, GetVisualRepresentation); case TinyDroplet tiny: - return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; + return new DrawableTinyDroplet(tiny); case Droplet droplet: return new DrawableDroplet(droplet); } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index ceb05d349f..9c376f340a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.MathUtils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; @@ -78,12 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI if (!fruit.StaysOnPlate) runAfterLoaded(() => MovableCatcher.Explode(caughtFruit)); - } if (fruit.HitObject.LastInCombo) { - if (judgement.IsHit) + if (((CatchJudgement)judgement).ShouldExplode) runAfterLoaded(() => MovableCatcher.Explode()); else MovableCatcher.Drop(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs index 72f0b046b6..de2bfaed9c 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Tests var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - columns[i].Add(new DrawableNote(obj, columns[i].Action)); + columns[i].Add(new DrawableNote(obj)); } } @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Tests var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - columns[i].Add(new DrawableHoldNote(obj, columns[i].Action)); + columns[i].Add(new DrawableHoldNote(obj)); } } @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Tests Origin = Anchor.Centre, Height = 0.85f, AccentColour = Color4.OrangeRed, - Action = action, + Action = { Value = action }, VisibleTimeRange = { Value = 2000 } }; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs index 4fdfac93b7..e827fa1fdc 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -63,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests AutoSizeAxes = Axes.Both, Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}") { - Child = new DrawableNote(note, ManiaAction.Key1) { AccentColour = Color4.OrangeRed } + Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed } } }; } @@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests AutoSizeAxes = Axes.Both, Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}") { - Child = new DrawableHoldNote(note, ManiaAction.Key1) + Child = new DrawableHoldNote(note) { RelativeSizeAxes = Axes.Both, AccentColour = Color4.OrangeRed, @@ -136,6 +137,13 @@ namespace osu.Game.Rulesets.Mania.Tests }; } + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + dependencies.CacheAs>(new Bindable()); + return dependencies; + } + protected override void Update() { base.Update(); @@ -145,9 +153,6 @@ namespace osu.Game.Rulesets.Mania.Tests if (!(obj.HitObject is IHasEndTime endTime)) continue; - if (!obj.HasNestedHitObjects) - continue; - foreach (var nested in obj.NestedHitObjects) { double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs index 9aff853ffd..8046c46fc1 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Tests var obj = new Note { Column = i, StartTime = Time.Current + 2000 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - stage.Add(new DrawableNote(obj, stage.Columns[i].Action)); + stage.Add(new DrawableNote(obj)); } } } @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Tests var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 }; obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - stage.Add(new DrawableHoldNote(obj, stage.Columns[i].Action)); + stage.Add(new DrawableHoldNote(obj)); } } } diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs index 9630ba9273..9055e48a4c 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mania.Judgements public class HoldNoteJudgement : ManiaJudgement { public override bool AffectsCombo => false; + protected override int NumericResultFor(HitResult result) => 0; } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index ce0276f759..597450f223 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -38,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly Container tickContainer; - public DrawableHoldNote(HoldNote hitObject, ManiaAction action) - : base(hitObject, action) + public DrawableHoldNote(HoldNote hitObject) + : base(hitObject) { RelativeSizeAxes = Axes.X; @@ -57,12 +57,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables HoldStartTime = () => holdStartTime }) }, - head = new DrawableHeadNote(this, action) + head = new DrawableHeadNote(this) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }, - tail = new DrawableTailNote(this, action) + tail = new DrawableTailNote(this) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime) return false; - if (action != Action) + if (action != Action.Value) return false; // The user has pressed during the body of the hold note, after the head note and its hit windows have passed @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!holdStartTime.HasValue) return false; - if (action != Action) + if (action != Action.Value) return false; holdStartTime = null; @@ -154,8 +154,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { private readonly DrawableHoldNote holdNote; - public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action) - : base(holdNote.HitObject.Head, action) + public DrawableHeadNote(DrawableHoldNote holdNote) + : base(holdNote.HitObject.Head) { this.holdNote = holdNote; } @@ -191,8 +191,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables private readonly DrawableHoldNote holdNote; - public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action) - : base(holdNote.HitObject.Tail, action) + public DrawableTailNote(DrawableHoldNote holdNote) + : base(holdNote.HitObject.Tail) { this.holdNote = holdNote; } @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!holdNote.holdStartTime.HasValue) return false; - if (action != Action) + if (action != Action.Value) return false; UpdateJudgement(true); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 1271fae0c1..cb6196a890 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -10,30 +11,26 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Objects.Drawables { - public abstract class DrawableManiaHitObject : DrawableHitObject - where TObject : ManiaHitObject + public abstract class DrawableManiaHitObject : DrawableHitObject { /// - /// The key that will trigger input for this hit object. + /// The which causes this to be hit. /// - protected ManiaAction Action { get; } - - public new TObject HitObject; + protected readonly IBindable Action = new Bindable(); protected readonly IBindable Direction = new Bindable(); - protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null) + protected DrawableManiaHitObject(ManiaHitObject hitObject) : base(hitObject) { - HitObject = hitObject; - - if (action != null) - Action = action.Value; } - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] IBindable action, [NotNull] IScrollingInfo scrollingInfo) { + if (action != null) + Action.BindTo(action); + Direction.BindTo(scrollingInfo.Direction); Direction.BindValueChanged(OnDirectionChanged, true); } @@ -42,6 +39,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } + } + + public abstract class DrawableManiaHitObject : DrawableManiaHitObject + where TObject : ManiaHitObject + { + public new readonly TObject HitObject; + + protected DrawableManiaHitObject(TObject hitObject) + : base(hitObject) + { + HitObject = hitObject; + } protected override void UpdateState(ArmedState state) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index fb4aa74ad1..18084c4c08 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { private readonly NotePiece headPiece; - public DrawableNote(Note hitObject, ManiaAction action) - : base(hitObject, action) + public DrawableNote(Note hitObject) + : base(hitObject) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public virtual bool OnPressed(ManiaAction action) { - if (action != Action) + if (action != Action.Value) return false; return UpdateJudgement(true); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index e731ce9195..a19a6fb5d4 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI.Components; @@ -19,21 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI private const float column_width = 45; private const float special_column_width = 70; - private ManiaAction action; - - public ManiaAction Action - { - get => action; - set - { - if (action == value) - return; - action = value; - - background.Action = value; - keyArea.Action = value; - } - } + public readonly Bindable Action = new Bindable(); private readonly ColumnBackground background; private readonly ColumnKeyArea keyArea; @@ -130,6 +118,13 @@ namespace osu.Game.Rulesets.Mania.UI } } + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + dependencies.CacheAs>(Action); + return dependencies; + } + /// /// Adds a DrawableHitObject to this Playfield. /// diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index 9b744bd254..19cc8fffef 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { public class ColumnBackground : CompositeDrawable, IKeyBindingHandler, IHasAccentColour { - public ManiaAction Action; + private readonly IBindable action = new Bindable(); private Box background; private Box backgroundOverlay; @@ -25,8 +25,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components private readonly IBindable direction = new Bindable(); [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load(IBindable action, IScrollingInfo scrollingInfo) { + this.action.BindTo(action); + InternalChildren = new[] { background = new Box @@ -91,14 +93,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components public bool OnPressed(ManiaAction action) { - if (action == Action) + if (action == this.action.Value) backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); return false; } public bool OnReleased(ManiaAction action) { - if (action == Action) + if (action == this.action.Value) backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); return false; } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index 4ce1614310..e30a033831 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs @@ -21,15 +21,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components private const float key_icon_size = 10; private const float key_icon_corner_radius = 3; - public ManiaAction Action; - + private readonly IBindable action = new Bindable(); private readonly IBindable direction = new Bindable(); private Container keyIcon; [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) + private void load(IBindable action, IScrollingInfo scrollingInfo) { + this.action.BindTo(action); + Drawable gradient; InternalChildren = new[] @@ -107,14 +108,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components public bool OnPressed(ManiaAction action) { - if (action == Action) + if (action == this.action.Value) keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); return false; } public bool OnReleased(ManiaAction action) { - if (action == Action) + if (action == this.action.Value) keyIcon.ScaleTo(1f, 125, Easing.OutQuint); return false; } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index fa6fba0cd8..e6ebf43c67 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -101,17 +101,15 @@ namespace osu.Game.Rulesets.Mania.UI protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) { - ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action; - - var holdNote = h as HoldNote; - if (holdNote != null) - return new DrawableHoldNote(holdNote, action); - - var note = h as Note; - if (note != null) - return new DrawableNote(note, action); - - return null; + switch (h) + { + case HoldNote holdNote: + return new DrawableHoldNote(holdNote); + case Note note: + return new DrawableNote(note); + default: + return null; + } } protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 7b68582944..4c7deb4567 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI var column = new Column(direction) { IsSpecial = isSpecial, - Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ + Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } }; AddColumn(column); diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index c7c9f4a01a..bbe2d67baa 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -15,10 +15,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { } - public override void PostProcess() + public override void PreProcess() { + base.PreProcess(); applyStacking((Beatmap)Beatmap); - base.PostProcess(); } private void applyStacking(Beatmap beatmap) @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps continue; double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; - double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; if (objectN.StartTime - endTime > stackThreshold) //We are no longer within stacking range of the next object. @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps OsuHitObject objectI = beatmap.HitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; - double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency; /* If this object is a hitcircle, then we enter this "special" case. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index b7c4470592..26becfdec9 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -12,11 +11,6 @@ namespace osu.Game.Rulesets.Osu.Judgements { public override HitResult MaxResult => HitResult.Great; - /// - /// The positional hit offset. - /// - public Vector2 PositionOffset; - protected override int NumericResultFor(HitResult result) { switch (result) diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs index c4e265aac9..d52de9f971 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Judgements public class OsuSliderTailJudgement : OsuJudgement { public override bool AffectsCombo => false; + protected override int NumericResultFor(HitResult result) => 0; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a133e85842..79705356fa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -96,7 +96,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddJudgement(new OsuJudgement { Result = result, - PositionOffset = Vector2.Zero //todo: set to correct value }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 2b1c37b2b6..464df56a3e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -96,9 +96,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.AccentColour = value; Body.AccentColour = AccentColour; Ball.AccentColour = AccentColour; - if (HasNestedHitObjects) - foreach (var drawableHitObject in NestedHitObjects) - drawableHitObject.AccentColour = AccentColour; + + foreach (var drawableHitObject in NestedHitObjects) + drawableHitObject.AccentColour = AccentColour; } } @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (!userTriggered && Time.Current >= slider.EndTime) { - var judgementsCount = NestedHitObjects.Count; + var judgementsCount = NestedHitObjects.Count(); var judgementsHit = NestedHitObjects.Count(h => h.IsHit); var hitFraction = (double)judgementsHit / judgementsCount; diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index f2d5631e93..04724931ae 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.UI { @@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) { Origin = Anchor.Centre, - Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset + Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition }; judgementLayer.Add(explosion); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 999e33e51a..e04b4d45f6 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI private void loadBarLines() { TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; - double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; + double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime); var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList(); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 1628423fe8..ad203d2107 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -11,6 +11,7 @@ using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; using osu.Game.Skinning; namespace osu.Game.Tests.Beatmaps.Formats @@ -211,5 +212,41 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); } } + + [Test] + public void TestDecodeCustomSamples() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("custom-samples.osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[0]).Name); + Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[1]).Name); + Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name); + Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[3]).Name); + } + + SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" }); + } + + [Test] + public void TestDecodeCustomHitObjectSamples() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("custom-hitobject-samples.osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + Assert.AreEqual("hit_1.wav", hitObjects[0].Samples[0].LookupNames.First()); + Assert.AreEqual("hit_2.wav", hitObjects[1].Samples[0].LookupNames.First()); + Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name); + Assert.AreEqual("hit_1.wav", hitObjects[3].Samples[0].LookupNames.First()); + } + + SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" }); + } } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b834be71f1..64bd563897 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -118,7 +118,11 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestParity(string beatmap) { var legacy = decode(beatmap, out Beatmap json); - json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert(); + json.WithDeepEqual(legacy) + .IgnoreProperty(r => r.DeclaringType == typeof(HitWindows) + // Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism + || r.Name == nameof(LegacyDecoder.LegacySampleControlPoint.CustomSampleBank)) + .Assert(); } /// diff --git a/osu.Game.Tests/Resources/custom-hitobject-samples.osu b/osu.Game.Tests/Resources/custom-hitobject-samples.osu new file mode 100644 index 0000000000..588672e2d9 --- /dev/null +++ b/osu.Game.Tests/Resources/custom-hitobject-samples.osu @@ -0,0 +1,16 @@ +osu file format v14 + +[General] +SampleSet: Normal + +[TimingPoints] +2170,468.75,4,1,0,40,1,0 +2638,-100,4,1,1,40,0,0 +3107,-100,4,1,2,40,0,0 +3576,-100,4,1,0,40,0,0 + +[HitObjects] +255,193,2170,1,0,0:0:0:0:hit_1.wav +256,191,2638,5,0,0:0:0:0:hit_2.wav +255,193,3107,1,0,0:0:0:0: +256,191,3576,1,0,0:0:0:0:hit_1.wav diff --git a/osu.Game.Tests/Resources/custom-samples.osu b/osu.Game.Tests/Resources/custom-samples.osu new file mode 100644 index 0000000000..1e0e6f558e --- /dev/null +++ b/osu.Game.Tests/Resources/custom-samples.osu @@ -0,0 +1,16 @@ +osu file format v14 + +[General] +SampleSet: Normal + +[TimingPoints] +2170,468.75,4,1,0,40,1,0 +2638,-100,4,1,1,40,0,0 +3107,-100,4,1,2,40,0,0 +3576,-100,4,1,0,40,0,0 + +[HitObjects] +255,193,2170,1,0,0:0:0:0: +256,191,2638,5,0,0:0:0:0: +255,193,3107,1,0,0:0:0:0: +256,191,3576,1,0,0:0:0:0: diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index f52fecfd02..ee66f53ddc 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -65,17 +65,19 @@ namespace osu.Game.Tests.Visual foreach (var rulesetInfo in rulesets.AvailableRulesets) { - var ruleset = rulesetInfo.CreateInstance(); + var instance = rulesetInfo.CreateInstance(); var testBeatmap = createTestBeatmap(rulesetInfo); beatmaps.Add(testBeatmap); + AddStep("set ruleset", () => Ruleset.Value = rulesetInfo); + selectBeatmap(testBeatmap); - testBeatmapLabels(ruleset); + testBeatmapLabels(instance); // TODO: adjust cases once more info is shown for other gamemodes - switch (ruleset) + switch (instance) { case OsuRuleset _: testInfoLabels(5); diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 3255478bea..73c37348d5 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual foreach (var rulesetInfo in rulesets.AvailableRulesets) { Ruleset ruleset = rulesetInfo.CreateInstance(); - AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo); + AddStep($"switch to {ruleset.Description}", () => Ruleset.Value = rulesetInfo); switch (ruleset) { diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index f635b74030..7e329ac651 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; namespace osu.Game.Audio { @@ -32,5 +33,21 @@ namespace osu.Game.Audio /// The sample volume. /// public int Volume; + + /// + /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). + /// + public virtual IEnumerable LookupNames + { + get + { + if (!string.IsNullOrEmpty(Namespace)) + yield return $"{Namespace}/{Bank}-{Name}"; + + yield return $"{Bank}-{Name}"; // Without namespace as a fallback even when we have a namespace + } + } + + public SampleInfo Clone() => (SampleInfo)MemberwiseClone(); } } diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index bf1cd7d4ee..0173125e8b 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -6,23 +6,9 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Beatmaps { - public interface IBeatmapProcessor - { - IBeatmap Beatmap { get; } - - /// - /// Post-processes to add mode-specific components that aren't added during conversion. - /// - /// An example of such a usage is for combo colours. - /// - /// - void PostProcess(); - } - /// - /// Processes a post-converted Beatmap. + /// Provides functionality to alter a after it has been converted. /// - /// The type of HitObject contained in the Beatmap. public class BeatmapProcessor : IBeatmapProcessor { public IBeatmap Beatmap { get; } @@ -32,13 +18,7 @@ namespace osu.Game.Beatmaps Beatmap = beatmap; } - /// - /// Post-processes a Beatmap to add mode-specific components that aren't added during conversion. - /// - /// An example of such a usage is for combo colours. - /// - /// - public virtual void PostProcess() + public virtual void PreProcess() { IHasComboInformation lastObj = null; @@ -62,5 +42,9 @@ namespace osu.Game.Beatmaps lastObj = obj; } } + + public virtual void PostProcess() + { + } } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ed8fbdbb26..ebebe42097 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -13,7 +13,13 @@ namespace osu.Game.Beatmaps [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } - public int? OnlineBeatmapSetID { get; set; } + private int? onlineBeatmapSetID; + + public int? OnlineBeatmapSetID + { + get { return onlineBeatmapSetID; } + set { onlineBeatmapSetID = value > 0 ? value : null; } + } public BeatmapMetadata Metadata { get; set; } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index db9e712d86..9ed476d97c 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -14,6 +14,15 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - public bool Equals(ControlPoint other) => Time.Equals(other?.Time); + /// + /// Whether this provides the same parametric changes as another . + /// Basically an equality check without considering the . + /// + /// The to compare to. + /// Whether this is equivalent to . + public virtual bool EquivalentTo(ControlPoint other) => true; + + public bool Equals(ControlPoint other) + => EquivalentTo(other) && Time.Equals(other?.Time); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 9f717d21e3..526bddf51a 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -17,5 +17,10 @@ namespace osu.Game.Beatmaps.ControlPoints } private double speedMultiplier = 1; + + public override bool EquivalentTo(ControlPoint other) + => base.EquivalentTo(other) + && other is DifficultyControlPoint difficulty + && SpeedMultiplier.Equals(difficulty.SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 73d5232f44..dd9d568133 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -14,5 +14,11 @@ namespace osu.Game.Beatmaps.ControlPoints /// Whether the first bar line of this control point is ignored. /// public bool OmitFirstBarLine; + + public override bool EquivalentTo(ControlPoint other) + => base.EquivalentTo(other) + && other is EffectControlPoint effect + && KiaiMode.Equals(effect.KiaiMode) + && OmitFirstBarLine.Equals(effect.OmitFirstBarLine); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 5d801a1163..acccbcde46 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -30,5 +30,25 @@ namespace osu.Game.Beatmaps.ControlPoints Name = sampleName, Volume = SampleVolume, }; + + /// + /// Applies and to a if necessary, returning the modified . + /// + /// The . This will not be modified. + /// The modified . This does not share a reference with . + public virtual SampleInfo ApplyTo(SampleInfo sampleInfo) + { + var newSampleInfo = sampleInfo.Clone(); + newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank; + newSampleInfo.Name = sampleInfo.Name; + newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume; + return newSampleInfo; + } + + public override bool EquivalentTo(ControlPoint other) + => base.EquivalentTo(other) + && other is SampleControlPoint sample + && SampleBank.Equals(sample.SampleBank) + && SampleVolume.Equals(sample.SampleVolume); } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index d20b1b87a6..eb60133fed 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -23,5 +23,11 @@ namespace osu.Game.Beatmaps.ControlPoints } private double beatLength = 1000; + + public override bool EquivalentTo(ControlPoint other) + => base.EquivalentTo(other) + && other is TimingControlPoint timing + && TimeSignature.Equals(timing.TimeSignature) + && BeatLength.Equals(timing.BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 581207607a..c79938e613 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -289,9 +289,9 @@ namespace osu.Game.Beatmaps.Formats if (split.Length >= 4) sampleSet = (LegacySampleBank)int.Parse(split[3]); - //SampleBank sampleBank = SampleBank.Default; - //if (split.Length >= 5) - // sampleBank = (SampleBank)int.Parse(split[4]); + int customSampleBank = 0; + if (split.Length >= 5) + customSampleBank = int.Parse(split[4]); int sampleVolume = defaultSampleVolume; if (split.Length >= 6) @@ -314,13 +314,9 @@ namespace osu.Game.Beatmaps.Formats if (stringSampleSet == @"none") stringSampleSet = @"normal"; - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); - SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time); - EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); - if (timingChange) { - beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint + handleTimingControlPoint(new TimingControlPoint { Time = time, BeatLength = beatLength, @@ -328,41 +324,68 @@ namespace osu.Game.Beatmaps.Formats }); } - if (speedMultiplier != difficultyPoint.SpeedMultiplier) + handleDifficultyControlPoint(new DifficultyControlPoint { - beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); - beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint - { - Time = time, - SpeedMultiplier = speedMultiplier - }); - } + Time = time, + SpeedMultiplier = speedMultiplier + }); - if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume) + handleEffectControlPoint(new EffectControlPoint { - beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint - { - Time = time, - SampleBank = stringSampleSet, - SampleVolume = sampleVolume - }); - } + Time = time, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature + }); - if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) + handleSampleControlPoint(new LegacySampleControlPoint { - beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint - { - Time = time, - KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature - }); - } + Time = time, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume, + CustomSampleBank = customSampleBank + }); } catch (FormatException e) { } } + private void handleTimingControlPoint(TimingControlPoint newPoint) + { + beatmap.ControlPointInfo.TimingPoints.Add(newPoint); + } + + private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) + { + var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); + + if (newPoint.EquivalentTo(existing)) + return; + + beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == newPoint.Time); + beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); + } + + private void handleEffectControlPoint(EffectControlPoint newPoint) + { + var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); + + if (newPoint.EquivalentTo(existing)) + return; + + beatmap.ControlPointInfo.EffectPoints.Add(newPoint); + } + + private void handleSampleControlPoint(SampleControlPoint newPoint) + { + var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); + + if (newPoint.EquivalentTo(existing)) + return; + + beatmap.ControlPointInfo.SamplePoints.Add(newPoint); + } + private void handleHitObject(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index e77efd8508..22a6acf459 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using osu.Framework.Logging; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; using OpenTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -167,5 +169,25 @@ namespace osu.Game.Beatmaps.Formats Pass = 2, Foreground = 3 } + + internal class LegacySampleControlPoint : SampleControlPoint + { + public int CustomSampleBank; + + public override SampleInfo ApplyTo(SampleInfo sampleInfo) + { + var baseInfo = base.ApplyTo(sampleInfo); + + if (CustomSampleBank > 1) + baseInfo.Name += CustomSampleBank; + + return baseInfo; + } + + public override bool EquivalentTo(ControlPoint other) + => base.EquivalentTo(other) + && other is LegacySampleControlPoint legacy + && CustomSampleBank == legacy.CustomSampleBank; + } } } diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index 00566093b8..cbf9d184ac 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -7,6 +7,9 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Beatmaps { + /// + /// Provides functionality to convert a for a . + /// public interface IBeatmapConverter { /// diff --git a/osu.Game/Beatmaps/IBeatmapProcessor.cs b/osu.Game/Beatmaps/IBeatmapProcessor.cs new file mode 100644 index 0000000000..282662373a --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapProcessor.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Beatmaps +{ + /// + /// Provides functionality to alter a after it has been converted. + /// + public interface IBeatmapProcessor + { + /// + /// The to process. This should already be converted to the applicable . + /// + IBeatmap Beatmap { get; } + + /// + /// Processes the converted prior to being invoked. + /// + /// Nested s generated during will not be present by this point, + /// and no mods will have been applied to the s. + /// + /// + /// + /// This can only be used to add alterations to s generated directly through the conversion process. + /// + void PreProcess(); + + /// + /// Processes the converted after has been invoked. + /// + /// Nested s generated during will be present by this point, + /// and mods will have been applied to all s. + /// + /// + /// + /// This should be used to add alterations to s while they are in their most playable state. + /// + void PostProcess(); + } +} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1a65611a3d..74da978d9c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -116,6 +116,10 @@ namespace osu.Game.Beatmaps mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); } + IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + + processor?.PreProcess(); + // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed foreach (var obj in converted.HitObjects) obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); @@ -124,8 +128,7 @@ namespace osu.Game.Beatmaps foreach (var obj in converted.HitObjects) mod.ApplyToHitObject(obj); - // Post-process - rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess(); + processor?.PostProcess(); return converted; } diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index d6b6595b69..39369350ef 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -8,16 +8,20 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using OpenTK; using osu.Framework.Configuration; +using osu.Framework.Input.Bindings; using osu.Game.Audio; +using osu.Game.Input.Bindings; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers { - public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner + public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler { private SampleChannel samplePopIn; private SampleChannel samplePopOut; + protected virtual bool PlaySamplesOnStateChange => true; + private PreviewTrackManager previewTrackManager; protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); @@ -63,18 +67,33 @@ namespace osu.Game.Graphics.Containers return base.OnClick(state); } + public bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Back) + { + State = Visibility.Hidden; + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; + private void onStateChanged(Visibility visibility) { switch (visibility) { case Visibility.Visible: if (OverlayActivationMode != OverlayActivation.Disabled) - samplePopIn?.Play(); + { + if (PlaySamplesOnStateChange) samplePopIn?.Play(); + } else State = Visibility.Hidden; break; case Visibility.Hidden: - samplePopOut?.Play(); + if (PlaySamplesOnStateChange) samplePopOut?.Play(); break; } } diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 5f57fb76b0..0bb33930b0 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -11,9 +11,9 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Configuration; using System; -using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Graphics.Textures; +using OpenTK.Input; namespace osu.Game.Graphics.Cursor { @@ -25,9 +25,8 @@ namespace osu.Game.Graphics.Cursor protected override Drawable CreateCursor() => new Cursor(); private Bindable cursorRotate; - private bool dragging; - - private bool startRotation; + private DragRotationState dragRotationState; + private Vector2 positionMouseDown; [BackgroundDependencyLoader(true)] private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager) @@ -40,18 +39,18 @@ namespace osu.Game.Graphics.Cursor protected override bool OnMouseMove(InputState state) { - if (cursorRotate && dragging) + if (dragRotationState != DragRotationState.NotDragging) { - Debug.Assert(state.Mouse.PositionMouseDown != null); - + var position = state.Mouse.Position; + var distance = Vector2Extensions.Distance(position, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. - // ReSharper disable once PossibleInvalidOperationException - startRotation |= Vector2Extensions.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30; - - if (startRotation) + if (dragRotationState == DragRotationState.DragStarted && distance > 30) + dragRotationState = DragRotationState.Rotating; + // don't rotate when distance is zero to avoid NaN + if (dragRotationState == DragRotationState.Rotating && distance > 0) { - Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value; + Vector2 offset = state.Mouse.Position - positionMouseDown; float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f; // Always rotate in the direction of least distance @@ -67,12 +66,6 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseMove(state); } - protected override bool OnDragStart(InputState state) - { - dragging = true; - return base.OnDragStart(state); - } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { ActiveCursor.Scale = new Vector2(1); @@ -80,6 +73,12 @@ namespace osu.Game.Graphics.Cursor ((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0; ((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); + + if (args.Button == MouseButton.Left && cursorRotate) + { + dragRotationState = DragRotationState.DragStarted; + positionMouseDown = state.Mouse.Position; + } return base.OnMouseDown(state, args); } @@ -87,14 +86,16 @@ namespace osu.Game.Graphics.Cursor { if (!state.Mouse.HasMainButtonPressed) { - dragging = false; - startRotation = false; - ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint); - ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); ActiveCursor.ScaleTo(1, 500, Easing.OutElastic); } + if (args.Button == MouseButton.Left) + { + if (dragRotationState == DragRotationState.Rotating) + ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); + dragRotationState = DragRotationState.NotDragging; + } return base.OnMouseUp(state, args); } @@ -160,5 +161,12 @@ namespace osu.Game.Graphics.Cursor cursorScale.TriggerChange(); } } + + private enum DragRotationState + { + NotDragging, + DragStarted, + Rotating, + } } } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 6500578de3..28d04c9a82 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -2,9 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK.Graphics; -using OpenTK.Input; using osu.Framework.Input; using System; +using osu.Game.Input.Bindings; +using OpenTK.Input; namespace osu.Game.Graphics.UserInterface { @@ -19,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface public Action Exit; private bool focus; + public bool HoldFocus { get { return focus; } @@ -26,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface { focus = value; if (!focus && HasFocus) - GetContainingInputManager().ChangeFocus(null); + base.KillFocus(); } } @@ -41,18 +43,34 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (!args.Repeat && args.Key == Key.Escape) - { - if (Text.Length > 0) - Text = string.Empty; - else - Exit?.Invoke(); - return true; - } + if (!HasFocus) return false; + + if (args.Key == Key.Escape) + return false; // disable the framework-level handling of escape key for confority (we use GlobalAction.Back). return base.OnKeyDown(state, args); } + public override bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Back) + { + if (Text.Length > 0) + { + Text = string.Empty; + return true; + } + } + + return base.OnPressed(action); + } + + protected override void KillFocus() + { + base.KillFocus(); + Exit?.Invoke(); + } + public override bool RequestsFocus => HoldFocus; } } diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index d34d2b2a7c..07920865c0 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface { protected override Drawable GetDrawableCharacter(char c) => new PasswordMaskChar(CalculatedTextSize); - public override bool AllowClipboardExport => false; + protected override bool AllowClipboardExport => false; private readonly CapsWarning warning; diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 6021af2028..88b0543de0 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -9,10 +9,12 @@ using osu.Framework.Input; using osu.Game.Graphics.Sprites; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface { - public class OsuTextBox : TextBox + public class OsuTextBox : TextBox, IKeyBindingHandler { protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f); protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f); @@ -33,10 +35,7 @@ namespace osu.Game.Graphics.UserInterface TextContainer.Height = 0.5f; CornerRadius = 5; - Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; + Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; }; } [BackgroundDependencyLoader] @@ -59,5 +58,18 @@ namespace osu.Game.Graphics.UserInterface } protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), TextSize = CalculatedTextSize }; + + public virtual bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Back) + { + KillFocus(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; } } diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index e50539e120..7d53c9e9d9 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -34,8 +34,6 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (HandlePendingText(state)) return true; - if (!state.Keyboard.ControlPressed && !state.Keyboard.ShiftPressed) { switch (args.Key) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index fced2e5d95..83ffd415ae 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,7 +45,9 @@ namespace osu.Game.Input.Bindings public IEnumerable InGameKeyBindings => new[] { new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), - new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry) + new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), + new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; protected override IEnumerable KeyBindingInputQueue => @@ -85,6 +87,12 @@ namespace osu.Game.Input.Bindings ToggleGameplayMouseButtons, [Description("Go back")] - Back + Back, + + [Description("Increase scroll speed")] + IncreaseScrollSpeed, + + [Description("Decrease scroll speed")] + DecreaseScrollSpeed, } } diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs new file mode 100644 index 0000000000..7eeacd56d7 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -0,0 +1,376 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180628011956_RemoveNegativeSetIDs")] + partial class RemoveNegativeSetIDs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs new file mode 100644 index 0000000000..c38cd19b42 --- /dev/null +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RemoveNegativeSetIDs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // There was a change that baetmaps were being loaded with "-1" online IDs, which is completely incorrect. + // This ensures there will not be unique key conflicts as a result of these incorrectly imported beatmaps. + migrationBuilder.Sql("UPDATE BeatmapSetInfo SET OnlineBeatmapSetID = null WHERE OnlineBeatmapSetID <= 0"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 3b6bb565b0..bbaaa0c756 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -49,7 +49,7 @@ namespace osu.Game.Online.API.Requests.Responses private DateTimeOffset submitted { get; set; } [JsonProperty(@"ranked_date")] - private DateTimeOffset ranked { get; set; } + private DateTimeOffset? ranked { get; set; } [JsonProperty(@"last_updated")] private DateTimeOffset lastUpdated { get; set; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ba8685b5b2..922d228701 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -85,7 +85,7 @@ namespace osu.Game private OnScreenDisplay onscreenDisplay; private Bindable configRuleset; - public Bindable Ruleset = new Bindable(); + private readonly Bindable ruleset = new Bindable(); private Bindable configSkin; @@ -147,10 +147,13 @@ namespace osu.Game dependencies.CacheAs(this); + dependencies.CacheAs(ruleset); + dependencies.CacheAs>(ruleset); + // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); - Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); - Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); + ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); @@ -216,7 +219,7 @@ namespace osu.Game return; } - Ruleset.Value = s.Ruleset; + ruleset.Value = s.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap); Beatmap.Value.Mods.Value = s.Mods; @@ -247,7 +250,8 @@ namespace osu.Game new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, - ActionRequested = action => volume.Adjust(action) + ActionRequested = action => volume.Adjust(action), + ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, mainContent = new Container { RelativeSizeAxes = Axes.Both }, overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, @@ -550,7 +554,7 @@ namespace osu.Game // the use case for not applying is in visual/unit tests. bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false; - Ruleset.Disabled = applyRestrictions; + ruleset.Disabled = applyRestrictions; Beatmap.Disabled = applyRestrictions; mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 5f90ec67ad..cd0913a25e 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -6,16 +6,16 @@ using System.Linq; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; using OpenTK.Input; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Dialog { @@ -23,6 +23,9 @@ namespace osu.Game.Overlays.Dialog { public static readonly float ENTER_DURATION = 500; public static readonly float EXIT_DURATION = 200; + + protected override bool BlockPassThroughMouse => false; + private readonly Vector2 ringSize = new Vector2(100f); private readonly Vector2 ringMinifiedSize = new Vector2(20f); private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f); @@ -34,26 +37,28 @@ namespace osu.Game.Overlays.Dialog private readonly SpriteText header; private readonly TextFlowContainer body; + private bool actionInvoked; + public FontAwesome Icon { - get { return icon.Icon; } - set { icon.Icon = value; } + get => icon.Icon; + set => icon.Icon = value; } public string HeaderText { - get { return header.Text; } - set { header.Text = value; } + get => header.Text; + set => header.Text = value; } public string BodyText { - set { body.Text = value; } + set => body.Text = value; } public IEnumerable Buttons { - get { return buttonsContainer.Children; } + get => buttonsContainer.Children; set { buttonsContainer.ChildrenEnumerable = value; @@ -62,71 +67,17 @@ namespace osu.Game.Overlays.Dialog var action = b.Action; b.Action = () => { - Hide(); + if (actionInvoked) return; + + actionInvoked = true; action?.Invoke(); + + Hide(); }; } } } - private void pressButtonAtIndex(int index) - { - if (index < Buttons.Count()) - Buttons.Skip(index).First().TriggerOnClick(); - } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Repeat) return false; - - if (args.Key == Key.Enter || args.Key == Key.KeypadEnter) - { - Buttons.OfType().FirstOrDefault()?.TriggerOnClick(); - return true; - } - - // press button at number if 1-9 on number row or keypad are pressed - var k = args.Key; - if (k >= Key.Number1 && k <= Key.Number9) - { - pressButtonAtIndex(k - Key.Number1); - return true; - } - - if (k >= Key.Keypad1 && k <= Key.Keypad9) - { - pressButtonAtIndex(k - Key.Keypad1); - return true; - } - - return base.OnKeyDown(state, args); - } - - protected override void PopIn() - { - base.PopIn(); - - // Reset various animations but only if the dialog animation fully completed - if (content.Alpha == 0) - { - buttonsContainer.TransformSpacingTo(buttonsEnterSpacing); - buttonsContainer.MoveToY(buttonsEnterSpacing.Y); - ring.ResizeTo(ringMinifiedSize); - } - - content.FadeIn(ENTER_DURATION, Easing.OutQuint); - ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint); - } - - protected override void PopOut() - { - base.PopOut(); - - content.FadeOut(EXIT_DURATION, Easing.InSine); - } - public PopupDialog() { RelativeSizeAxes = Axes.Both; @@ -136,9 +87,6 @@ namespace osu.Game.Overlays.Dialog content = new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 0.4f, Alpha = 0f, Children = new Drawable[] { @@ -243,5 +191,69 @@ namespace osu.Game.Overlays.Dialog }, }; } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) return false; + + if (args.Key == Key.Enter || args.Key == Key.KeypadEnter) + { + Buttons.OfType().FirstOrDefault()?.TriggerOnClick(); + return true; + } + + // press button at number if 1-9 on number row or keypad are pressed + var k = args.Key; + if (k >= Key.Number1 && k <= Key.Number9) + { + pressButtonAtIndex(k - Key.Number1); + return true; + } + + if (k >= Key.Keypad1 && k <= Key.Keypad9) + { + pressButtonAtIndex(k - Key.Keypad1); + return true; + } + + return base.OnKeyDown(state, args); + } + + protected override void PopIn() + { + base.PopIn(); + + actionInvoked = false; + + // Reset various animations but only if the dialog animation fully completed + if (content.Alpha == 0) + { + buttonsContainer.TransformSpacingTo(buttonsEnterSpacing); + buttonsContainer.MoveToY(buttonsEnterSpacing.Y); + ring.ResizeTo(ringMinifiedSize); + } + + content.FadeIn(ENTER_DURATION, Easing.OutQuint); + ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint); + buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint); + buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint); + } + + protected override void PopOut() + { + if (!actionInvoked) + // In the case a user did not choose an action before a hide was triggered, press the last button. + // This is presumed to always be a sane default "cancel" action. + buttonsContainer.Last().TriggerOnClick(); + + base.PopOut(); + content.FadeOut(EXIT_DURATION, Easing.InSine); + } + + private void pressButtonAtIndex(int index) + { + if (index < Buttons.Count()) + Buttons.Skip(index).First().TriggerOnClick(); + } } } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 7caab0dd6c..c40f517023 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -1,12 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Dialog; -using OpenTK.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays @@ -16,6 +13,20 @@ namespace osu.Game.Overlays private readonly Container dialogContainer; private PopupDialog currentDialog; + public DialogOverlay() + { + RelativeSizeAxes = Axes.Both; + + Child = dialogContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }; + + Width = 0.4f; + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + } + public void Push(PopupDialog dialog) { if (dialog == currentDialog) return; @@ -30,6 +41,10 @@ namespace osu.Game.Overlays State = Visibility.Visible; } + protected override bool PlaySamplesOnStateChange => false; + + protected override bool BlockPassThroughKeyboard => true; + private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) { if (v != Visibility.Hidden) return; @@ -50,32 +65,14 @@ namespace osu.Game.Overlays protected override void PopOut() { base.PopOut(); - this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); - } - public DialogOverlay() - { - RelativeSizeAxes = Axes.Both; - - Children = new Drawable[] + if (currentDialog?.State == Visibility.Visible) { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, - }, - }, - dialogContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }; + currentDialog.Hide(); + return; + } + + this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); } } } diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 4f4348c131..df98cc3c32 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -35,15 +35,13 @@ namespace osu.Game.Overlays.Direct } [BackgroundDependencyLoader(true)] - private void load(OsuGame game, RulesetStore rulesets, OsuColour colours) + private void load(RulesetStore rulesets, OsuColour colours, Bindable ruleset) { DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark; - Ruleset.Value = game?.Ruleset.Value ?? rulesets.GetRuleset(0); + Ruleset.Value = ruleset ?? rulesets.GetRuleset(0); foreach (var r in rulesets.AvailableRulesets) - { modeButtons.Add(new RulesetToggleButton(Ruleset, r)); - } } private class RulesetToggleButton : OsuClickableContainer diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index a12f9dee7e..5d4d627995 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.KeyBinding { if (bindTarget.IsHovered) { - bindTarget.UpdateKeyCombination(new KeyCombination(KeyCombination.FromInputState(state).Keys.Append(state.Mouse.ScrollDelta.Y > 0 ? InputKey.MouseWheelUp : InputKey.MouseWheelDown))); + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state, state.Mouse.ScrollDelta)); finalise(); return true; } @@ -202,9 +202,6 @@ namespace osu.Game.Overlays.KeyBinding switch (args.Key) { - case Key.Escape: - finalise(); - return true; case Key.Delete: { if (state.Keyboard.ShiftPressed) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f1624721da..c584a32a82 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Mods public readonly Bindable> SelectedMods = new Bindable>(); - public readonly Bindable Ruleset = new Bindable(); + public readonly IBindable Ruleset = new Bindable(); private void rulesetChanged(RulesetInfo newRuleset) { @@ -51,8 +51,8 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets, AudioManager audio) + [BackgroundDependencyLoader] + private void load(OsuColour colours, IBindable ruleset, AudioManager audio) { SelectedMods.ValueChanged += selectedModsChanged; @@ -60,13 +60,8 @@ namespace osu.Game.Overlays.Mods HighMultiplierColour = colours.Green; UnrankedLabel.Colour = colours.Blue; - if (osu != null) - Ruleset.BindTo(osu.Ruleset); - else - Ruleset.Value = rulesets.AvailableRulesets.First(); - - Ruleset.ValueChanged += rulesetChanged; - Ruleset.TriggerChange(); + Ruleset.BindTo(ruleset); + Ruleset.BindValueChanged(rulesetChanged, true); sampleOn = audio.Sample.Get(@"UI/check-on"); sampleOff = audio.Sample.Get(@"UI/check-off"); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 930b3c1eaa..18a371e904 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -56,7 +56,13 @@ namespace osu.Game.Overlays.Settings.Sections reloadSkins(); - skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin); + var skinBindable = config.GetBindable(OsuSetting.Skin); + + // Todo: This should not be necessary when OsuConfigManager is databased + if (skinDropdown.Items.All(s => s.Value != skinBindable.Value)) + skinBindable.Value = 0; + + skinDropdown.Bindable = skinBindable; } private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID)); diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 3078c44844..dae4f84b1a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -67,8 +67,8 @@ namespace osu.Game.Overlays.Toolbar }; } - [BackgroundDependencyLoader(true)] - private void load(RulesetStore rulesets, OsuGame game) + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets, Bindable parentRuleset) { this.rulesets = rulesets; foreach (var r in rulesets.AvailableRulesets) @@ -82,11 +82,7 @@ namespace osu.Game.Overlays.Toolbar ruleset.ValueChanged += rulesetChanged; ruleset.DisabledChanged += disabledChanged; - - if (game != null) - ruleset.BindTo(game.Ruleset); - else - ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(); + ruleset.BindTo(parentRuleset); } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 572b3f0c27..3a64e12b27 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -9,11 +9,13 @@ using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Volume { - public class VolumeControlReceptor : Container, IKeyBindingHandler, IHandleGlobalInput + public class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalInput { public Func ActionRequested; + public Func ScrollActionRequested; public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; + public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; public bool OnReleased(GlobalAction action) => false; } } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 1d392e6ee8..9aeca1f35f 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -12,17 +12,15 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; -using osu.Framework.Input.Bindings; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Input.Bindings; using OpenTK; using OpenTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container, IKeyBindingHandler + public class VolumeMeter : Container { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -226,59 +224,27 @@ namespace osu.Game.Overlays.Volume private const float adjust_step = 0.05f; - public void Increase() => adjust(1); - public void Decrease() => adjust(-1); - - private void adjust(int direction) - { - float amount = adjust_step * direction; - - // handle the case where the OnPressed action was actually a mouse wheel. - // this allows for precise wheel handling. - var state = GetContainingInputManager().CurrentState; - if (state.Mouse?.ScrollDelta.Y != 0) - { - OnScroll(state); - return; - } - - Volume += amount; - } - - public bool OnPressed(GlobalAction action) - { - if (!IsHovered) return false; - - switch (action) - { - case GlobalAction.DecreaseVolume: - Decrease(); - return true; - case GlobalAction.IncreaseVolume: - Increase(); - return true; - } - - return false; - } + public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise); + public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise); // because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible. - private double scrollAmount; + private double adjustAccumulator; + + private void adjust(double delta, bool isPrecise) + { + adjustAccumulator += delta * adjust_step * (isPrecise ? 0.1 : 1); + if (Math.Abs(adjustAccumulator) < Bindable.Precision) + return; + Volume += adjustAccumulator; + adjustAccumulator = 0; + } protected override bool OnScroll(InputState state) { - scrollAmount += adjust_step * state.Mouse.ScrollDelta.Y * (state.Mouse.HasPreciseScroll ? 0.1f : 1); - - if (Math.Abs(scrollAmount) < Bindable.Precision) - return true; - - Volume += scrollAmount; - scrollAmount = 0; + adjust(state.Mouse.ScrollDelta.Y, state.Mouse.HasPreciseScroll); return true; } - public bool OnReleased(GlobalAction action) => false; - private const float transition_length = 500; protected override bool OnHover(InputState state) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 1c9c615bbb..e40597b2d4 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays muteButton.Current.ValueChanged += _ => Show(); } - public bool Adjust(GlobalAction action) + public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) { if (!IsLoaded) return false; @@ -103,13 +103,13 @@ namespace osu.Game.Overlays if (State == Visibility.Hidden) Show(); else - volumeMeterMaster.Decrease(); + volumeMeterMaster.Decrease(amount, isPrecise); return true; case GlobalAction.IncreaseVolume: if (State == Visibility.Hidden) Show(); else - volumeMeterMaster.Increase(); + volumeMeterMaster.Increase(amount, isPrecise); return true; case GlobalAction.ToggleMute: Show(); diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 587f2c8d15..129dd07c3e 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -45,11 +45,15 @@ namespace osu.Game.Rulesets.Judgements public double TimeOffset { get; set; } /// - /// Whether the should affect the combo portion of the score. - /// If false, the will be considered for the bonus portion of the score. + /// Whether the should affect the current combo. /// public virtual bool AffectsCombo => true; + /// + /// Whether the should be counted as base (combo) or bonus score. + /// + public virtual bool IsBonus => !AffectsCombo; + /// /// The numeric representation for the result achieved. /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 88990d435c..a22aaa784f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual IEnumerable GetSamples() => HitObject.Samples; private readonly Lazy> nestedHitObjects = new Lazy>(); - public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated; - public IReadOnlyList NestedHitObjects => nestedHitObjects.Value; + public IEnumerable NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty(); public event Action OnJudgement; public event Action OnJudgementRemoved; @@ -50,12 +49,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Whether this and all of its nested s have been hit. /// - public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit)); + public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && NestedHitObjects.All(n => n.IsHit); /// /// Whether this and all of its nested s have been judged. /// - public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged)); + public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && NestedHitObjects.All(h => h.AllJudged); /// /// Whether this can be judged. @@ -90,13 +89,12 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject.SampleControlPoint == null) throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); - AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo - { - Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, - Name = s.Name, - Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume, - Namespace = SampleNamespace - }).ToArray())); + + samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); + foreach (var s in samples) + s.Namespace = SampleNamespace; + + AddInternal(Samples = new SkinnableSound(samples)); } } @@ -206,9 +204,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (AllJudged) return false; - if (HasNestedHitObjects) - foreach (var d in NestedHitObjects) - judgementOccurred |= d.UpdateJudgement(userTriggered); + foreach (var d in NestedHitObjects) + judgementOccurred |= d.UpdateJudgement(userTriggered); if (!ProvidesJudgement || judgementFinalized || judgementOccurred) return judgementOccurred; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9edd0f1f34..95589d8953 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; using System.Linq; @@ -196,9 +197,6 @@ namespace osu.Game.Rulesets.Objects.Legacy var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]); var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]); - // Let's not implement this for now, because this doesn't fit nicely into the bank structure - //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; - string stringBank = bank.ToString().ToLower(); if (stringBank == @"none") stringBank = null; @@ -211,6 +209,8 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 3) bankInfo.Volume = int.Parse(split[3]); + + bankInfo.Filename = split.Length > 4 ? split[4] : null; } /// @@ -252,6 +252,10 @@ namespace osu.Game.Rulesets.Objects.Legacy private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) { + // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario + if (!string.IsNullOrEmpty(bankInfo.Filename)) + return new List { new FileSampleInfo { Filename = bankInfo.Filename } }; + var soundTypes = new List { new SampleInfo @@ -297,14 +301,24 @@ namespace osu.Game.Rulesets.Objects.Legacy private class SampleBankInfo { + public string Filename; + public string Normal; public string Add; public int Volume; - public SampleBankInfo Clone() + public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); + } + + private class FileSampleInfo : SampleInfo + { + public string Filename; + + public override IEnumerable LookupNames => new[] { - return (SampleBankInfo)MemberwiseClone(); - } + Filename, + Path.ChangeExtension(Filename, null) + }; } [Flags] diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f818523a3d..82d9945ef7 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -57,8 +57,18 @@ namespace osu.Game.Rulesets /// public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap); + /// + /// Creates a to convert a to one that is applicable for this . + /// + /// The to be converted. + /// The . public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap); + /// + /// Optionally creates a to alter a after it has been converted. + /// + /// The to be processed. + /// The . public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null; public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a7a9fea5f2..513173ef2f 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -84,7 +84,13 @@ namespace osu.Game.Rulesets { try { - var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo), (RulesetInfo)null)).RulesetInfo; + var instanceInfo = ((Ruleset)Activator.CreateInstance(Type.GetType(r.InstantiationInfo, asm => + { + // for the time being, let's ignore the version being loaded. + // this allows for debug builds to successfully load rulesets (even though debug rulesets have a 0.0.0 version). + asm.Version = null; + return Assembly.Load(asm); + }, null), (RulesetInfo)null)).RulesetInfo; r.Name = instanceInfo.Name; r.ShortName = instanceInfo.ShortName; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index dd4120f2fb..9e8ea0f7c2 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -261,13 +261,19 @@ namespace osu.Game.Rulesets.Scoring break; } - baseScore += judgement.NumericResult; - rollingMaxBaseScore += judgement.MaxNumericResult; - JudgedHits++; } - else if (judgement.IsHit) - bonusScore += judgement.NumericResult; + + if (judgement.IsBonus) + { + if (judgement.IsHit) + bonusScore += judgement.NumericResult; + } + else + { + baseScore += judgement.NumericResult; + rollingMaxBaseScore += judgement.MaxNumericResult; + } } /// @@ -280,14 +286,18 @@ namespace osu.Game.Rulesets.Scoring HighestCombo.Value = judgement.HighestComboAtJudgement; if (judgement.AffectsCombo) + JudgedHits--; + + if (judgement.IsBonus) + { + if (judgement.IsHit) + bonusScore -= judgement.NumericResult; + } + else { baseScore -= judgement.NumericResult; rollingMaxBaseScore -= judgement.MaxNumericResult; - - JudgedHits--; } - else if (judgement.IsHit) - bonusScore -= judgement.NumericResult; } private void updateScore() diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 172b866505..cfcfca157b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -4,30 +4,33 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK.Input; namespace osu.Game.Rulesets.UI.Scrolling { /// /// A type of specialized towards scrolling s. /// - public abstract class ScrollingPlayfield : Playfield + public abstract class ScrollingPlayfield : Playfield, IKeyBindingHandler { /// /// The default span of time visible by the length of the scrolling axes. /// This is clamped between and . /// private const double time_span_default = 1500; + /// /// The minimum span of time that may be visible by the length of the scrolling axes. /// private const double time_span_min = 50; + /// /// The maximum span of time that may be visible by the length of the scrolling axes. /// private const double time_span_max = 10000; + /// /// The step increase/decrease of the span of time visible by the length of the scrolling axes. /// @@ -78,27 +81,26 @@ namespace osu.Game.Rulesets.UI.Scrolling HitObjects.TimeRange.BindTo(VisibleTimeRange); } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + public bool OnPressed(GlobalAction action) { if (!UserScrollSpeedAdjustment) return false; - if (state.Keyboard.ControlPressed) + switch (action) { - switch (args.Key) - { - case Key.Minus: - this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 600, Easing.OutQuint); - break; - case Key.Plus: - this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 600, Easing.OutQuint); - break; - } + case GlobalAction.IncreaseScrollSpeed: + this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint); + return true; + case GlobalAction.DecreaseScrollSpeed: + this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint); + return true; } return false; } + public bool OnReleased(GlobalAction action) => false; + protected sealed override HitObjectContainer CreateHitObjectContainer() { var container = new ScrollingHitObjectContainer(); diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs index 35a91275a7..d2b79e2fa7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -47,13 +47,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers } } - if (obj.HasNestedHitObjects) - { - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } + // Nested hitobjects don't need to scroll, but they do need accurate positions + UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs index 745352a4d3..25dea8dfbf 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers } } - if (obj.HasNestedHitObjects) - { - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); + ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } + // Nested hitobjects don't need to scroll, but they do need accurate positions + UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); } } diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 5e9863f642..9d9432b2ce 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -40,7 +40,14 @@ namespace osu.Game.Screens while (screen.LoadState < LoadState.Ready) Thread.Sleep(1); - base.Push(screen); + try + { + base.Push(screen); + } + catch (ScreenAlreadyExitedException) + { + // screen may have exited before the push was successful. + } } protected override void Update() diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 81abc4cd3d..374877673f 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -148,8 +148,6 @@ namespace osu.Game.Screens.Menu case Key.Space: logo?.TriggerOnClick(state); return true; - case Key.Escape: - return goBack(); } return false; @@ -181,16 +179,7 @@ namespace osu.Game.Screens.Menu } } - public bool OnReleased(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - return true; - default: - return false; - } - } + public bool OnReleased(GlobalAction action) => false; private void onPlay() { diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index 62605da5a4..4ada46749c 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -1,34 +1,34 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using osu.Game.Overlays; -using OpenTK.Input; namespace osu.Game.Screens.Menu { - public class ExitConfirmOverlay : HoldToConfirmOverlay + public class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler { - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + public bool OnPressed(GlobalAction action) { - if (args.Key == Key.Escape && !args.Repeat) + if (action == GlobalAction.Back) { BeginConfirm(); return true; } - return base.OnKeyDown(state, args); + return false; } - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + public bool OnReleased(GlobalAction action) { - if (args.Key == Key.Escape) + if (action == GlobalAction.Back) { AbortConfirm(); return true; } - return base.OnKeyUp(state, args); + return false; } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 61018f9e08..2a7daff3d0 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -17,7 +16,6 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using OpenTK; -using OpenTK.Input; using osu.Game.Overlays; using osu.Framework.Graphics.Containers; @@ -82,22 +80,21 @@ namespace osu.Game.Screens private SampleChannel sampleExit; [BackgroundDependencyLoader(true)] - private void load(BindableBeatmap beatmap, OsuGame osuGame, AudioManager audio) + private void load(BindableBeatmap beatmap, OsuGame osu, AudioManager audio, Bindable ruleset) { - if (beatmap != null) - Beatmap.BindTo(beatmap); + Beatmap.BindTo(beatmap); + Ruleset.BindTo(ruleset); - if (osuGame != null) + if (osu != null) { - Ruleset.BindTo(osuGame.Ruleset); - OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); + OverlayActivationMode.BindTo(osu.OverlayActivationMode); updateOverlayStates = () => { if (HideOverlaysOnEnter) - osuGame.CloseAllOverlays(); + osu.CloseAllOverlays(); else - osuGame.Toolbar.State = Visibility.Visible; + osu.Toolbar.State = Visibility.Visible; }; } @@ -106,6 +103,8 @@ namespace osu.Game.Screens public bool OnPressed(GlobalAction action) { + if (!IsCurrentScreen) return false; + if (action == GlobalAction.Back && AllowBackButton) { Exit(); @@ -117,20 +116,6 @@ namespace osu.Game.Screens public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Repeat || !IsCurrentScreen) return false; - - switch (args.Key) - { - case Key.Escape: - Exit(); - return true; - } - - return base.OnKeyDown(state, args); - } - protected override void OnResuming(Screen last) { sampleExit?.Play(); @@ -195,11 +180,10 @@ namespace osu.Game.Screens if (Background != null && !Background.Equals(nextOsu?.Background)) { - if (nextOsu != null) - //We need to use MakeCurrent in case we are jumping up multiple game screens. - nextOsu.Background?.MakeCurrent(); - else - Background.Exit(); + Background.Exit(); + + //We need to use MakeCurrent in case we are jumping up multiple game screens. + nextOsu?.Background?.MakeCurrent(); } if (base.OnExiting(next)) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 7b555776f7..bbed0c8843 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -1,12 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; -using OpenTK.Input; using osu.Game.Graphics; using OpenTK.Graphics; using osu.Framework.Allocation; -using System.Linq; namespace osu.Game.Screens.Play { @@ -21,16 +18,5 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (!args.Repeat && args.Key == Key.Escape) - { - InternalButtons.Children.Last().TriggerOnClick(); - return true; - } - - return base.OnKeyDown(state, args); - } } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index db099cd16c..52c08bb351 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -15,10 +15,13 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; using OpenTK.Input; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public abstract class GameplayMenuOverlay : OverlayContainer + public abstract class GameplayMenuOverlay : OverlayContainer, IKeyBindingHandler { private const int transition_duration = 200; private const int button_height = 70; @@ -31,6 +34,11 @@ namespace osu.Game.Screens.Play public Action OnRetry; public Action OnQuit; + /// + /// Action that is invoked when is triggered. + /// + protected virtual Action BackAction => () => InternalButtons.Children.Last().TriggerOnClick(); + public abstract string Header { get; } public abstract string Description { get; } @@ -219,6 +227,19 @@ namespace osu.Game.Screens.Play return base.OnKeyDown(state, args); } + public bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Back) + { + BackAction.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + private void buttonSelectionChanged(DialogButton button, bool isSelected) { if (!isSelected) diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 6262f71ddc..e2f133dde3 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -6,11 +6,9 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Framework.Timing; using osu.Game.Graphics; using OpenTK.Graphics; -using OpenTK.Input; namespace osu.Game.Screens.Play { @@ -138,16 +136,7 @@ namespace osu.Game.Screens.Play public override string Header => "paused"; public override string Description => "you're not going to do what i think you're going to do, are ya?"; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (!args.Repeat && args.Key == Key.Escape) - { - InternalButtons.Children.First().TriggerOnClick(); - return true; - } - - return base.OnKeyDown(state, args); - } + protected override Action BackAction => () => InternalButtons.Children.First().TriggerOnClick(); [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a2ed01f5a7..b406bda411 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -49,8 +49,6 @@ namespace osu.Game.Screens.Play public bool AllowLeadIn { get; set; } = true; public bool AllowResults { get; set; } = true; - protected override bool AllowBackButton => false; - private Bindable mouseWheelDisabled; private Bindable userAudioOffset; @@ -158,7 +156,8 @@ namespace osu.Game.Screens.Play userAudioOffset.TriggerChange(); ScoreProcessor = RulesetContainer.CreateScoreProcessor(); - config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); + if (!ScoreProcessor.Mode.Disabled) + config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); Children = new Drawable[] { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 7950018554..d26702fcf9 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -53,10 +53,9 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OsuGame osuGame) + private void load([CanBeNull] Bindable parentRuleset) { - if (osuGame != null) - ruleset.BindTo(osuGame.Ruleset); + ruleset.BindTo(parentRuleset); ruleset.ValueChanged += _ => updateDisplay(); } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index f9f3db3827..278d32b2d5 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -29,6 +29,7 @@ namespace osu.Game.Screens.Select private readonly TabControl groupTabs; private SortMode sort = SortMode.Title; + public SortMode Sort { get { return sort; } @@ -43,6 +44,7 @@ namespace osu.Game.Screens.Select } private GroupMode group = GroupMode.All; + public GroupMode Group { get { return group; } @@ -62,14 +64,15 @@ namespace osu.Game.Screens.Select Sort = sort, SearchText = searchTextBox.Text, AllowConvertedBeatmaps = showConverted, - Ruleset = ruleset + Ruleset = ruleset.Value }; public Action Exit; private readonly SearchTextBox searchTextBox; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => base.ReceiveMouseInputAt(screenSpacePos) || groupTabs.ReceiveMouseInputAt(screenSpacePos) || sortTabs.ReceiveMouseInputAt(screenSpacePos); + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => + base.ReceiveMouseInputAt(screenSpacePos) || groupTabs.ReceiveMouseInputAt(screenSpacePos) || sortTabs.ReceiveMouseInputAt(screenSpacePos); public FilterControl() { @@ -163,24 +166,22 @@ namespace osu.Game.Screens.Select searchTextBox.HoldFocus = true; } - private readonly Bindable ruleset = new Bindable(); + private readonly IBindable ruleset = new Bindable(); private Bindable showConverted; public readonly Box Background; [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, OsuGame osu, OsuConfigManager config) + private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) { sortTabs.AccentColour = colours.GreenLight; showConverted = config.GetBindable(OsuSetting.ShowConvertedBeatmaps); showConverted.ValueChanged += val => updateCriteria(); - if (osu != null) - ruleset.BindTo(osu.Ruleset); - ruleset.ValueChanged += val => updateCriteria(); - ruleset.TriggerChange(); + ruleset.BindTo(parentRuleset); + ruleset.BindValueChanged(val => updateCriteria(), true); } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 602b03380c..3fa12283b5 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Select private readonly Box box; private readonly Box light; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos); + public FooterButton() { Children = new Drawable[] diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 9eca3802ae..c171e791d2 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -19,7 +19,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; using osu.Framework.Configuration; -using osu.Framework.Logging; using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Leaderboards @@ -33,7 +32,7 @@ namespace osu.Game.Screens.Select.Leaderboards private FillFlowContainer scrollFlow; - private readonly Bindable ruleset = new Bindable(); + private readonly IBindable ruleset = new Bindable(); public Action ScoreSelected; @@ -41,7 +40,10 @@ namespace osu.Game.Screens.Select.Leaderboards private ScheduledDelegate showScoresDelegate; + private bool scoresLoadedOnce; + private IEnumerable scores; + public IEnumerable Scores { get { return scores; } @@ -49,6 +51,8 @@ namespace osu.Game.Screens.Select.Leaderboards { scores = value; + scoresLoadedOnce = true; + scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire(); scrollFlow = null; @@ -174,9 +178,8 @@ namespace osu.Game.Screens.Select.Leaderboards private APIAccess api; private BeatmapInfo beatmap; - private OsuGame osuGame; - private ScheduledDelegate pendingBeatmapSwitch; + private ScheduledDelegate pendingUpdateScores; public BeatmapInfo Beatmap { @@ -189,21 +192,17 @@ namespace osu.Game.Screens.Select.Leaderboards beatmap = value; Scores = null; - pendingBeatmapSwitch?.Cancel(); - pendingBeatmapSwitch = Schedule(updateScores); + updateScores(); } } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuGame osuGame) + private void load(APIAccess api, IBindable parentRuleset) { this.api = api; - this.osuGame = osuGame; - if (osuGame != null) - ruleset.BindTo(osuGame.Ruleset); - - ruleset.ValueChanged += r => updateScores(); + ruleset.BindTo(parentRuleset); + ruleset.ValueChanged += _ => updateScores(); if (api != null) api.OnStateChange += handleApiStateChange; @@ -231,51 +230,61 @@ namespace osu.Game.Screens.Select.Leaderboards private void updateScores() { - if (Scope == LeaderboardScope.Local) - { - // TODO: get local scores from wherever here. - PlaceholderState = PlaceholderState.NoScores; - return; - } + // don't display any scores or placeholder until the first Scores_Set has been called. + // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. + if (!scoresLoadedOnce) return; - if (Beatmap?.OnlineBeatmapID == null) - { - PlaceholderState = PlaceholderState.Unavailable; - return; - } + getScoresRequest?.Cancel(); + getScoresRequest = null; - if (api?.IsLoggedIn != true) + pendingUpdateScores?.Cancel(); + pendingUpdateScores = Schedule(() => { - PlaceholderState = PlaceholderState.NotLoggedIn; - return; - } - - if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) - { - PlaceholderState = PlaceholderState.NotSupporter; - return; - } - - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); - - getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); - getScoresRequest.Success += r => Schedule(() => - { - Scores = r.Scores; - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - }); - - getScoresRequest.Failure += e => Schedule(() => - { - if (e is OperationCanceledException) + if (Scope == LeaderboardScope.Local) + { + // TODO: get local scores from wherever here. + PlaceholderState = PlaceholderState.NoScores; return; + } - PlaceholderState = PlaceholderState.NetworkFailure; - Logger.Error(e, @"Couldn't fetch beatmap scores!"); + if (Beatmap?.OnlineBeatmapID == null) + { + PlaceholderState = PlaceholderState.Unavailable; + return; + } + + if (api?.IsLoggedIn != true) + { + PlaceholderState = PlaceholderState.NotLoggedIn; + return; + } + + if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + { + PlaceholderState = PlaceholderState.NotSupporter; + return; + } + + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); + + getScoresRequest = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope); + getScoresRequest.Success += r => Schedule(() => + { + Scores = r.Scores; + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + }); + + getScoresRequest.Failure += e => Schedule(() => + { + if (e is OperationCanceledException) + return; + + PlaceholderState = PlaceholderState.NetworkFailure; + }); + + api.Queue(getScoresRequest); }); - - api.Queue(getScoresRequest); } private Placeholder currentPlaceholder; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 70b473bcd9..94c16f1797 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -17,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; @@ -62,6 +64,8 @@ namespace osu.Game.Screens.Select private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; + protected new readonly Bindable Ruleset = new Bindable(); + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); @@ -123,7 +127,7 @@ namespace osu.Game.Screens.Select Size = new Vector2(carousel_width, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - SelectionChanged = carouselSelectionChanged, + SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, }, FilterControl = new FilterControl @@ -177,9 +181,13 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours) { dependencies.CacheAs(this); + dependencies.CacheAs(Ruleset); + dependencies.CacheAs>(Ruleset); + + base.Ruleset.ValueChanged += r => updateSelectedBeatmap(beatmapNoDebounce); if (Footer != null) { @@ -192,9 +200,6 @@ namespace osu.Game.Screens.Select if (this.beatmaps == null) this.beatmaps = beatmaps; - if (osu != null) - Ruleset.BindTo(osu.Ruleset); - this.beatmaps.ItemAdded += onBeatmapSetAdded; this.beatmaps.ItemRemoved += onBeatmapSetRemoved; this.beatmaps.BeatmapHidden += onBeatmapHidden; @@ -250,9 +255,6 @@ namespace osu.Game.Screens.Select private ScheduledDelegate selectionChangedDebounce; - // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. - private BeatmapInfo beatmapNoDebounce; - private void workingBeatmapChanged(WorkingBeatmap beatmap) { if (beatmap is DummyWorkingBeatmap) return; @@ -266,11 +268,17 @@ namespace osu.Game.Screens.Select } } + // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. + private BeatmapInfo beatmapNoDebounce; + private RulesetInfo rulesetNoDebounce; + /// - /// selection has been changed as the result of interaction with the carousel. + /// selection has been changed as the result of a user interaction. /// - private void carouselSelectionChanged(BeatmapInfo beatmap) + private void updateSelectedBeatmap(BeatmapInfo beatmap) { + var ruleset = base.Ruleset.Value; + void performLoad() { // We may be arriving here due to another component changing the bindable Beatmap. @@ -283,15 +291,18 @@ namespace osu.Game.Screens.Select ensurePlayingSelected(preview); } + Ruleset.Value = ruleset; + UpdateBeatmap(Beatmap.Value); } - if (beatmap?.Equals(beatmapNoDebounce) == true) + if (beatmap?.Equals(beatmapNoDebounce) == true && ruleset?.Equals(rulesetNoDebounce) == true) return; selectionChangedDebounce?.Cancel(); beatmapNoDebounce = beatmap; + rulesetNoDebounce = ruleset; if (beatmap == null) performLoad(); @@ -460,7 +471,7 @@ namespace osu.Game.Screens.Select { // in the case random selection failed, we want to trigger selectionChanged // to show the dummy beatmap (we have nothing else to display). - carouselSelectionChanged(null); + updateSelectedBeatmap(null); } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 1a11b0c03e..2fc9cff463 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -44,19 +44,17 @@ namespace osu.Game.Skinning private SampleChannel loadChannel(SampleInfo info, Func getSampleFunction) { - SampleChannel ch = null; + foreach (var lookup in info.LookupNames) + { + var ch = getSampleFunction($"Gameplay/{lookup}"); + if (ch == null) + continue; - if (info.Namespace != null) - ch = getSampleFunction($"Gameplay/{info.Namespace}/{info.Bank}-{info.Name}"); - - // try without namespace as a fallback. - if (ch == null) - ch = getSampleFunction($"Gameplay/{info.Bank}-{info.Name}"); - - if (ch != null) ch.Volume.Value = info.Volume / 100.0; + return ch; + } - return ch; + return null; } } } diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 1740658da6..5f70055021 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -1,10 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Configuration; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Rulesets; namespace osu.Game.Tests.Visual { @@ -13,6 +16,8 @@ namespace osu.Game.Tests.Visual private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); protected BindableBeatmap Beatmap => beatmap; + protected readonly Bindable Ruleset = new Bindable(); + protected DependencyContainer Dependencies { get; private set; } protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) @@ -22,13 +27,18 @@ namespace osu.Game.Tests.Visual Dependencies.CacheAs(beatmap); Dependencies.CacheAs(beatmap); + Dependencies.CacheAs(Ruleset); + Dependencies.CacheAs>(Ruleset); + return Dependencies; } [BackgroundDependencyLoader] - private void load(AudioManager audioManager) + private void load(AudioManager audioManager, RulesetStore rulesets) { beatmap.SetAudioManager(audioManager); + + Ruleset.Value = rulesets.AvailableRulesets.First(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 20c9646aa3..9afb1dd6cd 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -86,7 +86,11 @@ namespace osu.Game.Tests.Visual private readonly WeakList workingWeakReferences = new WeakList(); private readonly WeakList playerWeakReferences = new WeakList(); - private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance()); + private Player loadPlayerFor(RulesetInfo ri) + { + Ruleset.Value = ri; + return loadPlayerFor(ri.CreateInstance()); + } private Player loadPlayerFor(Ruleset r) { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9668d40fd5..da55726447 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,8 +18,8 @@ - - + +