diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index d775cb392f..0a248658a8 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The definitions for each stage in a . /// - public readonly List Stages = new List(); + public List Stages = new List(); /// /// Total number of columns represented by all stages in this . @@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// Creates a new . /// - /// The initial stage. - public ManiaBeatmap(StageDefinition initialStage) + /// The initial stages. + public ManiaBeatmap(StageDefinition defaultStage) { - Stages.Add(initialStage); + Stages.Add(defaultStage); } } } diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 5e12ef5581..01e2821540 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -17,8 +17,13 @@ namespace osu.Game.Rulesets.Mania public enum ManiaAction { - [Description("Special")] - Special, + [Description("Special 1")] + Special1 = 1, + [Description("Special 2")] + Special2, + + // This offsets the start value of normal keys in-case we add more special keys + // above at a later time, without breaking replays/configs. [Description("Key 1")] Key1 = 10, [Description("Key 2")] @@ -36,6 +41,24 @@ namespace osu.Game.Rulesets.Mania [Description("Key 8")] Key8, [Description("Key 9")] - Key9 + Key9, + [Description("Key 10")] + Key10, + [Description("Key 11")] + Key11, + [Description("Key 12")] + Key12, + [Description("Key 13")] + Key13, + [Description("Key 14")] + Key14, + [Description("Key 15")] + Key15, + [Description("Key 16")] + Key16, + [Description("Key 17")] + Key17, + [Description("Key 18")] + Key18, } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index e8b9828bff..3bfb4d3e44 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -1,12 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Graphics; @@ -86,7 +88,7 @@ namespace osu.Game.Rulesets.Mania }, }, new ManiaModRandom(), - new ManiaModKeyCoop(), + new ManiaModDualStages(), new MultiMod { Mods = new Mod[] @@ -117,42 +119,189 @@ namespace osu.Game.Rulesets.Mania { } - public override IEnumerable AvailableVariants => new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + public override IEnumerable AvailableVariants + { + get + { + for (int i = 1; i <= 9; i++) + yield return (int)PlayfieldType.Single + i; + for (int i = 2; i <= 18; i += 2) + yield return (int)PlayfieldType.Dual + i; + } + } public override IEnumerable GetDefaultKeyBindings(int variant = 0) { - var leftKeys = new[] + switch (getPlayfieldType(variant)) { - InputKey.A, - InputKey.S, - InputKey.D, - InputKey.F - }; + case PlayfieldType.Single: + return new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.A, + InputKey.S, + InputKey.D, + InputKey.F + }, + RightKeys = new[] + { + InputKey.J, + InputKey.K, + InputKey.L, + InputKey.Semicolon + }, + SpecialKey = InputKey.Space, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1, + }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: + int keys = getDualStageKeyCount(variant); - var rightKeys = new[] - { - InputKey.J, - InputKey.K, - InputKey.L, - InputKey.Semicolon - }; + var stage1Bindings = new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.Number1, + InputKey.Number2, + InputKey.Number3, + InputKey.Number4, + }, + RightKeys = new[] + { + InputKey.Z, + InputKey.X, + InputKey.C, + InputKey.V + }, + SpecialKey = InputKey.Tilde, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1 + }.GenerateKeyBindingsFor(keys, out var nextNormal); - ManiaAction currentKey = ManiaAction.Key1; + var stage2Bindings = new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.Number7, + InputKey.Number8, + InputKey.Number9, + InputKey.Number0 + }, + RightKeys = new[] + { + InputKey.O, + InputKey.P, + InputKey.BracketLeft, + InputKey.BracketRight + }, + SpecialKey = InputKey.BackSlash, + SpecialAction = ManiaAction.Special2, + NormalActionStart = nextNormal + }.GenerateKeyBindingsFor(keys, out _); - var bindings = new List(); + return stage1Bindings.Concat(stage2Bindings); + } - for (int i = leftKeys.Length - variant / 2; i < leftKeys.Length; i++) - bindings.Add(new KeyBinding(leftKeys[i], currentKey++)); - - for (int i = 0; i < variant / 2; i++) - bindings.Add(new KeyBinding(rightKeys[i], currentKey++)); - - if (variant % 2 == 1) - bindings.Add(new KeyBinding(InputKey.Space, ManiaAction.Special)); - - return bindings; + return new KeyBinding[0]; } - public override string GetVariantName(int variant) => $"{variant}K"; + public override string GetVariantName(int variant) + { + switch (getPlayfieldType(variant)) + { + default: + return $"{variant}K"; + case PlayfieldType.Dual: + { + var keys = getDualStageKeyCount(variant); + return $"{keys}K + {keys}K"; + } + } + } + + /// + /// Finds the number of keys for each stage in a variant. + /// + /// The variant. + private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2; + + /// + /// Finds the that corresponds to a variant value. + /// + /// The variant value. + /// The that corresponds to . + private PlayfieldType getPlayfieldType(int variant) + { + return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); + } + + private class VariantMappingGenerator + { + /// + /// All the s available to the left hand. + /// + public InputKey[] LeftKeys; + + /// + /// All the s available to the right hand. + /// + public InputKey[] RightKeys; + + /// + /// The for the special key. + /// + public InputKey SpecialKey; + + /// + /// The at which the normal columns should begin. + /// + public ManiaAction NormalActionStart; + + /// + /// The for the special column. + /// + public ManiaAction SpecialAction; + + /// + /// Generates a list of s for a specific number of columns. + /// + /// The number of columns that need to be bound. + /// The next to use for normal columns. + /// The keybindings. + public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) + { + ManiaAction currentNormalAction = NormalActionStart; + + var bindings = new List(); + + for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) + bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); + + for (int i = 0; i < columns / 2; i++) + bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + + if (columns % 2 == 1) + bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + + nextNormalAction = currentNormalAction; + return bindings; + } + } + } + + public enum PlayfieldType + { + /// + /// Columns are grouped into a single stage. + /// Number of columns in this stage lies at (item - Single). + /// + Single = 0, + /// + /// Columns are grouped into two stages. + /// Overall number of columns lies at (item - Dual), further computation is required for + /// number of columns in each individual stage. + /// + Dual = 1000, } } diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs new file mode 100644 index 0000000000..93d98b5d83 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public interface IPlayfieldTypeMod : IApplicableMod + { + /// + /// The which this requires. + /// + PlayfieldType PlayfieldType { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs new file mode 100644 index 0000000000..3330d87e88 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer + { + public override string Name => "Dual Stages"; + public override string ShortenedName => "DS"; + public override string Description => @"Double the stages, double the fun!"; + public override double ScoreMultiplier => 1; + public override bool Ranked => false; + + public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) + { + var mbc = (ManiaBeatmapConverter)beatmapConverter; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mbc.IsForCurrentRuleset) + return; + + mbc.TargetColumns *= 2; + } + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var mrc = (ManiaRulesetContainer)rulesetContainer; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mrc.IsForCurrentRuleset) + return; + + var newDefinitions = new List(); + foreach (var existing in mrc.Beatmap.Stages) + { + newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); + newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); + } + + mrc.Beatmap.Stages = newDefinitions; + } + + public PlayfieldType PlayfieldType => PlayfieldType.Dual; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKeyCoop.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKeyCoop.cs deleted file mode 100644 index 893e81f165..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKeyCoop.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKeyCoop : Mod - { - public override string Name => "KeyCoop"; - public override string ShortenedName => "2P"; - public override string Description => @"Double the key amount, double the fun!"; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - } -} diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs index 7bd47448c2..0e6d40dc67 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests RelativeChildSize = new Vector2(1, 10000), Children = new[] { - new DrawableHoldNote(new HoldNote { Duration = 1 }, ManiaAction.Key1) + new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1) { Y = 5000, Height = 1000, diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs index 0493c65acf..7d35ab2f4d 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs @@ -2,11 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -31,15 +33,43 @@ namespace osu.Game.Rulesets.Mania.Tests { var rng = new Random(1337); - AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal)); - AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal)); - AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left)); - AddStep("Right special style", () => createPlayfield(4, SpecialColumnPosition.Right)); - AddStep("5 columns", () => createPlayfield(5, SpecialColumnPosition.Normal)); - AddStep("8 columns", () => createPlayfield(8, SpecialColumnPosition.Normal)); - AddStep("Left special style", () => createPlayfield(8, SpecialColumnPosition.Left)); - AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right)); - AddStep("Reversed", () => createPlayfield(4, SpecialColumnPosition.Normal, true)); + AddStep("1 column", () => createPlayfield(1)); + AddStep("4 columns", () => createPlayfield(4)); + AddStep("5 columns", () => createPlayfield(5)); + AddStep("8 columns", () => createPlayfield(8)); + AddStep("4 + 4 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 4 }, + }; + createPlayfield(stages); + }); + + AddStep("2 + 4 + 2 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 2 }, + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 2 }, + }; + createPlayfield(stages); + }); + + AddStep("1 + 8 + 1 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 1 }, + new StageDefinition { Columns = 8 }, + new StageDefinition { Columns = 1 }, + }; + createPlayfield(stages); + }); + + AddStep("Reversed", () => createPlayfield(4, true)); AddStep("Notes with input", () => createPlayfieldWithNotes()); AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true)); @@ -48,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("Hit explosion", () => { - var playfield = createPlayfield(4, SpecialColumnPosition.Normal); + var playfield = createPlayfield(4); int col = rng.Next(0, 4); @@ -58,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Tests }; playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); + playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); }); } @@ -67,19 +98,29 @@ namespace osu.Game.Rulesets.Mania.Tests maniaRuleset = rulesets.GetRuleset(3); } - private ManiaPlayfield createPlayfield(int cols, SpecialColumnPosition specialPos, bool inverted = false) + private ManiaPlayfield createPlayfield(int cols, bool inverted = false) + { + var stages = new List + { + new StageDefinition { Columns = cols }, + }; + + return createPlayfield(stages, inverted); + } + + private ManiaPlayfield createPlayfield(List stages, bool inverted = false) { Clear(); - var inputManager = new ManiaInputManager(maniaRuleset, cols) { RelativeSizeAxes = Axes.Both }; + var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both }; Add(inputManager); ManiaPlayfield playfield; - inputManager.Add(playfield = new ManiaPlayfield(cols) + + inputManager.Add(playfield = new ManiaPlayfield(stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - SpecialColumnPosition = specialPos }); playfield.Inverted.Value = inverted; @@ -97,7 +138,12 @@ namespace osu.Game.Rulesets.Mania.Tests Add(inputManager); ManiaPlayfield playfield; - inputManager.Add(playfield = new ManiaPlayfield(4) + var stages = new List + { + new StageDefinition { Columns = 4 }, + }; + + inputManager.Add(playfield = new ManiaPlayfield(stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d79a4d62c4..882628642b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -47,6 +47,7 @@ namespace osu.Game.Rulesets.Mania.UI public Column() : base(ScrollingDirection.Up) { + RelativeSizeAxes = Axes.Y; Width = column_width; InternalChildren = new Drawable[] @@ -61,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.UI { Name = "Hit target + hit objects", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = ManiaPlayfield.HIT_TARGET_POSITION }, + Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION }, Children = new Drawable[] { new Container @@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.UI { Name = "Key", RelativeSizeAxes = Axes.X, - Height = ManiaPlayfield.HIT_TARGET_POSITION, + Height = ManiaStage.HIT_TARGET_POSITION, Children = new Drawable[] { new Box @@ -205,12 +206,12 @@ namespace osu.Game.Rulesets.Mania.UI { hitObject.Depth = (float)hitObject.HitObject.StartTime; hitObject.AccentColour = AccentColour; - hitObject.OnJudgement += onJudgement; + hitObject.OnJudgement += OnJudgement; HitObjects.Add(hitObject); } - private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { if (!judgement.IsHit) return; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index b36c9605cc..8a03f5a785 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.UI internal class DrawableManiaJudgement : DrawableJudgement { public DrawableManiaJudgement(Judgement judgement) - : base(judgement) + : base(judgement) { JudgementText.TextSize = 25; } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 7d3df6cda7..c008e71819 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -3,237 +3,84 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Graphics.Containers; using System; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using System.Linq; using System.Collections.Generic; +using System.Linq; using osu.Framework.Configuration; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { public class ManiaPlayfield : ScrollingPlayfield { - public const float HIT_TARGET_POSITION = 50; - - private SpecialColumnPosition specialColumnPosition; - /// - /// The style to use for the special column. - /// - public SpecialColumnPosition SpecialColumnPosition - { - get { return specialColumnPosition; } - set - { - if (IsLoaded) - throw new InvalidOperationException($"Setting {nameof(SpecialColumnPosition)} after the playfield is loaded requires re-creating the playfield."); - specialColumnPosition = value; - } - } - /// /// Whether this playfield should be inverted. This flips everything inside the playfield. /// public readonly Bindable Inverted = new Bindable(true); - private readonly FlowContainer columns; - public IEnumerable Columns => columns.Children; + public List Columns => stages.SelectMany(x => x.Columns).ToList(); + private readonly List stages = new List(); - protected override Container Content => content; - private readonly Container content; - - private List normalColumnColours = new List(); - private Color4 specialColumnColour; - - private readonly Container judgements; - - private readonly int columnCount; - - public ManiaPlayfield(int columnCount) + public ManiaPlayfield(List stageDefinitions) : base(ScrollingDirection.Up) { - this.columnCount = columnCount; + if (stageDefinitions == null) + throw new ArgumentNullException(nameof(stageDefinitions)); - if (columnCount <= 0) - throw new ArgumentException("Can't have zero or fewer columns."); + if (stageDefinitions.Count <= 0) + throw new ArgumentException("Can't have zero or fewer stages."); Inverted.Value = true; - Container topLevelContainer; - InternalChildren = new Drawable[] + GridContainer playfieldGrid; + InternalChild = playfieldGrid = new GridContainer { - new Container - { - Name = "Playfield elements", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new Container - { - Name = "Columns mask", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new Drawable[] - { - new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - columns = new FillFlowContainer - { - Name = "Columns", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 1, Right = 1 }, - Spacing = new Vector2(1, 0) - }, - } - }, - new Container - { - Name = "Barlines mask", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = 1366, // Bar lines should only be masked on the vertical axis - BypassAutoSizeAxes = Axes.Both, - Masking = true, - Child = content = new Container - { - Name = "Bar lines", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = HIT_TARGET_POSITION } - } - }, - judgements = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Y = HIT_TARGET_POSITION + 150, - BypassAutoSizeAxes = Axes.Both - }, - topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } - } - } + RelativeSizeAxes = Axes.Both, + Content = new[] { new Drawable[stageDefinitions.Count] } }; - var currentAction = ManiaAction.Key1; - for (int i = 0; i < columnCount; i++) + var normalColumnAction = ManiaAction.Key1; + var specialColumnAction = ManiaAction.Special1; + int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) { - var c = new Column(); - c.VisibleTimeRange.BindTo(VisibleTimeRange); + var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + newStage.VisibleTimeRange.BindTo(VisibleTimeRange); + newStage.Inverted.BindTo(Inverted); - c.IsSpecial = isSpecialColumn(i); - c.Action = c.IsSpecial ? ManiaAction.Special : currentAction++; + playfieldGrid.Content[0][i] = newStage; - topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); + stages.Add(newStage); + AddNested(newStage); - columns.Add(c); - AddNested(c); + firstColumnIndex += newStage.Columns.Count; } - - Inverted.ValueChanged += invertedChanged; - Inverted.TriggerChange(); } - private void invertedChanged(bool newValue) + public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); + + public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + + private ManiaStage getStageByColumn(int column) { - Scale = new Vector2(1, newValue ? -1 : 1); - judgements.Scale = Scale; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - normalColumnColours = new List + int sum = 0; + foreach (var stage in stages) { - colours.RedDark, - colours.GreenDark - }; - - specialColumnColour = colours.BlueDark; - - // Set the special column + colour + key - foreach (var column in Columns) - { - if (!column.IsSpecial) - continue; - - column.AccentColour = specialColumnColour; + sum = sum + stage.Columns.Count; + if (sum > column) + return stage; } - var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); - - // We'll set the colours of the non-special columns in a separate loop, because the non-special - // column colours are mirrored across their centre and special styles mess with this - for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) - { - Color4 colour = normalColumnColours[i % normalColumnColours.Count]; - nonSpecialColumns[i].AccentColour = colour; - nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; - } + return null; } internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { - judgements.Clear(); - judgements.Add(new DrawableManiaJudgement(judgement) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - } - - /// - /// Whether the column index is a special column for this playfield. - /// - /// The 0-based column index. - /// Whether the column is a special column. - private bool isSpecialColumn(int column) - { - switch (SpecialColumnPosition) - { - default: - case SpecialColumnPosition.Normal: - return columnCount % 2 == 1 && column == columnCount / 2; - case SpecialColumnPosition.Left: - return column == 0; - case SpecialColumnPosition.Right: - return column == columnCount - 1; - } - } - - public override void Add(DrawableHitObject h) - { - h.OnJudgement += OnJudgement; - Columns.ElementAt(((ManiaHitObject)h.HitObject).Column).Add(h); - } - - public void Add(DrawableBarLine barline) => HitObjects.Add(barline); - - protected override void Update() - { - // Due to masking differences, it is not possible to get the width of the columns container automatically - // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually - content.Width = columns.Width; + getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement); } } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 5bb980adb2..c438fe1abc 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using OpenTK; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -12,6 +11,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; namespace osu.Game.Rulesets.Mania.UI { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.UI { public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - public IEnumerable BarLines; + public IEnumerable BarLines; public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset) @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.UI double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; var timingPoints = Beatmap.ControlPointInfo.TimingPoints; - var barLines = new List(); + var barLines = new List(); for (int i = 0; i < timingPoints.Count; i++) { @@ -50,12 +51,12 @@ namespace osu.Game.Rulesets.Mania.UI int index = 0; for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) { - barLines.Add(new DrawableBarLine(new BarLine + barLines.Add(new BarLine { StartTime = t, ControlPoint = point, BeatIndex = index - })); + }); } } @@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.UI BarLines.ForEach(Playfield.Add); } - protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.TotalColumns) + protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -76,7 +77,11 @@ namespace osu.Game.Rulesets.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Beatmap.TotalColumns); + public override PassThroughInputManager CreateInputManager() + { + var variantType = Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single; + return new ManiaInputManager(Ruleset.RulesetInfo, (int)variantType + Beatmap.TotalColumns); + } protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs new file mode 100644 index 0000000000..ebd73d7dca --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -0,0 +1,229 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// A collection of s. + /// + internal class ManiaStage : ScrollingPlayfield + { + public const float HIT_TARGET_POSITION = 50; + + /// + /// Whether this playfield should be inverted. This flips everything inside the playfield. + /// + public readonly Bindable Inverted = new Bindable(true); + + public IReadOnlyList Columns => columnFlow.Children; + private readonly FillFlowContainer columnFlow; + + protected override Container Content => content; + private readonly Container content; + + public Container Judgements => judgements; + private readonly Container judgements; + + private readonly Container topLevelContainer; + + private List normalColumnColours = new List(); + private Color4 specialColumnColour; + + private readonly int firstColumnIndex; + private readonly StageDefinition definition; + + public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + : base(ScrollingDirection.Up) + { + this.firstColumnIndex = firstColumnIndex; + this.definition = definition; + + Name = "Stage"; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new Container + { + Name = "Columns mask", + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + columnFlow = new FillFlowContainer + { + Name = "Columns", + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Left = 1, Right = 1 }, + Spacing = new Vector2(1, 0) + }, + } + }, + new Container + { + Name = "Barlines mask", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1366, // Bar lines should only be masked on the vertical axis + BypassAutoSizeAxes = Axes.Both, + Masking = true, + Child = content = new Container + { + Name = "Bar lines", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = HIT_TARGET_POSITION } + } + }, + judgements = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Y = HIT_TARGET_POSITION + 150, + BypassAutoSizeAxes = Axes.Both + }, + topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + } + } + }; + + for (int i = 0; i < definition.Columns; i++) + { + var isSpecial = isSpecialColumn(i); + var column = new Column + { + IsSpecial = isSpecial, + Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ + }; + + AddColumn(column); + } + + Inverted.ValueChanged += invertedChanged; + Inverted.TriggerChange(); + } + + private void invertedChanged(bool newValue) + { + Scale = new Vector2(1, newValue ? -1 : 1); + Judgements.Scale = Scale; + } + + public void AddColumn(Column c) + { + c.VisibleTimeRange.BindTo(VisibleTimeRange); + + topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); + columnFlow.Add(c); + AddNested(c); + } + + /// + /// Whether the column index is a special column for this playfield. + /// + /// The 0-based column index. + /// Whether the column is a special column. + private bool isSpecialColumn(int column) => definition.Columns % 2 == 1 && column == definition.Columns / 2; + + public override void Add(DrawableHitObject h) + { + var maniaObject = (ManiaHitObject)h.HitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Add(h); + h.OnJudgement += OnJudgement; + } + + public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); + + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + judgements.Clear(); + judgements.Add(new DrawableManiaJudgement(judgement) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + normalColumnColours = new List + { + colours.RedDark, + colours.GreenDark + }; + + specialColumnColour = colours.BlueDark; + + // Set the special column + colour + key + foreach (var column in Columns) + { + if (!column.IsSpecial) + continue; + + column.AccentColour = specialColumnColour; + } + + var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); + + // We'll set the colours of the non-special columns in a separate loop, because the non-special + // column colours are mirrored across their centre and special styles mess with this + for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) + { + Color4 colour = normalColumnColours[i % normalColumnColours.Count]; + nonSpecialColumns[i].AccentColour = colour; + nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; + } + } + + protected override void Update() + { + // Due to masking differences, it is not possible to get the width of the columns container automatically + // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually + content.Width = columnFlow.Width; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/SpecialColumnPosition.cs b/osu.Game.Rulesets.Mania/UI/SpecialColumnPosition.cs deleted file mode 100644 index de017294e4..0000000000 --- a/osu.Game.Rulesets.Mania/UI/SpecialColumnPosition.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.UI -{ - public enum SpecialColumnPosition - { - /// - /// The special column will lie in the center of the columns. - /// - Normal, - /// - /// The special column will lie to the left of the columns. - /// - Left, - /// - /// The special column will lie to the right of the columns. - /// - Right - } -} diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 39f8333413..c3c0089f14 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -64,6 +64,7 @@ + @@ -83,7 +84,7 @@ - + @@ -115,12 +116,12 @@ + - diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs index 0bb7417088..4632c6c5f0 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -23,7 +23,7 @@ namespace osu.Game.Input.Bindings private KeyBindingStore store; - public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(); + public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); /// /// Create a new instance. diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index b17ea07355..a7fed7059b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.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 System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -36,8 +35,7 @@ namespace osu.Game.Rulesets.UI /// Whether we want our internal coordinate system to be scaled to a specified width. protected Playfield(float? customWidth = null) { - // Default height since we force relative size axes - Size = Vector2.One; + RelativeSizeAxes = Axes.Both; AddInternal(ScaledContent = new ScaledContainer { @@ -62,12 +60,6 @@ namespace osu.Game.Rulesets.UI Add(HitObjects); } - public override Axes RelativeSizeAxes - { - get { return Axes.Both; } - set { throw new InvalidOperationException($@"{nameof(Playfield)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); } - } - /// /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. /// diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index bb4466208b..375af75347 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.UI /// /// Whether the specified beatmap is assumed to be specific to the current ruleset. /// - protected readonly bool IsForCurrentRuleset; + public readonly bool IsForCurrentRuleset; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this);