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);