diff --git a/osu-framework b/osu-framework index 5c22092e59..f1527e5456 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 5c22092e590d589927962b8d0173dae5f9b1405c +Subproject commit f1527e5456cd228ddfb68cf6d56eb5d28dc360bf diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs index 37776b329f..644987ce5a 100644 --- a/osu.Desktop.Deploy/Program.cs +++ b/osu.Desktop.Deploy/Program.cs @@ -17,8 +17,8 @@ namespace osu.Desktop.Deploy { internal static class Program { - private const string nuget_path = @"packages\NuGet.CommandLine.3.5.0\tools\NuGet.exe"; - private const string squirrel_path = @"packages\squirrel.windows.1.5.2\tools\Squirrel.exe"; + private const string nuget_path = @"packages\NuGet.CommandLine.4.1.0\tools\NuGet.exe"; + private const string squirrel_path = @"packages\squirrel.windows.1.7.5\tools\Squirrel.exe"; private const string msbuild_path = @"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe"; public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"]; diff --git a/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs b/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs index 1861a77a3a..ffe37c83a6 100644 --- a/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Desktop.Tests/Visual/TestCaseKeyCounter.cs @@ -2,12 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.MathUtils; using osu.Game.Screens.Play; -using OpenTK; -using OpenTK.Graphics; using OpenTK.Input; namespace osu.Desktop.Tests.Visual @@ -41,38 +37,5 @@ namespace osu.Desktop.Tests.Visual Add(kc); } - private class TestSliderBar : SliderBar where T : struct - { - public Color4 Color - { - get { return Box.Colour; } - set { Box.Colour = value; } - } - - public Color4 SelectionColor - { - get { return SelectionBox.Colour; } - set { SelectionBox.Colour = value; } - } - - protected readonly Box SelectionBox; - protected readonly Box Box; - - public TestSliderBar() - { - Children = new Drawable[] - { - Box = new Box { RelativeSizeAxes = Axes.Both }, - SelectionBox = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - protected override void UpdateValue(float value) - { - SelectionBox.ScaleTo( - new Vector2(value, 1), - 300, Easing.OutQuint); - } - } } } diff --git a/osu.Desktop.Tests/Visual/TestCaseScrollingPlayfield.cs b/osu.Desktop.Tests/Visual/TestCaseScrollingPlayfield.cs index 43bd921cfc..e9c29d36c2 100644 --- a/osu.Desktop.Tests/Visual/TestCaseScrollingPlayfield.cs +++ b/osu.Desktop.Tests/Visual/TestCaseScrollingPlayfield.cs @@ -8,6 +8,7 @@ using osu.Desktop.Tests.Beatmaps; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Beatmaps; @@ -77,6 +78,8 @@ namespace osu.Desktop.Tests.Visual public override ScoreProcessor CreateScoreProcessor() => new TestScoreProcessor(); + public override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); + protected override BeatmapConverter CreateBeatmapConverter() => new TestBeatmapConverter(); protected override Playfield CreatePlayfield() => new TestPlayfield(scrollingAxes); diff --git a/osu.Desktop.Tests/Visual/TestCaseTaikoPlayfield.cs b/osu.Desktop.Tests/Visual/TestCaseTaikoPlayfield.cs index 45be9e800d..681805ced0 100644 --- a/osu.Desktop.Tests/Visual/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.Tests/Visual/TestCaseTaikoPlayfield.cs @@ -14,6 +14,11 @@ using osu.Game.Rulesets.Taiko.UI; using OpenTK; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps; +using osu.Desktop.Tests.Beatmaps; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; namespace osu.Desktop.Tests.Visual { @@ -27,10 +32,11 @@ namespace osu.Desktop.Tests.Visual protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private readonly TaikoPlayfield playfield; - private readonly Container playfieldContainer; + private TaikoRulesetContainer rulesetContainer; + private Container playfieldContainer; - public TestCaseTaikoPlayfield() + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) { AddStep("Hit!", () => addHitJudgement(false)); AddStep("Kiai hit", () => addHitJudgement(true)); @@ -51,6 +57,25 @@ namespace osu.Desktop.Tests.Visual AddStep("Height test 5", () => changePlayfieldSize(5)); AddStep("Reset height", () => changePlayfieldSize(6)); + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + + WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap + { + HitObjects = new List { new CentreHit() }, + BeatmapInfo = new BeatmapInfo + { + Difficulty = new BeatmapDifficulty(), + Metadata = new BeatmapMetadata + { + Artist = @"Unknown", + Title = @"Sample Beatmap", + Author = @"peppy", + }, + }, + ControlPointInfo = controlPointInfo + }); + var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; Add(playfieldContainer = new Container @@ -58,12 +83,9 @@ namespace osu.Desktop.Tests.Visual Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Height = TaikoPlayfield.DEFAULT_HEIGHT, + Height = 768, Clock = new FramedClock(rateAdjustClock), - Children = new[] - { - playfield = new TaikoPlayfield() - } + Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) } }); } @@ -128,18 +150,18 @@ namespace osu.Desktop.Tests.Visual } }; - playfield.OnJudgement(h); + rulesetContainer.Playfield.OnJudgement(h); if (RNG.Next(10) == 0) { h.Judgement.SecondHit = true; - playfield.OnJudgement(h); + rulesetContainer.Playfield.OnJudgement(h); } } private void addMissJudgement() { - playfield.OnJudgement(new DrawableTestHit(new Hit()) + rulesetContainer.Playfield.OnJudgement(new DrawableTestHit(new Hit()) { Judgement = new TaikoJudgement { @@ -151,22 +173,17 @@ namespace osu.Desktop.Tests.Visual private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine - { - StartTime = playfield.Time.Current + delay, - ScrollTime = scroll_time - }; + BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; - playfield.AddBarLine(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { - playfield.Add(new DrawableSwell(new Swell + rulesetContainer.Playfield.Add(new DrawableSwell(new Swell { - StartTime = playfield.Time.Current + scroll_time, + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, Duration = duration, - ScrollTime = scroll_time })); } @@ -177,43 +194,40 @@ namespace osu.Desktop.Tests.Visual var d = new DrumRoll { - StartTime = playfield.Time.Current + scroll_time, + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, - ScrollTime = scroll_time, }; - playfield.Add(new DrawableDrumRoll(d)); + rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = playfield.Time.Current + scroll_time, - ScrollTime = scroll_time, + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, IsStrong = strong }; if (strong) - playfield.Add(new DrawableCentreHitStrong(h)); + rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h)); else - playfield.Add(new DrawableCentreHit(h)); + rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = playfield.Time.Current + scroll_time, - ScrollTime = scroll_time, + StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, IsStrong = strong }; if (strong) - playfield.Add(new DrawableRimHitStrong(h)); + rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); else - playfield.Add(new DrawableRimHit(h)); + rulesetContainer.Playfield.Add(new DrawableRimHit(h)); } private class DrawableTestHit : DrawableHitObject diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index 683724b7b8..47e9b9905a 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -3,14 +3,14 @@ using System.ComponentModel; using osu.Framework.Input.Bindings; -using osu.Game.Input.Bindings; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch { - public class CatchInputManager : DatabasedKeyBindingInputManager + public class CatchInputManager : RulesetInputManager { public CatchInputManager(RulesetInfo ruleset) - : base(ruleset, simultaneousMode: SimultaneousBindingMode.Unique) + : base(ruleset, 0, SimultaneousBindingMode.Unique) { } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index b486566b7d..96f8a8df9b 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -1,14 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Scoring; @@ -23,12 +21,12 @@ namespace osu.Game.Rulesets.Catch public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(Key.Z, CatchAction.MoveLeft), - new KeyBinding(Key.Left, CatchAction.MoveLeft), - new KeyBinding(Key.X, CatchAction.MoveRight), - new KeyBinding(Key.Right, CatchAction.MoveRight), - new KeyBinding(Key.LShift, CatchAction.Dash), - new KeyBinding(Key.RShift, CatchAction.Dash), + new KeyBinding(InputKey.Z, CatchAction.MoveLeft), + new KeyBinding(InputKey.Left, CatchAction.MoveLeft), + new KeyBinding(InputKey.X, CatchAction.MoveRight), + new KeyBinding(InputKey.Right, CatchAction.MoveRight), + new KeyBinding(InputKey.Shift, CatchAction.Dash), + new KeyBinding(InputKey.Shift, CatchAction.Dash), }; public override IEnumerable GetModsFor(ModType type) @@ -101,13 +99,6 @@ namespace osu.Game.Rulesets.Catch public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; - public override IEnumerable CreateGameplayKeys() => new KeyCounter[] - { - new KeyCounterKeyboard(Key.ShiftLeft), - new KeyCounterMouse(MouseButton.Left), - new KeyCounterMouse(MouseButton.Right) - }; - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(); diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 1d037deaef..f8f74af6c9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override Playfield CreatePlayfield() => new CatchPlayfield(); - public override PassThroughInputManager CreateKeyBindingInputManager() => new CatchInputManager(Ruleset?.RulesetInfo); + public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h) { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 7e9615a703..c03a8438ae 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // The true distance, accounting for any repeats double distance = (distanceData?.Distance ?? 0) * repeatCount; // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier); + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs new file mode 100644 index 0000000000..b608e4d8d6 --- /dev/null +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania +{ + public class ManiaInputManager : RulesetInputManager + { + public ManiaInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum ManiaAction + { + // placeholder + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 50ad6960ae..c7809f83ed 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -6,7 +6,6 @@ using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Graphics; @@ -111,8 +110,6 @@ namespace osu.Game.Rulesets.Mania public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; - public override IEnumerable CreateGameplayKeys() => new KeyCounter[] { /* Todo: Should be keymod specific */ }; - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 7f6b19f3bd..9584dc34b8 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Lists; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -92,6 +93,8 @@ namespace osu.Game.Rulesets.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); + public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index fb0b2d907f..890c9116cf 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -79,6 +79,7 @@ + diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index ca1b44e1c7..b3043d18f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual void UpdateCurrentState(ArmedState state) { } + + private OsuInputManager osuActionInputManager; + internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); } public enum ComboResult diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 4a0b8422f1..8473cc2453 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -165,6 +166,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables glow.Colour = colours.BlueDark; } + protected override void Update() + { + disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); + + base.Update(); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index dbfd9b291b..0bcb8a4986 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -1,10 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using OpenTK; using OpenTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -96,11 +98,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return base.OnMouseMove(state); } + // If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos); + private bool tracking; public bool Tracking { get { return tracking; } - set + private set { if (value == tracking) return; @@ -117,8 +122,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { base.Update(); + // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position. if (Time.Current < slider.EndTime) - Tracking = canCurrentlyTrack && lastState != null && ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) && lastState.Mouse.HasMainButtonPressed; + Tracking = canCurrentlyTrack && lastState != null && base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); } public void UpdateProgress(double progress, int repeat) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index cd808abe9d..6577c7fd50 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -66,21 +66,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - Tracking |= state.Mouse.HasMainButtonPressed; - return base.OnMouseDown(state, args); - } - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - Tracking &= state.Mouse.HasMainButtonPressed; - return base.OnMouseUp(state, args); - } - protected override bool OnMouseMove(InputState state) { - Tracking |= state.Mouse.HasMainButtonPressed; mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position); return base.OnMouseMove(state); } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4d8c030ede..056bde4005 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier; + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate; diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 06836994d2..a65d28cec0 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -2,36 +2,15 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel; -using System.Linq; -using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Game.Input.Bindings; -using OpenTK.Input; -using KeyboardState = osu.Framework.Input.KeyboardState; -using MouseState = osu.Framework.Input.MouseState; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu { - public class OsuInputManager : DatabasedKeyBindingInputManager + public class OsuInputManager : RulesetInputManager { - public OsuInputManager(RulesetInfo ruleset) : base(ruleset, simultaneousMode: SimultaneousBindingMode.Unique) + public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) { - - } - protected override void TransformState(InputState state) - { - base.TransformState(state); - - var mouse = state.Mouse as MouseState; - var keyboard = state.Keyboard as KeyboardState; - - if (mouse != null && keyboard != null) - { - if (mouse.IsPressed(MouseButton.Left)) - keyboard.Keys = keyboard.Keys.Concat(new[] { Key.LastKey + 1 }); - if (mouse.IsPressed(MouseButton.Right)) - keyboard.Keys = keyboard.Keys.Concat(new[] { Key.LastKey + 2 }); - } } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 75b7be01a4..00413331e0 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -10,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.OsuDifficulty; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; @@ -27,10 +25,10 @@ namespace osu.Game.Rulesets.Osu public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(Key.Z, OsuAction.LeftButton), - new KeyBinding(Key.X, OsuAction.RightButton), - new KeyBinding(Key.LastKey + 1, OsuAction.LeftButton), - new KeyBinding(Key.LastKey + 2, OsuAction.RightButton), + new KeyBinding(InputKey.Z, OsuAction.LeftButton), + new KeyBinding(InputKey.X, OsuAction.RightButton), + new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), + new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] @@ -122,14 +120,6 @@ namespace osu.Game.Rulesets.Osu public override string Description => "osu!"; - public override IEnumerable CreateGameplayKeys() => new KeyCounter[] - { - new KeyCounterKeyboard(Key.Z), - new KeyCounterKeyboard(Key.X), - new KeyCounterMouse(MouseButton.Left), - new KeyCounterMouse(MouseButton.Right) - }; - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); public override SettingsSubsection CreateSettings() => new OsuSettings(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 917322de44..083f11945c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override Playfield CreatePlayfield() => new OsuPlayfield(); - public override PassThroughInputManager CreateKeyBindingInputManager() => new OsuInputManager(Ruleset?.RulesetInfo); + public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 44f61e2074..869d54dded 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -39,10 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps /// private const float taiko_base_distance = 100; + private bool isForCurrentRuleset; + protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) }; protected override Beatmap ConvertBeatmap(Beatmap original, bool isForCurrentRuleset) { + this.isForCurrentRuleset = isForCurrentRuleset; + // Rewrite the beatmap info to add the slider velocity multiplier BeatmapInfo info = original.BeatmapInfo.DeepClone(); info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; @@ -81,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); double speedAdjustment = difficultyPoint.SpeedMultiplier; - double speedAdjustedBeatLength = timingPoint.BeatLength * speedAdjustment; + double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; // The true distance, accounting for any repeats. This ends up being the drum roll distance later double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; @@ -94,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // For some reason, old osu! always uses speedAdjustment to determine the taiko velocity, but // only uses it to determine osu! velocity if beatmap version < 8. Let's account for that here. if (beatmap.BeatmapInfo.BeatmapVersion >= 8) - speedAdjustedBeatLength /= speedAdjustment; + speedAdjustedBeatLength *= speedAdjustment; // The velocity of the osu! hit object - calculated as the velocity of a slider double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; @@ -104,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats); - if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) + if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { List allSamples = curveData != null ? curveData.RepeatSamples : new List(new[] { samples }); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index 7507ee600e..9df1b41a8b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -2,16 +2,17 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using OpenTK; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { /// /// A line that scrolls alongside hit objects in the playfield and visualises control points. /// - public class DrawableBarLine : Container + public class DrawableBarLine : DrawableScrollingHitObject { /// /// The width of the line tracker. @@ -34,15 +35,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected readonly BarLine BarLine; public DrawableBarLine(BarLine barLine) + : base(barLine) { BarLine = barLine; Anchor = Anchor.CentreLeft; Origin = Anchor.Centre; - RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Width = tracker_width; Children = new[] @@ -56,24 +56,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Alpha = 0.75f } }; - - LifetimeStart = BarLine.StartTime - BarLine.ScrollTime * 2; - LifetimeEnd = BarLine.StartTime + BarLine.ScrollTime; } - protected override void LoadComplete() + protected override TaikoJudgement CreateJudgement() => null; + + protected override void UpdateState(ArmedState state) { - base.LoadComplete(); - this.Delay(BarLine.StartTime - Time.Current).FadeOut(base_fadeout_time * BarLine.ScrollTime / 1000); - } - - private void updateScrollPosition(double time) => this.MoveToX((float)((BarLine.StartTime - time) / BarLine.ScrollTime)); - - protected override void Update() - { - base.Update(); - - updateScrollPosition(Time.Current); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index c07be915d5..7a4cf1f1f7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -20,10 +20,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// private const float triangle_size = 20f; + private readonly Container triangleContainer; + public DrawableBarLineMajor(BarLine barLine) : base(barLine) { - Add(new Container + Add(triangleContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -53,5 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Tracker.Alpha = 1f; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime)) + triangleContainer.FadeOut(150); + } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index 8bb78669ca..26aa20f1af 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -4,13 +4,12 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableCentreHit : DrawableHit { - protected override Key[] HitKeys { get; } = { Key.F, Key.J }; + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; public DrawableCentreHit(Hit hit) : base(hit) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs index 434fb9377f..1dbab408de 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHitStrong.cs @@ -4,13 +4,12 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableCentreHitStrong : DrawableHitStrong { - protected override Key[] HitKeys { get; } = { Key.F, Key.J }; + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; public DrawableCentreHitStrong(Hit hit) : base(hit) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 74f0f2326d..5d6ea8f51e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -11,6 +11,7 @@ using OpenTK; using OpenTK.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -31,30 +32,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableDrumRoll(DrumRoll drumRoll) : base(drumRoll) { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; + Width = (float)HitObject.Duration; + + Container tickContainer; + MainPiece.Add(tickContainer = new Container + { + RelativeSizeAxes = Axes.Both, + RelativeChildOffset = new Vector2((float)HitObject.StartTime, 0), + RelativeChildSize = new Vector2((float)HitObject.Duration, 1) + }); foreach (var tick in drumRoll.Ticks) { - var newTick = new DrawableDrumRollTick(tick) - { - X = (float)((tick.StartTime - HitObject.StartTime) / HitObject.Duration) - }; - + var newTick = new DrawableDrumRollTick(tick); newTick.OnJudgement += onTickJudgement; AddNested(newTick); - MainPiece.Add(newTick); + tickContainer.Add(newTick); } } protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = HitObject.IsStrong }; - protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece - { - Length = (float)(HitObject.Duration / HitObject.ScrollTime), - PlayfieldLengthReference = () => Parent.DrawSize.X - }; + protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(); + + public override bool OnPressed(TaikoAction action) => false; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -63,17 +65,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables accentDarkColour = colours.YellowDarker; } - protected override void LoadComplete() - { - base.LoadComplete(); - - // This is naive, however it's based on the reasoning that the hit target - // is further than mid point of the play field, so the time taken to scroll in should always - // be greater than the time taken to scroll out to the left of the screen. - // Thus, using PreEmpt here is enough for the drum roll to completely scroll out. - LifetimeEnd = HitObject.EndTime + HitObject.ScrollTime; - } - private void onTickJudgement(DrawableHitObject obj) { if (obj.Judgement.Result == HitResult.Hit) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 0e1cd05de3..e94886df16 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; -using OpenTK.Input; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -15,9 +14,21 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public DrawableDrumRollTick(DrumRollTick tick) : base(tick) { + // Because ticks aren't added by the ScrollingPlayfield, we need to set the following properties ourselves + RelativePositionAxes = Axes.X; + X = (float)tick.StartTime; + FillMode = FillMode.Fit; } + protected override void LoadComplete() + { + base.LoadComplete(); + + // We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize + Width *= Parent.RelativeChildSize.X; + } + protected override TaikoPiece CreateMainPiece() => new TickPiece { Filled = HitObject.FirstTick @@ -47,12 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - protected override void UpdateScrollPosition(double time) - { - // Ticks don't move - } - - protected override bool HandleKeyPress(Key key) + public override bool OnPressed(TaikoAction action) { return Judgement.Result == HitResult.None && UpdateJudgement(true); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 5a17355cdb..5fcae7d0f4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// A list of keys which can result in hits for this HitObject. /// - protected abstract Key[] HitKeys { get; } + protected abstract TaikoAction[] HitActions { get; } /// /// Whether the last key pressed is a valid hit key. @@ -29,6 +28,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables FillMode = FillMode.Fit; } + protected override void LoadComplete() + { + base.LoadComplete(); + + // We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize + Width *= Parent.RelativeChildSize.X; + } + protected override void CheckJudgement(bool userTriggered) { if (!userTriggered) @@ -54,12 +61,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Judgement.Result = HitResult.Miss; } - protected override bool HandleKeyPress(Key key) + public override bool OnPressed(TaikoAction action) { if (Judgement.Result != HitResult.None) return false; - validKeyPressed = HitKeys.Contains(key); + validKeyPressed = HitActions.Contains(action); return UpdateJudgement(true); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs index 57e7eea470..256c50d33a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs @@ -3,10 +3,8 @@ using System; using System.Linq; -using osu.Framework.Input; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -20,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private double firstHitTime; private bool firstKeyHeld; - private Key firstHitKey; + private TaikoAction firstHitAction; protected DrawableHitStrong(Hit hit) : base(hit) @@ -46,18 +44,26 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Judgement.SecondHit = true; } - protected override bool HandleKeyPress(Key key) + public override bool OnReleased(TaikoAction action) + { + if (action == firstHitAction) + firstKeyHeld = false; + return base.OnReleased(action); + } + + public override bool OnPressed(TaikoAction action) { // Check if we've handled the first key if (Judgement.Result == HitResult.None) { // First key hasn't been handled yet, attempt to handle it - bool handled = base.HandleKeyPress(key); + bool handled = base.OnPressed(action); if (handled) { firstHitTime = Time.Current; - firstHitKey = key; + firstHitAction = action; + firstKeyHeld = true; } return handled; @@ -68,22 +74,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return false; // Don't handle represses of the first key - if (firstHitKey == key) + if (firstHitAction == action) return false; - // Don't handle invalid hit key presses - if (!HitKeys.Contains(key)) + // Don't handle invalid hit action presses + if (!HitActions.Contains(action)) return false; // Assume the intention was to hit the strong hit with both keys only if the first key is still being held down return firstKeyHeld && UpdateJudgement(true); } - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - firstKeyHeld = state.Keyboard.Keys.Contains(firstHitKey); - - return base.OnKeyDown(state, args); - } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 20e8d36105..9f0cd3e061 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -4,13 +4,12 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableRimHit : DrawableHit { - protected override Key[] HitKeys { get; } = { Key.D, Key.K }; + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; public DrawableRimHit(Hit hit) : base(hit) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs index 4b1bb62bab..294198f3ee 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHitStrong.cs @@ -4,13 +4,12 @@ using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { public class DrawableRimHitStrong : DrawableHitStrong { - protected override Key[] HitKeys { get; } = { Key.D, Key.K }; + protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; public DrawableRimHitStrong(Hit hit) : base(hit) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index e861af03cf..fc3bc82520 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK; using OpenTK.Graphics; -using OpenTK.Input; using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -35,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; - private readonly Key[] rimKeys = { Key.D, Key.K }; - private readonly Key[] centreKeys = { Key.F, Key.J }; - private Key[] lastKeySet; + private readonly TaikoAction[] rimActions = { TaikoAction.LeftRim, TaikoAction.RightRim }; + private readonly TaikoAction[] centreActions = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; + private TaikoAction[] lastAction; /// /// The amount of times the user has hit this swell. @@ -118,7 +117,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables }); MainPiece.Add(symbol = new SwellSymbolPiece()); - } [BackgroundDependencyLoader] @@ -129,6 +127,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables targetRing.BorderColour = colours.YellowDark.Opacity(0.25f); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize + Width *= Parent.RelativeChildSize.X; + } + protected override void CheckJudgement(bool userTriggered) { if (userTriggered) @@ -189,21 +195,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Expire(); } - protected override void UpdateScrollPosition(double time) + protected override void Update() { - // Make the swell stop at the hit target - double t = Math.Min(HitObject.StartTime, time); + base.Update(); + // Make the swell stop at the hit target + X = (float)Math.Max(Time.Current, HitObject.StartTime); + + double t = Math.Min(HitObject.StartTime, Time.Current); if (t == HitObject.StartTime && !hasStarted) { OnStart?.Invoke(); hasStarted = true; } - - base.UpdateScrollPosition(t); } - protected override bool HandleKeyPress(Key key) + public override bool OnPressed(TaikoAction action) { if (Judgement.Result != HitResult.None) return false; @@ -213,12 +220,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return false; // Find the keyset which this key corresponds to - var keySet = rimKeys.Contains(key) ? rimKeys : centreKeys; + var keySet = rimActions.Contains(action) ? rimActions : centreActions; // Ensure alternating keysets - if (keySet == lastKeySet) + if (keySet == lastAction) return false; - lastKeySet = keySet; + lastAction = keySet; UpdateJudgement(true); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 510994ed70..765002fb34 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -1,26 +1,19 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using OpenTK; -using OpenTK.Input; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { - public abstract class DrawableTaikoHitObject : DrawableHitObject - where TaikoHitType : TaikoHitObject + public abstract class DrawableTaikoHitObject + : DrawableScrollingHitObject, IKeyBindingHandler + where TaikoHitType : TaikoHitObject { - /// - /// A list of keys which this hit object will accept. These are the standard Taiko keys for now. - /// These should be moved to bindings later. - /// - private readonly List validKeys = new List(new[] { Key.D, Key.F, Key.J, Key.K }); - public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); protected readonly TaikoPiece MainPiece; @@ -38,44 +31,16 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Both; Size = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); - RelativePositionAxes = Axes.X; - Add(MainPiece = CreateMainPiece()); MainPiece.KiaiMode = HitObject.Kiai; - - LifetimeStart = HitObject.StartTime - HitObject.ScrollTime * 2; } protected override TaikoJudgement CreateJudgement() => new TaikoJudgement(); protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); - /// - /// Sets the scroll position of the DrawableHitObject relative to the offset between - /// a time value and the HitObject's StartTime. - /// - /// - protected virtual void UpdateScrollPosition(double time) => X = (float)((HitObject.StartTime - time) / HitObject.ScrollTime); + public abstract bool OnPressed(TaikoAction action); - protected override void Update() - { - UpdateScrollPosition(Time.Current); - } - - protected virtual bool HandleKeyPress(Key key) => false; - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - // Make sure we don't handle held-down keys - if (args.Repeat) - return false; - - // Check if we've pressed a valid taiko key - if (!validKeys.Contains(args.Key)) - return false; - - // Handle it! - return HandleKeyPress(args.Key); - } + public virtual bool OnReleased(TaikoAction action) => false; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs index 2f5b4eefd6..4642e318c4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs @@ -1,23 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { public class ElongatedCirclePiece : CirclePiece { - /// - /// As we are being used to define the absolute size of hits, we need to be given a relative reference of our containing playfield container. - /// - public Func PlayfieldLengthReference; - - /// - /// The length of this piece as a multiple of the value returned by - /// - public float Length; - public ElongatedCirclePiece() { RelativeSizeAxes = Axes.Y; @@ -35,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces Right = padding, }; - Width = (PlayfieldLengthReference?.Invoke() ?? 0) * Length + DrawHeight; + Width = Parent.DrawSize.X + DrawHeight; } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index fd9daa269c..526d23f51a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -80,7 +80,6 @@ namespace osu.Game.Rulesets.Taiko.Objects ret.Add(new DrumRollTick { FirstTick = first, - ScrollTime = ScrollTime, TickSpacing = tickSpacing, StartTime = t, IsStrong = IsStrong, diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 7e7dc3662f..f1c0afc675 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -24,16 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE; - /// - /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. - /// - private const double scroll_time = 6000; - - /// - /// Our adjusted taking into consideration local and other speed multipliers. - /// - public double ScrollTime; - /// /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. @@ -49,12 +39,8 @@ namespace osu.Game.Rulesets.Taiko.Objects { base.ApplyDefaults(controlPointInfo, difficulty); - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier; - Kiai |= effectPoint.KiaiMode; } } diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs new file mode 100644 index 0000000000..1e3c0fbcf2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko +{ + public class TaikoInputManager : RulesetInputManager + { + public TaikoInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum TaikoAction + { + [Description("Left (Rim)")] + LeftRim, + [Description("Left (Centre)")] + LeftCentre, + [Description("Right (Centre)")] + RightCentre, + [Description("Right (Rim)")] + RightRim + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 4d4bbb7bc5..f457732085 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -1,18 +1,17 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Scoring; +using osu.Framework.Input.Bindings; namespace osu.Game.Rulesets.Taiko { @@ -20,6 +19,18 @@ namespace osu.Game.Rulesets.Taiko { public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset); + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.D, TaikoAction.LeftRim), + new KeyBinding(InputKey.F, TaikoAction.LeftCentre), + new KeyBinding(InputKey.J, TaikoAction.RightCentre), + new KeyBinding(InputKey.K, TaikoAction.RightRim), + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), + new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim), + }; + public override IEnumerable GetModsFor(ModType type) { switch (type) @@ -90,14 +101,6 @@ namespace osu.Game.Rulesets.Taiko public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; - public override IEnumerable CreateGameplayKeys() => new KeyCounter[] - { - new KeyCounterKeyboard(Key.D), - new KeyCounterKeyboard(Key.F), - new KeyCounterKeyboard(Key.J), - new KeyCounterKeyboard(Key.K) - }; - public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 0255171284..659f421ebe 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -3,13 +3,12 @@ using System; using OpenTK; -using OpenTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Game.Graphics; namespace osu.Game.Rulesets.Taiko.UI @@ -36,8 +35,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.X, X = -middle_split / 2, - RimKey = Key.D, - CentreKey = Key.F + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre }, new TaikoHalfDrum(true) { @@ -47,8 +46,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.X, X = middle_split / 2, - RimKey = Key.K, - CentreKey = Key.J + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre } }; } @@ -56,17 +55,17 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// A half-drum. Contains one centre and one rim hit. /// - private class TaikoHalfDrum : Container + private class TaikoHalfDrum : Container, IKeyBindingHandler { /// /// The key to be used for the rim of the half-drum. /// - public Key RimKey; + public TaikoAction RimAction; /// /// The key to be used for the centre of the half-drum. /// - public Key CentreKey; + public TaikoAction CentreAction; private readonly Sprite rim; private readonly Sprite rimHit; @@ -124,20 +123,17 @@ namespace osu.Game.Rulesets.Taiko.UI centreHit.Colour = colours.Pink; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + public bool OnPressed(TaikoAction action) { - if (args.Repeat) - return false; - Drawable target = null; Drawable back = null; - if (args.Key == CentreKey) + if (action == CentreAction) { target = centreHit; back = centre; } - else if (args.Key == RimKey) + else if (action == RimAction) { target = rimHit; back = rim; @@ -166,6 +162,8 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } + + public bool OnReleased(TaikoAction action) => false; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 119cd64fef..678de7f713 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -18,7 +18,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoPlayfield : Playfield + public class TaikoPlayfield : ScrollingPlayfield { /// /// Default height of a when inside a . @@ -35,16 +35,18 @@ namespace osu.Game.Rulesets.Taiko.UI /// private const float left_area_size = 240; - protected override Container Content => hitObjectContainer; private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; - private readonly Container barLineContainer; private readonly Container judgementContainer; - private readonly Container hitObjectContainer; + protected override Container Content => content; + private readonly Container content; + private readonly Container topLevelHitContainer; + private readonly Container barlineContainer; + private readonly Container overlayBackgroundContainer; private readonly Container backgroundContainer; @@ -52,6 +54,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Box background; public TaikoPlayfield() + : base(Axes.X) { AddRangeInternal(new Drawable[] { @@ -84,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI { new Container { - Name = "Masked elements", + Name = "Masked elements before hit objects", RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, Masking = true, @@ -96,23 +99,27 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, BlendingMode = BlendingMode.Additive, }, - barLineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, new HitTarget { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit - }, - hitObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, + } } }, + barlineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } + }, + content = new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true + }, kiaiExplosionContainer = new Container { Name = "Kiai hit explosions", @@ -181,6 +188,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, } }); + + VisibleTimeRange.Value = 6000; } [BackgroundDependencyLoader] @@ -199,17 +208,16 @@ namespace osu.Game.Rulesets.Taiko.UI base.Add(h); + var barline = h as DrawableBarLine; + if (barline != null) + barlineContainer.Add(barline.CreateProxy()); + // Swells should be moved at the very top of the playfield when they reach the hit target var swell = h as DrawableSwell; if (swell != null) swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy()); } - public void AddBarLine(DrawableBarLine barLine) - { - barLineContainer.Add(barLine); - } - public override void OnJudgement(DrawableHitObject judgedObject) { bool wasHit = judgedObject.Judgement.Result == HitResult.Hit; @@ -230,7 +238,7 @@ namespace osu.Game.Rulesets.Taiko.UI if (!secondHit) { - if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell)) + if (judgedObject.X >= -0.05f && judgedObject is DrawableHit) { // If we're far enough away from the left stage, we should bring outselves in front of it topLevelHitContainer.Add(judgedObject.CreateProxy()); @@ -245,4 +253,4 @@ namespace osu.Game.Rulesets.Taiko.UI hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 29f563b735..1ecd699a8e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -18,10 +18,11 @@ using osu.Game.Rulesets.Taiko.Replays; using OpenTK; using osu.Game.Rulesets.Beatmaps; using System.Linq; +using osu.Framework.Input; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoRulesetContainer : RulesetContainer + public class TaikoRulesetContainer : ScrollingRulesetContainer { public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset) @@ -36,11 +37,6 @@ namespace osu.Game.Rulesets.Taiko.UI private void loadBarLines() { - var taikoPlayfield = Playfield as TaikoPlayfield; - - if (taikoPlayfield == null) - return; - TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; @@ -72,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; - taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); + Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); double bl = currentPoint.BeatLength; if (bl < 800) @@ -97,6 +93,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); + public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); + protected override Playfield CreatePlayfield() => new TaikoPlayfield { Anchor = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 59c6f4db0a..33748a267f 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -86,6 +86,7 @@ + diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 30c7884334..c622d5310c 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -10,4 +10,4 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double SpeedMultiplier = 1; } -} \ No newline at end of file +} diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 2fb9a568f8..479f274efb 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; namespace osu.Game.Beatmaps { @@ -76,8 +75,6 @@ namespace osu.Game.Beatmaps public override string Description => "dummy"; - public override IEnumerable CreateGameplayKeys() => new List(); - public DummyRuleset(RulesetInfo rulesetInfo) : base(rulesetInfo) { diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 46cbad2487..b51ea607dd 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -280,7 +280,7 @@ namespace osu.Game.Beatmaps.Formats double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); - double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1; + double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; if (split.Length >= 3) @@ -331,6 +331,7 @@ namespace osu.Game.Beatmaps.Formats if (speedMultiplier != difficultyPoint.SpeedMultiplier) { + beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = time, diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 87b4ab9e40..1a980e6bd5 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -44,19 +44,31 @@ namespace osu.Game.Graphics.UserInterface { List bars = Children.ToList(); foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) + { + float length = MaxValue ?? value.Max(); + if (length != 0) + length = bar.Value / length; + + float size = value.Count(); + if (size != 0) + size = 1.0f / size; + if (bar.Bar != null) { - bar.Bar.Length = bar.Value / (MaxValue ?? value.Max()); - bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1); + bar.Bar.Length = length; + bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1); } else + { Add(new Bar { RelativeSizeAxes = Axes.Both, - Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1), - Length = bar.Value / (MaxValue ?? value.Max()), + Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1), + Length = length, Direction = Direction, }); + } + } //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList()); } diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 3c454f2af2..5650fc6f48 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -16,7 +17,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { - public class OsuButton : Button + public class OsuButton : Button, IFilterable { private Box hover; @@ -108,5 +109,15 @@ namespace osu.Game.Graphics.UserInterface Content.ScaleTo(1, 1000, Easing.OutElastic); return base.OnMouseUp(state, args); } + + public string[] FilterTerms => new[] { Text }; + + public bool MatchingFilter + { + set + { + this.FadeTo(value ? 1 : 0); + } + } } } \ No newline at end of file diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs index f739ba2a59..0a4fcf4389 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 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.Allocation; using osu.Framework.Input.Bindings; @@ -17,7 +18,7 @@ namespace osu.Game.Input.Bindings { private readonly RulesetInfo ruleset; - private readonly int variant; + private readonly int? variant; private KeyBindingStore store; @@ -29,11 +30,14 @@ namespace osu.Game.Input.Bindings /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. /// Specify how to deal with multiple matches of s and s. - protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int variant = 0, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) + protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) : base(simultaneousMode) { this.ruleset = ruleset; this.variant = variant; + + if (ruleset != null && variant == null) + throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } [BackgroundDependencyLoader] diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs index 31be2e6adc..5ea66fa600 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Input; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -22,16 +21,16 @@ namespace osu.Game.Input.Bindings public override IEnumerable DefaultKeyBindings => new[] { - new KeyBinding(Key.F8, GlobalAction.ToggleChat), - new KeyBinding(Key.F9, GlobalAction.ToggleSocial), - new KeyBinding(new[] { Key.LControl, Key.LAlt, Key.R }, GlobalAction.ResetInputSettings), - new KeyBinding(new[] { Key.LControl, Key.T }, GlobalAction.ToggleToolbar), - new KeyBinding(new[] { Key.LControl, Key.O }, GlobalAction.ToggleSettings), - new KeyBinding(new[] { Key.LControl, Key.D }, GlobalAction.ToggleDirect), + new KeyBinding(InputKey.F8, GlobalAction.ToggleChat), + new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), + new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), + new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), + new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), + new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect), }; - protected override IEnumerable GetKeyboardInputQueue() => - handler == null ? base.GetKeyboardInputQueue() : new[] { handler }.Concat(base.GetKeyboardInputQueue()); + protected override IEnumerable KeyBindingInputQueue => + handler == null ? base.KeyBindingInputQueue : new[] { handler }.Concat(base.KeyBindingInputQueue); } public enum GlobalAction diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index f50acb4802..56302acafe 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Input public KeyBindingStore(SQLiteConnection connection, RulesetStore rulesets, Storage storage = null) : base(connection, storage) { - foreach (var info in rulesets.Query()) + foreach (var info in rulesets.AllRulesets) { var ruleset = info.CreateInstance(); foreach (var variant in ruleset.AvailableVariants) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 01685cc7dc..bec4aadb18 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -26,6 +26,8 @@ namespace osu.Game.Online.Chat public readonly SortedList Messages = new SortedList(Comparer.Default); + private readonly List pendingMessages = new List(); + public Bindable Joined = new Bindable(); public bool ReadOnly => Name != "#lazer"; @@ -38,6 +40,16 @@ namespace osu.Game.Online.Chat } public event Action> NewMessagesArrived; + public event Action PendingMessageResolved; + public event Action MessageRemoved; + + public void AddLocalEcho(LocalEchoMessage message) + { + pendingMessages.Add(message); + Messages.Add(message); + + NewMessagesArrived?.Invoke(new[] { message }); + } public void AddNewMessages(params Message[] messages) { @@ -52,11 +64,42 @@ namespace osu.Game.Online.Chat private void purgeOldMessages() { - int messageCount = Messages.Count; + // never purge local echos + int messageCount = Messages.Count - pendingMessages.Count; if (messageCount > MAX_HISTORY) Messages.RemoveRange(0, messageCount - MAX_HISTORY); } + /// + /// Replace or remove a message from the channel. + /// + /// The local echo message (client-side). + /// The response message, or null if the message became invalid. + public void ReplaceMessage(LocalEchoMessage echo, Message final) + { + if (!pendingMessages.Remove(echo)) + throw new InvalidOperationException("Attempted to remove echo that wasn't present"); + + Messages.Remove(echo); + + if (final == null) + { + MessageRemoved?.Invoke(echo); + return; + } + + if (Messages.Contains(final)) + { + // message already inserted, so let's throw away this update. + // we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed. + MessageRemoved?.Invoke(echo); + return; + } + + Messages.Add(final); + PendingMessageResolved?.Invoke(echo, final); + } + public override string ToString() => Name; } } diff --git a/osu.Game/Online/Chat/LocalEchoMessage.cs b/osu.Game/Online/Chat/LocalEchoMessage.cs new file mode 100644 index 0000000000..079ec58686 --- /dev/null +++ b/osu.Game/Online/Chat/LocalEchoMessage.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Online.Chat +{ + public class LocalEchoMessage : Message + { + public LocalEchoMessage() : base(null) + { + } + } +} diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 4c7e099647..509861868a 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.Chat public class Message : IComparable, IEquatable { [JsonProperty(@"message_id")] - public readonly long Id; + public readonly long? Id; //todo: this should be inside sender. [JsonProperty(@"sender_id")] @@ -37,14 +37,22 @@ namespace osu.Game.Online.Chat { } - public Message(long id) + public Message(long? id) { Id = id; } - public int CompareTo(Message other) => Id.CompareTo(other.Id); + public int CompareTo(Message other) + { + if (!Id.HasValue) + return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp); + if (!other.Id.HasValue) + return -1; - public bool Equals(Message other) => Id == other?.Id; + return Id.Value.CompareTo(other.Id.Value); + } + + public virtual bool Equals(Message other) => Id == other?.Id; public override int GetHashCode() => Id.GetHashCode(); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0d1e8396e4..e3525e423c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -182,7 +182,11 @@ namespace osu.Game LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(social = new SocialOverlay { Depth = -1 }, mainContent.Add); LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); - LoadComponentAsync(settings = new MainSettings { Depth = -1 }, overlayContent.Add); + LoadComponentAsync(settings = new MainSettings + { + GetToolbarHeight = () => ToolbarOffset, + Depth = -1 + }, overlayContent.Add); LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); LoadComponentAsync(musicController = new MusicController { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f77be1ecc6..952dc900a2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -94,13 +94,15 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + private SQLiteConnection connection; + [BackgroundDependencyLoader] private void load() { dependencies.Cache(this); dependencies.Cache(LocalConfig); - SQLiteConnection connection = Host.Storage.GetDatabase(@"client"); + connection = Host.Storage.GetDatabase(@"client"); connection.CreateTable(); @@ -237,6 +239,8 @@ namespace osu.Game LocalConfig.Save(); } + connection.Dispose(); + base.Dispose(isDisposing); } } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index fcebca6fe3..afc6589f57 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -2,26 +2,24 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Graphics.Effects; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Allocation; using osu.Game.Users; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Chat { public class ChatLine : Container { - public readonly Message Message; - - private static readonly Color4[] username_colours = { + private static readonly Color4[] username_colours = + { OsuColour.FromHex("588c7e"), OsuColour.FromHex("b2a367"), OsuColour.FromHex("c98f65"), @@ -69,6 +67,8 @@ namespace osu.Game.Overlays.Chat private Color4 customUsernameColour; + private OsuSpriteText timestamp; + public ChatLine(Message message) { Message = message; @@ -79,6 +79,26 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = padding, Right = padding }; } + private Message message; + private OsuSpriteText username; + private OsuTextFlowContainer contentFlow; + + public Message Message + { + get { return message; } + set + { + if (message == value) return; + + message = value; + + if (!IsLoaded) + return; + + updateMessageContent(); + } + } + [BackgroundDependencyLoader(true)] private void load(OsuColour colours, UserProfileOverlay profile) { @@ -86,49 +106,54 @@ namespace osu.Game.Overlays.Chat loadProfile = u => profile?.ShowUser(u); } + private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); + protected override void LoadComplete() { base.LoadComplete(); - bool hasBackground = !string.IsNullOrEmpty(Message.Sender.Colour); - Drawable username = new OsuSpriteText + bool hasBackground = senderHasBackground; + + Drawable effectedUsername = username = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Text = $@"{Message.Sender.Username}" + (hasBackground ? "" : ":"), - Colour = hasBackground ? customUsernameColour : username_colours[Message.UserId % username_colours.Length], + Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], TextSize = text_size, }; if (hasBackground) { // Background effect - username = username.WithEffect(new EdgeEffect + effectedUsername = new Container { + AutoSizeAxes = Axes.Both, + Masking = true, CornerRadius = 4, - Parameters = new EdgeEffectParameters - { - Radius = 1, - Colour = OsuColour.FromHex(Message.Sender.Colour), - Type = EdgeEffectType.Shadow, - } - }, d => - { - d.Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }; - d.Y = 3; - }) - // Drop shadow effect - .WithEffect(new EdgeEffect - { - CornerRadius = 4, - Parameters = new EdgeEffectParameters + EdgeEffect = new EdgeEffectParameters { Roundness = 1, Offset = new Vector2(0, 3), Radius = 3, Colour = Color4.Black.Opacity(0.3f), Type = EdgeEffectType.Shadow, + }, + // Drop shadow effect + Child = new Container + { + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Radius = 1, + Colour = OsuColour.FromHex(message.Sender.Colour), + Type = EdgeEffectType.Shadow, + }, + Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }, + Y = 3, + Child = username, } - }); + }; } Children = new Drawable[] @@ -138,23 +163,21 @@ namespace osu.Game.Overlays.Chat Size = new Vector2(message_padding, text_size), Children = new Drawable[] { - new OsuSpriteText + timestamp = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = @"Exo2.0-SemiBold", - Text = $@"{Message.Timestamp.LocalDateTime:HH:mm:ss}", FixedWidth = true, TextSize = text_size * 0.75f, - Alpha = 0.4f, }, new ClickableContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - Child = username, - Action = () => loadProfile(Message.Sender), + Child = effectedUsername, + Action = () => loadProfile(message.Sender), }, } }, @@ -165,18 +188,27 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - new OsuTextFlowContainer(t => + contentFlow = new OsuTextFlowContainer(t => { t.TextSize = text_size; }) { - t.TextSize = text_size; - }) - { - Text = Message.Content, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, } } } }; + + updateMessageContent(); + FinishTransforms(true); + } + + private void updateMessageContent() + { + this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); + timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); + + timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; + username.Text = $@"{message.Sender.Username}" + (senderHasBackground ? "" : ":"); + contentFlow.Text = message.Content; } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 8a2fa95ed1..840edaece2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,8 +16,19 @@ namespace osu.Game.Overlays.Chat { public class DrawableChannel : Container { + private class ChatLineContainer : FillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) + { + var xC = (ChatLine)x; + var yC = (ChatLine)y; + + return xC.Message.CompareTo(yC.Message); + } + } + public readonly Channel Channel; - private readonly FillFlowContainer flow; + private readonly ChatLineContainer flow; private readonly ScrollContainer scroll; public DrawableChannel(Channel channel) @@ -32,20 +45,19 @@ namespace osu.Game.Overlays.Chat // Some chat lines have effects that slightly protrude to the bottom, // which we do not want to mask away, hence the padding. Padding = new MarginPadding { Bottom = 5 }, - Children = new Drawable[] + Child = flow = new ChatLineContainer { - flow = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 20, Right = 20 } - } - } + Padding = new MarginPadding { Left = 20, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, } }; - channel.NewMessagesArrived += newMessagesArrived; + Channel.NewMessagesArrived += newMessagesArrived; + Channel.MessageRemoved += messageRemoved; + Channel.PendingMessageResolved += pendingMessageResolved; } [BackgroundDependencyLoader] @@ -63,14 +75,17 @@ namespace osu.Game.Overlays.Chat protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + Channel.NewMessagesArrived -= newMessagesArrived; + Channel.MessageRemoved -= messageRemoved; + Channel.PendingMessageResolved -= pendingMessageResolved; } private void newMessagesArrived(IEnumerable newMessages) { + // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); - //up to last Channel.MAX_HISTORY messages flow.AddRange(displayMessages.Select(m => new ChatLine(m))); if (!IsLoaded) return; @@ -90,6 +105,24 @@ namespace osu.Game.Overlays.Chat } } + private void pendingMessageResolved(Message existing, Message updated) + { + var found = flow.Children.LastOrDefault(c => c.Message == existing); + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + flow.Remove(found); + found.Message = updated; + flow.Add(found); + } + } + + private void messageRemoved(Message removed) + { + flow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + } + private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index e7a4c1c0ab..6dd5425fd1 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -6,23 +6,23 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using OpenTK; +using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Threading; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; -using osu.Game.Graphics.UserInterface; -using osu.Framework.Graphics.UserInterface; -using OpenTK.Graphics; -using osu.Framework.Input; -using osu.Game.Configuration; -using osu.Game.Graphics; using osu.Game.Overlays.Chat; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays { @@ -37,7 +37,7 @@ namespace osu.Game.Overlays private readonly LoadingAnimation loading; - private readonly FocusedTextBox inputTextBox; + private readonly FocusedTextBox textbox; private APIAccess api; @@ -130,7 +130,7 @@ namespace osu.Game.Overlays }, Children = new Drawable[] { - inputTextBox = new FocusedTextBox + textbox = new FocusedTextBox { RelativeSizeAxes = Axes.Both, Height = 1, @@ -175,7 +175,7 @@ namespace osu.Game.Overlays if (state == Visibility.Visible) { - inputTextBox.HoldFocus = false; + textbox.HoldFocus = false; if (1f - chatHeight.Value < channel_selection_min_height) { chatContainer.ResizeHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint); @@ -186,7 +186,7 @@ namespace osu.Game.Overlays } else { - inputTextBox.HoldFocus = true; + textbox.HoldFocus = true; } }; } @@ -242,8 +242,8 @@ namespace osu.Game.Overlays protected override void OnFocus(InputState state) { - //this is necessary as inputTextBox is masked away and therefore can't get focus :( - GetContainingInputManager().ChangeFocus(inputTextBox); + //this is necessary as textbox is masked away and therefore can't get focus :( + GetContainingInputManager().ChangeFocus(textbox); base.OnFocus(state); } @@ -252,7 +252,7 @@ namespace osu.Game.Overlays this.MoveToY(0, transition_length, Easing.OutQuint); this.FadeIn(transition_length, Easing.OutQuint); - inputTextBox.HoldFocus = true; + textbox.HoldFocus = true; base.PopIn(); } @@ -261,7 +261,7 @@ namespace osu.Game.Overlays this.MoveToY(Height, transition_length, Easing.InSine); this.FadeOut(transition_length, Easing.InSine); - inputTextBox.HoldFocus = false; + textbox.HoldFocus = false; base.PopOut(); } @@ -336,7 +336,7 @@ namespace osu.Game.Overlays currentChannel = value; - inputTextBox.Current.Disabled = currentChannel.ReadOnly; + textbox.Current.Disabled = currentChannel.ReadOnly; channelTabs.Current.Value = value; var loaded = loadedChannels.Find(d => d.Channel == value); @@ -414,6 +414,7 @@ namespace osu.Game.Overlays if (fetchReq != null) return; fetchReq = new GetMessagesRequest(careChannels, lastMessageId); + fetchReq.Success += delegate (List messages) { foreach (var group in messages.Where(m => m.TargetType == TargetType.Channel).GroupBy(m => m.TargetId)) @@ -424,6 +425,7 @@ namespace osu.Game.Overlays Debug.Write("success!"); fetchReq = null; }; + fetchReq.Failure += delegate { Debug.Write("failure!"); @@ -437,51 +439,42 @@ namespace osu.Game.Overlays { var postText = textbox.Text; + textbox.Text = string.Empty; + if (string.IsNullOrEmpty(postText)) return; + var target = currentChannel; + + if (target == null) return; + if (!api.IsLoggedIn) { - currentChannel?.AddNewMessages(new ErrorMessage("Please login to participate in chat!")); - textbox.Text = string.Empty; + target.AddNewMessages(new ErrorMessage("Please login to participate in chat!")); return; } - if (currentChannel == null) return; - if (postText[0] == '/') { // TODO: handle commands - currentChannel.AddNewMessages(new ErrorMessage("Chat commands are not supported yet!")); - textbox.Text = string.Empty; + target.AddNewMessages(new ErrorMessage("Chat commands are not supported yet!")); return; } - var message = new Message + var message = new LocalEchoMessage { Sender = api.LocalUser.Value, Timestamp = DateTimeOffset.Now, - TargetType = TargetType.Channel, //TODO: read this from currentChannel - TargetId = currentChannel.Id, + TargetType = TargetType.Channel, //TODO: read this from channel + TargetId = target.Id, Content = postText }; - textbox.ReadOnly = true; var req = new PostMessageRequest(message); - req.Failure += e => - { - textbox.FlashColour(Color4.Red, 1000); - textbox.ReadOnly = false; - }; - - req.Success += m => - { - currentChannel.AddNewMessages(m); - - textbox.ReadOnly = false; - textbox.Text = string.Empty; - }; + target.AddLocalEcho(message); + req.Failure += e => target.ReplaceMessage(message, null); + req.Success += m => target.ReplaceMessage(message, m); api.Queue(req); } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 44aee0e666..a3361743a1 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Input; @@ -123,46 +124,74 @@ namespace osu.Game.Overlays.KeyBinding base.OnHoverLost(state); } - public override bool AcceptsFocus => true; + public override bool AcceptsFocus => bindTarget == null; private KeyButton bindTarget; - protected override void OnFocus(InputState state) - { - AutoSizeDuration = 500; - AutoSizeEasing = Easing.OutQuint; - - pressAKey.FadeIn(300, Easing.OutQuint); - pressAKey.Padding = new MarginPadding(); - - base.OnFocus(state); - } + public bool AllowMainMouseButtons; private bool isModifier(Key k) => k < Key.F1; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - switch (args.Key) - { - case Key.Escape: - GetContainingInputManager().ChangeFocus(null); - return true; - case Key.Delete: - bindTarget.UpdateKeyCombination(Key.Unknown); - store.Update(bindTarget.KeyBinding); - GetContainingInputManager().ChangeFocus(null); - return true; - } + protected override bool OnClick(InputState state) => true; + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { if (HasFocus) { - bindTarget.UpdateKeyCombination(state.Keyboard.Keys.ToArray()); - if (!isModifier(args.Key)) + if (bindTarget.IsHovered) + { + if (!AllowMainMouseButtons) + { + switch (args.Button) + { + case MouseButton.Left: + case MouseButton.Right: + return true; + } + } + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + return true; + } + } + + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + if (HasFocus && !state.Mouse.Buttons.Any()) + { + if (bindTarget.IsHovered) finalise(); + else + updateBindTarget(); return true; } - return base.OnKeyDown(state, args); + return base.OnMouseUp(state, args); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (!HasFocus) + return false; + + switch (args.Key) + { + case Key.Escape: + finalise(); + return true; + case Key.Delete: + bindTarget.UpdateKeyCombination(InputKey.None); + finalise(); + return true; + } + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state)); + if (!isModifier(args.Key)) finalise(); + + return true; } protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) @@ -178,27 +207,48 @@ namespace osu.Game.Overlays.KeyBinding private void finalise() { - store.Update(bindTarget.KeyBinding); - GetContainingInputManager().ChangeFocus(null); + if (bindTarget != null) + { + store.Update(bindTarget.KeyBinding); + + bindTarget.IsBinding = false; + Schedule(() => + { + // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) + bindTarget = null; + }); + } + + if (HasFocus) + GetContainingInputManager().ChangeFocus(null); + + pressAKey.FadeOut(300, Easing.OutQuint); + pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight }; + } + + protected override void OnFocus(InputState state) + { + AutoSizeDuration = 500; + AutoSizeEasing = Easing.OutQuint; + + pressAKey.FadeIn(300, Easing.OutQuint); + pressAKey.Padding = new MarginPadding(); + + updateBindTarget(); + base.OnFocus(state); } protected override void OnFocusLost(InputState state) { - bindTarget.IsBinding = false; - bindTarget = null; - - pressAKey.FadeOut(300, Easing.OutQuint); - pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight }; + finalise(); base.OnFocusLost(state); } - protected override bool OnClick(InputState state) + private void updateBindTarget() { if (bindTarget != null) bindTarget.IsBinding = false; bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault(); if (bindTarget != null) bindTarget.IsBinding = true; - - return bindTarget != null; } private class KeyButton : Container @@ -296,7 +346,7 @@ namespace osu.Game.Overlays.KeyBinding } } - public void UpdateKeyCombination(params Key[] newCombination) + public void UpdateKeyCombination(KeyCombination newCombination) { KeyBinding.KeyCombination = newCombination; Text.Text = KeyBinding.KeyCombination.ReadableString(); diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs index 19baeca771..44c28ee9c8 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSection.cs @@ -40,7 +40,10 @@ namespace osu.Game.Overlays.KeyBinding foreach (Enum v in Enum.GetValues(enumType)) // one row per valid action. - Add(new KeyBindingRow(v, bindings.Where(b => b.Action.Equals((int)(object)v)))); + Add(new KeyBindingRow(v, bindings.Where(b => b.Action.Equals((int)(object)v))) + { + AllowMainMouseButtons = Ruleset != null + }); } } } \ No newline at end of file diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index 7d6ef7ffa6..827d361099 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays { AddSection(new GlobalKeyBindingsSection(global, "Global")); - foreach (var ruleset in rulesets.Query()) + foreach (var ruleset in rulesets.AllRulesets) AddSection(new RulesetBindingsSection(ruleset)); } diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index 6302c56018..b4d9cac045 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -13,6 +13,7 @@ using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; using osu.Game.Screens.Ranking; using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Overlays { @@ -40,27 +41,35 @@ namespace osu.Game.Overlays public MainSettings() : base(true) { - keyBindingOverlay = new KeyBindingOverlay { Depth = 1 }; + keyBindingOverlay = new KeyBindingOverlay + { + Depth = 1, + Anchor = Anchor.TopRight, + }; keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged; } public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible; + private const float hidden_width = 120; + private void keyBindingOverlay_StateChanged(VisibilityContainer container, Visibility visibility) { - const float hidden_width = 120; - switch (visibility) { case Visibility.Visible: - Background.FadeTo(0.9f, 500, Easing.OutQuint); - SectionsContainer.FadeOut(100); - ContentContainer.MoveToX(hidden_width - ContentContainer.DrawWidth, 500, Easing.OutQuint); + Background.FadeTo(0.9f, 300, Easing.OutQuint); + Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + + SectionsContainer.FadeOut(300, Easing.OutQuint); + ContentContainer.MoveToX(hidden_width - WIDTH, 500, Easing.OutQuint); backButton.Delay(100).FadeIn(100); break; case Visibility.Hidden: Background.FadeTo(0.6f, 500, Easing.OutQuint); + Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + SectionsContainer.FadeIn(500, Easing.OutQuint); ContentContainer.MoveToX(0, 500, Easing.OutQuint); @@ -69,39 +78,26 @@ namespace osu.Game.Overlays } } - protected override void PopOut() - { - base.PopOut(); - keyBindingOverlay.State = Visibility.Hidden; - } + protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? hidden_width - WIDTH : base.ExpandedPosition; [BackgroundDependencyLoader] private void load() { - AddInternal(keyBindingOverlay); - AddInternal(backButton = new BackButton + ContentContainer.Add(keyBindingOverlay); + + ContentContainer.Add(backButton = new BackButton { Alpha = 0, - Height = 150, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Width = hidden_width, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Action = () => keyBindingOverlay.Hide() }); } - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - keyBindingOverlay.Margin = new MarginPadding { Left = ContentContainer.Margin.Left + ContentContainer.DrawWidth + ContentContainer.X }; - - backButton.Margin = new MarginPadding { Left = ContentContainer.Margin.Left }; - backButton.Width = ContentContainer.DrawWidth + ContentContainer.X; - } - private class BackButton : OsuClickableContainer { - private FillFlowContainer flow; private AspectContainer aspect; [BackgroundDependencyLoader] @@ -116,29 +112,22 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - flow = new FillFlowContainer + new SpriteIcon { - Anchor = Anchor.TopCentre, - RelativePositionAxes = Axes.Y, - Y = 0.4f, - AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Children = new[] - { - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_left }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_left }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_left }, - } + Y = -15, + Size = new Vector2(15), + Shadow = true, + Icon = FontAwesome.fa_chevron_left }, new OsuSpriteText { - Anchor = Anchor.TopCentre, - RelativePositionAxes = Axes.Y, - Y = 0.7f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 15, TextSize = 12, Font = @"Exo2.0-Bold", - Origin = Anchor.Centre, Text = @"back", }, } @@ -146,18 +135,6 @@ namespace osu.Game.Overlays }; } - protected override bool OnHover(InputState state) - { - flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - flow.TransformSpacingTo(new Vector2(0), 500, Easing.OutQuint); - base.OnHoverLost(state); - } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); @@ -171,4 +148,4 @@ namespace osu.Game.Overlays } } } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 746b6dd50f..dcab942522 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -258,7 +258,10 @@ namespace osu.Game.Overlays Type = EdgeEffectType.Glow, Radius = 8, }; + } + protected override void LoadComplete() + { updateGlow(); FinishTransforms(true); } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 3f8b6186c4..7ae45159d9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -171,7 +171,7 @@ namespace osu.Game.Overlays.Settings.Sections.General break; } - if (form != null) GetContainingInputManager().ChangeFocus(form); + if (form != null) GetContainingInputManager()?.ChangeFocus(form); } public override bool AcceptsFocus => true; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index f62087ee71..ddef5d1001 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -25,7 +26,7 @@ namespace osu.Game.Overlays public const float SIDEBAR_WIDTH = Sidebar.DEFAULT_WIDTH; - private const float width = 400; + protected const float WIDTH = 400; private const float sidebar_padding = 10; @@ -33,14 +34,17 @@ namespace osu.Game.Overlays protected override Container Content => ContentContainer; - private Sidebar sidebar; + protected Sidebar Sidebar; private SidebarButton selectedSidebarButton; protected SettingsSectionsContainer SectionsContainer; private SearchTextBox searchTextBox; - private Func getToolbarHeight; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; private readonly bool showSidebar; @@ -55,23 +59,27 @@ namespace osu.Game.Overlays protected virtual IEnumerable CreateSections() => null; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuGame game) + [BackgroundDependencyLoader] + private void load() { InternalChild = ContentContainer = new Container { - Width = width, + Width = WIDTH, RelativeSizeAxes = Axes.Y, Children = new Drawable[] { Background = new Box { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Scale = new Vector2(2, 1), // over-extend to the left for transitions RelativeSizeAxes = Axes.Both, Colour = Color4.Black, Alpha = 0.6f, }, SectionsContainer = new SettingsSectionsContainer { + Masking = true, RelativeSizeAxes = Axes.Both, ExpandableHeader = CreateHeader(), FixedHeader = searchTextBox = new SearchTextBox @@ -94,20 +102,18 @@ namespace osu.Game.Overlays if (showSidebar) { - AddInternal(sidebar = new Sidebar { Width = SIDEBAR_WIDTH }); + AddInternal(Sidebar = new Sidebar { Width = SIDEBAR_WIDTH }); SectionsContainer.SelectedSection.ValueChanged += section => { selectedSidebarButton.Selected = false; - selectedSidebarButton = sidebar.Children.Single(b => b.Section == section); + selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section); selectedSidebarButton.Selected = true; }; } searchTextBox.Current.ValueChanged += newValue => SectionsContainer.SearchContainer.SearchTerm = newValue; - getToolbarHeight = () => game?.ToolbarOffset ?? 0; - CreateSections()?.ForEach(AddSection); } @@ -115,7 +121,7 @@ namespace osu.Game.Overlays { SectionsContainer.Add(section); - if (sidebar != null) + if (Sidebar != null) { var button = new SidebarButton { @@ -123,15 +129,15 @@ namespace osu.Game.Overlays Action = s => { SectionsContainer.ScrollTo(s); - sidebar.State = ExpandedState.Contracted; + Sidebar.State = ExpandedState.Contracted; }, }; - sidebar.Add(button); + Sidebar.Add(button); if (selectedSidebarButton == null) { - selectedSidebarButton = sidebar.Children.First(); + selectedSidebarButton = Sidebar.Children.First(); selectedSidebarButton.Selected = true; } } @@ -145,20 +151,24 @@ namespace osu.Game.Overlays { base.PopIn(); - ContentContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH / 2); + ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); searchTextBox.HoldFocus = true; } + protected virtual float ExpandedPosition => 0; + protected override void PopOut() { base.PopOut(); - ContentContainer.MoveToX(-width, TRANSITION_LENGTH, Easing.OutQuint); - sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH / 2); + ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) @@ -179,8 +189,8 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); - ContentContainer.Margin = new MarginPadding { Left = sidebar?.DrawWidth ?? 0 }; - ContentContainer.Padding = new MarginPadding { Top = getToolbarHeight() }; + ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; + ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected class SettingsSectionsContainer : SectionsContainer diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index f9821782a7..32344ca7eb 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -58,8 +58,8 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { - new ToolbarSocialButton(), new ToolbarChatButton(), + new ToolbarSocialButton(), new ToolbarMusicButton(), new ToolbarButton { diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 8cfedc9599..3fba168216 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -106,6 +106,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Whether a hit was processed. protected bool UpdateJudgement(bool userTriggered) { + if (Judgement == null) + return false; + var partial = Judgement as IPartialJudgement; // Never re-process non-partial hits diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs index f67dd4dfea..a7c4dd0333 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableScrollingHitObject.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + public override bool RemoveWhenNotAlive => false; + protected DrawableScrollingHitObject(TObject hitObject) : base(hitObject) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index ffe40e4f2e..479cacc52b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects.Legacy TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier; + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index e5fa7117f3..32d958b462 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; @@ -46,8 +45,6 @@ namespace osu.Game.Rulesets public abstract string Description { get; } - public abstract IEnumerable CreateGameplayKeys(); - public virtual SettingsSubsection CreateSettings() => null; /// diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index bc23649ede..740369b1b6 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -6,7 +6,7 @@ using SQLite.Net.Attributes; namespace osu.Game.Rulesets { - public class RulesetInfo + public class RulesetInfo : IEquatable { [PrimaryKey, AutoIncrement] public int? ID { get; set; } @@ -21,5 +21,7 @@ namespace osu.Game.Rulesets public bool Available { get; set; } public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); + + public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 3b9b8d2d57..4f1a85cf2d 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Timing /// /// The multiplier which this provides. /// - public double Multiplier => 1000 / TimingPoint.BeatLength / DifficultyPoint.SpeedMultiplier; + public double Multiplier => 1000 / TimingPoint.BeatLength * DifficultyPoint.SpeedMultiplier; /// /// The that provides the timing information for this . @@ -62,4 +62,4 @@ namespace osu.Game.Rulesets.Timing public int CompareTo(MultiplierControlPoint other) => StartTime.CompareTo(other?.StartTime); } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Timing/ScrollingContainer.cs b/osu.Game/Rulesets/Timing/ScrollingContainer.cs index 843f307d0d..9bb32ead77 100644 --- a/osu.Game/Rulesets/Timing/ScrollingContainer.cs +++ b/osu.Game/Rulesets/Timing/ScrollingContainer.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Timing /// internal Axes ScrollingAxes; + public override bool RemoveWhenNotAlive => false; + /// /// The control point that defines the speed adjustments for this container. This is set by the . /// diff --git a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs index 9d5f49e155..5d6c03b9de 100644 --- a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs +++ b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Timing /// public Axes ScrollingAxes { get; internal set; } + public override bool RemoveWhenNotAlive => false; + /// /// The that defines the speed adjustments. /// diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 8512d4b5e1..50fcaa128c 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI /// /// The key conversion input manager for this RulesetContainer. /// - protected readonly PassThroughInputManager KeyConversionInputManager; + public readonly PassThroughInputManager KeyBindingInputManager; /// /// Whether we are currently providing the local user a gameplay cursor. @@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.UI internal RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; - KeyConversionInputManager = CreateKeyBindingInputManager(); - KeyConversionInputManager.RelativeSizeAxes = Axes.Both; + KeyBindingInputManager = CreateInputManager(); + KeyBindingInputManager.RelativeSizeAxes = Axes.Both; } /// @@ -92,10 +92,10 @@ namespace osu.Game.Rulesets.UI public abstract ScoreProcessor CreateScoreProcessor(); /// - /// Creates a key conversion input manager. + /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// /// The input manager. - public virtual PassThroughInputManager CreateKeyBindingInputManager() => new PassThroughInputManager(); + public abstract PassThroughInputManager CreateInputManager(); protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new FramedReplayInputHandler(replay); @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.UI InputManager.Add(content = new Container { RelativeSizeAxes = Axes.Both, - Children = new[] { KeyConversionInputManager } + Children = new[] { KeyBindingInputManager } }); AddInternal(InputManager); @@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.UI [BackgroundDependencyLoader] private void load() { - KeyConversionInputManager.Add(Playfield = CreatePlayfield()); + KeyBindingInputManager.Add(Playfield = CreatePlayfield()); loadObjects(); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs new file mode 100644 index 0000000000..6d22b2e91e --- /dev/null +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + public abstract class RulesetInputManager : DatabasedKeyBindingInputManager, ICanAttachKeyCounter + where T : struct + { + protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) + { + } + + public void Attach(KeyCounterCollection keyCounter) + { + var receptor = new ActionReceptor(keyCounter); + Add(receptor); + keyCounter.SetReceptor(receptor); + + keyCounter.AddRange(DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); + } + + public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler + { + public ActionReceptor(KeyCounterCollection target) + : base(target) + { + } + + public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action)); + + public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action)); + } + } + + public interface ICanAttachKeyCounter + { + void Attach(KeyCounterCollection keyCounter); + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/UI/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/ScrollingPlayfield.cs index 66ca2dd7c4..0a8469c928 100644 --- a/osu.Game/Rulesets/UI/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/ScrollingPlayfield.cs @@ -7,6 +7,7 @@ using System.Linq; using OpenTK.Input; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Framework.MathUtils; @@ -150,11 +151,7 @@ namespace osu.Game.Rulesets.UI /// public readonly BindableBool Reversed = new BindableBool(); - /// - /// Hit objects that are to be re-processed on the next update. - /// - private readonly List queuedHitObjects = new List(); - private readonly List speedAdjustments = new List(); + private readonly Container speedAdjustments; private readonly Axes scrollingAxes; @@ -165,6 +162,11 @@ namespace osu.Game.Rulesets.UI public ScrollingHitObjectContainer(Axes scrollingAxes) { this.scrollingAxes = scrollingAxes; + + AddInternal(speedAdjustments = new Container { RelativeSizeAxes = Axes.Both }); + + // Default speed adjustment + AddSpeedAdjustment(new SpeedAdjustmentContainer(new MultiplierControlPoint(0))); } /// @@ -176,9 +178,22 @@ namespace osu.Game.Rulesets.UI speedAdjustment.ScrollingAxes = scrollingAxes; speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); speedAdjustment.Reversed.BindTo(Reversed); - speedAdjustments.Add(speedAdjustment); - AddInternal(speedAdjustment); + + // We now need to re-sort the hit objects in the last speed adjustment prior to this one, to see if they need a new parent + var previousSpeedAdjustment = speedAdjustments.LastOrDefault(s => s.ControlPoint.StartTime < speedAdjustment.ControlPoint.StartTime); + if (previousSpeedAdjustment == null) + return; + + foreach (DrawableHitObject h in previousSpeedAdjustment.Children) + { + var newSpeedAdjustment = adjustmentContainerFor(h); + if (newSpeedAdjustment == previousSpeedAdjustment) + continue; + + previousSpeedAdjustment.Remove(h); + newSpeedAdjustment.Add(h); + } } public override IEnumerable Objects => speedAdjustments.SelectMany(s => s.Children); @@ -193,30 +208,14 @@ namespace osu.Game.Rulesets.UI if (!(hitObject is IScrollingHitObject)) throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}."); - queuedHitObjects.Add(hitObject); + var target = adjustmentContainerFor(hitObject); + if (target == null) + throw new InvalidOperationException($"A {nameof(SpeedAdjustmentContainer)} to container {hitObject} could not be found."); + + target.Add(hitObject); } - public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject)) || queuedHitObjects.Remove(hitObject); - - protected override void Update() - { - base.Update(); - - // Todo: At the moment this is going to re-process every single Update, however this will only be a null-op - // when there are no SpeedAdjustmentContainers available. This should probably error or something, but it's okay for now. - - for (int i = queuedHitObjects.Count - 1; i >= 0; i--) - { - var hitObject = queuedHitObjects[i]; - - var target = adjustmentContainerFor(hitObject); - if (target != null) - { - target.Add(hitObject); - queuedHitObjects.RemoveAt(i); - } - } - } + public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject)); /// /// Finds the which provides the speed adjustment active at the start time @@ -236,4 +235,4 @@ namespace osu.Game.Rulesets.UI private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? speedAdjustments.LastOrDefault(); } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index cf6bc03c60..ddcf031bd4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Play public virtual void BindRulesetContainer(RulesetContainer rulesetContainer) { - rulesetContainer.InputManager.Add(KeyCounter.GetReceptor()); + (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); replayLoaded = rulesetContainer.HasReplayLoaded; diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/KeyCounterAction.cs new file mode 100644 index 0000000000..a6baeeafb6 --- /dev/null +++ b/osu.Game/Screens/Play/KeyCounterAction.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input.Bindings; + +namespace osu.Game.Screens.Play +{ + public class KeyCounterAction : KeyCounter, IKeyBindingHandler + where T : struct + { + public T Action { get; } + + public KeyCounterAction(T action) : base($"B{(int)(object)action + 1}") + { + Action = action; + } + + public bool OnPressed(T action) + { + if (action.Equals(Action)) IsLit = true; + return false; + } + + public bool OnReleased(T action) + { + if (action.Equals(Action)) IsLit = false; + return false; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 63c29a8f78..0f6c5984c4 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -117,27 +118,36 @@ namespace osu.Game.Screens.Play return receptor ?? (receptor = new Receptor(this)); } + public void SetReceptor(Receptor receptor) + { + if (this.receptor != null) + throw new InvalidOperationException("Cannot set a new receptor when one is already active"); + + this.receptor = receptor; + } + public class Receptor : Drawable { - private readonly KeyCounterCollection target; + protected readonly KeyCounterCollection Target; public Receptor(KeyCounterCollection target) { RelativeSizeAxes = Axes.Both; - this.target = target; + Depth = float.MinValue; + Target = target; } public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; public override bool HandleInput => true; - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => target.Children.Any(c => c.TriggerOnKeyDown(state, args)); + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyDown(state, args)); - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => target.Children.Any(c => c.TriggerOnKeyUp(state, args)); + protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyUp(state, args)); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => target.Children.Any(c => c.TriggerOnMouseDown(state, args)); + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => Target.Children.Any(c => c.TriggerOnMouseDown(state, args)); - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => target.Children.Any(c => c.TriggerOnMouseUp(state, args)); + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => Target.Children.Any(c => c.TriggerOnMouseUp(state, args)); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 00f1bd1b6b..3f399d69e6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -76,8 +76,6 @@ namespace osu.Game.Screens.Play sampleRestart = audio.Sample.Get(@"Gameplay/restart"); - Ruleset rulesetInstance; - WorkingBeatmap working = Beatmap.Value; Beatmap beatmap; @@ -89,7 +87,7 @@ namespace osu.Game.Screens.Play throw new InvalidOperationException("Beatmap was not loaded"); ruleset = osu?.Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; - rulesetInstance = ruleset.CreateInstance(); + var rulesetInstance = ruleset.CreateInstance(); try { @@ -192,7 +190,6 @@ namespace osu.Game.Screens.Play scoreProcessor = RulesetContainer.CreateScoreProcessor(); - hudOverlay.KeyCounter.AddRange(rulesetInstance.CreateGameplayKeys()); hudOverlay.BindProcessor(scoreProcessor); hudOverlay.BindRulesetContainer(RulesetContainer); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index afab9e8746..aa554152f9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -253,8 +253,6 @@ namespace osu.Game.Screens.Select } else { - Ruleset.Value = beatmap.Ruleset; - if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID) sampleChangeDifficulty.Play(); else @@ -372,6 +370,9 @@ namespace osu.Game.Screens.Select if (!track.IsRunning) { + // Ensure the track is added to the TrackManager, since it is removed after the player finishes the map. + // Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once. + Game.Audio.Track.AddItemToList(track); if (preview) track.Seek(Beatmap.Value.Metadata.PreviewTime); track.Start(); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 07ab58bffc..11b99c1796 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -101,6 +101,7 @@ + @@ -128,6 +129,8 @@ + +