diff --git a/osu-framework b/osu-framework index 736a139a74..2610a31337 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 736a139a748eba7cebea41a09b404d47ca589522 +Subproject commit 2610a3133721b0bc4af852342aa2a179d0e66497 diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 5f08048bf9..cdce598ce8 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -32,6 +32,10 @@ false + + $(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll + True + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True diff --git a/osu.Game.Rulesets.Catch/packages.config b/osu.Game.Rulesets.Catch/packages.config index b39a85a382..e67d3e9b34 100644 --- a/osu.Game.Rulesets.Catch/packages.config +++ b/osu.Game.Rulesets.Catch/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file 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/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index c008e71819..c3cbf81af1 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -7,10 +7,12 @@ using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Configuration; 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 @@ -78,6 +80,12 @@ namespace osu.Game.Rulesets.Mania.UI return null; } + [BackgroundDependencyLoader] + private void load(ManiaConfigManager maniaConfig) + { + maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange); + } + internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { 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 c438fe1abc..436d5c1ea6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -10,8 +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; @@ -77,11 +80,9 @@ namespace osu.Game.Rulesets.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() - { - var variantType = Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single; - return new ManiaInputManager(Ruleset.RulesetInfo, (int)variantType + 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); @@ -103,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/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index c3c0089f14..b9e7f8e60f 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -32,6 +32,10 @@ false + + $(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll + True + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True @@ -58,6 +62,7 @@ + diff --git a/osu.Game.Rulesets.Mania/packages.config b/osu.Game.Rulesets.Mania/packages.config index b39a85a382..e67d3e9b34 100644 --- a/osu.Game.Rulesets.Mania/packages.config +++ b/osu.Game.Rulesets.Mania/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file 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 6cf353eaf2..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,14 +59,14 @@ 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; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6f3bb34a89..af947817c0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -73,12 +73,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(InitialCircle); - var spanDuration = s.Curve.Distance / s.Velocity; foreach (var tick in s.NestedHitObjects.OfType()) { - var spanStartTime = s.StartTime + tick.SpanIndex * spanDuration; + 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 + spanDuration; + var fadeOutTime = spanStartTime + s.SpanDuration; var drawableTick = new DrawableSliderTick(tick) { @@ -93,15 +92,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var repeatPoint in s.NestedHitObjects.OfType()) { - var repeatStartTime = s.StartTime + (repeatPoint.RepeatIndex + 1) * spanDuration; - var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); - var fadeOutTime = repeatStartTime + spanDuration; - var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { - FadeInTime = fadeInTime, - FadeOutTime = fadeOutTime, - Position = repeatPoint.Position, + Position = repeatPoint.Position }; repeatPoints.Add(drawableRepeatPoint); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 0e7c2f3d4d..f217ae89e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects 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; 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 35635788f2..79bb14a475 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -60,6 +60,11 @@ namespace osu.Game.Rulesets.Osu.Objects public List> RepeatSamples { get; set; } = new List>(); public int RepeatCount { get; set; } + /// + /// The length of one span of this . + /// + public double SpanDuration => Duration / this.SpanCount(); + private int stackHeight; public override int StackHeight @@ -102,13 +107,12 @@ namespace osu.Game.Rulesets.Osu.Objects var length = Curve.Distance; var tickDistance = Math.Min(TickDistance, length); - var spanDuration = length / Velocity; var minDistanceFromEnd = Velocity * 0.01; for (var span = 0; span < this.SpanCount(); span++) { - var spanStartTime = StartTime + span * spanDuration; + var spanStartTime = StartTime + span * SpanDuration; var reversed = span % 2 == 1; for (var d = tickDistance; d <= length; d += tickDistance) @@ -133,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects AddNested(new SliderTick { SpanIndex = span, - StartTime = spanStartTime + timeProgress * spanDuration, + StartTime = spanStartTime + timeProgress * SpanDuration, Position = Curve.PositionAt(distanceProgress), StackHeight = StackHeight, Scale = Scale, @@ -146,16 +150,13 @@ namespace osu.Game.Rulesets.Osu.Objects private void createRepeatPoints() { - var repeatDuration = Distance / Velocity; - for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) { - var repeatStartTime = StartTime + repeat * repeatDuration; - AddNested(new RepeatPoint { RepeatIndex = repeatIndex, - StartTime = repeatStartTime, + SpanDuration = SpanDuration, + StartTime = StartTime + repeat * SpanDuration, Position = Curve.PositionAt(repeat % 2), StackHeight = StackHeight, Scale = Scale, diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index ddf24cc405..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? diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index a59d4607df..74a3883f0a 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -33,6 +33,10 @@ false + + $(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll + True + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True diff --git a/osu.Game.Rulesets.Osu/packages.config b/osu.Game.Rulesets.Osu/packages.config index b39a85a382..e67d3e9b34 100644 --- a/osu.Game.Rulesets.Osu/packages.config +++ b/osu.Game.Rulesets.Osu/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 7fb764fd0b..5795048322 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -32,6 +32,10 @@ false + + $(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll + True + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True diff --git a/osu.Game.Rulesets.Taiko/packages.config b/osu.Game.Rulesets.Taiko/packages.config index b39a85a382..e67d3e9b34 100644 --- a/osu.Game.Rulesets.Taiko/packages.config +++ b/osu.Game.Rulesets.Taiko/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 059adc6d55..d30241fae4 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -33,6 +33,10 @@ $(SolutionDir)\packages\DeepEqual.1.6.0.0\lib\net40\DeepEqual.dll + + $(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll + True + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index 62ddb99609..c16d10bf45 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -1,11 +1,12 @@ - - - - - - - + + + + + + + + \ No newline at end of file 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/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs similarity index 87% rename from osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs rename to osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 4632c6c5f0..b6bc348a52 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Input.Bindings /// A KeyBindingInputManager with a database backing for custom overrides. /// /// The type of the custom action. - public class DatabasedKeyBindingInputManager : KeyBindingContainer + public class DatabasedKeyBindingContainer : KeyBindingContainer where T : struct { private readonly RulesetInfo ruleset; @@ -31,7 +31,7 @@ namespace osu.Game.Input.Bindings /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. /// Specify how to deal with multiple matches of s and s. - public DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) + public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) : base(simultaneousMode) { this.ruleset = ruleset; diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs similarity index 91% rename from osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs rename to osu.Game/Input/Bindings/GlobalActionContainer.cs index dcebe939d4..46cda845aa 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -10,11 +10,11 @@ using osu.Framework.Input.Bindings; namespace osu.Game.Input.Bindings { - public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager, IHandleGlobalInput + public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalInput { private readonly Drawable handler; - public GlobalKeyBindingInputManager(OsuGameBase game) + public GlobalActionContainer(OsuGameBase game) { if (game is IKeyBindingHandler) handler = game; 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/OsuGame.cs b/osu.Game/OsuGame.cs index 1e175ff8c4..bd71d37f97 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using osu.Framework.Configuration; using osu.Framework.Screens; using osu.Game.Configuration; @@ -27,6 +28,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using OpenTK.Graphics; namespace osu.Game @@ -71,6 +73,7 @@ namespace osu.Game private OsuScreen screenStack; private VolumeControl volume; + private OnScreenDisplay onscreenDisplay; private Bindable configRuleset; public Bindable Ruleset = new Bindable(); @@ -79,6 +82,9 @@ namespace osu.Game private SettingsOverlay settings; + // todo: move this to SongSelect once Screen has the ability to unsuspend. + public readonly Bindable> SelectedMods = new Bindable>(new List()); + public OsuGame(string[] args = null) { this.args = args; @@ -110,7 +116,7 @@ namespace osu.Game Task.Run(() => BeatmapManager.Import(paths.ToArray())); } - dependencies.CacheAs(this); + dependencies.CacheAs(this); configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); @@ -195,7 +201,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 +238,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 8b317ca59a..937b204c81 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.CacheAs(this); + dependencies.CacheAs(this); dependencies.Cache(LocalConfig); runMigrations(); @@ -109,6 +111,7 @@ 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. @@ -209,10 +212,10 @@ namespace osu.Game { base.LoadComplete(); - GlobalKeyBindingInputManager globalBinding; + GlobalActionContainer globalBinding; CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; - CursorOverrideContainer.Child = globalBinding = new GlobalKeyBindingInputManager(this) + CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index f5b3096404..a4c1621266 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding public override FontAwesome Icon => FontAwesome.fa_osu_hot; public override string Header => "Global"; - public GlobalKeyBindingsSection(GlobalKeyBindingInputManager manager) + public GlobalKeyBindingsSection(GlobalActionContainer manager) { Add(new DefaultBindingsSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager)); @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.KeyBinding { protected override string Header => string.Empty; - public DefaultBindingsSubsection(GlobalKeyBindingInputManager manager) + public DefaultBindingsSubsection(GlobalActionContainer manager) : base(null) { Defaults = manager.GlobalKeyBindings; @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.KeyBinding { protected override string Header => "In Game"; - public InGameKeyBindingsSubsection(GlobalKeyBindingInputManager manager) : base(null) + public InGameKeyBindingsSubsection(GlobalActionContainer manager) : base(null) { Defaults = manager.InGameKeyBindings; } diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index 18e43ad39b..b311ee68c0 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); [BackgroundDependencyLoader(permitNulls: true)] - private void load(RulesetStore rulesets, GlobalKeyBindingInputManager global) + private void load(RulesetStore rulesets, GlobalActionContainer global) { AddSection(new GlobalKeyBindingsSection(global)); diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 80823c56bf..91063bfa38 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -188,17 +188,19 @@ namespace osu.Game.Overlays.Mods start = Mods.Length - 1; for (int i = start; i < Mods.Length && i >= 0; i += direction) - { - if (Mods[i].HasImplementation) - { - changeSelectedIndex(i); - return; - } - } + if (SelectAt(i)) return; Deselect(); } + public bool SelectAt(int index) + { + if (!Mods[index].HasImplementation) return false; + + changeSelectedIndex(index); + return true; + } + public void Deselect() => changeSelectedIndex(-1); private void displayMod(Mod mod) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index a43c54f994..03c1f0468c 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -113,6 +113,23 @@ namespace osu.Game.Overlays.Mods } } + /// + /// Select one or more mods in this section. + /// + /// The types of s which should be deselected. + public void SelectTypes(IEnumerable mods) + { + foreach (var button in buttons) + { + for (int i = 0; i < button.Mods.Length; i++) + { + foreach (var mod in mods) + if (mod.GetType().IsInstanceOfType(button.Mods[i])) + button.SelectAt(i); + } + } + } + protected ModSection() { AutoSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 96faa376ba..d7268fb186 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -51,6 +51,8 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets) { + SelectedMods.ValueChanged += selectedModsChanged; + LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; @@ -63,6 +65,37 @@ namespace osu.Game.Overlays.Mods Ruleset.TriggerChange(); } + private void selectedModsChanged(IEnumerable obj) + { + foreach (ModSection section in ModSectionsContainer.Children) + section.SelectTypes(obj); + + updateMods(); + } + + private void updateMods() + { + double multiplier = 1.0; + bool ranked = true; + + foreach (Mod mod in SelectedMods.Value) + { + multiplier *= mod.ScoreMultiplier; + ranked &= mod.Ranked; + } + + MultiplierLabel.Text = $"{multiplier:N2}x"; + if (!ranked) + MultiplierLabel.Text += " (Unranked)"; + + if (multiplier > 1.0) + MultiplierLabel.FadeColour(HighMultiplierColour, 200); + else if (multiplier < 1.0) + MultiplierLabel.FadeColour(LowMultiplierColour, 200); + else + MultiplierLabel.FadeColour(Color4.White, 200); + } + protected override void PopOut() { base.PopOut(); @@ -97,6 +130,7 @@ namespace osu.Game.Overlays.Mods { foreach (ModSection section in ModSectionsContainer.Children) section.DeselectAll(); + refreshSelectedMods(); } @@ -119,30 +153,7 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - private void refreshSelectedMods() - { - SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); - - double multiplier = 1.0; - bool ranked = true; - - foreach (Mod mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - ranked &= mod.Ranked; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - if (!ranked) - MultiplierLabel.Text += " (Unranked)"; - - if (multiplier > 1.0) - MultiplierLabel.FadeColour(HighMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierLabel.FadeColour(LowMultiplierColour, 200); - else - MultiplierLabel.FadeColour(Color4.White, 200); - } + private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); public ModSelectOverlay() { 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/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/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 375af75347..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; + } + } } /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index e32b38a013..037f9136a8 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { - public class RulesetKeyBindingContainer : DatabasedKeyBindingInputManager + public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer { public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ca4cb98bc8..72864482d5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -209,6 +209,8 @@ namespace osu.Game.Screens.Play { Action = () => { + if (!IsCurrentScreen) return; + //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(); @@ -274,6 +276,8 @@ namespace osu.Game.Screens.Play { onCompletionEvent = Schedule(delegate { + if (!IsCurrentScreen) return; + var score = new Score { Beatmap = Beatmap.Value.BeatmapInfo, diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 6fdd38ce30..739bc39269 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -47,10 +48,13 @@ namespace osu.Game.Screens.Select private SampleChannel sampleConfirm; [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay) + private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay, OsuGame game) { sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection"); + if (game != null) + modSelect.SelectedMods.BindTo(game.SelectedMods); + Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); @@ -121,6 +125,9 @@ namespace osu.Game.Screens.Select if (Beatmap.Value.Track != null) Beatmap.Value.Track.Looping = false; + Beatmap.Value.Mods.UnbindBindings(); + Beatmap.Value.Mods.Value = new Mod[] { }; + return false; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 79f00cc988..2421a4fdfe 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.CacheAs(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 85da54e317..4e048d60b9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -91,6 +91,10 @@ $(SolutionDir)\packages\Humanizer.Core.2.2.0\lib\netstandard1.0\Humanizer.dll + + $(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll + True + $(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll @@ -265,10 +269,17 @@ + + + + + + 20180125143340_Settings.cs + @@ -314,6 +325,8 @@ + + @@ -433,8 +446,8 @@ - - + + diff --git a/osu.Game/packages.config b/osu.Game/packages.config index 2938739eef..0216c8ae67 100644 --- a/osu.Game/packages.config +++ b/osu.Game/packages.config @@ -47,6 +47,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste +