diff --git a/.editorconfig b/.editorconfig index 67f98f94eb..a5f7795882 100644 --- a/.editorconfig +++ b/.editorconfig @@ -191,4 +191,7 @@ dotnet_diagnostic.IDE0052.severity = silent #Rules for disposable dotnet_diagnostic.IDE0067.severity = none dotnet_diagnostic.IDE0068.severity = none -dotnet_diagnostic.IDE0069.severity = none \ No newline at end of file +dotnet_diagnostic.IDE0069.severity = none + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 2cd40c8675..2d3478f256 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4fd0e5e8c7..8c278604aa 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -113,7 +113,7 @@ platform :ios do souyuz( platform: "ios", - plist_path: "../osu.iOS/Info.plist" + plist_path: "osu.iOS/Info.plist" ) end @@ -127,7 +127,7 @@ platform :ios do end lane :update_version do |options| - options[:plist_path] = '../osu.iOS/Info.plist' + options[:plist_path] = 'osu.iOS/Info.plist' app_version(options) end diff --git a/osu.Android.props b/osu.Android.props index f3fb949f76..1a76a24496 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index df54df7b01..8c48158acd 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })] [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })] [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })] + [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index d6bba3d55e..3c636a5b97 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; @@ -38,7 +40,11 @@ namespace osu.Game.Rulesets.Catch.Tests new Vector2(width, 0) }), StartTime = i * 2000, - NewCombo = i % 8 == 0 + NewCombo = i % 8 == 0, + Samples = new List(new[] + { + new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 } + }) }); } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index bb14988414..15e6e98f5a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps if (amount > 0) { // Clamp to the right bound - if (position + amount < 1) + if (position + amount < CatchPlayfield.WIDTH) position += amount; } else diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 04932ecdbb..5985ec9b68 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -27,6 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects set => x = value; } + /// + /// Whether this object can be placed on the catcher's plate. + /// + public virtual bool CanBePlated => false; + /// /// A random offset applied to , set by the . /// @@ -100,6 +105,14 @@ namespace osu.Game.Rulesets.Catch.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; } + /// + /// Represents a single object that can be caught by the catcher. + /// + public abstract class PalpableCatchHitObject : CatchHitObject + { + public override bool CanBePlated => true; + } + public enum FruitVisualRepresentation { Pear, diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index c6345a9df7..2fe017dc62 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -15,14 +15,12 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public abstract class PalpableCatchHitObject : DrawableCatchHitObject - where TObject : CatchHitObject + public abstract class PalpableDrawableCatchHitObject : DrawableCatchHitObject + where TObject : PalpableCatchHitObject { - public override bool CanBePlated => true; - protected Container ScaleContainer { get; private set; } - protected PalpableCatchHitObject(TObject hitObject) + protected PalpableDrawableCatchHitObject(TObject hitObject) : base(hitObject) { Origin = Anchor.Centre; @@ -65,9 +63,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public abstract class DrawableCatchHitObject : DrawableHitObject { - public virtual bool CanBePlated => false; - - public virtual bool StaysOnPlate => CanBePlated; + public virtual bool StaysOnPlate => HitObject.CanBePlated; public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index cad8892283..77ae7e9a54 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -9,7 +9,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableDroplet : PalpableCatchHitObject + public class DrawableDroplet : PalpableDrawableCatchHitObject { public override bool StaysOnPlate => false; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index fae5a10d04..c1c34e4157 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -8,7 +8,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Objects.Drawables { - public class DrawableFruit : PalpableCatchHitObject + public class DrawableFruit : PalpableDrawableCatchHitObject { public DrawableFruit(Fruit h) : base(h) diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs index 7b0bb3f0ae..9c1004a04b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Catch.Objects { - public class Droplet : CatchHitObject + public class Droplet : PalpableCatchHitObject { public override Judgement CreateJudgement() => new CatchDropletJudgement(); } diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index 6f0423b420..43486796ad 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Catch.Objects { - public class Fruit : CatchHitObject + public class Fruit : PalpableCatchHitObject { public override Judgement CreateJudgement() => new CatchJudgement(); } diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json new file mode 100644 index 0000000000..3bde97070c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset-expected-conversion.json @@ -0,0 +1,17 @@ +{ + "Mappings": [{ + "StartTime": 3368, + "Objects": [{ + "StartTime": 3368, + "Position": 374 + }] + }, + { + "StartTime": 3501, + "Objects": [{ + "StartTime": 3501, + "Position": 446 + }] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu new file mode 100644 index 0000000000..6630f369d5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/right-bound-hr-offset.osu @@ -0,0 +1,20 @@ +osu file format v14 + +[General] +StackLeniency: 0.7 +Mode: 2 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:9.6 +ApproachRate:9.6 +SliderMultiplier:1.9 +SliderTickRate:1 + +[TimingPoints] +2169,266.666666666667,4,2,1,70,1,0 + +[HitObjects] +374,60,3368,1,0,0:0:0:0: +410,146,3501,1,2,0:1:0:0: \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index 8ee23461ba..efc1b24ed5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -10,15 +10,21 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { + private const float playfield_size_adjust = 0.8f; + protected override Container Content => content; private readonly Container content; public CatchPlayfieldAdjustmentContainer() { - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; + // because we are using centre anchor/origin, we will need to limit visibility in the future + // to ensure tall windows do not get a readability advantage. + // it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values + // which are compatible with TopCentre alignment. + Anchor = Anchor.Centre; + Origin = Anchor.Centre; - Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate + Size = new Vector2(playfield_size_adjust); InternalChild = new Container { @@ -27,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, FillAspectRatio = 4f / 3, - Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both } + Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, } }; } @@ -40,8 +46,14 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); + // in stable, fruit fall vertically from -100 to 340. + // to emulate this, we want to make our playfield 440 gameplay pixels high. + // we then offset it -100 vertically in the position set below. + const float stable_v_offset_ratio = 440 / 384f; + Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH); - Size = Vector2.Divide(Vector2.One, Scale); + Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X); + Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale); } } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8820dff730..e4a3c01dbc 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -216,6 +216,9 @@ namespace osu.Game.Rulesets.Catch.UI /// Whether the catch is possible. public bool AttemptCatch(CatchHitObject fruit) { + if (!fruit.CanBePlated) + return false; + var halfCatchWidth = catchWidth * 0.5f; // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 4255c3b1af..03ebf01b9b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI lastPlateableFruit.OnLoadComplete += _ => action(); } - if (result.IsHit && fruit.CanBePlated) + if (result.IsHit && fruit.HitObject.CanBePlated) { // create a new (cloned) fruit to stay on the plate. the original is faded out immediately. var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs new file mode 100644 index 0000000000..f2cc254e38 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModInvert : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestInversion() => CreateModTest(new ModTestData + { + Mod = new ManiaModInvert(), + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + }); + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs index efcfe11dad..5fa687298a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -15,7 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components AccentColour.Value = colours.Yellow; Background.Alpha = 0.5f; - Foreground.Alpha = 0; } + + protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0); } } diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 294aab1e4e..28e5d2cc1b 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Judgements { public class HoldNoteTickJudgement : ManiaJudgement { - protected override int NumericResultFor(HitResult result) => 20; + protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 68dce8b139..2795868c97 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -220,6 +220,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), new ManiaModDifficultyAdjust(), + new ManiaModInvert(), }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs new file mode 100644 index 0000000000..1ea45c295c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion + { + public override string Name => "Invert"; + + public override string Acronym => "IN"; + public override double ScoreMultiplier => 1; + + public override string Description => "Hold the keys. To the beat."; + + public override IconUsage? Icon => FontAwesome.Solid.YinYang; + + public override ModType Type => ModType.Conversion; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var maniaBeatmap = (ManiaBeatmap)beatmap; + + var newObjects = new List(); + + foreach (var column in maniaBeatmap.HitObjects.GroupBy(h => h.Column)) + { + var newColumnObjects = new List(); + + var locations = column.OfType().Select(n => (startTime: n.StartTime, samples: n.Samples)) + .Concat(column.OfType().SelectMany(h => new[] + { + (startTime: h.StartTime, samples: h.GetNodeSamples(0)), + (startTime: h.EndTime, samples: h.GetNodeSamples(1)) + })) + .OrderBy(h => h.startTime).ToList(); + + for (int i = 0; i < locations.Count - 1; i++) + { + // Full duration of the hold note. + double duration = locations[i + 1].startTime - locations[i].startTime; + + // Beat length at the end of the hold note. + double beatLength = beatmap.ControlPointInfo.TimingPointAt(locations[i + 1].startTime).BeatLength; + + // Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes. + duration = Math.Max(duration / 2, duration - beatLength / 4); + + newColumnObjects.Add(new HoldNote + { + Column = column.Key, + StartTime = locations[i].startTime, + Duration = duration, + NodeSamples = new List> { locations[i].samples, Array.Empty() } + }); + } + + newObjects.AddRange(newColumnObjects); + } + + maniaBeatmap.HitObjects = newObjects.OrderBy(h => h.StartTime).ToList(); + + // No breaks + maniaBeatmap.Breaks.Clear(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index d2412df7c3..229ce355d7 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private readonly Container tailMaskingContainer; + private readonly SkinnableDrawable bodyPiece; + /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. /// @@ -79,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyOffsetContainer = new Container { RelativeSizeAxes = Axes.X, - Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece + Child = bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece { RelativeSizeAxes = Axes.Both }) @@ -191,6 +193,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // Samples are played by the head/tail notes. } + public override void OnKilled() + { + base.OnKilled(); + (bodyPiece.Drawable as IHoldNoteBody)?.Recycle(); + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs index bc4a095395..9999983af5 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/DefaultBodyPiece.cs @@ -19,24 +19,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// /// Represents length-wise portion of a hold note. /// - public class DefaultBodyPiece : CompositeDrawable + public class DefaultBodyPiece : CompositeDrawable, IHoldNoteBody { protected readonly Bindable AccentColour = new Bindable(); - - private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); - private readonly IBindable isHitting = new Bindable(); + protected readonly IBindable IsHitting = new Bindable(); protected Drawable Background { get; private set; } - protected BufferedContainer Foreground { get; private set; } - - private BufferedContainer subtractionContainer; - private Container subtractionLayer; + private Container foregroundContainer; public DefaultBodyPiece() { Blending = BlendingParameters.Additive; - - AddLayout(subtractionCache); } [BackgroundDependencyLoader(true)] @@ -45,7 +38,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces InternalChildren = new[] { Background = new Box { RelativeSizeAxes = Axes.Both }, - Foreground = new BufferedContainer + foregroundContainer = new Container { RelativeSizeAxes = Axes.Both } + }; + + if (drawableObject != null) + { + var holdNote = (DrawableHoldNote)drawableObject; + + AccentColour.BindTo(drawableObject.AccentColour); + IsHitting.BindTo(holdNote.IsHitting); + } + + AccentColour.BindValueChanged(onAccentChanged, true); + + Recycle(); + } + + public void Recycle() => foregroundContainer.Child = CreateForeground(); + + protected virtual Drawable CreateForeground() => new ForegroundPiece + { + AccentColour = { BindTarget = AccentColour }, + IsHitting = { BindTarget = IsHitting } + }; + + private void onAccentChanged(ValueChangedEvent accent) => Background.Colour = accent.NewValue.Opacity(0.7f); + + private class ForegroundPiece : CompositeDrawable + { + public readonly Bindable AccentColour = new Bindable(); + public readonly IBindable IsHitting = new Bindable(); + + private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); + + private BufferedContainer foregroundBuffer; + private BufferedContainer subtractionBuffer; + private Container subtractionLayer; + + public ForegroundPiece() + { + RelativeSizeAxes = Axes.Both; + + AddLayout(subtractionCache); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = foregroundBuffer = new BufferedContainer { Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, @@ -53,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both }, - subtractionContainer = new BufferedContainer + subtractionBuffer = new BufferedContainer { RelativeSizeAxes = Axes.Both, // This is needed because we're blending with another object @@ -77,60 +117,51 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } } - } - }; - - if (drawableObject != null) - { - var holdNote = (DrawableHoldNote)drawableObject; - - AccentColour.BindTo(drawableObject.AccentColour); - isHitting.BindTo(holdNote.IsHitting); - } - - AccentColour.BindValueChanged(onAccentChanged, true); - isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true); - } - - private void onAccentChanged(ValueChangedEvent accent) - { - Foreground.Colour = accent.NewValue.Opacity(0.5f); - Background.Colour = accent.NewValue.Opacity(0.7f); - - const float animation_length = 50; - - Foreground.ClearTransforms(false, nameof(Foreground.Colour)); - - if (isHitting.Value) - { - // wait for the next sync point - double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); - using (Foreground.BeginDelayedSequence(synchronisedOffset)) - Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); - } - - subtractionCache.Invalidate(); - } - - protected override void Update() - { - base.Update(); - - if (!subtractionCache.IsValid) - { - subtractionLayer.Width = 5; - subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); - subtractionLayer.EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.White, - Type = EdgeEffectType.Glow, - Radius = DrawWidth }; - Foreground.ForceRedraw(); - subtractionContainer.ForceRedraw(); + AccentColour.BindValueChanged(onAccentChanged, true); + IsHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent(AccentColour.Value, AccentColour.Value)), true); + } - subtractionCache.Validate(); + private void onAccentChanged(ValueChangedEvent accent) + { + foregroundBuffer.Colour = accent.NewValue.Opacity(0.5f); + + const float animation_length = 50; + + foregroundBuffer.ClearTransforms(false, nameof(foregroundBuffer.Colour)); + + if (IsHitting.Value) + { + // wait for the next sync point + double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); + using (foregroundBuffer.BeginDelayedSequence(synchronisedOffset)) + foregroundBuffer.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(foregroundBuffer.Colour, animation_length).Loop(); + } + + subtractionCache.Invalidate(); + } + + protected override void Update() + { + base.Update(); + + if (!subtractionCache.IsValid) + { + subtractionLayer.Width = 5; + subtractionLayer.Height = Math.Max(0, DrawHeight - DrawWidth); + subtractionLayer.EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.White, + Type = EdgeEffectType.Glow, + Radius = DrawWidth + }; + + foregroundBuffer.ForceRedraw(); + subtractionBuffer.ForceRedraw(); + + subtractionCache.Validate(); + } } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs new file mode 100644 index 0000000000..ac3792c01d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/IHoldNoteBody.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces +{ + /// + /// Interface for mania hold note bodies. + /// + public interface IHoldNoteBody + { + /// + /// Recycles the contents of this to free used resources. + /// + void Recycle(); + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index a100c9a58e..6cc7ff92d3 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mania.Objects { StartTime = StartTime, Column = Column, - Samples = getNodeSamples(0), + Samples = GetNodeSamples(0), }); AddNested(Tail = new TailNote { StartTime = EndTime, Column = Column, - Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1), + Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1), }); } @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - private IList getNodeSamples(int nodeIndex) => + public IList GetNodeSamples(int nodeIndex) => nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples; } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index 12747924de..7c5d41efcf 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -66,6 +67,9 @@ namespace osu.Game.Rulesets.Mania.Skinning public void Animate(JudgementResult result) { + if (result.Judgement is HoldNoteTickJudgement) + return; + (explosion as IFramedAnimation)?.GotoFrame(0); explosion?.FadeInFromZero(80) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 94b5ee9486..7f5b9a6ee0 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The minimum time range. This occurs at a of 40. /// - public const double MIN_TIME_RANGE = 150; + public const double MIN_TIME_RANGE = 340; /// /// The maximum time range. This occurs at a of 1. /// - public const double MAX_TIME_RANGE = 6000; + public const double MAX_TIME_RANGE = 13720; protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 9c4b6f774f..0b1232b8db 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { - protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK; + protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index de3ae27e55..f54e7a9a15 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects { public override bool AffectsCombo => false; - protected override int NumericResultFor(HitResult result) => SCORE_PER_TICK; + protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; } diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs new file mode 100644 index 0000000000..b0baf0385e --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneScoreProcessor : OsuTestScene + { + [Test] + public void TestNoScoreIncreaseFromMiss() + { + var beatmap = new Beatmap { HitObjects = { new TestHitObject() } }; + + var scoreProcessor = new ScoreProcessor(); + scoreProcessor.ApplyBeatmap(beatmap); + + // Apply a miss judgement + scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss }); + + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); + } + + private class TestHitObject : HitObject + { + public override Judgement CreateJudgement() => new TestJudgement(); + } + + private class TestJudgement : Judgement + { + protected override int NumericResultFor(HitResult result) => 100; + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index e698d31176..4fac7bb45f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -71,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); - AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddStep("exit loader", () => loader.Exit()); AddUntilStep("wait for not current", () => !loader.IsCurrentScreen()); AddAssert("player did not load", () => player == null); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index 030d420ec0..09b4f9b761 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -20,7 +20,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - TextSize = 40, Margin = new MarginPadding(20), }; Add(score); @@ -30,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Margin = new MarginPadding(10), - TextSize = 40, }; Add(comboCounter); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index b4985cad9f..f819ae4682 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -4,8 +4,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets; using osuTK.Input; @@ -15,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneToolbar : OsuManualInputManagerTestScene { - private Toolbar toolbar; + private TestToolbar toolbar; [Resolved] private RulesetStore rulesets { get; set; } @@ -23,7 +25,7 @@ namespace osu.Game.Tests.Visual.Menus [SetUp] public void SetUp() => Schedule(() => { - Child = toolbar = new Toolbar { State = { Value = Visibility.Visible } }; + Child = toolbar = new TestToolbar { State = { Value = Visibility.Visible } }; }); [Test] @@ -72,5 +74,24 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected)); } } + + [TestCase(OverlayActivation.All)] + [TestCase(OverlayActivation.Disabled)] + public void TestRespectsOverlayActivation(OverlayActivation mode) + { + AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode); + AddStep("hide toolbar", () => toolbar.Hide()); + AddStep("try to show toolbar", () => toolbar.Show()); + + if (mode == OverlayActivation.Disabled) + AddAssert("toolbar still hidden", () => toolbar.State.Value == Visibility.Hidden); + else + AddAssert("toolbar is visible", () => toolbar.State.Value == Visibility.Visible); + } + + public class TestToolbar : Toolbar + { + public new Bindable OverlayActivationMode => base.OverlayActivationMode; + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs index 61859c9da3..3924b0333f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Multiplayer { @@ -10,11 +12,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected override bool UseOnlineAPI => true; + [Cached] + private MusicController musicController { get; set; } = new MusicController(); + public TestSceneMultiScreen() { Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); - AddStep(@"show", () => LoadScreen(multi)); + AddStep("show", () => LoadScreen(multi)); + AddUntilStep("wait for loaded", () => multi.IsLoaded); } } } diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs index 2e7484542a..695c6d6f3e 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -127,21 +128,29 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private class MatchScoreCounter : ScoreCounter { + private OsuSpriteText displayedSpriteText; + public MatchScoreCounter() { Margin = new MarginPadding { Top = bar_height, Horizontal = 10 }; - - Winning = false; - - DisplayedCountSpriteText.Spacing = new Vector2(-6); } public bool Winning { - set => DisplayedCountSpriteText.Font = value + set => updateFont(value); + } + + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => + { + displayedSpriteText = s; + displayedSpriteText.Spacing = new Vector2(-6); + updateFont(false); + }); + + private void updateFont(bool winning) + => displayedSpriteText.Font = winning ? OsuFont.Torus.With(weight: FontWeight.Bold, size: 50, fixedWidth: true) : OsuFont.Torus.With(weight: FontWeight.Regular, size: 40, fixedWidth: true); - } } } } diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 7b1a174c1e..307ee1c773 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -31,6 +31,7 @@ namespace osu.Game.Tournament public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff"); private Drawable heightWarning; private Bindable windowSize; + private Bindable windowMode; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) @@ -43,6 +44,12 @@ namespace osu.Game.Tournament heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; }), true); + windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); + windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + { + windowMode.Value = WindowMode.Windowed; + }), true); + AddRange(new[] { new Container diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index ac399e37c4..b4bcf285b9 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -109,6 +109,15 @@ namespace osu.Game.Beatmaps // Convert IBeatmap converted = converter.Convert(); + // Apply conversion mods to the result + foreach (var mod in mods.OfType()) + { + if (cancellationSource.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); + + mod.ApplyToBeatmap(converted); + } + // Apply difficulty mods if (mods.Any(m => m is IApplicableToDifficulty)) { diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 0d173e2d3e..1ec4dfc91a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -74,9 +74,9 @@ namespace osu.Game.Graphics.UserInterface protected override Color4 SelectionColour => new Color4(249, 90, 255, 255); - protected override void OnTextAdded(string added) + protected override void OnUserTextAdded(string added) { - base.OnTextAdded(added); + base.OnUserTextAdded(added); if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) capsTextAddedSample?.Play(); @@ -84,9 +84,9 @@ namespace osu.Game.Graphics.UserInterface textAddedSamples[RNG.Next(0, 3)]?.Play(); } - protected override void OnTextRemoved(string removed) + protected override void OnUserTextRemoved(string removed) { - base.OnTextRemoved(removed); + base.OnUserTextRemoved(removed); textRemovedSample?.Play(); } diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index 940c9808ce..3ea9c1053c 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Utils; namespace osu.Game.Graphics.UserInterface @@ -23,12 +25,11 @@ namespace osu.Game.Graphics.UserInterface public PercentageCounter() { - DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true); Current.Value = DisplayedCount = 1.0f; } [BackgroundDependencyLoader] - private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + private void load(OsuColour colours) => Colour = colours.BlueLighter; protected override string FormatCount(double count) => count.FormatAccuracy(); @@ -37,6 +38,9 @@ namespace osu.Game.Graphics.UserInterface return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; } + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f, fixedWidth: true)); + public override void Increment(double amount) { Current.Value += amount; diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index cd244ed7e6..6763198213 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -7,12 +7,12 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using System; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; -using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public abstract class RollingCounter : Container, IHasAccentColour + public abstract class RollingCounter : Container where T : struct, IEquatable { /// @@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface /// public Bindable Current = new Bindable(); - protected SpriteText DisplayedCountSpriteText; + private SpriteText displayedCountSpriteText; /// /// If true, the roll-up duration will be proportional to change in value. @@ -46,57 +46,39 @@ namespace osu.Game.Graphics.UserInterface public virtual T DisplayedCount { get => displayedCount; - set { if (EqualityComparer.Default.Equals(displayedCount, value)) return; displayedCount = value; - DisplayedCountSpriteText.Text = FormatCount(value); + if (displayedCountSpriteText != null) + displayedCountSpriteText.Text = FormatCount(value); } } public abstract void Increment(T amount); - public float TextSize - { - get => DisplayedCountSpriteText.Font.Size; - set => DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: value); - } - - public Color4 AccentColour - { - get => DisplayedCountSpriteText.Colour; - set => DisplayedCountSpriteText.Colour = value; - } - /// /// Skeleton of a numeric counter which value rolls over time. /// protected RollingCounter() { - Children = new Drawable[] - { - DisplayedCountSpriteText = new OsuSpriteText { Font = OsuFont.Numeric } - }; - - TextSize = 40; AutoSizeAxes = Axes.Both; - DisplayedCount = Current.Value; - Current.ValueChanged += val => { - if (IsLoaded) TransformCount(displayedCount, val.NewValue); + if (IsLoaded) + TransformCount(DisplayedCount, val.NewValue); }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - - DisplayedCountSpriteText.Text = FormatCount(Current.Value); + displayedCountSpriteText = CreateSpriteText(); + displayedCountSpriteText.Text = FormatCount(DisplayedCount); + Child = displayedCountSpriteText; } /// @@ -167,5 +149,10 @@ namespace osu.Game.Graphics.UserInterface this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing); } + + protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40f), + }; } } diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 01d8edaecf..faabe69f87 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -24,12 +25,11 @@ namespace osu.Game.Graphics.UserInterface /// How many leading zeroes the counter will have. public ScoreCounter(uint leading = 0) { - DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true); LeadingZeroes = leading; } [BackgroundDependencyLoader] - private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + private void load(OsuColour colours) => Colour = colours.BlueLighter; protected override double GetProportionalDuration(double currentValue, double newValue) { @@ -49,6 +49,9 @@ namespace osu.Game.Graphics.UserInterface return ((long)count).ToString(format); } + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true)); + public override void Increment(double amount) { Current.Value += amount; diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index af03cbb63e..aac0166774 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -19,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + private void load(OsuColour colours) => Colour = colours.BlueLighter; protected override string FormatCount(int count) { @@ -35,5 +37,8 @@ namespace osu.Game.Graphics.UserInterface { Current.Value += amount; } + + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f)); } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 6ae420b162..45b07581ec 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -53,6 +53,7 @@ namespace osu.Game.Input.Bindings public IEnumerable InGameKeyBindings => new[] { new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), + new KeyBinding(InputKey.ExtraMouseButton2, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index b60d71cfe7..87b283f6b5 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -82,20 +82,10 @@ namespace osu.Game.Online.Leaderboards Children = new Drawable[] { - new Container + new RankLabel(rank) { RelativeSizeAxes = Axes.Y, Width = rank_width, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20, italics: true), - Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0), - }, - }, }, content = new Container { @@ -356,6 +346,25 @@ namespace osu.Game.Online.Leaderboards } } + private class RankLabel : Container, IHasTooltip + { + public RankLabel(int? rank) + { + if (rank >= 1000) + TooltipText = $"#{rank:N0}"; + + Child = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20, italics: true), + Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0), + }; + } + + public string TooltipText { get; } + } + public class LeaderboardScoreStatistic { public IconUsage Icon; diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index beac6adc59..3bf9e85428 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar private const double transition_time = 500; - private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); + protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. public override bool PropagateNonPositionalInputSubTree => true; @@ -89,14 +89,8 @@ namespace osu.Game.Overlays.Toolbar // Bound after the selector is added to the hierarchy to give it a chance to load the available rulesets rulesetSelector.Current.BindTo(parentRuleset); - State.ValueChanged += visibility => - { - if (overlayActivationMode.Value == OverlayActivation.Disabled) - Hide(); - }; - if (osuGame != null) - overlayActivationMode.BindTo(osuGame.OverlayActivationMode); + OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); } public class ToolbarBackground : Container @@ -137,6 +131,17 @@ namespace osu.Game.Overlays.Toolbar } } + protected override void UpdateState(ValueChangedEvent state) + { + if (state.NewValue == Visibility.Visible && OverlayActivationMode.Value == OverlayActivation.Disabled) + { + State.Value = Visibility.Hidden; + return; + } + + base.UpdateState(state); + } + protected override void PopIn() { this.MoveToY(0, transition_time, Easing.OutQuint); diff --git a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs new file mode 100644 index 0000000000..d45311675d --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to the generated by the . + /// + public interface IApplicableAfterBeatmapConversion : IApplicableMod + { + /// + /// Applies this to the after conversion has taken place. + /// + /// The converted . + void ApplyToBeatmap(IBeatmap beatmap); + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index f1cdfd93c8..eac47aa089 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -133,17 +133,19 @@ namespace osu.Game.Rulesets.Scoring } } + double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + if (result.Judgement.IsBonus) { if (result.IsHit) - bonusScore += result.Judgement.NumericResultFor(result); + bonusScore += scoreIncrease; } else { if (result.HasResult) scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - baseScore += result.Judgement.NumericResultFor(result); + baseScore += scoreIncrease; rollingMaxBaseScore += result.Judgement.MaxNumericResult; } @@ -169,17 +171,19 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; + double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + if (result.Judgement.IsBonus) { if (result.IsHit) - bonusScore -= result.Judgement.NumericResultFor(result); + bonusScore -= scoreIncrease; } else { if (result.HasResult) scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; - baseScore -= result.Judgement.NumericResultFor(result); + baseScore -= scoreIncrease; rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index e9ff0b5598..67442aa55e 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Graphics; @@ -32,6 +33,8 @@ namespace osu.Game.Screens.Edit Container mainContent; + LoadingSpinner spinner; + Children = new Drawable[] { mainContent = new Container @@ -44,6 +47,10 @@ namespace osu.Game.Screens.Edit Top = vertical_margins + timeline_height, Bottom = vertical_margins }, + Child = spinner = new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + }, }, new Container { @@ -87,9 +94,10 @@ namespace osu.Game.Screens.Edit } }, }; - LoadComponentAsync(CreateMainContent(), content => { + spinner.State.Value = Visibility.Hidden; + mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f09745cf71..26aefa138b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -232,7 +232,6 @@ namespace osu.Game.Screens.Play protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter { - TextSize = 20, BypassAutoSizeAxes = Axes.X, Anchor = Anchor.TopLeft, Origin = Anchor.TopRight, @@ -241,14 +240,12 @@ namespace osu.Game.Screens.Play protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) { - TextSize = 40, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter { - TextSize = 20, BypassAutoSizeAxes = Axes.X, Anchor = Anchor.TopRight, Origin = Anchor.TopLeft, diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs index 2a0e33aab7..6933456e7e 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Utils; @@ -43,16 +44,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public Counter() - { - DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); - DisplayedCountSpriteText.Spacing = new Vector2(-2, 0); - } - protected override string FormatCount(double count) => count.FormatAccuracy(); public override void Increment(double amount) => Current.Value += amount; + + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => + { + s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); + s.Spacing = new Vector2(-2, 0); + }); } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index 817cc9b8c2..043a560d12 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osuTK; @@ -43,11 +44,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public Counter() + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => { - DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); - DisplayedCountSpriteText.Spacing = new Vector2(-2, 0); - } + s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true); + s.Spacing = new Vector2(-2, 0); + }); public override void Increment(int amount) => Current.Value += amount; diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index cab04edb8b..7f6fd1eabe 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Ranking.Expanded.Accuracy; using osuTK; @@ -23,15 +24,19 @@ namespace osu.Game.Screens.Ranking.Expanded // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - DisplayedCountSpriteText.Anchor = Anchor.TopCentre; - DisplayedCountSpriteText.Origin = Anchor.TopCentre; - - DisplayedCountSpriteText.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); - DisplayedCountSpriteText.Spacing = new Vector2(-5, 0); } protected override string FormatCount(long count) => count.ToString("N0"); + protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s => + { + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.TopCentre; + + s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); + s.Spacing = new Vector2(-5, 0); + }); + public override void Increment(long amount) => Current.Value += amount; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a12ce138bd..d1e2033596 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0170e94140..9b25eaab41 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - +