diff --git a/osu-framework b/osu-framework index 7ae3cf1a10..415884e7e1 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 7ae3cf1a10fa973a49995a71cbaf768702be1cce +Subproject commit 415884e7e19f9062a4fac457a7ce19b566fa2ee7 diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index ba7414b758..223f9ce5f8 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -56,7 +56,6 @@ namespace osu.Desktop.VisualTests.Tests Result = HitResult.Hit, TaikoResult = hitResult, TimeOffset = 0, - ComboAtHit = 1, SecondHit = RNG.Next(10) == 0 } }); @@ -69,8 +68,7 @@ namespace osu.Desktop.VisualTests.Tests Judgement = new TaikoJudgement { Result = HitResult.Miss, - TimeOffset = 0, - ComboAtHit = 0 + TimeOffset = 0 } }); } diff --git a/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs index 766a492bf4..1b9bedf7fb 100644 --- a/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs @@ -19,7 +19,7 @@ namespace osu.Game.Modes.Catch.Scoring { } - protected override void OnNewJugement(CatchJudgement judgement) + protected override void OnNewJudgement(CatchJudgement judgement) { } } diff --git a/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs index c6b223af6d..0f87030e25 100644 --- a/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs @@ -19,7 +19,7 @@ namespace osu.Game.Modes.Mania.Scoring { } - protected override void OnNewJugement(ManiaJudgement judgement) + protected override void OnNewJudgement(ManiaJudgement judgement) { } } diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs index dcc25a997a..68c5ec0a45 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -38,7 +38,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables Colour = AccentColour, Hit = () => { - if (Judgement.Result.HasValue) return false; + if (Judgement.Result != HitResult.None) return false; Judgement.PositionOffset = Vector2.Zero; //todo: set to correct value UpdateJudgement(true); diff --git a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs index b71e7dbadd..0bd587e8ea 100644 --- a/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Modes.Osu.Scoring Accuracy.Value = 1; } - protected override void OnNewJugement(OsuJudgement judgement) + protected override void OnNewJudgement(OsuJudgement judgement) { if (judgement != null) { diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs index e50a685e24..f4745730db 100644 --- a/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs @@ -22,7 +22,7 @@ namespace osu.Game.Modes.Taiko.Judgements /// The result value for the combo portion of the score. /// public int ResultValueForScore => NumericResultForScore(TaikoResult); - + /// /// The result value for the accuracy portion of the score. /// @@ -32,7 +32,7 @@ namespace osu.Game.Modes.Taiko.Judgements /// The maximum result value for the combo portion of the score. /// public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT); - + /// /// The maximum result value for the accuracy portion of the score. /// @@ -45,7 +45,7 @@ namespace osu.Game.Modes.Taiko.Judgements /// /// Whether this Judgement has a secondary hit in the case of finishers. /// - public bool SecondHit; + public virtual bool SecondHit { get; set; } /// /// Computes the numeric result value for the combo portion of the score. diff --git a/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs b/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs new file mode 100644 index 0000000000..ee978d0026 --- /dev/null +++ b/osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Judgements; + +namespace osu.Game.Modes.Taiko.Judgements +{ + public class TaikoStrongHitJudgement : TaikoJudgement, IPartialJudgement + { + public bool Changed { get; set; } + + public override bool SecondHit + { + get { return base.SecondHit; } + set + { + if (base.SecondHit == value) + return; + base.SecondHit = value; + + Changed = true; + } + } + } +} diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs index 1e270c6751..5217fd9085 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableDrumRollTick.cs @@ -47,7 +47,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override bool HandleKeyPress(Key key) { - return !Judgement.Result.HasValue && UpdateJudgement(true); + return Judgement.Result == HitResult.None && UpdateJudgement(true); } } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs index ae328fe9ca..c8a7355e3c 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs @@ -68,7 +68,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override bool HandleKeyPress(Key key) { - if (Judgement.Result.HasValue) + if (Judgement.Result != HitResult.None) return false; validKeyPressed = HitKeys.Contains(key); diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs index 5e225e1dce..a6cb6ae7fa 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs @@ -5,6 +5,8 @@ using OpenTK.Input; using System; using System.Linq; using osu.Framework.Input; +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Taiko.Judgements; namespace osu.Game.Modes.Taiko.Objects.Drawable { @@ -25,9 +27,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable { } + protected override TaikoJudgement CreateJudgement() => new TaikoStrongHitJudgement(); + protected override void CheckJudgement(bool userTriggered) { - if (!Judgement.Result.HasValue) + if (Judgement.Result == HitResult.None) { base.CheckJudgement(userTriggered); return; @@ -45,7 +49,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override bool HandleKeyPress(Key key) { // Check if we've handled the first key - if (!Judgement.Result.HasValue) + if (Judgement.Result == HitResult.None) { // First key hasn't been handled yet, attempt to handle it bool handled = base.HandleKeyPress(key); diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs index d789d12c90..ccd6380542 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs @@ -219,7 +219,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable protected override bool HandleKeyPress(Key key) { - if (Judgement.Result.HasValue) + if (Judgement.Result != HitResult.None) return false; // Don't handle keys before the swell starts diff --git a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs index ec98feddae..2321ad30ee 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawable/Pieces/CirclePiece.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Backgrounds; using OpenTK.Graphics; using System; +using osu.Game.Graphics; namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces { @@ -18,7 +19,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable.Pieces /// a rounded (_[-Width-]_) figure such that a regular "circle" is the result of a parent with Width = 0. /// /// - public class CirclePiece : Container + public class CirclePiece : Container, IHasAccentColour { public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; public const float SYMBOL_BORDER = 8; diff --git a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs index 2ab31c5efb..fa7e18cadb 100644 --- a/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs @@ -96,9 +96,9 @@ namespace osu.Game.Modes.Taiko.Scoring /// /// The multiple of the original score added to the combo portion of the score - /// for correctly hitting an accented hit object with both keys. + /// for correctly hitting a strong hit object with both keys. /// - private double accentedHitScale; + private double strongHitScale; private double hpIncreaseTick; private double hpIncreaseGreat; @@ -128,12 +128,12 @@ namespace osu.Game.Modes.Taiko.Scoring hpIncreaseGood = hpMultiplierNormal * hp_hit_good; hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); - var accentedHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong); + var strongHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong); // This is a linear function that awards: - // 10 times bonus points for hitting an accented hit object with both keys with 30 accented hit objects in the map - // 3 times bonus points for hitting an accented hit object with both keys with 120 accented hit objects in the map - accentedHitScale = -7d / 90d * MathHelper.Clamp(accentedHits.Count, 30, 120) + 111d / 9d; + // 10 times bonus points for hitting a strong hit object with both keys with 30 strong hit objects in the map + // 3 times bonus points for hitting a strong hit object with both keys with 120 strong hit objects in the map + strongHitScale = -7d / 90d * MathHelper.Clamp(strongHits.Count, 30, 120) + 111d / 9d; foreach (var obj in beatmap.HitObjects) { @@ -179,7 +179,7 @@ namespace osu.Game.Modes.Taiko.Scoring maxComboPortion = comboPortion; } - protected override void OnNewJugement(TaikoJudgement judgement) + protected override void OnNewJudgement(TaikoJudgement judgement) { bool isTick = judgement is TaikoDrumRollTickJudgement; @@ -187,29 +187,12 @@ namespace osu.Game.Modes.Taiko.Scoring if (!isTick) totalHits++; + // Apply combo changes, must be done before the hit score is added + if (!isTick && judgement.Result == HitResult.Hit) + Combo.Value++; + // Apply score changes - if (judgement.Result == HitResult.Hit) - { - double baseValue = judgement.ResultValueForScore; - - // Add bonus points for hitting an accented hit object with the second key - if (judgement.SecondHit) - baseValue += baseValue * accentedHitScale; - - // Add score to portions - if (isTick) - bonusScore += baseValue; - else - { - Combo.Value++; - - // A relevance factor that needs to be applied to make higher combos more relevant - // Value is capped at 400 combo - double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base))); - - comboPortion += baseValue * comboRelevance; - } - } + addHitScore(judgement); // Apply HP changes switch (judgement.Result) @@ -235,7 +218,43 @@ namespace osu.Game.Modes.Taiko.Scoring break; } - // Compute the new score + accuracy + calculateScore(); + } + + protected override void OnJudgementChanged(TaikoJudgement judgement) + { + // Apply score changes + addHitScore(judgement); + + calculateScore(); + } + + private void addHitScore(TaikoJudgement judgement) + { + if (judgement.Result != HitResult.Hit) + return; + + double baseValue = judgement.ResultValueForScore; + + // Add increased score for hitting a strong hit object with the second key + if (judgement.SecondHit) + baseValue *= strongHitScale; + + // Add score to portions + if (judgement is TaikoDrumRollTickJudgement) + bonusScore += baseValue; + else + { + // A relevance factor that needs to be applied to make higher combos more relevant + // Value is capped at 400 combo + double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base))); + + comboPortion += baseValue * comboRelevance; + } + } + + private void calculateScore() + { int scoreForAccuracy = 0; int maxScoreForAccuracy = 0; diff --git a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj index 4e42ea5761..917be7a084 100644 --- a/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj +++ b/osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj @@ -50,6 +50,7 @@ + diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs new file mode 100644 index 0000000000..f959bc8760 --- /dev/null +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Game.Graphics.Transforms; + +namespace osu.Game.Graphics +{ + /// + /// A type of drawable that has an accent colour. + /// The accent colour is used to colorize various objects inside a drawable + /// without colorizing the drawable itself. + /// + public interface IHasAccentColour : IDrawable + { + Color4 AccentColour { get; set; } + } + + public static class AccentedColourExtensions + { + /// + /// Tweens the accent colour of a drawable to another colour. + /// + /// The drawable to apply the accent colour to. + /// The new accent colour. + /// The tween duration. + /// The tween easing. + public static void FadeAccent(this IHasAccentColour accentedDrawable, Color4 newColour, double duration = 0, EasingTypes easing = EasingTypes.None) + { + accentedDrawable.TransformTo(accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent()); + } + } +} diff --git a/osu.Game/Graphics/Transforms/TransformAccent.cs b/osu.Game/Graphics/Transforms/TransformAccent.cs new file mode 100644 index 0000000000..406d1ea9eb --- /dev/null +++ b/osu.Game/Graphics/Transforms/TransformAccent.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; +using osu.Framework.MathUtils; + +namespace osu.Game.Graphics.Transforms +{ + public class TransformAccent : Transform + { + /// + /// Current value of the transformed colour in linear colour space. + /// + public override Color4 CurrentValue + { + get + { + double time = Time?.Current ?? 0; + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + } + + public override void Apply(Drawable d) + { + base.Apply(d); + + var accented = d as IHasAccentColour; + if (accented != null) + accented.AccentColour = CurrentValue; + } + } +} diff --git a/osu.Game/Modes/Judgements/IPartialJudgement.cs b/osu.Game/Modes/Judgements/IPartialJudgement.cs new file mode 100644 index 0000000000..2ca1ffce4d --- /dev/null +++ b/osu.Game/Modes/Judgements/IPartialJudgement.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Modes.Objects.Drawables; +using osu.Game.Modes.Scoring; + +namespace osu.Game.Modes.Judgements +{ + /// + /// Inidicates that the judgement this is attached to is a partial judgement and the scoring value may change. + /// + /// This judgement will be continually processed by + /// unless the result is a miss and will trigger a full re-process of the when changed. + /// + /// + public interface IPartialJudgement + { + /// + /// Indicates that this partial judgement has changed and requires a full re-process of the . + /// + /// This is set to false once the judgement has been re-processed. + /// + /// + bool Changed { get; set; } + } +} diff --git a/osu.Game/Modes/Judgements/Judgement.cs b/osu.Game/Modes/Judgements/Judgement.cs index d916fc15de..677ec8bca9 100644 --- a/osu.Game/Modes/Judgements/Judgement.cs +++ b/osu.Game/Modes/Judgements/Judgement.cs @@ -10,7 +10,7 @@ namespace osu.Game.Modes.Judgements /// /// Whether this judgement is the result of a hit or a miss. /// - public HitResult? Result; + public HitResult Result; /// /// The offset at which this judgement occurred. @@ -20,7 +20,7 @@ namespace osu.Game.Modes.Judgements /// /// The combo after this judgement was processed. /// - public ulong? ComboAtHit; + public int ComboAtHit; /// /// The string representation for the result achieved. diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs index 3998a3e385..ed8269876e 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs @@ -93,16 +93,26 @@ namespace osu.Game.Modes.Objects.Drawables /// Whether a hit was processed. protected bool UpdateJudgement(bool userTriggered) { - if (Judgement.Result != null) + IPartialJudgement partial = Judgement as IPartialJudgement; + + // Never re-process non-partial hits + if (Judgement.Result != HitResult.None && partial == null) return false; + // Update the judgement state double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; - Judgement.TimeOffset = Time.Current - endTime; + // Update the judgement state + bool hadResult = Judgement.Result != HitResult.None; CheckJudgement(userTriggered); - if (Judgement.Result == null) + // Don't process judgements with no result + if (Judgement.Result == HitResult.None) + return false; + + // Don't process judgements that previously had results but the results were unchanged + if (hadResult && partial?.Changed != true) return false; switch (Judgement.Result) @@ -117,6 +127,9 @@ namespace osu.Game.Modes.Objects.Drawables OnJudgement?.Invoke(this); + if (partial != null) + partial.Changed = false; + return true; } diff --git a/osu.Game/Modes/Objects/Drawables/HitResult.cs b/osu.Game/Modes/Objects/Drawables/HitResult.cs index 1bbf9269bb..e036610ae2 100644 --- a/osu.Game/Modes/Objects/Drawables/HitResult.cs +++ b/osu.Game/Modes/Objects/Drawables/HitResult.cs @@ -7,8 +7,19 @@ namespace osu.Game.Modes.Objects.Drawables { public enum HitResult { + /// + /// Indicates that the object has not been judged yet. + /// + [Description("")] + None, + /// + /// Indicates that the object has been judged as a miss. + /// [Description(@"Miss")] Miss, + /// + /// Indicates that the object has been judged as a hit. + /// [Description(@"Hit")] Hit, } diff --git a/osu.Game/Modes/Scoring/ScoreProcessor.cs b/osu.Game/Modes/Scoring/ScoreProcessor.cs index 393d651dbf..a64b4d4013 100644 --- a/osu.Game/Modes/Scoring/ScoreProcessor.cs +++ b/osu.Game/Modes/Scoring/ScoreProcessor.cs @@ -141,11 +141,17 @@ namespace osu.Game.Modes.Scoring /// The judgement to add. protected void AddJudgement(TJudgement judgement) { - Judgements.Add(judgement); + bool exists = Judgements.Contains(judgement); - OnNewJugement(judgement); + if (!exists) + { + Judgements.Add(judgement); + OnNewJudgement(judgement); - judgement.ComboAtHit = (ulong)Combo.Value; + judgement.ComboAtHit = Combo.Value; + } + else + OnJudgementChanged(judgement); UpdateFailed(); } @@ -158,9 +164,21 @@ namespace osu.Game.Modes.Scoring } /// - /// Update any values that potentially need post-processing on a judgement change. + /// Updates any values that need post-processing. Invoked when a new judgement has occurred. + /// + /// This is not triggered when existing judgements are changed - for that see . + /// /// /// The judgement that triggered this calculation. - protected abstract void OnNewJugement(TJudgement judgement); + protected abstract void OnNewJudgement(TJudgement judgement); + + /// + /// Updates any values that need post-processing. Invoked when an existing judgement has changed. + /// + /// This is not triggered when a new judgement has occurred - for that see . + /// + /// + /// The judgement that triggered this calculation. + protected virtual void OnJudgementChanged(TJudgement judgement) { } } } \ No newline at end of file diff --git a/osu.Game/Modes/UI/HitRenderer.cs b/osu.Game/Modes/UI/HitRenderer.cs index 8c1e495c7a..e36d2a101c 100644 --- a/osu.Game/Modes/UI/HitRenderer.cs +++ b/osu.Game/Modes/UI/HitRenderer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Modes.UI public event Action OnJudgement; protected override Container Content => content; - protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result.HasValue); + protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result != HitResult.None); /// /// The playfield. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0dc2da48d1..ecb3f5084c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -82,7 +82,9 @@ + + @@ -98,6 +100,7 @@ +