diff --git a/osu.Android.props b/osu.Android.props index f18400bf2f..8a9bf1b9cd 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 5aff4e200b..9ac223a0d7 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -6,7 +6,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; @@ -29,11 +28,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables [Resolved(canBeNull: true)] private ManiaPlayfield playfield { get; set; } - /// - /// Gets the samples that are played by this object during gameplay. - /// - public ISampleInfo[] GetGameplaySamples() => Samples.Samples; - protected override float SamplePlaybackPosition { get diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9b5893b268..f5e30efd91 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,6 +18,7 @@ using osuTK; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.UI { @@ -28,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI public const float COLUMN_WIDTH = 80; public const float SPECIAL_COLUMN_WIDTH = 70; - /// - /// For hitsounds played by this (i.e. not as a result of hitting a hitobject), - /// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key. - /// - private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; - /// /// The index of this column as part of the whole playfield. /// @@ -45,10 +39,10 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly DrawablePool hitExplosionPool; private readonly OrderedHitPolicy hitPolicy; - private readonly Container hitSounds; - public Container UnderlayElements => HitObjectArea.UnderlayElements; + private readonly GameplaySampleTriggerSource sampleTriggerSource; + public Column(int index) { Index = index; @@ -64,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI InternalChildren = new[] { hitExplosionPool = new DrawablePool(5), + sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, @@ -72,12 +67,6 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both }, background, - hitSounds = new Container - { - Name = "Column samples pool", - RelativeSizeAxes = Axes.Both, - Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray() - }, TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } }; @@ -133,29 +122,12 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } - private int nextHitSoundIndex; - public bool OnPressed(ManiaAction action) { if (action != Action.Value) return false; - var nextObject = - HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? - // fallback to non-alive objects to find next off-screen object - HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? - HitObjectContainer.Objects.LastOrDefault(); - - if (nextObject is DrawableManiaHitObject maniaObject) - { - var hitSound = hitSounds[nextHitSoundIndex]; - - hitSound.Samples = maniaObject.GetGameplaySamples(); - hitSound.Play(); - - nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds; - } - + sampleTriggerSource.Play(); return true; } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index f9b8e9a985..269a855219 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Origin = Anchor.Centre, Children = new Drawable[] { - new TaikoPlayfield(new ControlPointInfo()), + new TaikoPlayfield(), hoc = new ScrollingHitObjectContainer() } }; @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Origin = Anchor.Centre, Children = new Drawable[] { - new TaikoPlayfield(new ControlPointInfo()), + new TaikoPlayfield(), hoc = new ScrollingHitObjectContainer() } }; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 055a292fe8..24db046748 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.UI; using osuTK; @@ -17,6 +16,13 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [BackgroundDependencyLoader] private void load() { + var playfield = new TaikoPlayfield(); + + var beatmap = CreateWorkingBeatmap(new TaikoRuleset().RulesetInfo).GetPlayableBeatmap(new TaikoRuleset().RulesetInfo); + + foreach (var h in beatmap.HitObjects) + playfield.Add(h); + SetContents(_ => new TaikoInputManager(new TaikoRuleset().RulesetInfo) { RelativeSizeAxes = Axes.Both, @@ -25,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(new ControlPointInfo()) + Child = new InputDrum(playfield.HitObjectContainer) } }); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs index f96297a06d..6f2fcd08f1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Beatmap.Value.Track.Start(); }); - AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield(new ControlPointInfo()) + AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs deleted file mode 100644 index e4dc261363..0000000000 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ /dev/null @@ -1,104 +0,0 @@ -// 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.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Audio; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Taiko.Audio -{ - /// - /// Stores samples for the input drum. - /// The lifetime of the samples is adjusted so that they are only alive during the appropriate sample control point. - /// - public class DrumSampleContainer : LifetimeManagementContainer - { - private readonly ControlPointInfo controlPoints; - private readonly Dictionary mappings = new Dictionary(); - - private readonly IBindableList samplePoints = new BindableList(); - - public DrumSampleContainer(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - } - - [BackgroundDependencyLoader] - private void load() - { - samplePoints.BindTo(controlPoints.SamplePoints); - samplePoints.BindCollectionChanged((_, __) => recreateMappings(), true); - } - - private void recreateMappings() - { - mappings.Clear(); - ClearInternal(); - - SampleControlPoint[] points = samplePoints.Count == 0 - ? new[] { controlPoints.SamplePointAt(double.MinValue) } - : samplePoints.ToArray(); - - for (int i = 0; i < points.Length; i++) - { - var samplePoint = points[i]; - - var lifetimeStart = i > 0 ? samplePoint.Time : double.MinValue; - var lifetimeEnd = i + 1 < points.Length ? points[i + 1].Time : double.MaxValue; - - AddInternal(mappings[samplePoint.Time] = new DrumSample(samplePoint) - { - LifetimeStart = lifetimeStart, - LifetimeEnd = lifetimeEnd - }); - } - } - - public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time]; - - public class DrumSample : CompositeDrawable - { - public override bool RemoveWhenNotAlive => false; - - public PausableSkinnableSound Centre { get; private set; } - public PausableSkinnableSound Rim { get; private set; } - - private readonly SampleControlPoint samplePoint; - - private Bindable sampleBank; - private BindableNumber sampleVolume; - - public DrumSample(SampleControlPoint samplePoint) - { - this.samplePoint = samplePoint; - } - - [BackgroundDependencyLoader] - private void load() - { - sampleBank = samplePoint.SampleBankBindable.GetBoundCopy(); - sampleBank.BindValueChanged(_ => recreate()); - - sampleVolume = samplePoint.SampleVolumeBindable.GetBoundCopy(); - sampleVolume.BindValueChanged(_ => recreate()); - - recreate(); - } - - private void recreate() - { - InternalChildren = new Drawable[] - { - Centre = new PausableSkinnableSound(samplePoint.GetSampleInfo()), - Rim = new PausableSkinnableSound(samplePoint.GetSampleInfo(HitSampleInfo.HIT_CLAP)) - }; - } - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 90c99316b1..9b73e644c5 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -18,12 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { internal class TaikoBeatmapConverter : BeatmapConverter { - /// - /// osu! is generally slower than taiko, so a factor is added to increase - /// speed. This must be used everywhere slider length or beat length is used. - /// - public const float LEGACY_VELOCITY_MULTIPLIER = 1.4f; - /// /// Because swells are easier in taiko than spinners are in osu!, /// legacy taiko multiplies a factor when converting the number of required hits. @@ -52,10 +46,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { - // Rewrite the beatmap info to add the slider velocity multiplier - original.BeatmapInfo = original.BeatmapInfo.Clone(); - original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); - original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER; + if (!(original.BeatmapInfo.BaseDifficulty is TaikoMutliplierAppliedDifficulty)) + { + // Rewrite the beatmap info to add the slider velocity multiplier + original.BeatmapInfo = original.BeatmapInfo.Clone(); + original.BeatmapInfo.BaseDifficulty = new TaikoMutliplierAppliedDifficulty(original.BeatmapInfo.BaseDifficulty); + } Beatmap converted = base.ConvertBeatmap(original, cancellationToken); @@ -155,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // The true distance, accounting for any repeats. This ends up being the drum roll distance later int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER; + double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); @@ -194,5 +190,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } protected override Beatmap CreateBeatmap() => new TaikoBeatmap(); + + private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty + { + public TaikoMutliplierAppliedDifficulty(BeatmapDifficulty difficulty) + { + difficulty.CopyTo(this); + SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + } + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index c0377c67a5..b0634295d0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -6,10 +6,10 @@ using System; using System.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Judgements; using osuTK; @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; SliderPath IHasPath.Path - => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); + => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); #endregion } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 795885d4b9..9d35093591 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -7,7 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Centre; [Resolved] - private DrumSampleContainer sampleContainer { get; set; } + private DrumSampleTriggerSource sampleTriggerSource { get; set; } public LegacyHalfDrum(bool flipped) { @@ -143,17 +144,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public bool OnPressed(TaikoAction action) { Drawable target = null; - var drumSample = sampleContainer.SampleAt(Time.Current); if (action == CentreAction) { target = Centre; - drumSample.Centre?.Play(); + sampleTriggerSource.Play(HitType.Centre); } else if (action == RimAction) { target = Rim; - drumSample.Rim?.Play(); + sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 650ce1f5a3..6ddbf3c16b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); - protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); + protected override Playfield CreatePlayfield() => new TaikoPlayfield(); public override DrawableHitObject CreateDrawableRepresentation(TaikoHitObject h) => null; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs new file mode 100644 index 0000000000..3279d128d3 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs @@ -0,0 +1,30 @@ +// 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 osu.Game.Audio; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class DrumSampleTriggerSource : GameplaySampleTriggerSource + { + public DrumSampleTriggerSource(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + public void Play(HitType hitType) + { + var hitObject = GetMostValidObject(); + + if (hitObject == null) + return; + + PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) }); + } + + public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead"); + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1ca1be1bdf..ddfaf64549 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -2,18 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Audio; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.UI { @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.UI private const float middle_split = 0.025f; [Cached] - private DrumSampleContainer sampleContainer; + private DrumSampleTriggerSource sampleTriggerSource; - public InputDrum(ControlPointInfo controlPoints) + public InputDrum(HitObjectContainer hitObjectContainer) { - sampleContainer = new DrumSampleContainer(controlPoints); + sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); RelativeSizeAxes = Axes.Both; } @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } }), - sampleContainer + sampleTriggerSource }; } @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centreHit; [Resolved] - private DrumSampleContainer sampleContainer { get; set; } + private DrumSampleTriggerSource sampleTriggerSource { get; set; } public TaikoHalfDrum(bool flipped) { @@ -156,21 +156,19 @@ namespace osu.Game.Rulesets.Taiko.UI Drawable target = null; Drawable back = null; - var drumSample = sampleContainer.SampleAt(Time.Current); - if (action == CentreAction) { target = centreHit; back = centre; - drumSample.Centre?.Play(); + sampleTriggerSource.Play(HitType.Centre); } else if (action == RimAction) { target = rimHit; back = rim; - drumSample.Rim?.Play(); + sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 0d9e08b8b7..d650cab729 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; @@ -27,8 +26,6 @@ namespace osu.Game.Rulesets.Taiko.UI { public class TaikoPlayfield : ScrollingPlayfield { - private readonly ControlPointInfo controlPoints; - /// /// Default height of a when inside a . /// @@ -56,11 +53,6 @@ namespace osu.Game.Rulesets.Taiko.UI private Container hitTargetOffsetContent; - public TaikoPlayfield(ControlPointInfo controlPoints) - { - this.controlPoints = controlPoints; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -131,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(controlPoints) + new InputDrum(HitObjectContainer) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 855a75117d..96986f06d5 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -49,6 +49,22 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); } + [TestCaseSource(nameof(allBeatmaps))] + public void TestEncodeDecodeStabilityDoubleConvert(string name) + { + var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name); + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); + + // run an extra convert. this is expected to be stable. + decodedAfterEncode.beatmap = convert(decodedAfterEncode.beatmap); + + sort(decoded.beatmap); + sort(decodedAfterEncode.beatmap); + + Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); + Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); + } + [Test] public void TestEncodeMultiSegmentSliderWithFloatingPointError() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs new file mode 100644 index 0000000000..fccc1a377c --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -0,0 +1,135 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneGameplaySampleTriggerSource : PlayerTestScene + { + private TestGameplaySampleTriggerSource sampleTriggerSource; + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + private Beatmap beatmap; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, + Ruleset = ruleset + } + }; + + const double start_offset = 8000; + const double spacing = 2000; + + double t = start_offset; + beatmap.HitObjects.AddRange(new[] + { + new HitCircle + { + // intentionally start objects a bit late so we can test the case of no alive objects. + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } + }, + new HitCircle + { + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) } + }, + new HitCircle + { + StartTime = t += spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }, + SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + }, + new HitCircle + { + StartTime = t + spacing, + Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }, + SampleControlPoint = new SampleControlPoint { SampleBank = "soft" }, + }, + }); + + return beatmap; + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("Add trigger source", () => Player.HUDOverlay.Add(sampleTriggerSource = new TestGameplaySampleTriggerSource(Player.DrawableRuleset.Playfield.HitObjectContainer))); + } + + [Test] + public void TestCorrectHitObject() + { + HitObjectLifetimeEntry nextObjectEntry = null; + + AddAssert("no alive objects", () => getNextAliveObject() == null); + + AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]); + + AddUntilStep("get next object", () => + { + var nextDrawableObject = getNextAliveObject(); + + if (nextDrawableObject != null) + { + nextObjectEntry = nextDrawableObject.Entry; + InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre); + return true; + } + + return false; + }); + + AddUntilStep("hit first hitobject", () => + { + InputManager.Click(MouseButton.Left); + return nextObjectEntry.Result.HasResult; + }); + + AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); + + AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]); + AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + + AddUntilStep("no alive objects", () => getNextAliveObject() == null); + AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + } + + private DrawableHitObject getNextAliveObject() => + Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); + + [Test] + public void TestSampleTriggering() + { + AddRepeatStep("trigger sample", () => sampleTriggerSource.Play(), 10); + } + + public class TestGameplaySampleTriggerSource : GameplaySampleTriggerSource + { + public TestGameplaySampleTriggerSource(HitObjectContainer hitObjectContainer) + : base(hitObjectContainer) + { + } + + public new HitObject GetMostValidObject() => base.GetMostValidObject(); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 18e4a6c575..bfcb55ce33 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -19,6 +20,7 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Users; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Multiplayer { @@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGeneral() { - int[] userIds = Enumerable.Range(0, 4).Select(i => PLAYER_1_ID + i).ToArray(); + int[] userIds = getPlayerIds(4); start(userIds); loadSpectateScreen(); @@ -314,6 +316,36 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000); } + [Test] + public void TestPlayersLeaveWhileSpectating() + { + start(getPlayerIds(4)); + sendFrames(getPlayerIds(4), 300); + + loadSpectateScreen(); + + for (int count = 3; count >= 0; count--) + { + var id = PLAYER_1_ID + count; + + end(id); + AddUntilStep($"{id} area grayed", () => getInstance(id).Colour != Color4.White); + AddUntilStep($"{id} score quit set", () => getLeaderboardScore(id).HasQuit.Value); + sendFrames(getPlayerIds(count), 300); + } + + Player player = null; + + AddStep($"get {PLAYER_1_ID} player instance", () => player = getInstance(PLAYER_1_ID).ChildrenOfType().Single()); + + start(new[] { PLAYER_1_ID }); + sendFrames(PLAYER_1_ID, 300); + + AddAssert($"{PLAYER_1_ID} player instance still same", () => getInstance(PLAYER_1_ID).ChildrenOfType().Single() == player); + AddAssert($"{PLAYER_1_ID} area still grayed", () => getInstance(PLAYER_1_ID).Colour != Color4.White); + AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value); + } + private void loadSpectateScreen(bool waitForPlayerLoad = true) { AddStep("load screen", () => @@ -333,14 +365,32 @@ namespace osu.Game.Tests.Visual.Multiplayer { foreach (int id in userIds) { - OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true); + var user = new MultiplayerRoomUser(id) + { + User = new User { Id = id }, + }; + OnlinePlayDependencies.Client.AddUser(user.User, true); SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId); - playingUsers.Add(new MultiplayerRoomUser(id)); + + playingUsers.Add(user); } }); } + private void end(int userId) + { + AddStep($"end play for {userId}", () => + { + var user = playingUsers.Single(u => u.UserID == userId); + + OnlinePlayDependencies.Client.RemoveUser(user.User.AsNonNull()); + SpectatorClient.EndPlay(userId); + + playingUsers.Remove(user); + }); + } + private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count); private void sendFrames(int[] userIds, int count = 10) @@ -374,5 +424,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId); + + private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); + + private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index 6fafb8f87a..e1ea02ba67 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Cursor; @@ -55,20 +56,24 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - Child = new OsuContextMenuContainer + Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = new Container + Child = new OsuContextMenuContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 500, - AutoSizeAxes = Axes.Y, - Child = component = new LabelledColourPalette + RelativeSizeAxes = Axes.Both, + Child = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - ColourNamePrefix = "My colour #" + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledColourPalette + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ColourNamePrefix = "My colour #" + } } } }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs new file mode 100644 index 0000000000..4b74e37ec4 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDropdown.cs @@ -0,0 +1,34 @@ +// 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.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledDropdown : OsuTestScene + { + [Test] + public void TestLabelledDropdown() + => AddStep(@"create dropdown", () => Child = new LabelledDropdown + { + Label = @"Countdown speed", + Items = new[] + { + @"Half", + @"Normal", + @"Double" + }, + Description = @"This is a description" + }); + + [Test] + public void TestLabelledEnumDropdown() + => AddStep(@"create dropdown", () => Child = new LabelledEnumDropdown + { + Label = @"Beatmap status", + Description = @"This is a description" + }); + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs new file mode 100644 index 0000000000..8b91339479 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs @@ -0,0 +1,25 @@ +// 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.Graphics; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuLogo : OsuTestScene + { + [Test] + public void TestBasic() + { + AddStep("Add logo", () => + { + Child = new OsuLogo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + } + } +} diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 5d8f0405ca..f6d28c15e0 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -1,14 +1,12 @@ // 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.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; @@ -131,25 +129,5 @@ namespace osu.Game.Tournament.Screens.Setup resolution.Value = $"{ScreenSpaceDrawQuad.Width:N0}x{ScreenSpaceDrawQuad.Height:N0}"; } - - public class LabelledDropdown : LabelledComponent, T> - { - public LabelledDropdown() - : base(true) - { - } - - public IEnumerable Items - { - get => Component.Items; - set => Component.Items = value; - } - - protected override OsuDropdown CreateComponent() => new OsuDropdown - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - }; - } } } diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index c56fec67aa..1844b193f2 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -32,7 +32,23 @@ namespace osu.Game.Beatmaps /// /// Returns a shallow-clone of this . /// - public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone(); + public BeatmapDifficulty Clone() + { + var diff = new BeatmapDifficulty(); + CopyTo(diff); + return diff; + } + + public void CopyTo(BeatmapDifficulty difficulty) + { + difficulty.ApproachRate = ApproachRate; + difficulty.DrainRate = DrainRate; + difficulty.CircleSize = CircleSize; + difficulty.OverallDifficulty = OverallDifficulty; + + difficulty.SliderMultiplier = SliderMultiplier; + difficulty.SliderTickRate = SliderTickRate; + } /// /// Maps a difficulty value [0, 10] to a two-piece linear range of values. diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 353636c8af..3eb766a667 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -10,7 +10,6 @@ using Newtonsoft.Json; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; -using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -18,7 +17,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey + public class BeatmapInfo : IEquatable, IHasPrimaryKey { public int ID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 7824205257..6ae7f7481e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -153,6 +153,11 @@ namespace osu.Game.Beatmaps if (!storage.Exists(cache_database_name)) return false; + if (string.IsNullOrEmpty(beatmap.MD5Hash) + && string.IsNullOrEmpty(beatmap.Path) + && beatmap.OnlineBeatmapID == null) + return false; + try { using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 3210ef0112..199f719893 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -259,10 +259,10 @@ namespace osu.Game.Beatmaps.Drawables private readonly IBindable starDifficulty = new Bindable(); - public bool SetContent(object content) + public void SetContent(object content) { if (!(content is DifficultyIconTooltipContent iconContent)) - return false; + return; difficultyName.Text = iconContent.Beatmap.Version; @@ -273,8 +273,6 @@ namespace osu.Game.Beatmaps.Drawables starRating.Text = $"{difficulty.NewValue.Stars:0.##}"; difficultyFlow.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars); }, true); - - return true; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs index c239fda455..dde7680989 100644 --- a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs @@ -4,12 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 246dc991d5..fb6806a13d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -24,6 +24,12 @@ namespace osu.Game.Beatmaps.Formats { public const int LATEST_VERSION = 128; + /// + /// osu! is generally slower than taiko, so a factor is added to increase + /// speed. This must be used everywhere slider length or beat length is used. + /// + public const float LEGACY_TAIKO_VELOCITY_MULTIPLIER = 1.4f; + private readonly IBeatmap beatmap; [CanBeNull] @@ -140,9 +146,9 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}")); writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}")); - // Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER) + // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 - ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / 1.4f}") + ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") : FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}")); writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 769b33009a..f61dd269e1 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -4,12 +4,11 @@ using System.Collections.Generic; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; -using osu.Game.IO.Serialization; using osu.Game.Rulesets.Objects; namespace osu.Game.Beatmaps { - public interface IBeatmap : IJsonSerializable + public interface IBeatmap { /// /// This beatmap's info. diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 269360c492..35c48a50d0 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -153,7 +153,7 @@ namespace osu.Game.Graphics.Backgrounds TriangleParticle newParticle = parts[i]; // Scale moved distance by the size of the triangle. Smaller triangles should move more slowly. - newParticle.Position.Y += parts[i].Scale * movedDistance; + newParticle.Position.Y += Math.Max(0.5f, parts[i].Scale) * movedDistance; newParticle.Colour.A = adjustedAlpha; parts[i] = newParticle; diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 81dca99ddd..35d7b4e795 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -31,12 +31,9 @@ namespace osu.Game.Graphics.Cursor private readonly OsuSpriteText text; private bool instantMovement = true; - public override bool SetContent(object content) + public override void SetContent(LocalisableString contentString) { - if (!(content is LocalisableString contentString)) - return false; - - if (contentString == text.Text) return true; + if (contentString == text.Text) return; text.Text = contentString; @@ -47,8 +44,6 @@ namespace osu.Game.Graphics.Cursor } else AutoSizeDuration = 0; - - return true; } public OsuTooltip() diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs index 67fcab43f7..3094f9cc2b 100644 --- a/osu.Game/Graphics/DateTooltip.cs +++ b/osu.Game/Graphics/DateTooltip.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Graphics { - public class DateTooltip : VisibilityContainer, ITooltip + public class DateTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText dateText, timeText; private readonly Box background; @@ -63,14 +63,10 @@ namespace osu.Game.Graphics protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - public bool SetContent(object content) + public void SetContent(DateTimeOffset date) { - if (!(content is DateTimeOffset date)) - return false; - dateText.Text = $"{date:d MMMM yyyy} "; timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; - return true; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs new file mode 100644 index 0000000000..44f09f13eb --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDropdown.cs @@ -0,0 +1,31 @@ +// 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 osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledDropdown : LabelledComponent, TItem> + { + public LabelledDropdown() + : base(true) + { + } + + public IEnumerable Items + { + get => Component.Items; + set => Component.Items = value; + } + + protected sealed override OsuDropdown CreateComponent() => CreateDropdown().With(d => + { + d.RelativeSizeAxes = Axes.X; + d.Width = 0.5f; + }); + + protected virtual OsuDropdown CreateDropdown() => new OsuDropdown(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs new file mode 100644 index 0000000000..b818c394ae --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledEnumDropdown.cs @@ -0,0 +1,14 @@ +// 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 osu.Game.Graphics.UserInterface; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledEnumDropdown : LabelledDropdown + where TEnum : struct, Enum + { + protected override OsuDropdown CreateDropdown() => new OsuEnumDropdown(); + } +} diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/JsonSerializableExtensions.cs similarity index 76% rename from osu.Game/IO/Serialization/IJsonSerializable.cs rename to osu.Game/IO/Serialization/JsonSerializableExtensions.cs index c8d5ce39a6..5b47d0bad1 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/JsonSerializableExtensions.cs @@ -7,21 +7,14 @@ using osu.Framework.IO.Serialization; namespace osu.Game.IO.Serialization { - public interface IJsonSerializable - { - } - public static class JsonSerializableExtensions { - public static string Serialize(this IJsonSerializable obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); + public static string Serialize(this object obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); - /// - /// Creates the default that should be used for all s. - /// public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f2d575550a..69f6bc1b7b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -13,7 +13,6 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -342,11 +341,7 @@ namespace osu.Game globalBindings = new GlobalActionContainer(this) }; - MenuCursorContainer.Child = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } - }; + MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 2dcb2f1777..5a6cde8229 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index b16fb76ec3..60e341d2ac 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -6,12 +6,12 @@ using System.Linq; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a154016824..8fe1d35b62 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -192,6 +193,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (showPerformancePoints) { + Debug.Assert(score.PP != null); + content.Add(new OsuSpriteText { Text = score.PP.ToLocalisableString(@"N0"), diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 23069eccdf..883e83ce6e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0; - ppColumn.Text = value.PP.ToLocalisableString(@"N0"); + ppColumn.Text = value.PP?.ToLocalisableString(@"N0"); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index c934020059..5c3906cb39 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -127,7 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public int? ScorePosition { - set => rankText.Text = value == null ? (LocalisableString)"-" : value.ToLocalisableString(@"\##"); + set => rankText.Text = value?.ToLocalisableString(@"\##") ?? (LocalisableString)"-"; } /// diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index b1e9abe3aa..cde4589c98 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Mods/ModButtonTooltip.cs b/osu.Game/Overlays/Mods/ModButtonTooltip.cs index 666ed07e28..89fcd61d76 100644 --- a/osu.Game/Overlays/Mods/ModButtonTooltip.cs +++ b/osu.Game/Overlays/Mods/ModButtonTooltip.cs @@ -18,7 +18,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModButtonTooltip : VisibilityContainer, ITooltip + public class ModButtonTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText descriptionText; private readonly Box background; @@ -82,12 +82,9 @@ namespace osu.Game.Overlays.Mods private Mod lastMod; - public bool SetContent(object content) + public void SetContent(Mod mod) { - if (!(content is Mod mod)) - return false; - - if (mod.Equals(lastMod)) return true; + if (mod.Equals(lastMod)) return; lastMod = mod; @@ -99,15 +96,7 @@ namespace osu.Game.Overlays.Mods incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); - if (!incompatibleMods.Value.Any()) - { - incompatibleText.Text = "Compatible with all mods"; - return true; - } - - incompatibleText.Text = "Incompatible with:"; - - return true; + incompatibleText.Text = !incompatibleMods.Value.Any() ? "Compatible with all mods" : "Incompatible with:"; } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index f15fa2705a..180a288729 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index 877637be22..2c8c421eba 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs index 1235836aac..b098f9f840 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 74a25591b4..7ba8ae7c80 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics; @@ -80,14 +81,13 @@ namespace osu.Game.Overlays.Profile.Header.Components { } - public override bool SetContent(object content) + public override void SetContent(object content) { if (!(content is TooltipDisplayContent info)) - return false; + return; Counter.Text = info.Rank; BottomText.Text = info.Time; - return true; } } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 9e52751904..8ca6961950 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 438f52a2ce..cf930e985c 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs index 34211b40b7..bd6cb4d09b 100644 --- a/osu.Game/Overlays/Profile/Sections/CounterPill.cs +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; -using osu.Framework.Localisation; +using osu.Framework.Extensions.LocalisationExtensions; namespace osu.Game.Overlays.Profile.Sections { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index 449b1da35d..a75235359a 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Graphics.Sprites; using osu.Framework.Utils; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using osuTK; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index ac94f0fc87..85287d2325 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using static osu.Game.Users.User; @@ -49,14 +50,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical this.tooltipCounterName = tooltipCounterName; } - public override bool SetContent(object content) + public override void SetContent(object content) { if (!(content is TooltipDisplayContent info) || info.Name != tooltipCounterName) - return false; + return; Counter.Text = info.Count; BottomText.Text = info.Date; - return true; } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index eb55a0a78d..762716efab 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Resources.Localisation.Web; using osu.Framework.Localisation; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 4e4a665a60..f77464ecb9 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -1,9 +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 osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index b7a08b6c5e..b88cc32ff7 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -268,7 +268,7 @@ namespace osu.Game.Overlays.Profile background.Colour = colours.Gray1; } - public abstract bool SetContent(object content); + public abstract void SetContent(object content); private bool instantMove = true; diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 5309778a47..0f071883ca 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 85a317728f..a908380e95 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -9,8 +9,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Resources.Localisation.Web; -using osu.Framework.Localisation; namespace osu.Game.Overlays.Rankings.Tables { diff --git a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs index 6facf1e7a2..17c17b1f1a 100644 --- a/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/PerformanceTable.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Rankings.Tables protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] { - new RowText { Text = item.PP.ToLocalisableString(@"N0"), } + new RowText { Text = item.PP?.ToLocalisableString(@"N0"), } }; } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index bc8eac16a9..6e6230f958 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; diff --git a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs index b6bb66e2c8..934da4501e 100644 --- a/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/ScoresTable.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index b96ab556df..cc2ef55a2b 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 9f3b5eaf5b..1199d8a956 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; using osu.Game.Utils; @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Mods /// The base class for gameplay modifiers. /// [ExcludeFromDynamicCompile] - public abstract class Mod : IMod, IEquatable, IJsonSerializable, IDeepCloneable + public abstract class Mod : IMod, IEquatable, IDeepCloneable { /// /// The name of this mod. diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 4b3c3f90f0..dcd2cc8b55 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -3,14 +3,13 @@ using System; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Timing { /// /// A control point which adds an aggregated multiplier based on the provided 's BeatLength and 's SpeedMultiplier. /// - public class MultiplierControlPoint : IJsonSerializable, IComparable + public class MultiplierControlPoint : IComparable { /// /// The time in milliseconds at which this starts. diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs new file mode 100644 index 0000000000..c18698f77e --- /dev/null +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A component which can trigger the most appropriate hit sound for a given point in time, based on the state of a + /// + public class GameplaySampleTriggerSource : CompositeDrawable + { + /// + /// The number of concurrent samples allowed to be played concurrently so that it feels better when spam-pressing a key. + /// + private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY; + + private readonly HitObjectContainer hitObjectContainer; + + private int nextHitSoundIndex; + + private readonly Container hitSounds; + + public GameplaySampleTriggerSource(HitObjectContainer hitObjectContainer) + { + this.hitObjectContainer = hitObjectContainer; + + InternalChild = hitSounds = new Container + { + Name = "concurrent sample pool", + ChildrenEnumerable = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new PausableSkinnableSound()) + }; + } + + private HitObjectLifetimeEntry fallbackObject; + + /// + /// Play the most appropriate hit sound for the current point in time. + /// + public virtual void Play() + { + var nextObject = GetMostValidObject(); + + if (nextObject == null) + return; + + var samples = nextObject.Samples + .Select(s => nextObject.SampleControlPoint.ApplyTo(s)) + .Cast() + .ToArray(); + + PlaySamples(samples); + } + + protected void PlaySamples(ISampleInfo[] samples) + { + var hitSound = getNextSample(); + hitSound.Samples = samples; + hitSound.Play(); + } + + protected HitObject GetMostValidObject() + { + // The most optimal lookup case we have is when an object is alive. There are usually very few alive objects so there's no drawbacks in attempting this lookup each time. + var hitObject = hitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.HasResult != true)?.HitObject; + + // In the case a next object isn't available in drawable form, we need to do a somewhat expensive traversal to get a valid sound to play. + if (hitObject == null) + { + // This lookup can be skipped if the last entry is still valid (in the future and not yet hit). + if (fallbackObject == null || fallbackObject.Result?.HasResult == true) + { + // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). + // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. + fallbackObject = hitObjectContainer.Entries + .Where(e => e.Result?.HasResult != true) + .OrderBy(e => e.HitObject.StartTime) + .FirstOrDefault(); + + // In the case there are no unjudged objects, the last hit object should be used instead. + fallbackObject ??= hitObjectContainer.Entries.LastOrDefault(); + } + + hitObject = fallbackObject?.HitObject; + } + + return hitObject; + } + + private SkinnableSound getNextSample() + { + SkinnableSound hitSound = hitSounds[nextHitSoundIndex]; + + // round robin over available samples to allow for concurrent playback. + nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds; + + return hitSound; + } + } +} diff --git a/osu.Game/Screens/Edit/ClipboardContent.cs b/osu.Game/Screens/Edit/ClipboardContent.cs index b2edbedccc..0348a7c15d 100644 --- a/osu.Game/Screens/Edit/ClipboardContent.cs +++ b/osu.Game/Screens/Edit/ClipboardContent.cs @@ -4,13 +4,12 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using osu.Game.IO.Serialization; using osu.Game.IO.Serialization.Converters; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class ClipboardContent : IJsonSerializable + public class ClipboardContent { [JsonConverter(typeof(TypedListConverter))] public IList HitObjects; diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs index c6ced02021..b271a145f5 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -17,9 +16,6 @@ namespace osu.Game.Screens.Edit [Resolved] private OsuColour colours { get; set; } - [Cached] - protected readonly OverlayColourProvider ColourProvider; - private Container roundedContent; protected override Container Content => roundedContent; @@ -27,7 +23,6 @@ namespace osu.Game.Screens.Edit public EditorRoundedScreen(EditorScreenMode mode) : base(mode) { - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index d7fe5207d0..2810f78835 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -4,6 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -15,6 +17,9 @@ namespace osu.Game.Screens.Edit [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } + [Cached] + protected readonly OverlayColourProvider ColourProvider; + protected override Container Content => content; private readonly Container content; @@ -28,7 +33,9 @@ namespace osu.Game.Screens.Edit Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } protected override void PopIn() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index cf3d76a3fb..cca1394b6d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Logging; @@ -72,6 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private readonly Bindable filter = new Bindable(new FilterCriteria()); private readonly IBindable operationInProgress = new Bindable(); private readonly IBindable isIdle = new BindableBool(); + private PopoverContainer popoverContainer; private LoadingLayer loadingLayer; private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; @@ -90,7 +92,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge InternalChildren = new Drawable[] { ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter), - new Container + popoverContainer = new PopoverContainer { Name = @"Rooms area", RelativeSizeAxes = Axes.Both, @@ -285,7 +287,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge searchTextBox.HoldFocus = false; // ensure any password prompt is dismissed. - this.HidePopover(); + popoverContainer.HidePopover(); } public void Join(Room room, string password) => Schedule(() => diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index cf0dfbb585..b8f47c16ff 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -61,7 +61,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate playerClocks.Add(clock); } - public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock); + public void RemovePlayerClock(ISpectatorPlayerClock clock) + { + playerClocks.Remove(clock); + clock.Stop(); + } protected override void Update() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index d10917259d..bf7c738882 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Screens.Play; @@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + [Resolved] + private OsuColour colours { get; set; } + [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -215,6 +219,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void EndGameplay(int userId) { RemoveUser(userId); + + var instance = instances.Single(i => i.UserId == userId); + + instance.FadeColour(colours.Gray4, 400, Easing.OutQuint); + syncManager.RemovePlayerClock(instance.GameplayClock); leaderboard.RemoveClock(userId); } diff --git a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs index 68e3f0df7d..d04e60a2ab 100644 --- a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs +++ b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index b64e5ca98f..a2b84c79af 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Extensions; -using osu.Game.IO.Serialization; using osu.Game.Skinning; using osuTK; @@ -18,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD /// Serialised information governing custom changes to an . /// [Serializable] - public class SkinnableInfo : IJsonSerializable + public class SkinnableInfo { public Type Type { get; set; } diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 820d776e63..4520e2e825 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play Beatmap.Value = gameplayState.Beatmap; Ruleset.Value = gameplayState.Ruleset.RulesetInfo; - this.Push(new SpectatorPlayerLoader(gameplayState.Score)); + this.Push(new SpectatorPlayerLoader(gameplayState.Score, () => new SoloSpectatorPlayer(gameplayState.Score))); } } diff --git a/osu.Game/Screens/Play/SoloSpectatorPlayer.cs b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs new file mode 100644 index 0000000000..969a5bf2b4 --- /dev/null +++ b/osu.Game/Screens/Play/SoloSpectatorPlayer.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Online.Spectator; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Play +{ + public class SoloSpectatorPlayer : SpectatorPlayer + { + private readonly Score score; + + public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null) + : base(score, configuration) + { + this.score = score; + } + + [BackgroundDependencyLoader] + private void load() + { + SpectatorClient.OnUserBeganPlaying += userBeganPlaying; + } + + public override bool OnExiting(IScreen next) + { + SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; + + return base.OnExiting(next); + } + + private void userBeganPlaying(int userId, SpectatorState state) + { + if (userId != score.ScoreInfo.UserID) return; + + Schedule(() => + { + if (this.IsCurrentScreen()) this.Exit(); + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (SpectatorClient != null) + SpectatorClient.OnUserBeganPlaying -= userBeganPlaying; + } + } +} diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 1dae28092a..d7e42a9cd1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -14,16 +14,16 @@ using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { - public class SpectatorPlayer : Player + public abstract class SpectatorPlayer : Player { [Resolved] - private SpectatorClient spectatorClient { get; set; } + protected SpectatorClient SpectatorClient { get; private set; } private readonly Score score; protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap - public SpectatorPlayer(Score score, PlayerConfiguration configuration = null) + protected SpectatorPlayer(Score score, PlayerConfiguration configuration = null) : base(configuration) { this.score = score; @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - spectatorClient.OnUserBeganPlaying += userBeganPlaying; - AddInternal(new OsuSpriteText { Text = $"Watching {score.ScoreInfo.User.Username} playing live!", @@ -50,7 +48,7 @@ namespace osu.Game.Screens.Play // Start gameplay along with the very first arrival frame (the latest one). score.Replay.Frames.Clear(); - spectatorClient.OnNewFrames += userSentFrames; + SpectatorClient.OnNewFrames += userSentFrames; } private void userSentFrames(int userId, FrameDataBundle bundle) @@ -93,31 +91,17 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - spectatorClient.OnUserBeganPlaying -= userBeganPlaying; - spectatorClient.OnNewFrames -= userSentFrames; + SpectatorClient.OnNewFrames -= userSentFrames; return base.OnExiting(next); } - private void userBeganPlaying(int userId, SpectatorState state) - { - if (userId != score.ScoreInfo.UserID) return; - - Schedule(() => - { - if (this.IsCurrentScreen()) this.Exit(); - }); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (spectatorClient != null) - { - spectatorClient.OnUserBeganPlaying -= userBeganPlaying; - spectatorClient.OnNewFrames -= userSentFrames; - } + if (SpectatorClient != null) + SpectatorClient.OnNewFrames -= userSentFrames; } } } diff --git a/osu.Game/Screens/Play/SpectatorPlayerLoader.cs b/osu.Game/Screens/Play/SpectatorPlayerLoader.cs index bdd23962dc..10cc36c9a9 100644 --- a/osu.Game/Screens/Play/SpectatorPlayerLoader.cs +++ b/osu.Game/Screens/Play/SpectatorPlayerLoader.cs @@ -11,12 +11,7 @@ namespace osu.Game.Screens.Play { public readonly ScoreInfo Score; - public SpectatorPlayerLoader(Score score) - : this(score, () => new SpectatorPlayer(score)) - { - } - - public SpectatorPlayerLoader(Score score, Func createPlayer) + public SpectatorPlayerLoader(Score score, Func createPlayer) : base(createPlayer) { if (score.Replay == null) diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs index a7f28b932a..eabc476db9 100644 --- a/osu.Game/Screens/Select/Details/UserRatings.cs +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -8,8 +8,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Beatmaps; -using osu.Framework.Localisation; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Screens.Select.Details diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 72d10019b2..a882148392 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -3,8 +3,8 @@ using System; using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; -using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select @@ -64,8 +64,7 @@ namespace osu.Game.Screens.Select return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, - (string s, out BeatmapSetOnlineStatus val) => Enum.TryParse(value, true, out val)); + return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, tryParseEnum); case "creator": return TryUpdateCriteriaText(ref criteria.Creator, op, value); @@ -120,6 +119,14 @@ namespace osu.Game.Screens.Select private static bool tryParseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); + private static bool tryParseEnum(string value, out TEnum result) where TEnum : struct + { + if (Enum.TryParse(value, true, out result)) return true; + + value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture)); + return Enum.TryParse(value, true, out result); + } + /// /// Attempts to parse a keyword filter with the specified and textual . /// If the value indicates a valid textual filter, the function returns true and the resulting data is stored into diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index c5e2e67eaf..752794d25a 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing.Input; using osu.Game.Graphics.Cursor; @@ -35,11 +34,7 @@ namespace osu.Game.Tests.Visual { MenuCursorContainer cursorContainer; - CompositeDrawable mainContent = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, } - }; + CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor) { diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index e763558647..d14dbb49f3 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -3,6 +3,7 @@ using System; using Humanizer; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; namespace osu.Game.Utils diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a89d57cd1f..b4d4aa3070 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index bb4700a081..29e9b9fe20 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - +