diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 900691ecae..1ad45d2f13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; @@ -31,10 +31,23 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private Container droppedObjectContainer; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + private readonly Container trailContainer; private TestCatcher catcher; + public TestSceneCatcher() + { + Add(trailContainer = new Container + { + Anchor = Anchor.Centre, + Depth = -1 + }); + Add(droppedObjectContainer = new DroppedObjectContainer()); + } + [SetUp] public void SetUp() => Schedule(() => { @@ -43,20 +56,13 @@ namespace osu.Game.Rulesets.Catch.Tests CircleSize = 0, }; - var trailContainer = new Container(); - droppedObjectContainer = new Container(); - catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty); + if (catcher != null) + Remove(catcher); - Child = new Container + Add(catcher = new TestCatcher(trailContainer, difficulty) { - Anchor = Anchor.Centre, - Children = new Drawable[] - { - trailContainer, - droppedObjectContainer, - catcher - } - }; + Anchor = Anchor.Centre + }); }); [Test] @@ -293,8 +299,8 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty) - : base(trailsTarget, droppedObjectTarget, difficulty) + public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty) + : base(trailsTarget, difficulty) { } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 4af5098451..877e115e2f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; @@ -97,18 +96,12 @@ namespace osu.Game.Rulesets.Catch.Tests SetContents(_ => { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both - }; - return new CatchInputManager(catchRuleset) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - droppedObjectContainer, - new TestCatcherArea(droppedObjectContainer, beatmapDifficulty) + new TestCatcherArea(beatmapDifficulty) { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, @@ -126,9 +119,13 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) - : base(droppedObjectContainer, beatmapDifficulty) + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + : base(beatmapDifficulty) { + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); } public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 683a776dcc..7fa981d492 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -118,11 +118,10 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("create hyper-dashing catcher", () => { - Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container()) + Child = setupSkinHierarchy(catcherArea = new TestCatcherArea { Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(4f), + Origin = Anchor.Centre }, skin); }); @@ -206,5 +205,18 @@ namespace osu.Game.Rulesets.Catch.Tests { } } + + private class TestCatcherArea : CatcherArea + { + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + + public TestCatcherArea() + { + Scale = new Vector2(4f); + + AddInternal(droppedObjectContainer = new DroppedObjectContainer()); + } + } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 644facdabc..05cd29dff5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -27,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.UI /// public const float CENTER_X = WIDTH / 2; + [Cached] + private readonly DroppedObjectContainer droppedObjectContainer; + internal readonly CatcherArea CatcherArea; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => @@ -35,12 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty) { - var droppedObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }; - - CatcherArea = new CatcherArea(droppedObjectContainer, difficulty) + CatcherArea = new CatcherArea(difficulty) { Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, @@ -48,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new[] { - droppedObjectContainer, + droppedObjectContainer = new DroppedObjectContainer(), CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer.CreateProxy(), // This ordering (`CatcherArea` before `HitObjectContainer`) is important to diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 1f01dbabb5..dcab9459ee 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -79,7 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Contains objects dropped from the plate. /// - private readonly Container droppedObjectTarget; + [Resolved] + private DroppedObjectContainer droppedObjectTarget { get; set; } public CatcherAnimationState CurrentState { @@ -134,10 +135,9 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null) { this.trailsTarget = trailsTarget; - this.droppedObjectTarget = droppedObjectTarget; Origin = Anchor.TopCentre; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index cdb15c2b4c..fea314df8d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; - public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) + public CatcherArea(BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Children = new Drawable[] @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.UI Margin = new MarginPadding { Bottom = 350f }, X = CatchPlayfield.CENTER_X }, - MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X }, + MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }, }; } diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs index 80522ab36b..c961d98dc5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override void FreeAfterUse() { ClearTransforms(); + Alpha = 1; base.FreeAfterUse(); } } diff --git a/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs new file mode 100644 index 0000000000..b44b0caae4 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs @@ -0,0 +1,17 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class DroppedObjectContainer : Container + { + public DroppedObjectContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 97e3d82664..d1212096bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -23,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => "It never gets boring!"; - // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. - // The closer the hit objects draw to the border, the sharper the turn - private const float playfield_edge_ratio = 0.375f; - - private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; - private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; - - private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; private Random rng; @@ -113,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods distanceToPrev * (float)Math.Sin(current.AngleRad) ); - posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev); + posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); @@ -185,73 +177,6 @@ namespace osu.Game.Rulesets.Osu.Mods } } - /// - /// Determines the position of the current hit object relative to the previous one. - /// - /// The position of the current hit object relative to the previous one - private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev) - { - var relativeRotationDistance = 0f; - - if (prevPosChanged.X < playfield_middle.X) - { - relativeRotationDistance = Math.Max( - (border_distance_x - prevPosChanged.X) / border_distance_x, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, - relativeRotationDistance - ); - } - - if (prevPosChanged.Y < playfield_middle.Y) - { - relativeRotationDistance = Math.Max( - (border_distance_y - prevPosChanged.Y) / border_distance_y, - relativeRotationDistance - ); - } - else - { - relativeRotationDistance = Math.Max( - (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, - relativeRotationDistance - ); - } - - return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2); - } - - /// - /// Rotates vector "initial" towards vector "destinantion" - /// - /// Vector to rotate to "destination" - /// Vector "initial" should be rotated to - /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors. - /// Resulting vector - private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance) - { - var initialAngleRad = Math.Atan2(initial.Y, initial.X); - var destAngleRad = Math.Atan2(destination.Y, destination.X); - - var diff = destAngleRad - initialAngleRad; - - while (diff < -Math.PI) diff += 2 * Math.PI; - - while (diff > Math.PI) diff -= 2 * Math.PI; - - var finalAngleRad = initialAngleRad + relativeDistance * diff; - - return new Vector2( - initial.Length * (float)Math.Cos(finalAngleRad), - initial.Length * (float)Math.Sin(finalAngleRad) - ); - } - private class RandomObjectInfo { public float AngleRad { get; set; } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs new file mode 100644 index 0000000000..06b964a647 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.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; +using osu.Game.Rulesets.Osu.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Utils +{ + public static class OsuHitObjectGenerationUtils + { + // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. + // The closer the hit objects draw to the border, the sharper the turn + private const float playfield_edge_ratio = 0.375f; + + private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio; + private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio; + + private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2; + + /// + /// Rotate a hit object away from the playfield edge, while keeping a constant distance + /// from the previous object. + /// + /// + /// The extent of rotation depends on the position of the hit object. Hit objects + /// closer to the playfield edge will be rotated to a larger extent. + /// + /// Position of the previous hit object. + /// Position of the hit object to be rotated, relative to the previous hit object. + /// + /// The extent of rotation. + /// 0 means the hit object is never rotated. + /// 1 means the hit object will be fully rotated towards playfield center when it is originally at playfield edge. + /// + /// The new position of the hit object, relative to the previous one. + public static Vector2 RotateAwayFromEdge(Vector2 prevObjectPos, Vector2 posRelativeToPrev, float rotationRatio = 0.5f) + { + var relativeRotationDistance = 0f; + + if (prevObjectPos.X < playfield_middle.X) + { + relativeRotationDistance = Math.Max( + (border_distance_x - prevObjectPos.X) / border_distance_x, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x, + relativeRotationDistance + ); + } + + if (prevObjectPos.Y < playfield_middle.Y) + { + relativeRotationDistance = Math.Max( + (border_distance_y - prevObjectPos.Y) / border_distance_y, + relativeRotationDistance + ); + } + else + { + relativeRotationDistance = Math.Max( + (prevObjectPos.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y, + relativeRotationDistance + ); + } + + return RotateVectorTowardsVector( + posRelativeToPrev, + playfield_middle - prevObjectPos, + Math.Min(1, relativeRotationDistance * rotationRatio) + ); + } + + /// + /// Rotates vector "initial" towards vector "destination". + /// + /// The vector to be rotated. + /// The vector that "initial" should be rotated towards. + /// How much "initial" should be rotated. 0 means no rotation. 1 means "initial" is fully rotated to equal "destination". + /// The rotated vector. + public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio) + { + var initialAngleRad = MathF.Atan2(initial.Y, initial.X); + var destAngleRad = MathF.Atan2(destination.Y, destination.X); + + var diff = destAngleRad - initialAngleRad; + + while (diff < -MathF.PI) diff += 2 * MathF.PI; + + while (diff > MathF.PI) diff -= 2 * MathF.PI; + + var finalAngleRad = initialAngleRad + rotationRatio * diff; + + return new Vector2( + initial.Length * MathF.Cos(finalAngleRad), + initial.Length * MathF.Sin(finalAngleRad) + ); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2885dbee00..df8ef92a05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -107,7 +106,6 @@ namespace osu.Game.Tests.Visual.UserInterface var conversionMods = osu.GetModsFor(ModType.Conversion); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); @@ -120,8 +118,6 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); testUnimplementedMod(targetMod); } @@ -149,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface changeRuleset(0); - AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); + AddAssert("ensure mods still selected", () => modDisplay.Current.Value.SingleOrDefault(m => m is OsuModNoFail) != null); changeRuleset(3); @@ -316,17 +312,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); } - private void testMultiplierTextColour(Mod mod, Func getCorrectColour) - { - checkLabelColor(() => Color4.White); - selectNext(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(getCorrectColour); - selectPrevious(mod); - AddWaitStep("wait for changing colour", 1); - checkLabelColor(() => Color4.White); - } - private void testModsWithSameBaseType(Mod modA, Mod modB) { selectNext(modA); @@ -348,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert($"check {mod.Name} is selected", () => { var button = modSelect.GetModButton(mod); - return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + return modSelect.SelectedMods.Value.SingleOrDefault(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; }); } @@ -370,8 +355,6 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); - private void createDisplay(Func createOverlayFunc) { Children = new Drawable[] @@ -408,7 +391,6 @@ namespace osu.Game.Tests.Visual.UserInterface return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); } - public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new Color4 LowMultiplierColour => base.LowMultiplierColour; diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 3e50613093..55636495df 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -149,7 +150,7 @@ namespace osu.Game.Configuration break; case IBindable bindable: - var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); + var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); @@ -183,5 +184,17 @@ namespace osu.Game.Configuration => obj.GetSettingsSourceProperties() .OrderBy(attr => attr.Item1) .ToArray(); + + private class ModSettingsEnumDropdown : SettingsEnumDropdown + where T : struct, Enum + { + protected override OsuDropdown CreateDropdown() => new ModDropdownControl(); + + private class ModDropdownControl : DropdownControl + { + // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); + } + } } } diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index 3c66f31c58..dc1fac47a8 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -103,8 +103,11 @@ namespace osu.Game.Localisation [Description(@"简体中文")] zh, - [Description(@"繁體中文(香港)")] - zh_hk, + // Traditional Chinese (Hong Kong) is listed in web sources but has no associated localisations, + // and was wrongly falling back to Simplified Chinese. + // Can be revisited if localisations ever arrive. + // [Description(@"繁體中文(香港)")] + // zh_hk, [Description(@"繁體中文(台灣)")] zh_tw diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index f43420e35e..01cfe9a55b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -240,12 +240,15 @@ namespace osu.Game.Overlays.Chat { get { + if (sender.Equals(User.SYSTEM_USER)) + return Array.Empty(); + List items = new List { new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) }; - if (sender.Id != api.LocalUser.Value.Id) + if (!sender.Equals(api.LocalUser.Value)) items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction)); return items.ToArray(); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e31e307d4d..e4aab978fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,9 +37,6 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; - protected readonly Drawable MultiplierSection; - protected readonly OsuSpriteText MultiplierLabel; - protected readonly FillFlowContainer FooterContainer; protected override bool BlockNonPositionalInput => false; @@ -324,30 +321,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - MultiplierSection = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(footer_button_spacing / 2, 0), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"Score Multiplier:", - Font = OsuFont.GetFont(size: 30), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - MultiplierLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes. - }, - }, - }, } } }, @@ -361,11 +334,8 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, AudioManager audio, OsuGameBase osu) + private void load(AudioManager audio, OsuGameBase osu) { - LowMultiplierColour = colours.Red; - HighMultiplierColour = colours.Green; - availableMods = osu.AvailableMods.GetBoundCopy(); sampleOn = audio.Samples.Get(@"UI/check-on"); @@ -495,26 +465,6 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.UpdateSelectedButtons(selectedMods); - - updateMultiplier(); - } - - private void updateMultiplier() - { - var multiplier = 1.0; - - foreach (var mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - if (multiplier > 1.0) - MultiplierLabel.FadeColour(HighMultiplierColour, 200); - else if (multiplier < 1.0) - MultiplierLabel.FadeColour(LowMultiplierColour, 200); - else - MultiplierLabel.FadeColour(Color4.White, 200); } private void modButtonPressed(Mod selectedMod) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 5e2e9fd087..d5abaaab4e 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -32,7 +32,6 @@ namespace osu.Game.Screens.OnlinePlay { IsValidMod = m => true; - MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; Drawable selectAllButton;