1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-16 00:22:58 +08:00

Merge branch 'master' into osu-target-mod

This commit is contained in:
Henry Lin 2021-06-26 11:26:00 +08:00
commit c543080923
17 changed files with 205 additions and 196 deletions

View File

@ -6,8 +6,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.UI;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -31,10 +31,23 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } private OsuConfigManager config { get; set; }
private Container<CaughtObject> droppedObjectContainer; [Cached]
private readonly DroppedObjectContainer droppedObjectContainer;
private readonly Container trailContainer;
private TestCatcher catcher; private TestCatcher catcher;
public TestSceneCatcher()
{
Add(trailContainer = new Container
{
Anchor = Anchor.Centre,
Depth = -1
});
Add(droppedObjectContainer = new DroppedObjectContainer());
}
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
@ -43,20 +56,13 @@ namespace osu.Game.Rulesets.Catch.Tests
CircleSize = 0, CircleSize = 0,
}; };
var trailContainer = new Container(); if (catcher != null)
droppedObjectContainer = new Container<CaughtObject>(); Remove(catcher);
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
Child = new Container Add(catcher = new TestCatcher(trailContainer, difficulty)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre
Children = new Drawable[] });
{
trailContainer,
droppedObjectContainer,
catcher
}
};
}); });
[Test] [Test]
@ -293,8 +299,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>(); public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
public TestCatcher(Container trailsTarget, Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty) public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty)
: base(trailsTarget, droppedObjectTarget, difficulty) : base(trailsTarget, difficulty)
{ {
} }
} }

View File

@ -6,7 +6,6 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -97,18 +96,12 @@ namespace osu.Game.Rulesets.Catch.Tests
SetContents(_ => SetContents(_ =>
{ {
var droppedObjectContainer = new Container<CaughtObject>
{
RelativeSizeAxes = Axes.Both
};
return new CatchInputManager(catchRuleset) return new CatchInputManager(catchRuleset)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
droppedObjectContainer, new TestCatcherArea(beatmapDifficulty)
new TestCatcherArea(droppedObjectContainer, beatmapDifficulty)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
@ -126,9 +119,13 @@ namespace osu.Game.Rulesets.Catch.Tests
private class TestCatcherArea : CatcherArea private class TestCatcherArea : CatcherArea
{ {
public TestCatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty beatmapDifficulty) [Cached]
: base(droppedObjectContainer, beatmapDifficulty) private readonly DroppedObjectContainer droppedObjectContainer;
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
: base(beatmapDifficulty)
{ {
AddInternal(droppedObjectContainer = new DroppedObjectContainer());
} }
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1); public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);

View File

@ -118,11 +118,10 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("create hyper-dashing catcher", () => AddStep("create hyper-dashing catcher", () =>
{ {
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container<CaughtObject>()) Child = setupSkinHierarchy(catcherArea = new TestCatcherArea
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre
Scale = new Vector2(4f),
}, skin); }, 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());
}
}
} }
} }

View File

