diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 9319fb3dfb..fbb2db33b0 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseAutoJuiceStream : TestCasePlayer + public class TestCaseAutoJuiceStream : PlayerTestCase { public TestCaseAutoJuiceStream() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs index 9e1c44ba40..d413b53d17 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs @@ -8,11 +8,12 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + public class TestCaseBananaShower : PlayerTestCase { public override IReadOnlyList RequiredTypes => new[] { @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBananaShower), typeof(CatchRuleset), - typeof(CatchRulesetContainer), + typeof(DrawableCatchRuleset), }; public TestCaseBananaShower() diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs index 8f9dd73b80..5b242d05d7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchPlayer : PlayerTestCase { public TestCaseCatchPlayer() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs index 1e3d60d968..5a16a23a4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs @@ -4,11 +4,12 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchStacker : PlayerTestCase { public TestCaseCatchStacker() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs index 7451986a8b..a7e7f0ab14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs @@ -1,22 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer + public class TestCaseHyperDash : PlayerTestCase { public TestCaseHyperDash() : base(new CatchRuleset()) { } + [BackgroundDependencyLoader] + private void load() + { + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + } + protected override IBeatmap CreateBeatmap(Ruleset ruleset) { var beatmap = new Beatmap @@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests } }; - // Should produce a hperdash + // Should produce a hyper-dash beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true }); beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, }); @@ -38,11 +44,5 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - - protected override void AddCheckSteps(Func player) - { - base.AddCheckSteps(player); - AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - } } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index af8206d95a..5140135f80 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableCatchRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 82cda7df47..71268d899d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -21,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods private CatchPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (CatchPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (CatchPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class CatchFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index e1fda1a7b3..af614f95d0 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public CatchScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs similarity index 88% rename from osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs rename to osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index f421969449..406dc10eea 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -17,13 +17,13 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI { - public class CatchRulesetContainer : ScrollingRulesetContainer + public class DrawableCatchRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant; protected override bool UserScrollSpeedAdjustment => false; - public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { Direction.Value = ScrollingDirection.Down; @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); - public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs similarity index 83% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index 89e531fd9f..acafaffee6 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -10,11 +10,11 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditRulesetContainer : ManiaRulesetContainer + public class DrawableManiaEditRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3dbbd132a6..56c9471462 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Cached(Type = typeof(IManiaHitObjectComposer))] public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { - protected new ManiaEditRulesetContainer RulesetContainer { get; private set; } + protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; } public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -32,23 +32,23 @@ namespace osu.Game.Rulesets.Mania.Edit /// /// The screen-space position. /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition); + public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition); private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns; + public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) { - RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(RulesetContainer.ScrollingInfo); + dependencies.CacheAs(DrawableRuleset.ScrollingInfo); - return RulesetContainer; + return DrawableRuleset; } protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 2b6b7377ae..a4a10f1742 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableManiaRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index cf3d0734fb..5c914d8eac 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { } - public ManiaScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public ManiaScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs similarity index 92% rename from osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs rename to osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index d8b7dc0381..a019401d5b 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -28,8 +28,10 @@ using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaRulesetContainer : ScrollingRulesetContainer + public class DrawableManiaRuleset : DrawableScrollingRuleset { + protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; + public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; public IEnumerable BarLines; @@ -38,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { // Generate the bar lines @@ -97,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.UI public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); + protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) { diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs index f5fe36b56a..8d097ff1c1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs @@ -4,12 +4,13 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer + public class TestCaseHitCircleLongCombo : PlayerTestCase { public TestCaseHitCircleLongCombo() : base(new OsuRuleset()) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs index 9f13c19390..720c3c66fe 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseOsuPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseOsuPlayer : PlayerTestCase { public TestCaseOsuPlayer() : base(new OsuRuleset()) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs similarity index 82% rename from osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 7886a2393c..50b3eabcf4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -8,9 +8,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuEditRulesetContainer : OsuRulesetContainer + public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 174321b8b9..dd3925e04f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - => new OsuEditRulesetContainer(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap) + => new DrawableOsuEditRuleset(ruleset, beatmap); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index a203e23687..a1f4dfe1da 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -18,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBlinds : Mod, IApplicableToRulesetContainer, IApplicableToScoreProcessor + public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor { public override string Name => "Blinds"; public override string Description => "Play with blinds on your screen."; @@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1.12; private DrawableOsuBlinds blinds; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - rulesetContainer.Overlays.Add(blinds = new DrawableOsuBlinds(rulesetContainer.Playfield.HitObjectContainer, rulesetContainer.Beatmap)); + drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap)); } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index efcab28310..ec23570f54 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -13,7 +13,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer + public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods state.Apply(osuInputManager.CurrentState, osuInputManager); } - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // grab the input manager for future use. - osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager; + osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; osuInputManager.AllowUserPresses = false; } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d9c046a579..7d3aff7801 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableOsuRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 4f97cc0da5..2c8bf11016 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public OsuScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs similarity index 86% rename from osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs rename to osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 81482a9a01..b632e0fb05 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -17,11 +17,11 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { - public class OsuRulesetContainer : RulesetContainer + public class DrawableOsuRuleset : DrawableRuleset { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override Playfield CreatePlayfield() => new OsuPlayfield(); - public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index 00e1b649d9..369cdd49d2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private TaikoRulesetContainer rulesetContainer; + private DrawableTaikoRuleset drawableRuleset; private Container playfieldContainer; [BackgroundDependencyLoader] @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap) } }); } @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -154,33 +154,33 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; + BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; - rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { var swell = new Swell { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, Duration = duration, }; swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableSwell(swell)); + drawableRuleset.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration) @@ -190,40 +190,40 @@ namespace osu.Game.Rulesets.Taiko.Tests var d = new DrumRoll { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, }; d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); + drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); + drawableRuleset.Playfield.Add(new DrawableCentreHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableRimHit(h)); + drawableRuleset.Playfield.Add(new DrawableRimHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index b99ec57166..b7db3307ad 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Mods private TaikoPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (TaikoPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (TaikoPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class TaikoFlashlight : Flashlight diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 73cd9ba821..442cca49f8 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring /// private double hpMissMultiplier; - public TaikoScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public TaikoScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 08a56488aa..3e94775eb6 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) => new DrawableTaikoRuleset(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs similarity index 92% rename from osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs rename to osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 7a73f4bd2a..899b91863e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -20,13 +20,13 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoRulesetContainer : ScrollingRulesetContainer + public class DrawableTaikoRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override bool UserScrollSpeedAdjustment => false; - public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { Direction.Value = ScrollingDirection.Left; @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); + protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index cb527adb98..35b941b52b 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class TaikoPlayfield : ScrollingPlayfield { /// - /// Default height of a when inside a . + /// Default height of a when inside a . /// public const float DEFAULT_HEIGHT = 178; diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs deleted file mode 100644 index a5decaa9fb..0000000000 --- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseAllPlayers : TestCasePlayer - { - } -} diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index 09a745c913..4d6a0ae7b8 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -11,7 +10,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : TestCasePlayer + public class TestCaseAutoplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { @@ -24,11 +23,10 @@ namespace osu.Game.Tests.Visual }; } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } private class ScoreAccessiblePlayer : Player diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 8636e7721e..de34ff822e 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -371,7 +371,7 @@ namespace osu.Game.Tests.Visual Thread.Sleep(1); StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); ReplacesBackground.BindTo(Background.StoryboardReplacesBackground); - RulesetContainer.IsPaused.BindTo(IsPaused); + DrawableRuleset.IsPaused.BindTo(IsPaused); } } diff --git a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs new file mode 100644 index 0000000000..3e009ae080 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Lists; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePlayerReferenceLeaking : AllPlayersTestCase + { + private readonly WeakList workingWeakReferences = new WeakList(); + + private readonly WeakList playerWeakReferences = new WeakList(); + + protected override void AddCheckSteps() + { + AddUntilStep("no leaked beatmaps", () => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + workingWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }); + + AddUntilStep("no leaked players", () => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + playerWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) + { + var working = base.CreateWorkingBeatmap(beatmap, clock); + workingWeakReferences.Add(working); + return working; + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + var player = base.CreatePlayer(ruleset); + playerWeakReferences.Add(player); + return player; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 5e23b21521..3a7e2352f8 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -12,7 +11,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("Player instantiated with a replay.")] - public class TestCaseReplay : TestCasePlayer + public class TestCaseReplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { @@ -21,11 +20,10 @@ namespace osu.Game.Tests.Visual return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)player()).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index f9df025be8..73aa12a3db 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap) { throw new NotImplementedException(); } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index db8bdde6bb..aad55f8a38 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { + mods = mods.Select(m => m.CreateCopy()).ToArray(); + beatmap.Mods.Value = mods; IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs similarity index 74% rename from osu.Game/Rulesets/Edit/EditRulesetContainer.cs rename to osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 8992be2da2..76a2e7af12 100644 --- a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -11,14 +11,14 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit { - public abstract class EditRulesetContainer : CompositeDrawable + public abstract class DrawableEditRuleset : CompositeDrawable { /// - /// The contained by this . + /// The contained by this . /// public abstract Playfield Playfield { get; } - internal EditRulesetContainer() + internal DrawableEditRuleset() { RelativeSizeAxes = Axes.Both; } @@ -38,21 +38,21 @@ namespace osu.Game.Rulesets.Edit internal abstract DrawableHitObject Remove(HitObject hitObject); } - public class EditRulesetContainer : EditRulesetContainer + public class DrawableEditRuleset : DrawableEditRuleset where TObject : HitObject { - public override Playfield Playfield => rulesetContainer.Playfield; + public override Playfield Playfield => drawableRuleset.Playfield; - private Ruleset ruleset => rulesetContainer.Ruleset; - private Beatmap beatmap => rulesetContainer.Beatmap; + private Ruleset ruleset => drawableRuleset.Ruleset; + private Beatmap beatmap => drawableRuleset.Beatmap; - private readonly RulesetContainer rulesetContainer; + private readonly DrawableRuleset drawableRuleset; - public EditRulesetContainer(RulesetContainer rulesetContainer) + public DrawableEditRuleset(DrawableRuleset drawableRuleset) { - this.rulesetContainer = rulesetContainer; + this.drawableRuleset = drawableRuleset; - InternalChild = rulesetContainer; + InternalChild = drawableRuleset; Playfield.DisplayJudgements.Value = false; } @@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Edit processor?.PostProcess(); // Add visual representation - var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); + var drawableObject = drawableRuleset.GetVisualRepresentation(tObject); - rulesetContainer.Playfield.Add(drawableObject); - rulesetContainer.Playfield.PostProcess(); + drawableRuleset.Playfield.Add(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } @@ -97,8 +97,8 @@ namespace osu.Game.Rulesets.Edit // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); - rulesetContainer.Playfield.Remove(drawableObject); - rulesetContainer.Playfield.PostProcess(); + drawableRuleset.Playfield.Remove(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 025564e249..45bf9b8be7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - public IEnumerable HitObjects => RulesetContainer.Playfield.AllHitObjects; + public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; protected readonly Ruleset Ruleset; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit private readonly List layerContainers = new List(); - protected EditRulesetContainer RulesetContainer { get; private set; } + protected DrawableEditRuleset DrawableRuleset { get; private set; } private BlueprintContainer blueprintContainer; @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Edit try { - RulesetContainer = CreateRulesetContainer(); - RulesetContainer.Clock = framedClock; + DrawableRuleset = CreateDrawableRuleset(); + DrawableRuleset.Clock = framedClock; } catch (Exception e) { @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - RulesetContainer, + DrawableRuleset, layerAboveRuleset } } @@ -140,27 +140,27 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = RulesetContainer.Playfield.Anchor; - l.Origin = RulesetContainer.Playfield.Origin; - l.Position = RulesetContainer.Playfield.Position; - l.Size = RulesetContainer.Playfield.Size; + l.Anchor = DrawableRuleset.Playfield.Anchor; + l.Origin = DrawableRuleset.Playfield.Origin; + l.Position = DrawableRuleset.Playfield.Position; + l.Size = DrawableRuleset.Playfield.Size; }); } /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// - public virtual bool CursorInPlacementArea => RulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); /// /// Adds a to the and visualises it. /// /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject)); + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject)); - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject)); + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract EditRulesetContainer CreateRulesetContainer(); + internal abstract DrawableEditRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Edit { } - internal override EditRulesetContainer CreateRulesetContainer() - => new EditRulesetContainer(CreateRulesetContainer(Ruleset, Beatmap.Value)); + internal override DrawableEditRuleset CreateDrawableRuleset() + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value)); - protected abstract RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs similarity index 50% rename from osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs rename to osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs index addb96a4fe..b012beb0c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs @@ -7,15 +7,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { /// - /// An interface for s that can be applied to s. + /// An interface for s that can be applied to s. /// - public interface IApplicableToRulesetContainer : IApplicableMod + public interface IApplicableToDrawableRuleset : IApplicableMod where TObject : HitObject { /// - /// Applies this to a . + /// Applies this to a . /// - /// The to apply to. - void ApplyToRulesetContainer(RulesetContainer rulesetContainer); + /// The to apply to. + void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 705c5c4ef6..1f9907caa7 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -65,5 +65,10 @@ namespace osu.Game.Rulesets.Mods /// [JsonIgnore] public virtual Type[] IncompatibleMods => new Type[] { }; + + /// + /// Creates a copy of this initialised to a default state. + /// + public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 44c78f8436..1c76abbc4b 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -11,10 +11,10 @@ using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset where T : HitObject { - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap)); + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); } public abstract class ModAutoplay : Mod, IApplicableFailOverride diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index ab095f417a..23e928d991 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods } } - public abstract class ModFlashlight : ModFlashlight, IApplicableToRulesetContainer, IApplicableToScoreProcessor + public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor where T : HitObject { public const double FLASHLIGHT_FADE_DURATION = 800; @@ -45,15 +45,15 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); flashlight.Combo = Combo; flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; - rulesetContainer.KeyBindingInputManager.Add(flashlight); + drawableRuleset.KeyBindingInputManager.Add(flashlight); - flashlight.Breaks = rulesetContainer.Beatmap.Breaks; + flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } public abstract Flashlight CreateFlashlight(); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 41281e805e..0089d1eb88 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,12 +8,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasCombo + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition, IHasCombo { public double EndTime { get; set; } public double Duration => EndTime - StartTime; + public float X => 256; // Required for CatchBeatmapConverter + public bool NewCombo { get; set; } public int ComboOffset { get; set; } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 70f15b99bd..feac49ca2c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63e1c93dd5..0fddb19a6c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -210,15 +210,15 @@ namespace osu.Game.Rulesets.Scoring { } - public ScoreProcessor(RulesetContainer rulesetContainer) + public ScoreProcessor(DrawableRuleset drawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); - rulesetContainer.OnNewResult += applyResult; - rulesetContainer.OnRevertResult += revertResult; + drawableRuleset.OnNewResult += applyResult; + drawableRuleset.OnRevertResult += revertResult; - ApplyBeatmap(rulesetContainer.Beatmap); - SimulateAutoplay(rulesetContainer.Beatmap); + ApplyBeatmap(drawableRuleset.Beatmap); + SimulateAutoplay(drawableRuleset.Beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs similarity index 65% rename from osu.Game/Rulesets/UI/RulesetContainer.cs rename to osu.Game/Rulesets/UI/DrawableRuleset.cs index d8813631dc..abf0fb2696 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -25,16 +25,16 @@ using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { /// - /// Base RulesetContainer. Doesn't hold objects. - /// - /// Should not be derived - derive instead. - /// + /// Displays an interactive ruleset gameplay instance. /// - public abstract class RulesetContainer : Container, IProvideCursor + /// The type of HitObject contained by this DrawableRuleset. + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + where TObject : HitObject { /// /// The selected variant. @@ -42,27 +42,11 @@ namespace osu.Game.Rulesets.UI public virtual int Variant => 0; /// - /// The input manager for this RulesetContainer. - /// - internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; - - /// - /// The key conversion input manager for this RulesetContainer. + /// The key conversion input manager for this DrawableRuleset. /// public PassThroughInputManager KeyBindingInputManager; - /// - /// Whether a replay is currently loaded. - /// - public readonly BindableBool HasReplayLoaded = new BindableBool(); - - public abstract IEnumerable Objects { get; } - - /// - /// The point in time at which gameplay starts, including any required lead-in for display purposes. - /// Defaults to two seconds before the first . Override as necessary. - /// - public virtual double GameplayStartTime => Objects.First().StartTime - 2000; + public override double GameplayStartTime => Objects.First().StartTime - 2000; private readonly Lazy playfield; @@ -74,27 +58,54 @@ namespace osu.Game.Rulesets.UI /// /// Place to put drawables above hit objects but below UI. /// - public Container Overlays { get; protected set; } + public Container Overlays { get; private set; } - public CursorContainer Cursor => Playfield.Cursor; + /// + /// Invoked when a has been applied by a . + /// + public event Action OnNewResult; - public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; + /// + /// Invoked when a is being reverted by a . + /// + public event Action OnRevertResult; - protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + /// + /// The beatmap. + /// + public Beatmap Beatmap; - public readonly Ruleset Ruleset; + public override IEnumerable Objects => Beatmap.HitObjects; protected IRulesetConfigManager Config { get; private set; } + /// + /// The mods which are to be applied. + /// + private readonly IEnumerable mods; + + private FrameStabilityContainer frameStabilityContainer; + private OnScreenDisplay onScreenDisplay; /// - /// A visual representation of a . + /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// - /// The ruleset being repesented. - protected RulesetContainer(Ruleset ruleset) + /// The ruleset being represented. + /// The beatmap to create the hit renderer for. + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap) + : base(ruleset) { - Ruleset = ruleset; + Debug.Assert(workingBeatmap != null, "DrawableRuleset initialized with a null beatmap."); + + RelativeSizeAxes = Axes.Both; + + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + + mods = workingBeatmap.Mods.Value; + applyBeatmapMods(mods); + + KeyBindingInputManager = CreateInputManager(); playfield = new Lazy(CreatePlayfield); IsPaused.ValueChanged += paused => @@ -122,164 +133,101 @@ namespace osu.Game.Rulesets.UI return dependencies; } - public abstract ScoreProcessor CreateScoreProcessor(); + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + KeyBindingInputManager.AddRange(new Drawable[] + { + Playfield + }); + + InternalChildren = new Drawable[] + { + frameStabilityContainer = new FrameStabilityContainer + { + Child = KeyBindingInputManager, + }, + Overlays = new Container { RelativeSizeAxes = Axes.Both } + }; + + applyRulesetMods(mods, config); + + loadObjects(); + } + + /// + /// Creates and adds drawable representations of hit objects to the play field. + /// + private void loadObjects() + { + foreach (TObject h in Beatmap.HitObjects) + addRepresentation(h); + + Playfield.PostProcess(); + + foreach (var mod in mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); + } + + /// + /// Creates and adds the visual representation of a to this . + /// + /// The to add the visual representation for. + private void addRepresentation(TObject hitObject) + { + var drawableObject = GetVisualRepresentation(hitObject); + + if (drawableObject == null) + return; + + drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); + drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); + + Playfield.Add(drawableObject); + } + + public override void SetReplayScore(Score replayScore) + { + if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + + var handler = (ReplayScore = replayScore) != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + replayInputManager.ReplayInputHandler = handler; + frameStabilityContainer.ReplayInputHandler = handler; + + HasReplayLoaded.Value = replayInputManager.ReplayInputHandler != null; + + if (replayInputManager.ReplayInputHandler != null) + replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + } + + /// + /// Creates a DrawableHitObject from a HitObject. + /// + /// The HitObject to make drawable. + /// The DrawableHitObject. + public abstract DrawableHitObject GetVisualRepresentation(TObject h); + + public void Attach(KeyCounterCollection keyCounter) => + (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// /// The input manager. - public abstract PassThroughInputManager CreateInputManager(); + protected abstract PassThroughInputManager CreateInputManager(); protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - protected FrameStabilityContainer FrameStabilityContainer; - - public Score ReplayScore { get; private set; } - - /// - /// Whether the game is paused. Used to block user input. - /// - public readonly BindableBool IsPaused = new BindableBool(); - - /// - /// Sets a replay to be used, overriding local input. - /// - /// The replay, null for local input. - public virtual void SetReplayScore(Score replayScore) - { - if (ReplayInputManager == null) - throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); - - ReplayScore = replayScore; - - var handler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; - - ReplayInputManager.ReplayInputHandler = handler; - FrameStabilityContainer.ReplayInputHandler = handler; - - HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; - } - - /// - /// Creates the cursor. May be null if the doesn't provide a custom cursor. - /// - protected virtual CursorContainer CreateCursor() => null; - /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (Config != null) - { - onScreenDisplay?.StopTracking(this, Config); - Config = null; - } - } - } - - /// - /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield - /// and does not load drawable hit objects. - /// - /// Should not be derived - derive instead. - /// - /// - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - { - /// - /// Invoked when a has been applied by a . - /// - public event Action OnNewResult; - - /// - /// Invoked when a is being reverted by a . - /// - public event Action OnRevertResult; - - /// - /// The Beatmap - /// - public Beatmap Beatmap; - - /// - /// All the converted hit objects contained by this hit renderer. - /// - public override IEnumerable Objects => Beatmap.HitObjects; - - /// - /// The mods which are to be applied. - /// - protected IEnumerable Mods; - - /// - /// The this was created with. - /// - protected readonly WorkingBeatmap WorkingBeatmap; - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - protected override Container Content => content; - private Container content; - - /// - /// Whether to assume the beatmap passed into this is for the current ruleset. - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) - : base(ruleset) - { - Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); - - WorkingBeatmap = workingBeatmap; - // ReSharper disable once PossibleNullReferenceException - Mods = workingBeatmap.Mods.Value; - - RelativeSizeAxes = Axes.Both; - - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); - - KeyBindingInputManager = CreateInputManager(); - - applyBeatmapMods(Mods); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - KeyBindingInputManager.AddRange(new Drawable[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - Playfield - }); - - InternalChildren = new Drawable[] - { - FrameStabilityContainer = new FrameStabilityContainer - { - Child = KeyBindingInputManager, - }, - Overlays = new Container { RelativeSizeAxes = Axes.Both } - }; - - // Apply mods - applyRulesetMods(Mods, config); - - loadObjects(); - } - /// /// Applies the active mods to the Beatmap. /// @@ -294,7 +242,7 @@ namespace osu.Game.Rulesets.UI } /// - /// Applies the active mods to this RulesetContainer. + /// Applies the active mods to this DrawableRuleset. /// /// private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) @@ -302,83 +250,101 @@ namespace osu.Game.Rulesets.UI if (mods == null) return; - foreach (var mod in mods.OfType>()) - mod.ApplyToRulesetContainer(this); + foreach (var mod in mods.OfType>()) + mod.ApplyToDrawableRuleset(this); foreach (var mod in mods.OfType()) mod.ReadFromConfig(config); } - public override void SetReplayScore(Score replayScore) + #region IProvideCursor + + protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + + public override CursorContainer Cursor => Playfield.Cursor; + + public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; + + #endregion + + protected override void Dispose(bool isDisposing) { - base.SetReplayScore(replayScore); + base.Dispose(isDisposing); - if (ReplayInputManager?.ReplayInputHandler != null) - ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + if (Config != null) + { + onScreenDisplay?.StopTracking(this, Config); + Config = null; + } } - - /// - /// Creates and adds drawable representations of hit objects to the play field. - /// - private void loadObjects() - { - foreach (TObject h in Beatmap.HitObjects) - AddRepresentation(h); - - Playfield.PostProcess(); - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); - } - - /// - /// Creates and adds the visual representation of a to this . - /// - /// The to add the visual representation for. - internal void AddRepresentation(TObject hitObject) - { - var drawableObject = GetVisualRepresentation(hitObject); - - if (drawableObject == null) - return; - - drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); - drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); - - Playfield.Add(drawableObject); - } - - /// - /// Creates a DrawableHitObject from a HitObject. - /// - /// The HitObject to make drawable. - /// The DrawableHitObject. - public abstract DrawableHitObject GetVisualRepresentation(TObject h); } /// - /// A derivable RulesetContainer that manages the Playfield and HitObjects. + /// Displays an interactive ruleset gameplay instance. + /// + /// This type is required only for adding non-generic type to the draw hierarchy. + /// Once IDrawable is a thing, this can also become an interface. + /// /// - /// The type of Playfield contained by this RulesetContainer. - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - where TPlayfield : Playfield + public abstract class DrawableRuleset : CompositeDrawable { /// - /// The playfield. + /// Whether a replay is currently loaded. /// - protected new TPlayfield Playfield => (TPlayfield)base.Playfield; + public readonly BindableBool HasReplayLoaded = new BindableBool(); /// - /// Creates a hit renderer for a beatmap. + /// Whether the game is paused. Used to block user input. /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public readonly BindableBool IsPaused = new BindableBool(); + + /// ~ + /// The associated ruleset. + /// + public readonly Ruleset Ruleset; + + /// + /// Creates a ruleset visualisation for the provided ruleset. + /// + /// The ruleset. + internal DrawableRuleset(Ruleset ruleset) { + Ruleset = ruleset; } + + /// + /// All the converted hit objects contained by this hit renderer. + /// + public abstract IEnumerable Objects { get; } + + /// + /// The point in time at which gameplay starts, including any required lead-in for display purposes. + /// Defaults to two seconds before the first . Override as necessary. + /// + public abstract double GameplayStartTime { get; } + + /// + /// The currently loaded replay. Usually null in the case of a local player. + /// + public Score ReplayScore { get; protected set; } + + /// + /// The cursor being displayed by the . May be null if no cursor is provided. + /// + public abstract CursorContainer Cursor { get; } + + /// + /// Sets a replay to be used, overriding local input. + /// + /// The replay, null for local input. + public abstract void SetReplayScore(Score replayScore); + + /// + /// Create a for the associated ruleset and link with this + /// . + /// + /// A score processor. + public abstract ScoreProcessor CreateScoreProcessor(); } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs similarity index 89% rename from osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs rename to osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 7a60e0b021..3b368652f2 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -20,12 +21,11 @@ using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { /// - /// A type of that supports a . - /// s inside this will scroll within the playfield. + /// A type of that supports a . + /// s inside this will scroll within the playfield. /// - public abstract class ScrollingRulesetContainer : RulesetContainer, IKeyBindingHandler + public abstract class DrawableScrollingRuleset : DrawableRuleset, IKeyBindingHandler where TObject : HitObject - where TPlayfield : ScrollingPlayfield { /// /// The default span of time visible by the length of the scrolling axes. @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Provides the default s that adjust the scrolling rate of s - /// inside this . + /// inside this . /// /// private readonly SortedList controlPoints = new SortedList(Comparer.Default); @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { scrollingInfo = new LocalScrollingInfo(); @@ -167,6 +167,14 @@ namespace osu.Game.Rulesets.UI.Scrolling return false; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!(Playfield is ScrollingPlayfield)) + throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); + } + public bool OnReleased(GlobalAction action) => false; private class LocalScrollingInfo : IScrollingInfo diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f2d2381d20..0ba1e74aca 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit { this.host = host; - // TODO: should probably be done at a RulesetContainer level to share logic with Player. + // TODO: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 8ab23a620b..6080458aec 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Multi.Components private const float height = 30; private const float transition_duration = 100; - private Container rulesetContainer; + private Container drawableRuleset; public ModeTypeInfo() { @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Multi.Components LayoutDuration = 100, Children = new[] { - rulesetContainer = new Container + drawableRuleset = new Container { AutoSizeAxes = Axes.Both, }, @@ -55,11 +55,11 @@ namespace osu.Game.Screens.Multi.Components { if (item?.Beatmap != null) { - rulesetContainer.FadeIn(transition_duration); - rulesetContainer.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; + drawableRuleset.FadeIn(transition_duration); + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; } else - rulesetContainer.FadeOut(transition_duration); + drawableRuleset.FadeOut(transition_duration); } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 4fd0572c1a..285e6eab23 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working) + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, WorkingBeatmap working) { RelativeSizeAxes = Axes.Both; @@ -90,10 +90,10 @@ namespace osu.Game.Screens.Play }; BindProcessor(scoreProcessor); - BindRulesetContainer(rulesetContainer); + BindDrawableRuleset(drawableRuleset); - Progress.Objects = rulesetContainer.Objects; - Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value; + Progress.Objects = drawableRuleset.Objects; + Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); ModDisplay.Current.BindTo(working.Mods); @@ -143,13 +143,13 @@ namespace osu.Game.Screens.Play } } - protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer) + protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - Progress.BindRulestContainer(rulesetContainer); + Progress.BindDrawableRuleset(drawableRuleset); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7071841722..194d09f4b3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; protected ScoreProcessor ScoreProcessor { get; private set; } - protected RulesetContainer RulesetContainer { get; private set; } + protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } private FailOverlay failOverlay; @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Play EnableUserDim = { Value = true } }; - public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; + public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; private GameplayClockContainer gameplayClockContainer; @@ -101,11 +101,11 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - ScoreProcessor = RulesetContainer.CreateScoreProcessor(); + ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); + InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, DrawableRuleset.GameplayStartTime); gameplayClockContainer.Children = new Drawable[] { @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Play Start = gameplayClockContainer.Start, Stop = gameplayClockContainer.Stop, IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, + CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !DrawableRuleset.HasReplayLoaded.Value, Children = new[] { StoryboardContainer = CreateStoryboardContainer(), @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Play Child = new LocalSkinOverrideContainer(working.Skin) { RelativeSizeAxes = Axes.Both, - Child = RulesetContainer + Child = DrawableRuleset } }, new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) @@ -136,17 +136,17 @@ namespace osu.Game.Screens.Play Breaks = working.Beatmap.Breaks }, // display the cursor above some HUD elements. - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) + DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, working) { HoldToQuit = { Action = performUserRequestedExit }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = gameplayClockContainer.UserPlaybackRate } } }, - KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, + KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, RequestSeek = gameplayClockContainer.Seek, Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new SkipOverlay(RulesetContainer.GameplayStartTime) + new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSeek = gameplayClockContainer.Seek }, @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Play }; // bind clock into components that require it - RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused); + DrawableRuleset.IsPaused.BindTo(gameplayClockContainer.IsPaused); if (showStoryboard.Value) initializeStoryboard(false); @@ -201,18 +201,18 @@ namespace osu.Game.Screens.Play try { - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working); } catch (BeatmapInvalidForRulesetException) { - // we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset + // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value); } - if (!RulesetContainer.Objects.Any()) + if (!DrawableRuleset.Objects.Any()) { Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); return null; @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; var score = CreateScore(); - if (RulesetContainer.ReplayScore == null) + if (DrawableRuleset.ReplayScore == null) scoreManager.Import(score); this.Push(CreateResults(score)); @@ -276,7 +276,7 @@ namespace osu.Game.Screens.Play protected virtual ScoreInfo CreateScore() { - var score = RulesetContainer.ReplayScore?.ScoreInfo ?? new ScoreInfo + var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, @@ -350,7 +350,7 @@ namespace osu.Game.Screens.Play return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) + if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || DrawableRuleset?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) { gameplayClockContainer.ResetLocalAdjustments(); diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 3190139378..949b08d98d 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer?.SetReplayScore(score); + DrawableRuleset?.SetReplayScore(score); } protected override ScoreInfo CreateScore() => score.ScoreInfo; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index e3d6ca16a7..94b25e04a3 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -109,9 +109,9 @@ namespace osu.Game.Screens.Play replayLoaded.TriggerChange(); } - public void BindRulestContainer(RulesetContainer rulesetContainer) + public void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } private bool allowSeeking; diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs new file mode 100644 index 0000000000..507848730f --- /dev/null +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + /// + /// A base class which runs test for all available rulesets. + /// Steps to be run for each ruleset should be added via . + /// + public abstract class AllPlayersTestCase : RateAdjustedBeatmapTestCase + { + protected Player Player; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Add(new Box + { + RelativeSizeAxes = Framework.Graphics.Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + foreach (var r in rulesets.AvailableRulesets) + { + Player p = null; + AddStep(r.Name, () => p = loadPlayerFor(r)); + AddUntilStep(() => + { + if (p?.IsLoaded == true) + { + p = null; + return true; + } + + return false; + }, "player loaded"); + + AddCheckSteps(); + } + } + + protected abstract void AddCheckSteps(); + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) => + new TestWorkingBeatmap(beatmap, Clock); + + private Player loadPlayerFor(RulesetInfo ri) + { + Ruleset.Value = ri; + var r = ri.CreateInstance(); + + var beatmap = CreateBeatmap(r); + var working = CreateWorkingBeatmap(beatmap, Clock); + + Beatmap.Value = working; + Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + + Player?.Exit(); + Player = null; + + var player = CreatePlayer(r); + + LoadComponentAsync(player, p => + { + Player = p; + LoadScreen(p); + }); + + return player; + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } +} diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs new file mode 100644 index 0000000000..ad01d82281 --- /dev/null +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public abstract class PlayerTestCase : RateAdjustedBeatmapTestCase + { + private readonly Ruleset ruleset; + + protected Player Player; + + protected PlayerTestCase(Ruleset ruleset) + { + this.ruleset = ruleset; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep(ruleset.RulesetInfo.Name, loadPlayer); + AddUntilStep(() => Player.IsLoaded, "player loaded"); + } + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + protected virtual bool AllowFail => false; + + private void loadPlayer() + { + var beatmap = CreateBeatmap(ruleset); + + Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); + + if (!AllowFail) + Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + LoadComponentAsync(Player = CreatePlayer(ruleset), p => + { + Player = p; + LoadScreen(p); + }); + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } +}