diff --git a/LICENCE b/LICENCE index 797be26fff..a11a7ce75b 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2007-2017 ppy Pty Ltd . +Copyright (c) 2007-2018 ppy Pty Ltd . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/osu-framework b/osu-framework index 8f36ddab94..209021fb49 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 8f36ddab946ff538620081ede7719461d4732b79 +Subproject commit 209021fb491e21625127be5dbf5efb4c409e6f06 diff --git a/osu-resources b/osu-resources index 7724abdf1d..266965f0d7 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 7724abdf1d7c9705ba2e3989a9c604e17ccdc871 +Subproject commit 266965f0d795b94a126e2da302bd2c10eadd642a diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index 4c529f57e5..bb7d382cee 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -12,12 +12,12 @@ click the circles. to the beat. click the circles. testing - Copyright ppy Pty Ltd 2007-2017 + Copyright ppy Pty Ltd 2007-2018 en-AU - + diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index f57952f95e..fa8958687c 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -23,6 +23,5 @@ namespace osu.Game.Rulesets.Catch MoveRight, [Description("Engage dash")] Dash, - PositionUpdate } } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 188af58e6a..be1e360fce 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -21,9 +21,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// private const float base_scoring_distance = 100; - public readonly SliderCurve Curve = new SliderCurve(); - - public int RepeatCount { get; set; } = 1; + public int RepeatCount { get; set; } public double Velocity; public double TickDistance; @@ -55,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects var length = Curve.Distance; var tickDistance = Math.Min(TickDistance, length); - var repeatDuration = length / Velocity; + var spanDuration = length / Velocity; var minDistanceFromEnd = Velocity * 0.01; @@ -67,10 +65,10 @@ namespace osu.Game.Rulesets.Catch.Objects X = X }); - for (var repeat = 0; repeat < RepeatCount; repeat++) + for (var span = 0; span < this.SpanCount(); span++) { - var repeatStartTime = StartTime + repeat * repeatDuration; - var reversed = repeat % 2 == 1; + var spanStartTime = StartTime + span * spanDuration; + var reversed = span % 2 == 1; for (var d = tickDistance; d <= length; d += tickDistance) { @@ -80,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects var timeProgress = d / length; var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - var lastTickTime = repeatStartTime + timeProgress * repeatDuration; + var lastTickTime = spanStartTime + timeProgress * spanDuration; AddNested(new Droplet { StartTime = lastTickTime, @@ -95,17 +93,17 @@ namespace osu.Game.Rulesets.Catch.Objects }); } - double tinyTickInterval = tickDistance / length * repeatDuration; + double tinyTickInterval = tickDistance / length * spanDuration; while (tinyTickInterval > 100) tinyTickInterval /= 2; - for (double t = 0; t < repeatDuration; t += tinyTickInterval) + for (double t = 0; t < spanDuration; t += tinyTickInterval) { - double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; + double progress = reversed ? 1 - t / spanDuration : t / spanDuration; AddNested(new TinyDroplet { - StartTime = repeatStartTime + t, + StartTime = spanStartTime + t, ComboColour = ComboColour, X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo @@ -121,15 +119,15 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = Samples, ComboColour = ComboColour, - StartTime = repeatStartTime + repeatDuration, + StartTime = spanStartTime + spanDuration, X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH }); } } - public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; - public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; + public float EndX => Curve.PositionAt(this.ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; public double Duration => EndTime - StartTime; @@ -139,6 +137,8 @@ namespace osu.Game.Rulesets.Catch.Objects set { Curve.Distance = value; } } + public SliderCurve Curve { get; } = new SliderCurve(); + public List ControlPoints { get { return Curve.ControlPoints; } @@ -152,17 +152,5 @@ namespace osu.Game.Rulesets.Catch.Objects get { return Curve.CurveType; } set { Curve.CurveType = value; } } - - public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); - - public double ProgressAt(double progress) - { - double p = progress * RepeatCount % 1; - if (RepeatAt(progress) % 2 == 1) - p = 1 - p; - return p; - } - - public int RepeatAt(double progress) => (int)(progress * RepeatCount); } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index bc53e6e869..f8ca75fae9 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -1,9 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; using osu.Game.Users; @@ -23,15 +26,78 @@ namespace osu.Game.Rulesets.Catch.Replays public override Replay Generate() { + // todo: add support for HT DT + const double dash_speed = CatcherArea.Catcher.BASE_SPEED; + const double movement_speed = dash_speed / 2; + float lastPosition = 0.5f; + double lastTime = 0; + // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled - Replay.Frames.Add(new CatchReplayFrame(-100000, 0)); + Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition)); + + void moveToNext(CatchHitObject h) + { + float positionChange = Math.Abs(lastPosition - h.X); + double timeAvailable = h.StartTime - lastTime; + + //So we can either make it there without a dash or not. + double speedRequired = positionChange / timeAvailable; + + bool dashRequired = speedRequired > movement_speed && h.StartTime != 0; + + // todo: get correct catcher size, based on difficulty CS. + const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; + + if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) + { + //we are already in the correct range. + lastTime = h.StartTime; + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition)); + return; + } + + if (h is BananaShower.Banana) + { + // auto bananas unrealistically warp to catch 100% combo. + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + else if (h.HyperDash) + { + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition, ReplayButtonState.Right1)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + else if (dashRequired) + { + //we do a movement in two parts - the dash part then the normal part... + double timeAtNormalSpeed = positionChange / movement_speed; + double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; + double timeAtDashSpeed = timeWeNeedToSave / 2; + + float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); + + //dash movement + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, ReplayButtonState.Left1)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + else + { + double timeBefore = positionChange / movement_speed; + + Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition, ReplayButtonState.Right1)); + Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); + } + + lastTime = h.StartTime; + lastPosition = h.X; + } foreach (var obj in Beatmap.HitObjects) { switch (obj) { case Fruit _: - Replay.Frames.Add(new CatchReplayFrame(obj.StartTime, obj.X)); + moveToNext(obj); break; } @@ -42,7 +108,7 @@ namespace osu.Game.Rulesets.Catch.Replays case BananaShower.Banana _: case TinyDroplet _: case Droplet _: - Replay.Frames.Add(new CatchReplayFrame(nestedObj.StartTime, nestedObj.X)); + moveToNext(nestedObj); break; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 146e31fa69..2f296a2504 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -14,15 +14,29 @@ namespace osu.Game.Rulesets.Catch.Replays { } - public override List GetPendingStates() => new List + public override List GetPendingStates() { - new CatchReplayState + if (!Position.HasValue) return new List(); + + var action = new List(); + + if (CurrentFrame.ButtonState == ReplayButtonState.Left1) + action.Add(CatchAction.Dash); + + if (Position.Value.X > CurrentFrame.Position.X) + action.Add(CatchAction.MoveRight); + else if (Position.Value.X < CurrentFrame.Position.X) + action.Add(CatchAction.MoveLeft); + + return new List { - PressedActions = new List { CatchAction.PositionUpdate }, - CatcherX = ((CatchReplayFrame)CurrentFrame).MouseX - }, - new CatchReplayState { PressedActions = new List() }, - }; + new CatchReplayState + { + PressedActions = action, + CatcherX = Position.Value.X + }, + }; + } public class CatchReplayState : ReplayState { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index c47f60ec3c..0194fc93a4 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Catch.Replays { public override bool IsImportant => MouseX > 0; - public CatchReplayFrame(double time, float? x = null) - : base(time, x ?? -1, null, ReplayButtonState.None) + public CatchReplayFrame(double time, float? x = null, ReplayButtonState button = ReplayButtonState.None) + : base(time, x ?? -1, null, button) { } } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 17c78f3aa0..5252ba294a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -21,7 +21,7 @@ using OpenTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { - public class CatcherArea : Container, IKeyBindingHandler + public class CatcherArea : Container { public const float CATCHER_SIZE = 172; @@ -84,16 +84,14 @@ namespace osu.Game.Rulesets.Catch.UI } } - public bool OnPressed(CatchAction action) + protected override void Update() { - if (action != CatchAction.PositionUpdate) return false; + base.Update(); - CatchFramedReplayInputHandler.CatchReplayState state = (CatchFramedReplayInputHandler.CatchReplayState)GetContainingInputManager().CurrentState; + var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState; - if (state.CatcherX.HasValue) + if (state?.CatcherX != null) MovableCatcher.X = state.CatcherX.Value; - - return true; } public bool OnReleased(CatchAction action) => false; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index d775cb392f..0a248658a8 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The definitions for each stage in a . /// - public readonly List Stages = new List(); + public List Stages = new List(); /// /// Total number of columns represented by all stages in this . @@ -24,10 +24,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// Creates a new . /// - /// The initial stage. - public ManiaBeatmap(StageDefinition initialStage) + /// The initial stages. + public ManiaBeatmap(StageDefinition defaultStage) { - Stages.Add(initialStage); + Stages.Add(defaultStage); } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 557ce5eb1b..9922d4c8ad 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (curveData == null) return HitObject.Samples; - double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.RepeatCount; + double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); return curveData.RepeatSamples[index]; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 1d739c114e..a102781e70 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private readonly double endTime; private readonly double segmentDuration; - private readonly int repeatCount; + private readonly int spanCount; private PatternType convertType; @@ -39,25 +39,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var distanceData = hitObject as IHasDistance; var repeatsData = hitObject as IHasRepeats; - repeatCount = repeatsData?.RepeatCount ?? 1; + spanCount = repeatsData?.SpanCount() ?? 1; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); // The true distance, accounting for any repeats - double distance = (distanceData?.Distance ?? 0) * repeatCount; + double distance = (distanceData?.Distance ?? 0) * spanCount; // The velocity of the osu! hit object - calculated as the velocity of a slider double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; endTime = hitObject.StartTime + osuDuration; - segmentDuration = (endTime - HitObject.StartTime) / repeatCount; + segmentDuration = (endTime - HitObject.StartTime) / spanCount; } public override Pattern Generate() { - if (repeatCount > 1) + if (spanCount > 1) { if (segmentDuration <= 90) return generateRandomHoldNotes(HitObject.StartTime, 1); @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (segmentDuration <= 120) { convertType |= PatternType.ForceNotStack; - return generateRandomNotes(HitObject.StartTime, repeatCount + 1); + return generateRandomNotes(HitObject.StartTime, spanCount + 1); } if (segmentDuration <= 160) @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (duration >= 4000) return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); - if (segmentDuration > 400 && repeatCount < TotalColumns - 1 - RandomStart) + if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) return generateTiledHoldNotes(HitObject.StartTime); return generateHoldAndNormalNotes(HitObject.StartTime); @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); bool increasing = Random.NextDouble() > 0.5; - for (int i = 0; i <= repeatCount; i++) + for (int i = 0; i <= spanCount; i++) { addToPattern(pattern, column, startTime, startTime); startTime += segmentDuration; @@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - for (int i = 0; i <= repeatCount; i++) + for (int i = 0; i <= spanCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); - int columnRepeat = Math.Min(repeatCount, TotalColumns); + int columnRepeat = Math.Min(spanCount, TotalColumns); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) @@ -409,7 +409,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int nextColumn = Random.Next(RandomStart, TotalColumns); var rowPattern = new Pattern(); - for (int i = 0; i <= repeatCount; i++) + for (int i = 0; i <= spanCount; i++) { if (!(ignoreHead && startTime == HitObject.StartTime)) { @@ -442,7 +442,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (curveData == null) return HitObject.Samples; - double segmentTime = (endTime - HitObject.StartTime) / repeatCount; + double segmentTime = (endTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); return curveData.RepeatSamples[index]; diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs new file mode 100644 index 0000000000..a4de360870 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration.Tracking; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; + +namespace osu.Game.Rulesets.Mania.Configuration +{ + public class ManiaConfigManager : RulesetConfigManager + { + public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) + : base(settings, ruleset, variant) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + + Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0); + } + + public override TrackedSettings CreateTrackedSettings() => new TrackedSettings + { + new TrackedSetting(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) + }; + } + + public enum ManiaSetting + { + ScrollTime + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 5e12ef5581..01e2821540 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -17,8 +17,13 @@ namespace osu.Game.Rulesets.Mania public enum ManiaAction { - [Description("Special")] - Special, + [Description("Special 1")] + Special1 = 1, + [Description("Special 2")] + Special2, + + // This offsets the start value of normal keys in-case we add more special keys + // above at a later time, without breaking replays/configs. [Description("Key 1")] Key1 = 10, [Description("Key 2")] @@ -36,6 +41,24 @@ namespace osu.Game.Rulesets.Mania [Description("Key 8")] Key8, [Description("Key 9")] - Key9 + Key9, + [Description("Key 10")] + Key10, + [Description("Key 11")] + Key11, + [Description("Key 12")] + Key12, + [Description("Key 13")] + Key13, + [Description("Key 14")] + Key14, + [Description("Key 15")] + Key15, + [Description("Key 16")] + Key16, + [Description("Key 17")] + Key17, + [Description("Key 18")] + Key18, } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index e8b9828bff..3bfb4d3e44 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -1,12 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Graphics; @@ -86,7 +88,7 @@ namespace osu.Game.Rulesets.Mania }, }, new ManiaModRandom(), - new ManiaModKeyCoop(), + new ManiaModDualStages(), new MultiMod { Mods = new Mod[] @@ -117,42 +119,189 @@ namespace osu.Game.Rulesets.Mania { } - public override IEnumerable AvailableVariants => new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + public override IEnumerable AvailableVariants + { + get + { + for (int i = 1; i <= 9; i++) + yield return (int)PlayfieldType.Single + i; + for (int i = 2; i <= 18; i += 2) + yield return (int)PlayfieldType.Dual + i; + } + } public override IEnumerable GetDefaultKeyBindings(int variant = 0) { - var leftKeys = new[] + switch (getPlayfieldType(variant)) { - InputKey.A, - InputKey.S, - InputKey.D, - InputKey.F - }; + case PlayfieldType.Single: + return new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.A, + InputKey.S, + InputKey.D, + InputKey.F + }, + RightKeys = new[] + { + InputKey.J, + InputKey.K, + InputKey.L, + InputKey.Semicolon + }, + SpecialKey = InputKey.Space, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1, + }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: + int keys = getDualStageKeyCount(variant); - var rightKeys = new[] - { - InputKey.J, - InputKey.K, - InputKey.L, - InputKey.Semicolon - }; + var stage1Bindings = new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.Number1, + InputKey.Number2, + InputKey.Number3, + InputKey.Number4, + }, + RightKeys = new[] + { + InputKey.Z, + InputKey.X, + InputKey.C, + InputKey.V + }, + SpecialKey = InputKey.Tilde, + SpecialAction = ManiaAction.Special1, + NormalActionStart = ManiaAction.Key1 + }.GenerateKeyBindingsFor(keys, out var nextNormal); - ManiaAction currentKey = ManiaAction.Key1; + var stage2Bindings = new VariantMappingGenerator + { + LeftKeys = new[] + { + InputKey.Number7, + InputKey.Number8, + InputKey.Number9, + InputKey.Number0 + }, + RightKeys = new[] + { + InputKey.O, + InputKey.P, + InputKey.BracketLeft, + InputKey.BracketRight + }, + SpecialKey = InputKey.BackSlash, + SpecialAction = ManiaAction.Special2, + NormalActionStart = nextNormal + }.GenerateKeyBindingsFor(keys, out _); - var bindings = new List(); + return stage1Bindings.Concat(stage2Bindings); + } - for (int i = leftKeys.Length - variant / 2; i < leftKeys.Length; i++) - bindings.Add(new KeyBinding(leftKeys[i], currentKey++)); - - for (int i = 0; i < variant / 2; i++) - bindings.Add(new KeyBinding(rightKeys[i], currentKey++)); - - if (variant % 2 == 1) - bindings.Add(new KeyBinding(InputKey.Space, ManiaAction.Special)); - - return bindings; + return new KeyBinding[0]; } - public override string GetVariantName(int variant) => $"{variant}K"; + public override string GetVariantName(int variant) + { + switch (getPlayfieldType(variant)) + { + default: + return $"{variant}K"; + case PlayfieldType.Dual: + { + var keys = getDualStageKeyCount(variant); + return $"{keys}K + {keys}K"; + } + } + } + + /// + /// Finds the number of keys for each stage in a variant. + /// + /// The variant. + private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2; + + /// + /// Finds the that corresponds to a variant value. + /// + /// The variant value. + /// The that corresponds to . + private PlayfieldType getPlayfieldType(int variant) + { + return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); + } + + private class VariantMappingGenerator + { + /// + /// All the s available to the left hand. + /// + public InputKey[] LeftKeys; + + /// + /// All the s available to the right hand. + /// + public InputKey[] RightKeys; + + /// + /// The for the special key. + /// + public InputKey SpecialKey; + + /// + /// The at which the normal columns should begin. + /// + public ManiaAction NormalActionStart; + + /// + /// The for the special column. + /// + public ManiaAction SpecialAction; + + /// + /// Generates a list of s for a specific number of columns. + /// + /// The number of columns that need to be bound. + /// The next to use for normal columns. + /// The keybindings. + public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) + { + ManiaAction currentNormalAction = NormalActionStart; + + var bindings = new List(); + + for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) + bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); + + for (int i = 0; i < columns / 2; i++) + bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + + if (columns % 2 == 1) + bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + + nextNormalAction = currentNormalAction; + return bindings; + } + } + } + + public enum PlayfieldType + { + /// + /// Columns are grouped into a single stage. + /// Number of columns in this stage lies at (item - Single). + /// + Single = 0, + /// + /// Columns are grouped into two stages. + /// Overall number of columns lies at (item - Dual), further computation is required for + /// number of columns in each individual stage. + /// + Dual = 1000, } } diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs new file mode 100644 index 0000000000..93d98b5d83 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public interface IPlayfieldTypeMod : IApplicableMod + { + /// + /// The which this requires. + /// + PlayfieldType PlayfieldType { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs new file mode 100644 index 0000000000..3330d87e88 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer + { + public override string Name => "Dual Stages"; + public override string ShortenedName => "DS"; + public override string Description => @"Double the stages, double the fun!"; + public override double ScoreMultiplier => 1; + public override bool Ranked => false; + + public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter) + { + var mbc = (ManiaBeatmapConverter)beatmapConverter; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mbc.IsForCurrentRuleset) + return; + + mbc.TargetColumns *= 2; + } + + public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var mrc = (ManiaRulesetContainer)rulesetContainer; + + // Although this can work, for now let's not allow keymods for mania-specific beatmaps + if (mrc.IsForCurrentRuleset) + return; + + var newDefinitions = new List(); + foreach (var existing in mrc.Beatmap.Stages) + { + newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); + newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); + } + + mrc.Beatmap.Stages = newDefinitions; + } + + public PlayfieldType PlayfieldType => PlayfieldType.Dual; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKeyCoop.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKeyCoop.cs deleted file mode 100644 index 893e81f165..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKeyCoop.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModKeyCoop : Mod - { - public override string Name => "KeyCoop"; - public override string ShortenedName => "2P"; - public override string Description => @"Double the key amount, double the fun!"; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - } -} diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs index 7bd47448c2..0e6d40dc67 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests RelativeChildSize = new Vector2(1, 10000), Children = new[] { - new DrawableHoldNote(new HoldNote { Duration = 1 }, ManiaAction.Key1) + new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1) { Y = 5000, Height = 1000, diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs index 0493c65acf..7d35ab2f4d 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs @@ -2,11 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; @@ -31,15 +33,43 @@ namespace osu.Game.Rulesets.Mania.Tests { var rng = new Random(1337); - AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal)); - AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal)); - AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left)); - AddStep("Right special style", () => createPlayfield(4, SpecialColumnPosition.Right)); - AddStep("5 columns", () => createPlayfield(5, SpecialColumnPosition.Normal)); - AddStep("8 columns", () => createPlayfield(8, SpecialColumnPosition.Normal)); - AddStep("Left special style", () => createPlayfield(8, SpecialColumnPosition.Left)); - AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right)); - AddStep("Reversed", () => createPlayfield(4, SpecialColumnPosition.Normal, true)); + AddStep("1 column", () => createPlayfield(1)); + AddStep("4 columns", () => createPlayfield(4)); + AddStep("5 columns", () => createPlayfield(5)); + AddStep("8 columns", () => createPlayfield(8)); + AddStep("4 + 4 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 4 }, + }; + createPlayfield(stages); + }); + + AddStep("2 + 4 + 2 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 2 }, + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 2 }, + }; + createPlayfield(stages); + }); + + AddStep("1 + 8 + 1 columns", () => + { + var stages = new List + { + new StageDefinition { Columns = 1 }, + new StageDefinition { Columns = 8 }, + new StageDefinition { Columns = 1 }, + }; + createPlayfield(stages); + }); + + AddStep("Reversed", () => createPlayfield(4, true)); AddStep("Notes with input", () => createPlayfieldWithNotes()); AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true)); @@ -48,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("Hit explosion", () => { - var playfield = createPlayfield(4, SpecialColumnPosition.Normal); + var playfield = createPlayfield(4); int col = rng.Next(0, 4); @@ -58,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Tests }; playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); + playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); }); } @@ -67,19 +98,29 @@ namespace osu.Game.Rulesets.Mania.Tests maniaRuleset = rulesets.GetRuleset(3); } - private ManiaPlayfield createPlayfield(int cols, SpecialColumnPosition specialPos, bool inverted = false) + private ManiaPlayfield createPlayfield(int cols, bool inverted = false) + { + var stages = new List + { + new StageDefinition { Columns = cols }, + }; + + return createPlayfield(stages, inverted); + } + + private ManiaPlayfield createPlayfield(List stages, bool inverted = false) { Clear(); - var inputManager = new ManiaInputManager(maniaRuleset, cols) { RelativeSizeAxes = Axes.Both }; + var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both }; Add(inputManager); ManiaPlayfield playfield; - inputManager.Add(playfield = new ManiaPlayfield(cols) + + inputManager.Add(playfield = new ManiaPlayfield(stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - SpecialColumnPosition = specialPos }); playfield.Inverted.Value = inverted; @@ -97,7 +138,12 @@ namespace osu.Game.Rulesets.Mania.Tests Add(inputManager); ManiaPlayfield playfield; - inputManager.Add(playfield = new ManiaPlayfield(4) + var stages = new List + { + new StageDefinition { Columns = 4 }, + }; + + inputManager.Add(playfield = new ManiaPlayfield(stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index d79a4d62c4..21f00c003b 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -11,13 +11,14 @@ using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using System; +using System.Linq; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { - public class Column : ScrollingPlayfield, IHasAccentColour + public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour { private const float key_icon_size = 10; private const float key_icon_corner_radius = 3; @@ -47,6 +48,7 @@ namespace osu.Game.Rulesets.Mania.UI public Column() : base(ScrollingDirection.Up) { + RelativeSizeAxes = Axes.Y; Width = column_width; InternalChildren = new Drawable[] @@ -61,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.UI { Name = "Hit target + hit objects", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = ManiaPlayfield.HIT_TARGET_POSITION }, + Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION }, Children = new Drawable[] { new Container @@ -115,7 +117,7 @@ namespace osu.Game.Rulesets.Mania.UI { Name = "Key", RelativeSizeAxes = Axes.X, - Height = ManiaPlayfield.HIT_TARGET_POSITION, + Height = ManiaStage.HIT_TARGET_POSITION, Children = new Drawable[] { new Box @@ -205,12 +207,12 @@ namespace osu.Game.Rulesets.Mania.UI { hitObject.Depth = (float)hitObject.HitObject.StartTime; hitObject.AccentColour = AccentColour; - hitObject.OnJudgement += onJudgement; + hitObject.OnJudgement += OnJudgement; HitObjects.Add(hitObject); } - private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { if (!judgement.IsHit) return; @@ -258,5 +260,26 @@ namespace osu.Game.Rulesets.Mania.UI public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false; public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false; } + + public bool OnPressed(ManiaAction action) + { + // Play the sounds of the next hitobject + if (HitObjects.AliveObjects.Any()) + { + // If there are alive hitobjects, we can abuse the fact that AliveObjects are sorted by time (see: Add()) + HitObjects.AliveObjects.First().PlaySamples(); + } + else + { + // If not, we do a slow search - we might want to do a BinarySearch here if this becomes problematic + // We fallback to LastOrDefault() if we're beyond the last note in the map + var hitObject = HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.LastOrDefault(); + hitObject?.PlaySamples(); + } + + return false; + } + + public bool OnReleased(ManiaAction action) => false; } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index b36c9605cc..8a03f5a785 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.UI internal class DrawableManiaJudgement : DrawableJudgement { public DrawableManiaJudgement(Judgement judgement) - : base(judgement) + : base(judgement) { JudgementText.TextSize = 25; } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 7d3df6cda7..c3cbf81af1 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -3,237 +3,92 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Graphics.Containers; using System; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using System.Linq; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.UI { public class ManiaPlayfield : ScrollingPlayfield { - public const float HIT_TARGET_POSITION = 50; - - private SpecialColumnPosition specialColumnPosition; - /// - /// The style to use for the special column. - /// - public SpecialColumnPosition SpecialColumnPosition - { - get { return specialColumnPosition; } - set - { - if (IsLoaded) - throw new InvalidOperationException($"Setting {nameof(SpecialColumnPosition)} after the playfield is loaded requires re-creating the playfield."); - specialColumnPosition = value; - } - } - /// /// Whether this playfield should be inverted. This flips everything inside the playfield. /// public readonly Bindable Inverted = new Bindable(true); - private readonly FlowContainer columns; - public IEnumerable Columns => columns.Children; + public List Columns => stages.SelectMany(x => x.Columns).ToList(); + private readonly List stages = new List(); - protected override Container Content => content; - private readonly Container content; - - private List normalColumnColours = new List(); - private Color4 specialColumnColour; - - private readonly Container judgements; - - private readonly int columnCount; - - public ManiaPlayfield(int columnCount) + public ManiaPlayfield(List stageDefinitions) : base(ScrollingDirection.Up) { - this.columnCount = columnCount; + if (stageDefinitions == null) + throw new ArgumentNullException(nameof(stageDefinitions)); - if (columnCount <= 0) - throw new ArgumentException("Can't have zero or fewer columns."); + if (stageDefinitions.Count <= 0) + throw new ArgumentException("Can't have zero or fewer stages."); Inverted.Value = true; - Container topLevelContainer; - InternalChildren = new Drawable[] + GridContainer playfieldGrid; + InternalChild = playfieldGrid = new GridContainer { - new Container - { - Name = "Playfield elements", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Children = new Drawable[] - { - new Container - { - Name = "Columns mask", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Masking = true, - Children = new Drawable[] - { - new Box - { - Name = "Background", - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black - }, - columns = new FillFlowContainer - { - Name = "Columns", - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 1, Right = 1 }, - Spacing = new Vector2(1, 0) - }, - } - }, - new Container - { - Name = "Barlines mask", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = 1366, // Bar lines should only be masked on the vertical axis - BypassAutoSizeAxes = Axes.Both, - Masking = true, - Child = content = new Container - { - Name = "Bar lines", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = HIT_TARGET_POSITION } - } - }, - judgements = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Y = HIT_TARGET_POSITION + 150, - BypassAutoSizeAxes = Axes.Both - }, - topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } - } - } + RelativeSizeAxes = Axes.Both, + Content = new[] { new Drawable[stageDefinitions.Count] } }; - var currentAction = ManiaAction.Key1; - for (int i = 0; i < columnCount; i++) + var normalColumnAction = ManiaAction.Key1; + var specialColumnAction = ManiaAction.Special1; + int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) { - var c = new Column(); - c.VisibleTimeRange.BindTo(VisibleTimeRange); + var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + newStage.VisibleTimeRange.BindTo(VisibleTimeRange); + newStage.Inverted.BindTo(Inverted); - c.IsSpecial = isSpecialColumn(i); - c.Action = c.IsSpecial ? ManiaAction.Special : currentAction++; + playfieldGrid.Content[0][i] = newStage; - topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); + stages.Add(newStage); + AddNested(newStage); - columns.Add(c); - AddNested(c); + firstColumnIndex += newStage.Columns.Count; } - - Inverted.ValueChanged += invertedChanged; - Inverted.TriggerChange(); } - private void invertedChanged(bool newValue) + public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); + + public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + + private ManiaStage getStageByColumn(int column) { - Scale = new Vector2(1, newValue ? -1 : 1); - judgements.Scale = Scale; + int sum = 0; + foreach (var stage in stages) + { + sum = sum + stage.Columns.Count; + if (sum > column) + return stage; + } + + return null; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(ManiaConfigManager maniaConfig) { - normalColumnColours = new List - { - colours.RedDark, - colours.GreenDark - }; - - specialColumnColour = colours.BlueDark; - - // Set the special column + colour + key - foreach (var column in Columns) - { - if (!column.IsSpecial) - continue; - - column.AccentColour = specialColumnColour; - } - - var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); - - // We'll set the colours of the non-special columns in a separate loop, because the non-special - // column colours are mirrored across their centre and special styles mess with this - for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) - { - Color4 colour = normalColumnColours[i % normalColumnColours.Count]; - nonSpecialColumns[i].AccentColour = colour; - nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; - } + maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange); } internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { - judgements.Clear(); - judgements.Add(new DrawableManiaJudgement(judgement) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - } - - /// - /// Whether the column index is a special column for this playfield. - /// - /// The 0-based column index. - /// Whether the column is a special column. - private bool isSpecialColumn(int column) - { - switch (SpecialColumnPosition) - { - default: - case SpecialColumnPosition.Normal: - return columnCount % 2 == 1 && column == columnCount / 2; - case SpecialColumnPosition.Left: - return column == 0; - case SpecialColumnPosition.Right: - return column == columnCount - 1; - } - } - - public override void Add(DrawableHitObject h) - { - h.OnJudgement += OnJudgement; - Columns.ElementAt(((ManiaHitObject)h.HitObject).Column).Add(h); - } - - public void Add(DrawableBarLine barline) => HitObjects.Add(barline); - - protected override void Update() - { - // Due to masking differences, it is not possible to get the width of the columns container automatically - // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually - content.Width = columns.Width; + getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement); } } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 5bb980adb2..436d5c1ea6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using OpenTK; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -11,7 +10,11 @@ using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; @@ -22,6 +25,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; namespace osu.Game.Rulesets.Mania.UI { @@ -29,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.UI { public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - public IEnumerable BarLines; + public IEnumerable BarLines; public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset) @@ -38,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; var timingPoints = Beatmap.ControlPointInfo.TimingPoints; - var barLines = new List(); + var barLines = new List(); for (int i = 0; i < timingPoints.Count; i++) { @@ -50,12 +54,12 @@ namespace osu.Game.Rulesets.Mania.UI int index = 0; for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) { - barLines.Add(new DrawableBarLine(new BarLine + barLines.Add(new BarLine { StartTime = t, ControlPoint = point, BeatIndex = index - })); + }); } } @@ -68,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.UI BarLines.ForEach(Playfield.Add); } - protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.TotalColumns) + protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -76,7 +80,9 @@ namespace osu.Game.Rulesets.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Beatmap.TotalColumns); + public override int Variant => (int)(Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns; + + public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap); @@ -98,5 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this); + + protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs new file mode 100644 index 0000000000..ebd73d7dca --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -0,0 +1,229 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Mania.UI +{ + /// + /// A collection of s. + /// + internal class ManiaStage : ScrollingPlayfield + { + public const float HIT_TARGET_POSITION = 50; + + /// + /// Whether this playfield should be inverted. This flips everything inside the playfield. + /// + public readonly Bindable Inverted = new Bindable(true); + + public IReadOnlyList Columns => columnFlow.Children; + private readonly FillFlowContainer columnFlow; + + protected override Container Content => content; + private readonly Container content; + + public Container Judgements => judgements; + private readonly Container judgements; + + private readonly Container topLevelContainer; + + private List normalColumnColours = new List(); + private Color4 specialColumnColour; + + private readonly int firstColumnIndex; + private readonly StageDefinition definition; + + public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + : base(ScrollingDirection.Up) + { + this.firstColumnIndex = firstColumnIndex; + this.definition = definition; + + Name = "Stage"; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + + InternalChildren = new Drawable[] + { + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + new Container + { + Name = "Columns mask", + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Children = new Drawable[] + { + new Box + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black + }, + columnFlow = new FillFlowContainer + { + Name = "Columns", + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Left = 1, Right = 1 }, + Spacing = new Vector2(1, 0) + }, + } + }, + new Container + { + Name = "Barlines mask", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1366, // Bar lines should only be masked on the vertical axis + BypassAutoSizeAxes = Axes.Both, + Masking = true, + Child = content = new Container + { + Name = "Bar lines", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Top = HIT_TARGET_POSITION } + } + }, + judgements = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Y = HIT_TARGET_POSITION + 150, + BypassAutoSizeAxes = Axes.Both + }, + topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } + } + } + }; + + for (int i = 0; i < definition.Columns; i++) + { + var isSpecial = isSpecialColumn(i); + var column = new Column + { + IsSpecial = isSpecial, + Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ + }; + + AddColumn(column); + } + + Inverted.ValueChanged += invertedChanged; + Inverted.TriggerChange(); + } + + private void invertedChanged(bool newValue) + { + Scale = new Vector2(1, newValue ? -1 : 1); + Judgements.Scale = Scale; + } + + public void AddColumn(Column c) + { + c.VisibleTimeRange.BindTo(VisibleTimeRange); + + topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); + columnFlow.Add(c); + AddNested(c); + } + + /// + /// Whether the column index is a special column for this playfield. + /// + /// The 0-based column index. + /// Whether the column is a special column. + private bool isSpecialColumn(int column) => definition.Columns % 2 == 1 && column == definition.Columns / 2; + + public override void Add(DrawableHitObject h) + { + var maniaObject = (ManiaHitObject)h.HitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Add(h); + h.OnJudgement += OnJudgement; + } + + public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); + + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + judgements.Clear(); + judgements.Add(new DrawableManiaJudgement(judgement) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + normalColumnColours = new List + { + colours.RedDark, + colours.GreenDark + }; + + specialColumnColour = colours.BlueDark; + + // Set the special column + colour + key + foreach (var column in Columns) + { + if (!column.IsSpecial) + continue; + + column.AccentColour = specialColumnColour; + } + + var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList(); + + // We'll set the colours of the non-special columns in a separate loop, because the non-special + // column colours are mirrored across their centre and special styles mess with this + for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++) + { + Color4 colour = normalColumnColours[i % normalColumnColours.Count]; + nonSpecialColumns[i].AccentColour = colour; + nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; + } + } + + protected override void Update() + { + // Due to masking differences, it is not possible to get the width of the columns container automatically + // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually + content.Width = columnFlow.Width; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/SpecialColumnPosition.cs b/osu.Game.Rulesets.Mania/UI/SpecialColumnPosition.cs deleted file mode 100644 index de017294e4..0000000000 --- a/osu.Game.Rulesets.Mania/UI/SpecialColumnPosition.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Mania.UI -{ - public enum SpecialColumnPosition - { - /// - /// The special column will lie in the center of the columns. - /// - Normal, - /// - /// The special column will lie to the left of the columns. - /// - Left, - /// - /// The special column will lie to the right of the columns. - /// - Right - } -} diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 39f8333413..9b6b546b5f 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -58,12 +58,14 @@ + + @@ -83,7 +85,7 @@ - + @@ -115,12 +117,12 @@ + - diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index d42338f20e..3dad5b508c 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps continue; double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; - float stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; if (objectN.StartTime - endTime > stackThreshold) //We are no longer within stacking range of the next object. @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps OsuHitObject objectI = beatmap.HitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; - float stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; + double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; /* If this object is a hitcircle, then we enter this "special" case. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 3a486e7763..b4dd08eadb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override double ScoreMultiplier => 1.06; - private const float fade_in_duration_multiplier = 0.4f; + private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; public void ApplyToDrawableHitObjects(IEnumerable drawables) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 28ff4b4cdf..6d556d0304 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly RepeatPoint repeatPoint; private readonly DrawableSlider drawableSlider; - public double FadeInTime; - public double FadeOutTime; + private double animDuration; public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) : base(repeatPoint) @@ -48,11 +47,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void UpdatePreemptState() { - var animIn = Math.Min(150, repeatPoint.StartTime - FadeInTime); + animDuration = Math.Min(150, repeatPoint.SpanDuration / 2); - this.FadeIn(animIn).ScaleTo(1.2f, animIn) + this.FadeIn(animDuration).ScaleTo(1.2f, animDuration / 2) .Then() - .ScaleTo(1, 150, Easing.Out); + .ScaleTo(1, animDuration / 2, Easing.Out); } protected override void UpdateCurrentState(ArmedState state) @@ -60,18 +59,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { case ArmedState.Idle: - this.Delay(FadeOutTime - repeatPoint.StartTime).FadeOut(); + this.Delay(HitObject.TimePreempt).FadeOut(); break; case ArmedState.Miss: - this.FadeOut(160); + this.FadeOut(animDuration); break; case ArmedState.Hit: - this.FadeOut(120, Easing.OutQuint) - .ScaleTo(Scale * 1.5f, 120, Easing.OutQuint); + this.FadeOut(animDuration, Easing.OutQuint) + .ScaleTo(Scale * 1.5f, animDuration, Easing.OutQuint); break; } } - public void UpdateSnakingPosition(Vector2 start, Vector2 end) => Position = repeatPoint.RepeatIndex % 2 == 1 ? end : start; + public void UpdateSnakingPosition(Vector2 start, Vector2 end) => Position = repeatPoint.RepeatIndex % 2 == 0 ? end : start; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index eb499b5da6..af947817c0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -10,6 +10,7 @@ using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -60,7 +61,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Samples = s.Samples, SampleControlPoint = s.SampleControlPoint, TimePreempt = s.TimePreempt, - TimeFadein = s.TimeFadein + TimeFadein = s.TimeFadein, + HitWindow300 = s.HitWindow300, + HitWindow100 = s.HitWindow100, + HitWindow50 = s.HitWindow50 }) }; @@ -69,12 +73,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(InitialCircle); - var repeatDuration = s.Curve.Distance / s.Velocity; foreach (var tick in s.NestedHitObjects.OfType()) { - var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); - var fadeOutTime = repeatStartTime + repeatDuration; + var spanStartTime = s.StartTime + tick.SpanIndex * s.SpanDuration; + var fadeInTime = spanStartTime + (tick.StartTime - spanStartTime) / 2 - (tick.SpanIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); + var fadeOutTime = spanStartTime + s.SpanDuration; var drawableTick = new DrawableSliderTick(tick) { @@ -89,15 +92,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var repeatPoint in s.NestedHitObjects.OfType()) { - var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; - var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); - var fadeOutTime = repeatStartTime + repeatDuration; - var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { - FadeInTime = fadeInTime, - FadeOutTime = fadeOutTime, - Position = repeatPoint.Position, + Position = repeatPoint.Position }; repeatPoints.Add(drawableRepeatPoint); @@ -106,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - private int currentRepeat; + private int currentSpan; public bool Tracking; protected override void Update() @@ -117,17 +114,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); - int repeat = slider.RepeatAt(progress); + int span = slider.SpanAt(progress); progress = slider.ProgressAt(progress); - if (repeat > currentRepeat) - currentRepeat = repeat; + if (span > currentSpan) + currentSpan = span; //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!InitialCircle.Judgements.Any(j => j.IsHit)) InitialCircle.Position = slider.Curve.PositionAt(progress); - foreach (var c in components.OfType()) c.UpdateProgress(progress, repeat); + foreach (var c in components.OfType()) c.UpdateProgress(progress, span); foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var t in ticks.Children) t.Tracking = Ball.Tracking; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index cf2ac5124f..2fda299389 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - public void UpdateProgress(double progress, int repeat) + public void UpdateProgress(double progress, int span) { Position = slider.Curve.PositionAt(progress); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index ddd0f2d650..6fe1fda8eb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -15,6 +15,7 @@ using OpenTK; using OpenTK.Graphics.ES30; using OpenTK.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -164,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return true; } - public void UpdateProgress(double progress, int repeat) + public void UpdateProgress(double progress, int span) { double start = 0; double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; - if (repeat >= slider.RepeatCount - 1) + if (span >= slider.SpanCount() - 1) { - if (Math.Min(repeat, slider.RepeatCount - 1) % 2 == 1) + if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) { start = 0; end = snakingOut ? progress : 1; diff --git a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs index 31125121b0..54f783b664 100644 --- a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs +++ b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs @@ -5,6 +5,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public interface ISliderProgress { - void UpdateProgress(double progress, int repeat); + void UpdateProgress(double progress, int span); } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 2d1331d30a..f217ae89e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -16,12 +16,12 @@ namespace osu.Game.Rulesets.Osu.Objects public const double OBJECT_RADIUS = 64; private const double hittable_range = 300; - private const double hit_window_50 = 150; - private const double hit_window_100 = 80; - private const double hit_window_300 = 30; + public double HitWindow50 = 150; + public double HitWindow100 = 80; + public double HitWindow300 = 30; - public float TimePreempt = 600; - public float TimeFadein = 400; + public double TimePreempt = 600; + public double TimeFadein = 400; public Vector2 Position { get; set; } public float X => Position.X; @@ -50,13 +50,13 @@ namespace osu.Game.Rulesets.Osu.Objects switch (result) { default: - return 300; + return hittable_range; case HitResult.Meh: - return 150; + return HitWindow50; case HitResult.Good: - return 80; + return HitWindow100; case HitResult.Great: - return 30; + return HitWindow300; } } @@ -78,6 +78,10 @@ namespace osu.Game.Rulesets.Osu.Objects TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); + HitWindow50 = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 200, 150, 100); + HitWindow100 = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 140, 100, 60); + HitWindow300 = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 80, 50, 20); + Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } } diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs index abdbb97072..eaaa8d7a7e 100644 --- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs @@ -1,10 +1,25 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; + namespace osu.Game.Rulesets.Osu.Objects { public class RepeatPoint : OsuHitObject { public int RepeatIndex { get; set; } + public double SpanDuration { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders + // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. + if (RepeatIndex > 0) + TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2da285a434..79bb14a475 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// private const float base_scoring_distance = 100; - public readonly SliderCurve Curve = new SliderCurve(); - - public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double Duration => EndTime - StartTime; - public override Vector2 EndPosition => PositionAt(1); + public override Vector2 EndPosition => this.PositionAt(1); + + public SliderCurve Curve { get; } = new SliderCurve(); public List ControlPoints { @@ -58,7 +58,12 @@ namespace osu.Game.Rulesets.Osu.Objects internal float LazyTravelDistance; public List> RepeatSamples { get; set; } = new List>(); - public int RepeatCount { get; set; } = 1; + public int RepeatCount { get; set; } + + /// + /// The length of one span of this . + /// + public double SpanDuration => Duration / this.SpanCount(); private int stackHeight; @@ -88,18 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects TickDistance = scoringDistance / difficulty.SliderTickRate; } - public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress)); - - public double ProgressAt(double progress) - { - double p = progress * RepeatCount % 1; - if (RepeatAt(progress) % 2 == 1) - p = 1 - p; - return p; - } - - public int RepeatAt(double progress) => (int)(progress * RepeatCount); - protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); @@ -114,14 +107,13 @@ namespace osu.Game.Rulesets.Osu.Objects var length = Curve.Distance; var tickDistance = Math.Min(TickDistance, length); - var repeatDuration = length / Velocity; var minDistanceFromEnd = Velocity * 0.01; - for (var repeat = 0; repeat < RepeatCount; repeat++) + for (var span = 0; span < this.SpanCount(); span++) { - var repeatStartTime = StartTime + repeat * repeatDuration; - var reversed = repeat % 2 == 1; + var spanStartTime = StartTime + span * SpanDuration; + var reversed = span % 2 == 1; for (var d = tickDistance; d <= length; d += tickDistance) { @@ -144,8 +136,8 @@ namespace osu.Game.Rulesets.Osu.Objects AddNested(new SliderTick { - RepeatIndex = repeat, - StartTime = repeatStartTime + timeProgress * repeatDuration, + SpanIndex = span, + StartTime = spanStartTime + timeProgress * SpanDuration, Position = Curve.PositionAt(distanceProgress), StackHeight = StackHeight, Scale = Scale, @@ -158,21 +150,18 @@ namespace osu.Game.Rulesets.Osu.Objects private void createRepeatPoints() { - var repeatDuration = Distance / Velocity; - - for (var repeat = 1; repeat < RepeatCount; repeat++) + for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) { - var repeatStartTime = StartTime + repeat * repeatDuration; - AddNested(new RepeatPoint { - RepeatIndex = repeat, - StartTime = repeatStartTime, + RepeatIndex = repeatIndex, + SpanDuration = SpanDuration, + StartTime = StartTime + repeat * SpanDuration, Position = Curve.PositionAt(repeat % 2), StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, - Samples = new List(RepeatSamples[repeat]) + Samples = new List(RepeatSamples[repeatIndex]) }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index e6955b48e7..5f9a978902 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -5,6 +5,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderTick : OsuHitObject { - public int RepeatIndex { get; set; } + public int SpanIndex { get; set; } } } diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index 557e59e6f4..4f950353dc 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Game.Rulesets.Objects.Types; using OpenTK; using osu.Game.Rulesets.Osu.Objects; diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index c395c5edb8..5060137ec6 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Fast Short Slider", () => testShortHighSpeed()); AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1)); AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2)); + AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6)); AddStep("Perfect Curve", testCurve); // TODO more curve types? @@ -84,14 +85,9 @@ namespace osu.Game.Rulesets.Osu.Tests private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2) { - repeats++; // The first run through the slider is considered a repeat - var repeatSamples = new List>(); - if (repeats > 1) - { - for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); - } + for (int i = 0; i < repeats; i++) + repeatSamples.Add(new List()); var slider = new Slider { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index a3c9857d0c..e5fe288f20 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -84,7 +84,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (distanceData != null) { - int repeats = repeatsData?.RepeatCount ?? 1; + // Number of spans of the object - one for the initial length and for each repeat + int spans = repeatsData?.SpanCount() ?? 1; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); @@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps 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; + double distance = distanceData.Distance * spans * legacy_velocity_multiplier; // The velocity of the taiko hit object - calculated as the velocity of a drum roll double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double osuDuration = distance / osuVelocity; // 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.BaseDifficulty.SliderTickRate, taikoDuration / repeats); + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans); if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index cd3f6d066f..dce6c0f55b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); Assert.IsTrue(sprite.IsDrawable); Assert.AreEqual(Anchor.Centre, sprite.Origin); - Assert.AreEqual(Path.Combine("SB", "lyric", "ja-21.png"), sprite.Path); + Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); var animation = background.Elements.ElementAt(12) as StoryboardAnimation; Assert.NotNull(animation); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(animation.IsDrawable); Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType); Assert.AreEqual(Anchor.Centre, animation.Origin); - Assert.AreEqual(Path.Combine("SB", "red jitter", "red_0000.jpg"), animation.Path); + Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path); Assert.AreEqual(78993, animation.StartTime); } } diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index 87492e2332..dd5420400f 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using NUnit.Framework; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -15,6 +16,7 @@ using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Tests.Visual { + [Ignore("CI regularly hangs on this TestCase...")] public class TestCaseWaveform : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 634b5bcd20..44289e2400 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -287,15 +287,16 @@ namespace osu.Game.Beatmaps Import(archive); downloadNotification.State = ProgressNotificationState.Completed; + currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); - - currentDownloads.Remove(request); }; - request.Failure += data => + request.Failure += error => { + if (error is OperationCanceledException) return; + downloadNotification.State = ProgressNotificationState.Completed; - Logger.Error(data, "Failed to get beatmap download information"); + Logger.Error(error, "Beatmap download failed!"); currentDownloads.Remove(request); }; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index d60297a26c..a4ff060c83 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -301,6 +301,6 @@ namespace osu.Game.Beatmaps.Formats } } - private string cleanFilename(string path) => FileSafety.PathSanitise(path.Trim('\"')); + private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"'))); } } diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs new file mode 100644 index 0000000000..1f7a84c6d3 --- /dev/null +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Configuration; +using osu.Game.Rulesets; + +namespace osu.Game.Configuration +{ + public abstract class DatabasedConfigManager : ConfigManager + where T : struct + { + private readonly SettingsStore settings; + + private readonly int variant; + + private readonly List databasedSettings; + + private readonly RulesetInfo ruleset; + + protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0) + { + this.settings = settings; + this.ruleset = ruleset; + this.variant = variant; + + databasedSettings = settings.Query(ruleset?.ID, variant); + + InitialiseDefaults(); + } + + protected override void PerformLoad() + { + } + + protected override bool PerformSave() + { + return true; + } + + protected override void AddBindable(T lookup, Bindable bindable) + { + base.AddBindable(lookup, bindable); + + var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup); + if (setting != null) + { + bindable.Parse(setting.Value); + } + else + { + settings.Update(setting = new DatabasedSetting + { + Key = lookup, + Value = bindable.Value, + RulesetID = ruleset?.ID, + Variant = variant, + }); + + databasedSettings.Add(setting); + } + + bindable.ValueChanged += v => + { + setting.Value = v; + settings.Update(setting); + }; + } + } +} diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs new file mode 100644 index 0000000000..7c2f65c854 --- /dev/null +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; + +namespace osu.Game.Configuration +{ + [Table("Settings")] + public class DatabasedSetting : IHasPrimaryKey + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int? RulesetID { get; set; } + + public int? Variant { get; set; } + + [Column("Key")] + public int IntKey + { + get => (int)Key; + private set => Key = value; + } + + [Column("Value")] + public string StringValue + { + get => Value.ToString(); + set => Value = value; + } + + public object Key; + public object Value; + + public DatabasedSetting(object key, object value) + { + Key = key; + Value = value; + } + + /// + /// Constructor for derived classes that may require serialisation. + /// + public DatabasedSetting() + { + } + + public override string ToString() => $"{Key}=>{Value}"; + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 23f7fd6ac1..33810c9712 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Configuration { - public class OsuConfigManager : ConfigManager + public class OsuConfigManager : IniConfigManager { protected override void InitialiseDefaults() { diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs new file mode 100644 index 0000000000..9b18151c84 --- /dev/null +++ b/osu.Game/Configuration/SettingsStore.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Database; + +namespace osu.Game.Configuration +{ + public class SettingsStore : DatabaseBackedStore + { + public event Action SettingChanged; + + public SettingsStore(Func createContext) + : base(createContext) + { + } + + /// + /// Retrieve s for a specified ruleset/variant content. + /// + /// The ruleset's internal ID. + /// An optional variant. + /// + public List Query(int? rulesetId = null, int? variant = null) => + GetContext().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); + + public void Update(DatabasedSetting setting) + { + var context = GetContext(); + + var newValue = setting.Value; + + Refresh(ref setting); + + setting.Value = newValue; + + context.SaveChanges(); + + SettingChanged?.Invoke(); + } + } +} diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 3184696266..ec9967e097 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -34,8 +34,14 @@ namespace osu.Game.Database if (context.Entry(obj).State != EntityState.Detached) return; var id = obj.ID; - obj = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); - context.Entry(obj).Reload(); + var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); + if (foundObject != null) + { + obj = foundObject; + context.Entry(obj).Reload(); + } + else + context.Add(obj); } /// diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 091ec3487c..0fa1f238a9 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -8,9 +8,10 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Input.Bindings; +using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; +using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace osu.Game.Database @@ -22,6 +23,7 @@ namespace osu.Game.Database public DbSet BeatmapMetadata { get; set; } public DbSet BeatmapSetInfo { get; set; } public DbSet DatabasedKeyBinding { get; set; } + public DbSet DatabasedSetting { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } @@ -86,9 +88,11 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.Variant); + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); modelBuilder.Entity().HasIndex(b => b.IntAction); + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.ReferenceCount); diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index 43fab9fead..6021cf08be 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface public Color4 FillColour { - set { fill.Colour = value; } + set { fill.FadeColour(value, 150, Easing.OutQuint); } } public Color4 BackgroundColour diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs index 0bb7417088..4632c6c5f0 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -23,7 +23,7 @@ namespace osu.Game.Input.Bindings private KeyBindingStore store; - public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(); + public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); /// /// Create a new instance. diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs index f5e54775fb..dcebe939d4 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -37,7 +37,8 @@ namespace osu.Game.Input.Bindings public IEnumerable InGameKeyBindings => new[] { - new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene) + new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), + new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry) }; protected override IEnumerable KeyBindingInputQueue => @@ -65,6 +66,8 @@ namespace osu.Game.Input.Bindings // In-Game Keybindings [Description("Skip Cutscene")] - SkipCutscene + SkipCutscene, + [Description("Quick Retry (Hold)")] + QuickRetry, } } diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs new file mode 100644 index 0000000000..8e045abc6f --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -0,0 +1,329 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180125143340_Settings")] + partial class Settings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs new file mode 100644 index 0000000000..86a6b2dc5e --- /dev/null +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class Settings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateTable( + name: "Settings", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Key = table.Column(type: "INTEGER", nullable: false), + RulesetID = table.Column(type: "INTEGER", nullable: true), + Value = table.Column(type: "TEXT", nullable: true), + Variant = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Settings", x => x.ID); + }); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding", + columns: new[] { "RulesetID", "Variant" }); + + migrationBuilder.CreateIndex( + name: "IX_Settings_RulesetID_Variant", + table: "Settings", + columns: new[] { "RulesetID", "Variant" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_KeyBinding_RulesetID_Variant", + table: "KeyBinding"); + + migrationBuilder.CreateIndex( + name: "IX_KeyBinding_Variant", + table: "KeyBinding", + column: "Variant"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index cd4d3c2854..157125102f 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -193,6 +193,28 @@ namespace osu.Game.Migrations b.ToTable("BeatmapSetInfo"); }); + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => { b.Property("ID") @@ -212,7 +234,7 @@ namespace osu.Game.Migrations b.HasIndex("IntAction"); - b.HasIndex("Variant"); + b.HasIndex("RulesetID", "Variant"); b.ToTable("KeyBinding"); }); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 5559aadeb4..1d657b8664 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -126,6 +126,7 @@ namespace osu.Game.Online.API userReq.Success += u => { LocalUser.Value = u; + Username = LocalUser.Value.Username; failureCount = 0; //we're connected! diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b48e25f1fe..034b857e02 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -71,6 +71,7 @@ namespace osu.Game private OsuScreen screenStack; private VolumeControl volume; + private OnScreenDisplay onscreenDisplay; private Bindable configRuleset; public Bindable Ruleset = new Bindable(); @@ -110,7 +111,7 @@ namespace osu.Game Task.Run(() => BeatmapManager.Import(paths.ToArray())); } - dependencies.Cache(this); + dependencies.CacheAs(this); configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); @@ -195,7 +196,7 @@ namespace osu.Game }, overlayContent.Add); loadComponentSingleFile(volume = new VolumeControl(), Add); - loadComponentSingleFile(new OnScreenDisplay(), Add); + loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); //overlay elements loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); @@ -232,6 +233,7 @@ namespace osu.Game forwardLoggedErrorsToNotifications(); dependencies.Cache(settings); + dependencies.Cache(onscreenDisplay); dependencies.Cache(social); dependencies.Cache(direct); dependencies.Cache(chat); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ef02f1a7ec..d0b9634696 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -44,6 +44,8 @@ namespace osu.Game protected KeyBindingStore KeyBindingStore; + protected SettingsStore SettingsStore; + protected CursorOverrideContainer CursorOverrideContainer; protected override string MainResourceFile => @"osu.Game.Resources.dll"; @@ -93,7 +95,7 @@ namespace osu.Game dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); - dependencies.Cache(this); + dependencies.CacheAs(this); dependencies.Cache(LocalConfig); runMigrations(); @@ -109,10 +111,11 @@ namespace osu.Game dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore)); + dependencies.Cache(SettingsStore = new SettingsStore(contextFactory.GetContext)); dependencies.Cache(new OsuColour()); //this completely overrides the framework default. will need to change once we make a proper FontStore. - dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }, true); + dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 7dd6be8dc6..e0d806c90f 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.Sprites; using OpenTK.Graphics; using osu.Framework.Input; using osu.Game.Graphics.UserInterface; -using osu.Framework.Logging; using osu.Game.Online.API.Requests; using osu.Framework.Configuration; using osu.Framework.Audio.Track; @@ -65,11 +64,14 @@ namespace osu.Game.Overlays.Direct Colour = Color4.Black.Opacity(0.3f), }; + private OsuColour colours; + [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) { this.beatmaps = beatmaps; this.beatmapSetOverlay = beatmapSetOverlay; + this.colours = colours; AddInternal(content = new Container { @@ -106,6 +108,8 @@ namespace osu.Game.Overlays.Direct beatmaps.BeatmapDownloadBegan += attachDownload; } + public override bool DisposeOnDeathRemoval => true; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -180,7 +184,6 @@ namespace osu.Game.Overlays.Direct { progressBar.Current.Value = 0; progressBar.FadeOut(500); - Logger.Error(e, "Failed to get beatmap download information"); }; request.DownloadProgressed += progress => progressBar.Current.Value = progress; @@ -188,7 +191,7 @@ namespace osu.Game.Overlays.Direct request.Success += data => { progressBar.Current.Value = 1; - progressBar.FadeOut(500); + progressBar.FillColour = colours.Yellow; }; } diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index c00fb9b122..1d67bc2d90 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -145,6 +145,12 @@ namespace osu.Game.Overlays.Direct } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Playing.Value = false; + } + private TrackLoader trackLoader; private AudioManager audio; diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index d5acb9dc86..05b5bba09c 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -298,7 +298,7 @@ namespace osu.Game.Overlays Task.Run(() => { var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList(); - var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID)).Select(r => r.OnlineBeatmapSetID).ToList(); + var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList(); var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList(); // may not need scheduling; loads async internally. diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d9e08a48ba..b3140d8bd0 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -5,8 +5,6 @@ using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; @@ -20,10 +18,12 @@ using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Music; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Music; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Overlays { @@ -65,6 +65,12 @@ namespace osu.Game.Overlays AlwaysPresent = true; } + protected override bool OnDragStart(InputState state) + { + base.OnDragStart(state); + return true; + } + protected override bool OnDrag(InputState state) { if (base.OnDrag(state)) return true; diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 65803b477b..bbb2c476f4 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Extensions; +using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -118,43 +118,62 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.FrameSync), v => display(v, "Frame Limiter", v.GetDescription(), "Ctrl+F7")); - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.AudioDevice), v => display(v, "Audio Device", string.IsNullOrEmpty(v) ? "Default" : v, v)); - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay), v => display(v, "Debug Logs", v ? "visible" : "hidden", "Ctrl+F10")); - - void displayResolution() => display(null, "Screen Resolution", frameworkConfig.Get(FrameworkSetting.Width) + "x" + frameworkConfig.Get(FrameworkSetting.Height)); - - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.Width), v => displayResolution()); - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.Height), v => displayResolution()); - - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity), v => display(v, "Cursor Sensitivity", v.ToString(@"0.##x"), "Ctrl+Alt+R to reset")); - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.ActiveInputHandlers), - delegate (string v) - { - bool raw = v.Contains("Raw"); - display(raw, "Raw Input", raw ? "enabled" : "disabled", "Ctrl+Alt+R to reset"); - }); - - trackSetting(frameworkConfig.GetBindable(FrameworkSetting.WindowMode), v => display(v, "Screen Mode", v.ToString(), "Alt+Enter")); + BeginTracking(this, frameworkConfig); } - private readonly List references = new List(); + private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>(); - private void trackSetting(Bindable bindable, Action action) + /// + /// Registers a to have its settings tracked by this . + /// + /// The object that is registering the to be tracked. + /// The to be tracked. + /// If is null. + /// If is already being tracked from the same . + public void BeginTracking(object source, ITrackableConfigManager configManager) { - // we need to keep references as we bind - references.Add(bindable); + if (configManager == null) throw new ArgumentNullException(nameof(configManager)); - bindable.ValueChanged += action; + if (trackedConfigManagers.ContainsKey((source, configManager))) + throw new InvalidOperationException($"{nameof(configManager)} is already registered."); + + var trackedSettings = configManager.CreateTrackedSettings(); + if (trackedSettings == null) + return; + + configManager.LoadInto(trackedSettings); + trackedSettings.SettingChanged += display; + + trackedConfigManagers.Add((source, configManager), trackedSettings); } - private void display(object rawValue, string settingName, string settingValue, string shortcut = @"") + /// + /// Unregisters a from having its settings tracked by this . + /// + /// The object that registered the to be tracked. + /// The that is being tracked. + /// If is null. + /// If is not being tracked from the same . + public void StopTracking(object source, ITrackableConfigManager configManager) + { + if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + + if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) + throw new InvalidOperationException($"{nameof(configManager)} is not registered."); + + existing.Unload(); + existing.SettingChanged -= display; + + trackedConfigManagers.Remove((source, configManager)); + } + + private void display(SettingDescription description) { Schedule(() => { - textLine1.Text = settingName.ToUpper(); - textLine2.Text = settingValue; - textLine3.Text = shortcut.ToUpper(); + textLine1.Text = description.Name.ToUpper(); + textLine2.Text = description.Value; + textLine3.Text = description.Shortcut.ToUpper(); box.Animate( b => b.FadeIn(500, Easing.OutQuint), @@ -167,16 +186,16 @@ namespace osu.Game.Overlays int optionCount = 0; int selectedOption = -1; - if (rawValue is bool) + if (description.RawValue is bool) { optionCount = 1; - if ((bool)rawValue) selectedOption = 0; + if ((bool)description.RawValue) selectedOption = 0; } - else if (rawValue is Enum) + else if (description.RawValue is Enum) { - var values = Enum.GetValues(rawValue.GetType()); + var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; - selectedOption = Convert.ToInt32(rawValue); + selectedOption = Convert.ToInt32(description.RawValue); } textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 64fa763a0b..1b0c821b54 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(SettingsOverlay settings) + private void load(MainSettings settings) { StateContainer = settings; } diff --git a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs new file mode 100644 index 0000000000..56eac730b0 --- /dev/null +++ b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration.Tracking; + +namespace osu.Game.Rulesets.Configuration +{ + public interface IRulesetConfigManager : ITrackableConfigManager + { + } +} diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs new file mode 100644 index 0000000000..9f244f6267 --- /dev/null +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Configuration; + +namespace osu.Game.Rulesets.Configuration +{ + public abstract class RulesetConfigManager : DatabasedConfigManager, IRulesetConfigManager + where T : struct + { + protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant) + { + } + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 8680ff4e83..2db02724ed 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -136,7 +136,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public event Action ApplyCustomUpdateState; - protected void PlaySamples() => Samples.ForEach(s => s?.Play()); + /// + /// Plays all the hitsounds for this . + /// + public void PlaySamples() => Samples.ForEach(s => s?.Play()); protected override void Update() { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 840322f8a8..5fdc9a07e1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -77,6 +77,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (repeatCount > 9000) throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + // osu-stable treated the first span of the slider as a repeat, but no repeats are happening + repeatCount = Math.Max(0, repeatCount - 1); + + if (split.Length > 7) length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); @@ -84,8 +88,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(split[10], bankInfo); // One node for each repeat + the start and end nodes - // Note that the first length of the slider is considered a repeat, but there are no actual repeats happening - int nodes = Math.Max(0, repeatCount - 1) + 2; + int nodes = repeatCount + 2; // Populate node sample bank infos with the default hit object sample bank var nodeBankInfos = new List(); @@ -128,7 +131,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // Generate the final per-node samples var nodeSamples = new List>(nodes); - for (int i = 0; i <= repeatCount; i++) + for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 4e01402668..df7c8b0a83 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Objects.Types; -using System; using System.Collections.Generic; using OpenTK; using osu.Game.Audio; @@ -18,34 +17,23 @@ namespace osu.Game.Rulesets.Objects.Legacy /// private const float base_scoring_distance = 100; + /// + /// s don't need a curve since they're converted to ruleset-specific hitobjects. + /// + public SliderCurve Curve { get; } = null; public List ControlPoints { get; set; } public CurveType CurveType { get; set; } public double Distance { get; set; } public List> RepeatSamples { get; set; } - public int RepeatCount { get; set; } = 1; + public int RepeatCount { get; set; } - public double EndTime => StartTime + RepeatCount * Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; public double Duration => EndTime - StartTime; public double Velocity = 1; - public Vector2 PositionAt(double progress) - { - throw new NotImplementedException(); - } - - public double ProgressAt(double progress) - { - throw new NotImplementedException(); - } - - public int RepeatAt(double progress) - { - throw new NotImplementedException(); - } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 92df232ea9..0254a829f4 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Objects.Types /// public interface IHasCurve : IHasDistance, IHasRepeats { + /// + /// The curve. + /// + SliderCurve Curve { get; } + /// /// The control points that shape the curve. /// @@ -20,7 +25,10 @@ namespace osu.Game.Rulesets.Objects.Types /// The type of curve. /// CurveType CurveType { get; } + } + public static class HasCurveExtensions + { /// /// Computes the position on the curve at a given progress, accounting for repeat logic. /// @@ -28,20 +36,28 @@ namespace osu.Game.Rulesets.Objects.Types /// /// /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - Vector2 PositionAt(double progress); + public static Vector2 PositionAt(this IHasCurve obj, double progress) + => obj.Curve.PositionAt(obj.ProgressAt(progress)); /// /// Finds the progress along the curve, accounting for repeat logic. /// /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - double ProgressAt(double progress); + public static double ProgressAt(this IHasCurve obj, double progress) + { + double p = progress * obj.SpanCount() % 1; + if (obj.SpanAt(progress) % 2 == 1) + p = 1 - p; + return p; + } /// - /// Determines which repeat of the curve the progress point is on. + /// Determines which span of the curve the progress point is on. /// /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - /// [0, RepeatCount] where 0 is the first run. - int RepeatAt(double progress); + /// [0, SpanCount) where 0 is the first run. + public static int SpanAt(this IHasCurve obj, double progress) + => (int)(progress * obj.SpanCount()); } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index bd097cb96f..75dd3776f9 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -21,4 +21,13 @@ namespace osu.Game.Rulesets.Objects.Types /// List> RepeatSamples { get; } } + + public static class HasRepeatsExtensions + { + /// + /// The amount of times the length of this spans. + /// + /// The object that has repeats. + public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1; + } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index b17ea07355..a7fed7059b 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -36,8 +35,7 @@ namespace osu.Game.Rulesets.UI /// Whether we want our internal coordinate system to be scaled to a specified width. protected Playfield(float? customWidth = null) { - // Default height since we force relative size axes - Size = Vector2.One; + RelativeSizeAxes = Axes.Both; AddInternal(ScaledContent = new ScaledContainer { @@ -62,12 +60,6 @@ namespace osu.Game.Rulesets.UI Add(HitObjects); } - public override Axes RelativeSizeAxes - { - get { return Axes.Both; } - set { throw new InvalidOperationException($@"{nameof(Playfield)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); } - } - /// /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. /// diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index bb4466208b..8f72644b28 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -16,6 +16,9 @@ using System.Linq; using osu.Framework.Configuration; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using OpenTK; @@ -35,6 +38,11 @@ namespace osu.Game.Rulesets.UI /// public bool AspectAdjust = true; + /// + /// The selected variant. + /// + public virtual int Variant => 0; + /// /// The input manager for this RulesetContainer. /// @@ -65,6 +73,14 @@ namespace osu.Game.Rulesets.UI protected readonly Ruleset Ruleset; + private IRulesetConfigManager rulesetConfig; + private OnScreenDisplay onScreenDisplay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + /// /// A visual representation of a . /// @@ -77,6 +93,20 @@ namespace osu.Game.Rulesets.UI Cursor = CreateCursor(); } + [BackgroundDependencyLoader(true)] + private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings) + { + this.onScreenDisplay = onScreenDisplay; + + rulesetConfig = CreateConfig(Ruleset, settings); + + if (rulesetConfig != null) + { + dependencies.Cache(rulesetConfig); + onScreenDisplay?.BeginTracking(this, rulesetConfig); + } + } + public abstract ScoreProcessor CreateScoreProcessor(); /// @@ -110,11 +140,24 @@ namespace osu.Game.Rulesets.UI /// protected virtual CursorContainer CreateCursor() => null; + protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null; + /// /// Creates a Playfield. /// /// The Playfield. protected abstract Playfield CreatePlayfield(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (rulesetConfig != null) + { + onScreenDisplay?.StopTracking(this, rulesetConfig); + rulesetConfig = null; + } + } } /// @@ -154,7 +197,7 @@ namespace osu.Game.Rulesets.UI /// /// Whether the specified beatmap is assumed to be specific to the current ruleset. /// - protected readonly bool IsForCurrentRuleset; + public readonly bool IsForCurrentRuleset; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 735c81aedf..af7c1ef5aa 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -41,6 +41,12 @@ namespace osu.Game.Screens.Play.BreaksOverlay private readonly InfoContainer info; private readonly ArrowsOverlay arrowsOverlay; + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) + : this(letterboxing) + { + bindProcessor(scoreProcessor); + } + public BreakOverlay(bool letterboxing) { this.letterboxing = letterboxing; @@ -148,7 +154,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay arrowsOverlay.Hide(); } - public void BindProcessor(ScoreProcessor processor) + private void bindProcessor(ScoreProcessor processor) { info.AccuracyDisplay.Current.BindTo(processor.Accuracy); info.GradeDisplay.Current.BindTo(processor.Rank); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 557c4585ca..cdb9c2383d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -6,6 +6,8 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -39,7 +41,7 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public HUDOverlay() + public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, DecoupleableInterpolatingFramedClock decoupledClock, WorkingBeatmap working, IAdjustableClock adjustableSourceClock) { RelativeSizeAxes = Axes.Both; @@ -59,6 +61,18 @@ namespace osu.Game.Screens.Play PlayerSettingsOverlay = CreatePlayerSettingsOverlay() } }); + + BindProcessor(scoreProcessor); + BindRulesetContainer(rulesetContainer); + + Progress.Objects = rulesetContainer.Objects; + Progress.AudioClock = decoupledClock; + Progress.AllowSeeking = rulesetContainer.HasReplayLoaded; + Progress.OnSeek = pos => decoupledClock.Seek(pos); + + ModDisplay.Current.BindTo(working.Mods); + + PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock; } [BackgroundDependencyLoader(true)] @@ -116,7 +130,7 @@ namespace osu.Game.Screens.Play } } - public virtual void BindRulesetContainer(RulesetContainer rulesetContainer) + protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer) { (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); @@ -202,7 +216,7 @@ namespace osu.Game.Screens.Play protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); - public virtual void BindProcessor(ScoreProcessor processor) + protected virtual void BindProcessor(ScoreProcessor processor) { ScoreCounter?.Current.BindTo(processor.TotalScore); AccuracyCounter?.Current.BindTo(processor.Accuracy); diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs index 06db1d9df1..f5df02d758 100644 --- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -1,18 +1,18 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; -using OpenTK.Input; using osu.Framework.Allocation; using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; using OpenTK.Graphics; namespace osu.Game.Screens.Play { - public class HotkeyRetryOverlay : Container + public class HotkeyRetryOverlay : Container, IKeyBindingHandler { public Action Action; @@ -40,28 +40,20 @@ namespace osu.Game.Screens.Play }; } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + public bool OnPressed(GlobalAction action) { - if (args.Repeat) return false; + if (action != GlobalAction.QuickRetry) return false; - if (args.Key == Key.Tilde) - { - overlay.FadeIn(activate_delay, Easing.Out); - return true; - } - - return base.OnKeyDown(state, args); + overlay.FadeIn(activate_delay, Easing.Out); + return true; } - protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) + public bool OnReleased(GlobalAction action) { - if (args.Key == Key.Tilde && !fired) - { - overlay.FadeOut(fadeout_delay, Easing.Out); - return true; - } + if (action != GlobalAction.QuickRetry) return false; - return base.OnKeyUp(state, args); + overlay.FadeOut(fadeout_delay, Easing.Out); + return true; } protected override void Update() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2eb8b0a7db..ca4cb98bc8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,35 +1,35 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Logging; -using osu.Framework.Screens; -using osu.Framework.Timing; -using osu.Game.Configuration; -using osu.Game.Rulesets; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Backgrounds; using System; using System.Linq; using System.Threading.Tasks; -using osu.Framework.Threading; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Ranking; +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using osu.Framework.Logging; +using osu.Framework.Screens; +using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play.BreaksOverlay; +using osu.Game.Screens.Ranking; using osu.Game.Storyboards.Drawables; +using OpenTK; namespace osu.Game.Screens.Play { @@ -79,7 +79,6 @@ namespace osu.Game.Screens.Play #endregion - private BreakOverlay breakOverlay; private Container storyboardContainer; private DrawableStoryboard storyboard; @@ -155,6 +154,8 @@ namespace osu.Game.Screens.Play userAudioOffset.ValueChanged += v => offsetClock.Offset = v; userAudioOffset.TriggerChange(); + scoreProcessor = RulesetContainer.CreateScoreProcessor(); + Children = new Drawable[] { storyboardContainer = new Container @@ -170,13 +171,12 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, - OnPause = () => { + OnPause = () => + { pauseContainer.Retries = RestartCount; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; }, - OnResume = () => { - hudOverlay.KeyCounter.IsCounting = true; - }, + OnResume = () => hudOverlay.KeyCounter.IsCounting = true, Children = new Drawable[] { new Container @@ -186,12 +186,12 @@ namespace osu.Game.Screens.Play Child = RulesetContainer, }, new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, - hudOverlay = new HUDOverlay + hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock) { Anchor = Anchor.Centre, Origin = Anchor.Centre }, - breakOverlay = new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks) + new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -207,7 +207,8 @@ namespace osu.Game.Screens.Play }, new HotkeyRetryOverlay { - Action = () => { + Action = () => + { //we want to hide the hitrenderer immediately (looks better). //we may be able to remove this once the mouse cursor trail is improved. RulesetContainer?.Hide(); @@ -216,26 +217,9 @@ namespace osu.Game.Screens.Play } }; - scoreProcessor = RulesetContainer.CreateScoreProcessor(); - if (showStoryboard) initializeStoryboard(false); - hudOverlay.BindProcessor(scoreProcessor); - hudOverlay.BindRulesetContainer(RulesetContainer); - - hudOverlay.Progress.Objects = RulesetContainer.Objects; - hudOverlay.Progress.AudioClock = decoupledClock; - hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos); - - hudOverlay.ModDisplay.Current.BindTo(working.Mods); - - breakOverlay.BindProcessor(scoreProcessor); - - hudOverlay.PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock; - hudOverlay.PlayerSettingsOverlay.VisualSettings.AudioClock = decoupledClock; - hudOverlay.PlayerSettingsOverlay.VisualSettings.FramedClock = offsetClock; - // Bind ScoreProcessor to ourselves scoreProcessor.AllJudged += onCompletion; scoreProcessor.Failed += onFail; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 357931b878..79f00cc988 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { - dependencies.Cache(this); + dependencies.CacheAs(this); if (Footer != null) { diff --git a/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs b/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs index 0c45729a18..63000d6c54 100644 --- a/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs +++ b/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs @@ -6,7 +6,7 @@ using osu.Framework.Platform; namespace osu.Game.Screens.Tournament.Components { - public class DrawingsConfigManager : ConfigManager + public class DrawingsConfigManager : IniConfigManager { protected override string Filename => @"drawings.ini"; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 31aac56b83..1f749750e8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -265,10 +265,17 @@ + + + + + + 20180125143340_Settings.cs + @@ -314,6 +321,8 @@ + + diff --git a/osu.Game/osu.nuspec b/osu.Game/osu.nuspec index 4c529f57e5..bb7d382cee 100644 --- a/osu.Game/osu.nuspec +++ b/osu.Game/osu.nuspec @@ -12,12 +12,12 @@ click the circles. to the beat. click the circles. testing - Copyright ppy Pty Ltd 2007-2017 + Copyright ppy Pty Ltd 2007-2018 en-AU - +