@ -3,7 +3,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
@ -27,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
public const float CENTER_X = WIDTH / 2; public const float CENTER_X = WIDTH / 2;
[Cached]
private readonly DroppedObjectContainer droppedObjectContainer;
internal readonly CatcherArea CatcherArea; internal readonly CatcherArea CatcherArea;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
@ -35,12 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI
public CatchPlayfield(BeatmapDifficulty difficulty) public CatchPlayfield(BeatmapDifficulty difficulty)
{ {
var droppedObjectContainer = new Container<CaughtObject> CatcherArea = new CatcherArea(difficulty)
{
RelativeSizeAxes = Axes.Both,
};
CatcherArea = new CatcherArea(droppedObjectContainer, difficulty)
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
@ -48,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.UI
InternalChildren = new[] InternalChildren = new[]
{ {
droppedObjectContainer, droppedObjectContainer = new DroppedObjectContainer(),
CatcherArea.MovableCatcher.CreateProxiedContent(), CatcherArea.MovableCatcher.CreateProxiedContent(),
HitObjectContainer.CreateProxy(), HitObjectContainer.CreateProxy(),
// This ordering (`CatcherArea` before `HitObjectContainer`) is important to // This ordering (`CatcherArea` before `HitObjectContainer`) is important to

View File

@ -79,7 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary> /// <summary>
/// Contains objects dropped from the plate. /// Contains objects dropped from the plate.
/// </summary> /// </summary>
private readonly Container<CaughtObject> droppedObjectTarget; [Resolved]
private DroppedObjectContainer droppedObjectTarget { get; set; }
public CatcherAnimationState CurrentState public CatcherAnimationState CurrentState
{ {
@ -134,10 +135,9 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool<CaughtBanana> caughtBananaPool; private readonly DrawablePool<CaughtBanana> caughtBananaPool;
private readonly DrawablePool<CaughtDroplet> caughtDropletPool; private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
public Catcher([NotNull] Container trailsTarget, [NotNull] Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty = null) public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
{ {
this.trailsTarget = trailsTarget; this.trailsTarget = trailsTarget;
this.droppedObjectTarget = droppedObjectTarget;
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
private int currentDirection; private int currentDirection;
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null) public CatcherArea(BeatmapDifficulty difficulty = null)
{ {
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
Children = new Drawable[] Children = new Drawable[]
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.UI
Margin = new MarginPadding { Bottom = 350f }, Margin = new MarginPadding { Bottom = 350f },
X = CatchPlayfield.CENTER_X X = CatchPlayfield.CENTER_X
}, },
MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X }, MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
}; };
} }

View File

@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Catch.UI
protected override void FreeAfterUse() protected override void FreeAfterUse()
{ {
ClearTransforms(); ClearTransforms();
Alpha = 1;
base.FreeAfterUse(); base.FreeAfterUse();
} }
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<CaughtObject>
{
public DroppedObjectContainer()
{
RelativeSizeAxes = Axes.Both;
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Osu.Utils;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
@ -23,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Description => "It never gets boring!"; 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 static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
private Random rng; private Random rng;
@ -113,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods
distanceToPrev * (float)Math.Sin(current.AngleRad) 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); current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
@ -185,73 +177,6 @@ namespace osu.Game.Rulesets.Osu.Mods
} }
} }
/// <summary>
/// Determines the position of the current hit object relative to the previous one.
/// </summary>
/// <returns>The position of the current hit object relative to the previous one</returns>
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);
}
/// <summary>
/// Rotates vector "initial" towards vector "destinantion"
/// </summary>
/// <param name="initial">Vector to rotate to "destination"</param>
/// <param name="destination">Vector "initial" should be rotated to</param>
/// <param name="relativeDistance">The angle the vector should be rotated relative to the difference between the angles of the the two vectors.</param>
/// <returns>Resulting vector</returns>
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 private class RandomObjectInfo
{ {
public float AngleRad { get; set; } public float AngleRad { get; set; }

View File

@ -0,0 +1,104 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
/// <summary>
/// Rotate a hit object away from the playfield edge, while keeping a constant distance
/// from the previous object.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="prevObjectPos">Position of the previous hit object.</param>
/// <param name="posRelativeToPrev">Position of the hit object to be rotated, relative to the previous hit object.</param>
/// <param name="rotationRatio">
/// 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.
/// </param>
/// <returns>The new position of the hit object, relative to the previous one.</returns>
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)
);
}
/// <summary>
/// Rotates vector "initial" towards vector "destination".
/// </summary>
/// <param name="initial">The vector to be rotated.</param>
/// <param name="destination">The vector that "initial" should be rotated towards.</param>
/// <param name="rotationRatio">How much "initial" should be rotated. 0 means no rotation. 1 means "initial" is fully rotated to equal "destination".</param>
/// <returns>The rotated vector.</returns>
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)
);
}
}
}

View File

