From d7fede96ef98c1139960c5e15e9bb885e0a039ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Aug 2017 11:09:34 +0900 Subject: [PATCH 1/8] Fix shadow on SpriteIcon being a bit off --- osu.Game/Graphics/SpriteIcon.cs | 46 +++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index 345c6e7639..4bf4af922a 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -18,18 +19,19 @@ namespace osu.Game.Graphics public SpriteIcon() { + spriteShadow = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Depth = 2, + Y = 2, + Colour = new Color4(0f, 0f, 0f, 0.2f), + }; + InternalChildren = new[] { - spriteShadow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Position = new Vector2(0, 0.06f), - Colour = new Color4(0f, 0f, 0f, 0.2f), - Alpha = 0 - }, spriteMain = new Sprite { Anchor = Anchor.Centre, @@ -60,10 +62,32 @@ namespace osu.Game.Graphics Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); } + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.Colour) > 0) + { + //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. + //squared result for quadratic fall-off seems to give the best result. + var avgColour = (Color4)DrawInfo.Colour.AverageColour; + + spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); + } + + return base.Invalidate(invalidation, source, shallPropagate); + } + public bool Shadow { get { return spriteShadow.IsPresent; } - set { spriteShadow.Alpha = value ? 1 : 0; } + set + { + if (value == (spriteShadow.IsAlive && spriteShadow.IsLoaded)) return; + + if (value) + AddInternal(spriteShadow); + else + RemoveInternal(spriteShadow); + } } private FontAwesome icon; From 3416925233038007eb5914ac15c38855740eaad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Aug 2017 11:09:44 +0900 Subject: [PATCH 2/8] Fix alignment of icons on mod buttons --- osu.Game/Rulesets/UI/ModIcon.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 03705c19e6..986d8c92dc 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -45,10 +45,11 @@ namespace osu.Game.Rulesets.UI }, modIcon = new SpriteIcon { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, Colour = OsuColour.Gray(84), Size = new Vector2(icon_size - 35), + Y = 25, Icon = mod.Icon }, }; From 224de9cc1ea9d911b1718f200ae5b3c4b8b10ec5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Aug 2017 11:59:58 +0900 Subject: [PATCH 3/8] Implement NoFail mod --- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ osu.Game/Rulesets/Mods/ModNoFail.cs | 5 +++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++++--- osu.Game/Screens/Play/Player.cs | 6 +++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index c00847b7d8..69daedb7a7 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -45,5 +45,10 @@ namespace osu.Game.Rulesets.Mods /// The mods this mod cannot be enabled with. /// public virtual Type[] IncompatibleMods => new Type[] { }; + + /// + /// Whether we should allow fails at the + /// + public virtual bool AllowFail => true; } } diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 613e1e1d4d..d41c4e3956 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -15,5 +15,10 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; + + /// + /// We never fail, 'yo. + /// + public override bool AllowFail => false; } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 79fb34a523..cd9089c859 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -16,8 +16,9 @@ namespace osu.Game.Rulesets.Scoring { /// /// Invoked when the ScoreProcessor is in a failed state. + /// Return true if the fail was permitted. /// - public event Action Failed; + public event Func Failed; /// /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the . @@ -106,8 +107,8 @@ namespace osu.Game.Rulesets.Scoring if (alreadyFailed || !HasFailed) return; - alreadyFailed = true; - Failed?.Invoke(); + if (Failed?.Invoke() != false) + alreadyFailed = true; } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 39128eb85e..345ad60d47 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -238,13 +238,17 @@ namespace osu.Game.Screens.Play } } - private void onFail() + private bool onFail() { + if (Beatmap.Value.Mods.Value.Any(m => !m.AllowFail)) + return false; + decoupledClock.Stop(); HasFailed = true; failOverlay.Retries = RestartCount; failOverlay.Show(); + return true; } protected override void OnEntering(Screen last) From e75f438c29370ff1d618b2691559ea240d060201 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Aug 2017 16:22:10 +0900 Subject: [PATCH 4/8] Add difficulty application mods Also fixes circular references when using IJsonSerializable. --- osu.Game/Beatmaps/Beatmap.cs | 3 ++- osu.Game/Beatmaps/WorkingBeatmap.cs | 9 +++++++- .../IO/Serialization/IJsonSerializable.cs | 2 +- .../Rulesets/Mods/IApplicableToDifficulty.cs | 15 +++++++++++++ osu.Game/Rulesets/Mods/ModEasy.cs | 12 +++++++++- osu.Game/Rulesets/Mods/ModHardRock.cs | 12 +++++++++- osu.Game/Rulesets/UI/HitRenderer.cs | 6 ++++- osu.Game/Screens/Play/Player.cs | 22 ++++++++++++------- osu.Game/osu.Game.csproj | 1 + 9 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 8668b0c995..82777734bb 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.IO.Serialization; namespace osu.Game.Beatmaps { @@ -45,7 +46,7 @@ namespace osu.Game.Beatmaps /// The original beatmap to use the parameters of. public Beatmap(Beatmap original = null) { - BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo; + BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo; ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo; Breaks = original?.Breaks ?? Breaks; ComboColors = original?.ComboColors ?? ComboColors; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index a6a3b23d5d..462f94ed7c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -52,7 +52,14 @@ namespace osu.Game.Beatmaps { lock (beatmapLock) { - return beatmap ?? (beatmap = GetBeatmap()); + if (beatmap != null) return beatmap; + + beatmap = GetBeatmap(); + + // use the database-backed info. + beatmap.BeatmapInfo = BeatmapInfo; + + return beatmap; } } } diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 7dbc860979..e725742726 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -13,7 +13,7 @@ namespace osu.Game.IO.Serialization { public static string Serialize(this IJsonSerializable obj) { - return JsonConvert.SerializeObject(obj); + return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); } public static T Deserialize(this string objString) diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs new file mode 100644 index 0000000000..58f5defb5e --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make general adjustments to difficulty. + /// + public interface IApplicableToDifficulty + { + void ApplyToDifficulty(BeatmapDifficulty difficulty); + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 8eef9c70d6..075a62b0d7 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -2,11 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Game.Beatmaps; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModEasy : Mod + public abstract class ModEasy : Mod, IApplicableToDifficulty { public override string Name => "Easy"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; @@ -15,5 +16,14 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 0.5; public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 0.5f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + } } } \ No newline at end of file diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 2516c02526..fe1529d0dc 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -2,16 +2,26 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Game.Beatmaps; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHardRock : Mod + public abstract class ModHardRock : Mod, IApplicableToDifficulty { public override string Name => "Hard Rock"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; + + public void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + const float ratio = 1.4f; + difficulty.CircleSize *= ratio; + difficulty.ApproachRate *= ratio; + difficulty.DrainRate *= ratio; + difficulty.OverallDifficulty *= ratio; + } } } \ No newline at end of file diff --git a/osu.Game/Rulesets/UI/HitRenderer.cs b/osu.Game/Rulesets/UI/HitRenderer.cs index 8fe1dd3e0a..2ce868b22e 100644 --- a/osu.Game/Rulesets/UI/HitRenderer.cs +++ b/osu.Game/Rulesets/UI/HitRenderer.cs @@ -153,6 +153,10 @@ namespace osu.Game.Rulesets.UI // Convert the beatmap Beatmap = converter.Convert(beatmap.Beatmap, isForCurrentRuleset); + // Apply difficulty adjustments from mods before using Difficulty. + foreach (var mod in Mods.OfType()) + mod.ApplyToDifficulty(Beatmap.BeatmapInfo.Difficulty); + // Apply defaults foreach (var h in Beatmap.HitObjects) h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); @@ -163,7 +167,7 @@ namespace osu.Game.Rulesets.UI ApplyBeatmap(); // Add mods, should always be the last thing applied to give full control to mods - applyMods(beatmap.Mods.Value); + applyMods(Mods); } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 345ad60d47..a13e9ed369 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; using osu.Framework.Audio.Sample; +using osu.Game.Beatmaps; namespace osu.Game.Screens.Play { @@ -77,23 +78,28 @@ namespace osu.Game.Screens.Play Ruleset rulesetInstance; + WorkingBeatmap working = Beatmap.Value; + Beatmap beatmap; + try { - if (Beatmap.Value.Beatmap == null) + beatmap = working.Beatmap; + + if (beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); - ruleset = osu?.Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; + ruleset = osu?.Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); try { - HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, ruleset.ID == Beatmap.Value.BeatmapInfo.Ruleset.ID); + HitRenderer = rulesetInstance.CreateHitRendererWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID); } catch (BeatmapInvalidForRulesetException) { // we may fail to create a HitRenderer if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. - ruleset = Beatmap.Value.BeatmapInfo.Ruleset; + ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, true); } @@ -110,11 +116,11 @@ namespace osu.Game.Screens.Play return; } - adjustableSourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var firstObjectTime = HitRenderer.Objects.First().StartTime; - decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.Value.BeatmapInfo.AudioLeadIn))); + decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))); decoupledClock.ProcessFrame(); offsetClock = new FramedOffsetClock(decoupledClock); @@ -127,7 +133,7 @@ namespace osu.Game.Screens.Play { adjustableSourceClock.Reset(); - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) + foreach (var mod in working.Mods.Value.OfType()) mod.ApplyToClock(adjustableSourceClock); decoupledClock.ChangeSource(adjustableSourceClock); @@ -195,7 +201,7 @@ namespace osu.Game.Screens.Play hudOverlay.Progress.AllowSeeking = HitRenderer.HasReplayLoaded; hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos); - hudOverlay.ModDisplay.Current.BindTo(Beatmap.Value.Mods); + hudOverlay.ModDisplay.Current.BindTo(working.Mods); //bind HitRenderer to ScoreProcessor and ourselves (for a pass situation) HitRenderer.OnAllJudged += onCompletion; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3f475a34c8..d07203aa25 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -117,6 +117,7 @@ + From 6f93aa6131838cc9f64ca79be6195ffe4828a374 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Aug 2017 09:01:47 +0900 Subject: [PATCH 5/8] Fix incomplete comment --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 69daedb7a7..f54267af29 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mods public virtual Type[] IncompatibleMods => new Type[] { }; /// - /// Whether we should allow fails at the + /// Whether we should allow failing at the current point in time. /// public virtual bool AllowFail => true; } From de3b65e1fa43b6b1a7791b61cb447686a4cdf8e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Aug 2017 09:45:44 +0900 Subject: [PATCH 6/8] Fix StoredBookmarks not being serialized --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 6f4a4a8a69..ec90cabf02 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps // Editor // This bookmarks stuff is necessary because DB doesn't know how to store int[] - public string StoredBookmarks { get; internal set; } + public string StoredBookmarks { get; set; } [Ignore] [JsonIgnore] From 8941c8658cf50f7e505a9c6bb4d559af30932f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Aug 2017 09:50:04 +0900 Subject: [PATCH 7/8] Use nested containers for shadow --- osu.Game/Graphics/SpriteIcon.cs | 38 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index 4bf4af922a..93c85d67cb 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -17,21 +17,28 @@ namespace osu.Game.Graphics private readonly Sprite spriteShadow; private readonly Sprite spriteMain; + private readonly Container shadowVisibility; + public SpriteIcon() { - spriteShadow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Depth = 2, - Y = 2, - Colour = new Color4(0f, 0f, 0f, 0.2f), - }; - - InternalChildren = new[] + InternalChildren = new Drawable[] { + shadowVisibility = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Child = spriteShadow = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Y = 2, + Colour = new Color4(0f, 0f, 0f, 0.2f), + }, + Alpha = 0, + }, spriteMain = new Sprite { Anchor = Anchor.Centre, @@ -81,12 +88,7 @@ namespace osu.Game.Graphics get { return spriteShadow.IsPresent; } set { - if (value == (spriteShadow.IsAlive && spriteShadow.IsLoaded)) return; - - if (value) - AddInternal(spriteShadow); - else - RemoveInternal(spriteShadow); + shadowVisibility.Alpha = value ? 1 : 0; } } From da84f1ce39b2e74cd2dc1b5475c81a085572e40a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Aug 2017 09:56:50 +0900 Subject: [PATCH 8/8] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 9ae8979ef7..107c551767 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 9ae8979ef7ef0968c90cdbce40a04969d43633c7 +Subproject commit 107c5517670ca88dbe8c83a97e37e99ac5742ee6