@ -10,7 +10,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -107,7 +106,6 @@ namespace osu.Game.Tests.Visual.UserInterface
var conversionMods = osu.GetModsFor(ModType.Conversion); var conversionMods = osu.GetModsFor(ModType.Conversion);
var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
@ -120,8 +118,6 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiMod(doubleTimeMod); testMultiMod(doubleTimeMod);
testIncompatibleMods(easy, hardRock); testIncompatibleMods(easy, hardRock);
testDeselectAll(easierMods.Where(m => !(m is MultiMod))); testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
testUnimplementedMod(targetMod); testUnimplementedMod(targetMod);
} }
@ -149,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface
changeRuleset(0); 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); changeRuleset(3);
@ -316,17 +312,6 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any());
} }
private void testMultiplierTextColour(Mod mod, Func<Color4> 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) private void testModsWithSameBaseType(Mod modA, Mod modB)
{ {
selectNext(modA); selectNext(modA);
@ -348,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert($"check {mod.Name} is selected", () => AddAssert($"check {mod.Name} is selected", () =>
{ {
var button = modSelect.GetModButton(mod); 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<Color4> getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour());
private void createDisplay(Func<TestModSelectOverlay> createOverlayFunc) private void createDisplay(Func<TestModSelectOverlay> createOverlayFunc)
{ {
Children = new Drawable[] Children = new Drawable[]
@ -408,7 +391,6 @@ namespace osu.Game.Tests.Visual.UserInterface
return section.ButtonsContainer.OfType<ModButton>().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); return section.ButtonsContainer.OfType<ModButton>().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType()));
} }
public new OsuSpriteText MultiplierLabel => base.MultiplierLabel;
public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new TriangleButton DeselectAllButton => base.DeselectAllButton;
public new Color4 LowMultiplierColour => base.LowMultiplierColour; public new Color4 LowMultiplierColour => base.LowMultiplierColour;

View File

@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
namespace osu.Game.Configuration namespace osu.Game.Configuration
@ -149,7 +150,7 @@ namespace osu.Game.Configuration
break; break;
case IBindable bindable: 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); var dropdown = (Drawable)Activator.CreateInstance(dropdownType);
dropdownType.GetProperty(nameof(SettingsDropdown<object>.LabelText))?.SetValue(dropdown, attr.Label); dropdownType.GetProperty(nameof(SettingsDropdown<object>.LabelText))?.SetValue(dropdown, attr.Label);
@ -183,5 +184,17 @@ namespace osu.Game.Configuration
=> obj.GetSettingsSourceProperties() => obj.GetSettingsSourceProperties()
.OrderBy(attr => attr.Item1) .OrderBy(attr => attr.Item1)
.ToArray(); .ToArray();
private class ModSettingsEnumDropdown<T> : SettingsEnumDropdown<T>
where T : struct, Enum
{
protected override OsuDropdown<T> 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);
}
}
} }
} }

View File

@ -103,8 +103,11 @@ namespace osu.Game.Localisation
[Description(@"简体中文")] [Description(@"简体中文")]
zh, zh,
[Description(@"繁體中文(香港)")] // Traditional Chinese (Hong Kong) is listed in web sources but has no associated localisations,
zh_hk, // and was wrongly falling back to Simplified Chinese.
// Can be revisited if localisations ever arrive.
// [Description(@"繁體中文(香港)")]
// zh_hk,
[Description(@"繁體中文(台灣)")] [Description(@"繁體中文(台灣)")]
zh_tw zh_tw

View File

@ -240,12 +240,15 @@ namespace osu.Game.Overlays.Chat
{ {
get get
{ {
if (sender.Equals(User.SYSTEM_USER))
return Array.Empty<MenuItem>();
List<MenuItem> items = new List<MenuItem> List<MenuItem> items = new List<MenuItem>
{ {
new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) 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)); items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction));
return items.ToArray(); return items.ToArray();

View File

@ -37,9 +37,6 @@ namespace osu.Game.Overlays.Mods
protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CustomiseButton;
protected readonly TriangleButton CloseButton; protected readonly TriangleButton CloseButton;
protected readonly Drawable MultiplierSection;
protected readonly OsuSpriteText MultiplierLabel;
protected readonly FillFlowContainer FooterContainer; protected readonly FillFlowContainer FooterContainer;
protected override bool BlockNonPositionalInput => false; protected override bool BlockNonPositionalInput => false;
@ -324,30 +321,6 @@ namespace osu.Game.Overlays.Mods
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = 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)] [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(); availableMods = osu.AvailableMods.GetBoundCopy();
sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOn = audio.Samples.Get(@"UI/check-on");
@ -495,26 +465,6 @@ namespace osu.Game.Overlays.Mods
foreach (var section in ModSectionsContainer.Children) foreach (var section in ModSectionsContainer.Children)
section.UpdateSelectedButtons(selectedMods); 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) private void modButtonPressed(Mod selectedMod)

View File

@ -261,7 +261,7 @@ namespace osu.Game.Screens.Menu
switch (state) switch (state)
{ {
default: default:
return true; return false;
case ButtonSystemState.Initial: case ButtonSystemState.Initial:
State = ButtonSystemState.TopLevel; State = ButtonSystemState.TopLevel;

View File

@ -32,7 +32,6 @@ namespace osu.Game.Screens.OnlinePlay
{ {
IsValidMod = m => true; IsValidMod = m => true;
MultiplierSection.Alpha = 0;
DeselectAllButton.Alpha = 0; DeselectAllButton.Alpha = 0;
Drawable selectAllButton; Drawable selectAllButton;