diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 58c24181d3..b51ecb4f7e 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -31,6 +31,12 @@
"commands": [
"CodeFileSanity"
]
+ },
+ "ppy.localisationanalyser.tools": {
+ "version": "2021.524.0",
+ "commands": [
+ "localisation"
+ ]
}
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 57550cfb93..b3842a528d 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index f697a77d94..0a7ef443b1 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -5,12 +5,14 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
@@ -32,6 +34,16 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep($"{term} Small", () => SetContents(() => testSingle(7, autoplay)));
}
+ [Test]
+ public void TestSpinningSamplePitchShift()
+ {
+ AddStep("Add spinner", () => SetContents(() => testSingle(5, true, 4000)));
+ AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
+ AddUntilStep("Pitch increases", () => getSpinningSample().Frequency.Value > 0.8);
+
+ PausableSkinnableSound getSpinningSample() => drawableSpinner.ChildrenOfType().FirstOrDefault(s => s.Samples.Any(i => i.LookupNames.Any(l => l.Contains("spinnerspin"))));
+ }
+
[TestCase(false)]
[TestCase(true)]
public void TestLongSpinner(bool autoplay)
@@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
base.Update();
if (auto)
- RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 3));
+ RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 2));
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index ec97f1fd78..e810d2fe0c 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
using osuTK;
@@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderSelectionBlueprint : OsuSelectionBlueprint
{
+ protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
+
protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleOverlay HeadOverlay { get; private set; }
protected SliderCircleOverlay TailOverlay { get; private set; }
@@ -236,7 +239,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
};
- public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
+ // Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions.
+ public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset)
+ ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 82b5492de6..0bec33bf77 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects;
- private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
+ [CanBeNull]
+ public PlaySliderBody SliderBody => Body.Drawable as PlaySliderBody;
public IBindable PathVersion => pathVersion;
private readonly Bindable pathVersion = new Bindable();
@@ -215,16 +216,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
- sliderBody?.UpdateProgress(completionProgress);
+ SliderBody?.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects)
{
- if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(sliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
+ if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0));
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
}
- Size = sliderBody?.Size ?? Vector2.Zero;
- OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
+ Size = SliderBody?.Size ?? Vector2.Zero;
+ OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
if (DrawSize != Vector2.Zero)
{
@@ -238,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void OnKilled()
{
base.OnKilled();
- sliderBody?.RecyclePath();
+ SliderBody?.RecyclePath();
}
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
@@ -324,7 +325,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
case ArmedState.Hit:
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
- if (sliderBody?.SnakingOut.Value == true)
+ if (SliderBody?.SnakingOut.Value == true)
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
break;
}
@@ -332,7 +333,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
}
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
private class DefaultSliderBody : PlaySliderBody
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 3a4753761a..19cee61f26 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Bindable isSpinning;
private bool spinnerFrequencyModulate;
+ private const float spinning_sample_initial_frequency = 1.0f;
+ private const float spinning_sample_modulated_base_frequency = 0.5f;
+
///
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
///
@@ -106,9 +109,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
isSpinning.BindValueChanged(updateSpinningSample);
}
- private const float spinning_sample_initial_frequency = 1.0f;
- private const float spinning_sample_modulated_base_frequency = 0.5f;
-
protected override void OnFree()
{
base.OnFree();
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index f495e0fb23..55c5b5b9c2 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Threading;
+using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.KeyBinding;
using osuTK.Input;
@@ -28,6 +29,39 @@ namespace osu.Game.Tests.Visual.Settings
panel.Show();
}
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("Scroll to top", () => panel.ChildrenOfType().First().ScrollToTop());
+ AddWaitStep("wait for scroll", 5);
+ }
+
+ [Test]
+ public void TestBindingMouseWheelToNonGameplay()
+ {
+ scrollToAndStartBinding("Increase volume");
+ AddStep("press k", () => InputManager.Key(Key.K));
+ checkBinding("Increase volume", "K");
+
+ AddStep("click again", () => InputManager.Click(MouseButton.Left));
+ AddStep("scroll mouse wheel", () => InputManager.ScrollVerticalBy(1));
+
+ checkBinding("Increase volume", "Wheel Up");
+ }
+
+ [Test]
+ public void TestBindingMouseWheelToGameplay()
+ {
+ scrollToAndStartBinding("Left button");
+ AddStep("press k", () => InputManager.Key(Key.Z));
+ checkBinding("Left button", "Z");
+
+ AddStep("click again", () => InputManager.Click(MouseButton.Left));
+ AddStep("scroll mouse wheel", () => InputManager.ScrollVerticalBy(1));
+
+ checkBinding("Left button", "Z");
+ }
+
[Test]
public void TestClickTwiceOnClearButton()
{
@@ -135,5 +169,37 @@ namespace osu.Game.Tests.Visual.Settings
AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding);
}
+
+ private void checkBinding(string name, string keyName)
+ {
+ AddAssert($"Check {name} is bound to {keyName}", () =>
+ {
+ var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name));
+ var firstButton = firstRow.ChildrenOfType().First();
+
+ return firstButton.Text.Text == keyName;
+ });
+ }
+
+ private void scrollToAndStartBinding(string name)
+ {
+ KeyBindingRow.KeyButton firstButton = null;
+
+ AddStep($"Scroll to {name}", () =>
+ {
+ var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name));
+ firstButton = firstRow.ChildrenOfType().First();
+
+ panel.ChildrenOfType().First().ScrollTo(firstButton);
+ });
+
+ AddWaitStep("wait for scroll", 5);
+
+ AddStep("click to bind", () =>
+ {
+ InputManager.MoveMouseTo(firstButton);
+ InputManager.Click(MouseButton.Left);
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs
new file mode 100644
index 0000000000..4fef93e291
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs
@@ -0,0 +1,183 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneUpdateableBeatmapSetCover : OsuTestScene
+ {
+ [Test]
+ public void TestLocal([Values] BeatmapSetCoverType coverType)
+ {
+ AddStep("setup cover", () => Child = new UpdateableBeatmapSetCover(coverType)
+ {
+ BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ });
+
+ AddUntilStep("wait for load", () => this.ChildrenOfType().SingleOrDefault()?.IsLoaded ?? false);
+ }
+
+ [Test]
+ public void TestUnloadAndReload()
+ {
+ OsuScrollContainer scroll = null;
+ List covers = new List();
+
+ AddStep("setup covers", () =>
+ {
+ BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
+
+ FillFlowContainer fillFlow;
+
+ Child = scroll = new OsuScrollContainer
+ {
+ Size = new Vector2(500f),
+ Child = fillFlow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(10),
+ Padding = new MarginPadding { Bottom = 550 }
+ }
+ };
+
+ var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType))
+ .Cast()
+ .ToList();
+
+ for (int i = 0; i < 25; i++)
+ {
+ var coverType = coverTypes[i % coverTypes.Count];
+
+ var cover = new UpdateableBeatmapSetCover(coverType)
+ {
+ BeatmapSet = setInfo,
+ Height = 100,
+ Masking = true,
+ };
+
+ if (coverType == BeatmapSetCoverType.Cover)
+ cover.Width = 500;
+ else if (coverType == BeatmapSetCoverType.Card)
+ cover.Width = 400;
+ else if (coverType == BeatmapSetCoverType.List)
+ cover.Size = new Vector2(100, 50);
+
+ fillFlow.Add(cover);
+ covers.Add(cover);
+ }
+ });
+
+ var loadedCovers = covers.Where(c => c.ChildrenOfType().SingleOrDefault()?.IsLoaded ?? false);
+
+ AddUntilStep("some loaded", () => loadedCovers.Any());
+ AddStep("scroll to end", () => scroll.ScrollToEnd());
+ AddUntilStep("all unloaded", () => !loadedCovers.Any());
+ }
+
+ [Test]
+ public void TestSetNullBeatmapWhileLoading()
+ {
+ TestUpdateableBeatmapSetCover updateableCover = null;
+
+ AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover
+ {
+ BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ });
+
+ AddStep("change model", () => updateableCover.BeatmapSet = null);
+ AddWaitStep("wait some", 5);
+ AddAssert("no cover added", () => !updateableCover.ChildrenOfType().Any());
+ }
+
+ [Test]
+ public void TestCoverChangeOnNewBeatmap()
+ {
+ TestUpdateableBeatmapSetCover updateableCover = null;
+ BeatmapSetCover initialCover = null;
+
+ AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover(0)
+ {
+ BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"),
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Alpha = 0.4f
+ });
+
+ AddUntilStep("cover loaded", () => updateableCover.ChildrenOfType().Any());
+ AddStep("store initial cover", () => initialCover = updateableCover.ChildrenOfType().Single());
+ AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1);
+
+ AddStep("switch beatmap",
+ () => updateableCover.BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg"));
+ AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType().Except(new[] { initialCover }).Any());
+ }
+
+ private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers { Cover = coverUrl }
+ }
+ };
+
+ private class TestUpdateableBeatmapSetCover : UpdateableBeatmapSetCover
+ {
+ private readonly int loadDelay;
+
+ public TestUpdateableBeatmapSetCover(int loadDelay = 10000)
+ {
+ this.loadDelay = loadDelay;
+ }
+
+ protected override Drawable CreateDrawable(BeatmapSetInfo model)
+ {
+ if (model == null)
+ return null;
+
+ return new TestBeatmapSetCover(model, loadDelay)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fill,
+ };
+ }
+ }
+
+ private class TestBeatmapSetCover : BeatmapSetCover
+ {
+ private readonly int loadDelay;
+
+ public TestBeatmapSetCover(BeatmapSetInfo set, int loadDelay)
+ : base(set)
+ {
+ this.loadDelay = loadDelay;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Thread.Sleep(loadDelay);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs
index 6c229755e7..7248c9213c 100644
--- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs
+++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs
@@ -1,6 +1,7 @@
// 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.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -8,78 +9,52 @@ using osu.Game.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
- public class UpdateableBeatmapSetCover : Container
+ public class UpdateableBeatmapSetCover : ModelBackedDrawable
{
- private Drawable displayedCover;
-
- private BeatmapSetInfo beatmapSet;
+ private readonly BeatmapSetCoverType coverType;
public BeatmapSetInfo BeatmapSet
{
- get => beatmapSet;
- set
- {
- if (value == beatmapSet) return;
-
- beatmapSet = value;
-
- if (IsLoaded)
- updateCover();
- }
+ get => Model;
+ set => Model = value;
}
- private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover;
-
- public BeatmapSetCoverType CoverType
+ public new bool Masking
{
- get => coverType;
- set
- {
- if (value == coverType) return;
-
- coverType = value;
-
- if (IsLoaded)
- updateCover();
- }
+ get => base.Masking;
+ set => base.Masking = value;
}
- public UpdateableBeatmapSetCover()
+ public UpdateableBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover)
{
- Child = new Box
+ this.coverType = coverType;
+
+ InternalChild = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f),
};
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateCover();
- }
+ protected override double LoadDelay => 500;
- private void updateCover()
- {
- displayedCover?.FadeOut(400);
- displayedCover?.Expire();
- displayedCover = null;
+ protected override double TransformDuration => 400;
- if (beatmapSet != null)
+ protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad)
+ => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad);
+
+ protected override Drawable CreateDrawable(BeatmapSetInfo model)
+ {
+ if (model == null)
+ return null;
+
+ return new BeatmapSetCover(model, coverType)
{
- Add(displayedCover = new DelayedLoadUnloadWrapper(() =>
- {
- var cover = new BeatmapSetCover(beatmapSet, coverType)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fill,
- };
- cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out);
- return cover;
- }));
- }
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fill,
+ };
}
}
}
diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs
index 9b53ee7b2d..1047aa4255 100644
--- a/osu.Game/Graphics/UserInterface/DialogButton.cs
+++ b/osu.Game/Graphics/UserInterface/DialogButton.cs
@@ -15,6 +15,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Effects;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@@ -180,9 +181,9 @@ namespace osu.Game.Graphics.UserInterface
}
}
- private string text;
+ private LocalisableString text;
- public string Text
+ public LocalisableString Text
{
get => text;
set
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
index cd8b486f23..23b09e8fb1 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
@@ -73,10 +73,11 @@ namespace osu.Game.Input.Bindings
else
{
KeyBindings = store.Query(ruleset?.ID, variant)
+ .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction))
// this ordering is important to ensure that we read entries from the database in the order
// enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise
// have been eaten by the music controller due to query order.
- .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)).ToList();
+ .ToList();
}
}
}
diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs
index 9d0cfedc03..3ef9923487 100644
--- a/osu.Game/Input/KeyBindingStore.cs
+++ b/osu.Game/Input/KeyBindingStore.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Input.Bindings;
using osu.Framework.Platform;
@@ -16,6 +17,17 @@ namespace osu.Game.Input
{
public event Action KeyBindingChanged;
+ ///
+ /// Keys which should not be allowed for gameplay input purposes.
+ ///
+ private static readonly IEnumerable banned_keys = new[]
+ {
+ InputKey.MouseWheelDown,
+ InputKey.MouseWheelLeft,
+ InputKey.MouseWheelUp,
+ InputKey.MouseWheelRight
+ };
+
public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null)
: base(contextFactory, storage)
{
@@ -93,6 +105,9 @@ namespace osu.Game.Input
using (ContextFactory.GetForWrite())
{
var dbKeyBinding = (DatabasedKeyBinding)keyBinding;
+
+ Debug.Assert(dbKeyBinding.RulesetID == null || CheckValidForGameplay(keyBinding.KeyCombination));
+
Refresh(ref dbKeyBinding);
if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination))
@@ -103,5 +118,16 @@ namespace osu.Game.Input
KeyBindingChanged?.Invoke();
}
+
+ public static bool CheckValidForGameplay(KeyCombination combination)
+ {
+ foreach (var key in banned_keys)
+ {
+ if (combination.Keys.Contains(key))
+ return false;
+ }
+
+ return true;
+ }
}
}
diff --git a/osu.Game/Localisation/ButtonSystem.ja.resx b/osu.Game/Localisation/ButtonSystem.ja.resx
new file mode 100644
index 0000000000..02f3e7ce2f
--- /dev/null
+++ b/osu.Game/Localisation/ButtonSystem.ja.resx
@@ -0,0 +1,38 @@
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ソロ
+
+
+ プレイリスト
+
+
+ 遊ぶ
+
+
+ マルチ
+
+
+ エディット
+
+
+ ブラウズ
+
+
+ 閉じる
+
+
+ 設定
+
+
diff --git a/osu.Game/Localisation/ButtonSystem.resx b/osu.Game/Localisation/ButtonSystem.resx
new file mode 100644
index 0000000000..d72ffff8be
--- /dev/null
+++ b/osu.Game/Localisation/ButtonSystem.resx
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ solo
+
+
+ multi
+
+
+ playlists
+
+
+ play
+
+
+ edit
+
+
+ browse
+
+
+ settings
+
+
+ back
+
+
+ exit
+
+
\ No newline at end of file
diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs
new file mode 100644
index 0000000000..8083f80782
--- /dev/null
+++ b/osu.Game/Localisation/ButtonSystemStrings.cs
@@ -0,0 +1,59 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class ButtonSystemStrings
+ {
+ private const string prefix = @"osu.Game.Localisation.ButtonSystem";
+
+ ///
+ /// "solo"
+ ///
+ public static LocalisableString Solo => new TranslatableString(getKey(@"solo"), @"solo");
+
+ ///
+ /// "multi"
+ ///
+ public static LocalisableString Multi => new TranslatableString(getKey(@"multi"), @"multi");
+
+ ///
+ /// "playlists"
+ ///
+ public static LocalisableString Playlists => new TranslatableString(getKey(@"playlists"), @"playlists");
+
+ ///
+ /// "play"
+ ///
+ public static LocalisableString Play => new TranslatableString(getKey(@"play"), @"play");
+
+ ///
+ /// "edit"
+ ///
+ public static LocalisableString Edit => new TranslatableString(getKey(@"edit"), @"edit");
+
+ ///
+ /// "browse"
+ ///
+ public static LocalisableString Browse => new TranslatableString(getKey(@"browse"), @"browse");
+
+ ///
+ /// "settings"
+ ///
+ public static LocalisableString Settings => new TranslatableString(getKey(@"settings"), @"settings");
+
+ ///
+ /// "back"
+ ///
+ public static LocalisableString Back => new TranslatableString(getKey(@"back"), @"back");
+
+ ///
+ /// "exit"
+ ///
+ public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"exit");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Localisation/Chat.resx b/osu.Game/Localisation/Chat.resx
new file mode 100644
index 0000000000..055e351463
--- /dev/null
+++ b/osu.Game/Localisation/Chat.resx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ chat
+
+
+ join the real-time discussion
+
+
\ No newline at end of file
diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs
new file mode 100644
index 0000000000..daddb602ad
--- /dev/null
+++ b/osu.Game/Localisation/ChatStrings.cs
@@ -0,0 +1,24 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class ChatStrings
+ {
+ private const string prefix = "osu.Game.Localisation.Chat";
+
+ ///
+ /// "chat"
+ ///
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "chat");
+
+ ///
+ /// "join the real-time discussion"
+ ///
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "join the real-time discussion");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/Common.resx b/osu.Game/Localisation/Common.resx
new file mode 100644
index 0000000000..59de16a037
--- /dev/null
+++ b/osu.Game/Localisation/Common.resx
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cancel
+
+
\ No newline at end of file
diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs
new file mode 100644
index 0000000000..f448158191
--- /dev/null
+++ b/osu.Game/Localisation/CommonStrings.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class CommonStrings
+ {
+ private const string prefix = "osu.Game.Localisation.Common";
+
+ ///
+ /// "Cancel"
+ ///
+ public static LocalisableString Cancel => new TranslatableString(getKey("cancel"), "Cancel");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs
new file mode 100644
index 0000000000..edcf264c7f
--- /dev/null
+++ b/osu.Game/Localisation/Language.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+
+namespace osu.Game.Localisation
+{
+ public enum Language
+ {
+ [Description("English")]
+ en,
+
+ [Description("日本語")]
+ ja
+ }
+}
diff --git a/osu.Game/Localisation/Notifications.resx b/osu.Game/Localisation/Notifications.resx
new file mode 100644
index 0000000000..08db240ba2
--- /dev/null
+++ b/osu.Game/Localisation/Notifications.resx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ notifications
+
+
+ waiting for 'ya
+
+
\ No newline at end of file
diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs
new file mode 100644
index 0000000000..092eec3a6b
--- /dev/null
+++ b/osu.Game/Localisation/NotificationsStrings.cs
@@ -0,0 +1,24 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class NotificationsStrings
+ {
+ private const string prefix = "osu.Game.Localisation.Notifications";
+
+ ///
+ /// "notifications"
+ ///
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "notifications");
+
+ ///
+ /// "waiting for 'ya"
+ ///
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "waiting for 'ya");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/NowPlaying.resx b/osu.Game/Localisation/NowPlaying.resx
new file mode 100644
index 0000000000..40fda3e25b
--- /dev/null
+++ b/osu.Game/Localisation/NowPlaying.resx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ now playing
+
+
+ manage the currently playing track
+
+
\ No newline at end of file
diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs
new file mode 100644
index 0000000000..d742a56895
--- /dev/null
+++ b/osu.Game/Localisation/NowPlayingStrings.cs
@@ -0,0 +1,24 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class NowPlayingStrings
+ {
+ private const string prefix = "osu.Game.Localisation.NowPlaying";
+
+ ///
+ /// "now playing"
+ ///
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "now playing");
+
+ ///
+ /// "manage the currently playing track"
+ ///
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "manage the currently playing track");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
new file mode 100644
index 0000000000..7b21e1af42
--- /dev/null
+++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Resources;
+using System.Threading.Tasks;
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public class ResourceManagerLocalisationStore : ILocalisationStore
+ {
+ private readonly Dictionary resourceManagers = new Dictionary();
+
+ public ResourceManagerLocalisationStore(string cultureCode)
+ {
+ EffectiveCulture = new CultureInfo(cultureCode);
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public string Get(string lookup)
+ {
+ var split = lookup.Split(':');
+
+ string ns = split[0];
+ string key = split[1];
+
+ lock (resourceManagers)
+ {
+ if (!resourceManagers.TryGetValue(ns, out var manager))
+ resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly);
+
+ try
+ {
+ return manager.GetString(key, EffectiveCulture);
+ }
+ catch (MissingManifestResourceException)
+ {
+ // in the case the manifest is missing, it is likely that the user is adding code-first implementations of new localisation namespaces.
+ // it's fine to ignore this as localisation will fallback to default values.
+ return null;
+ }
+ }
+ }
+
+ public Task GetAsync(string lookup)
+ {
+ return Task.FromResult(Get(lookup));
+ }
+
+ public Stream GetStream(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerable GetAvailableResources()
+ {
+ throw new NotImplementedException();
+ }
+
+ public CultureInfo EffectiveCulture { get; }
+ }
+}
diff --git a/osu.Game/Localisation/Settings.resx b/osu.Game/Localisation/Settings.resx
new file mode 100644
index 0000000000..85c224cedf
--- /dev/null
+++ b/osu.Game/Localisation/Settings.resx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ settings
+
+
+ change the way osu! behaves
+
+
\ No newline at end of file
diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs
new file mode 100644
index 0000000000..cfbd392691
--- /dev/null
+++ b/osu.Game/Localisation/SettingsStrings.cs
@@ -0,0 +1,24 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class SettingsStrings
+ {
+ private const string prefix = "osu.Game.Localisation.Settings";
+
+ ///
+ /// "settings"
+ ///
+ public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "settings");
+
+ ///
+ /// "change the way osu! behaves"
+ ///
+ public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "change the way osu! behaves");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 06e0b6e9bf..b7946f1c2f 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -51,6 +51,7 @@ using osu.Game.Utils;
using LogLevel = osu.Framework.Logging.LogLevel;
using osu.Game.Database;
using osu.Game.IO;
+using osu.Game.Localisation;
using osu.Game.Skinning.Editor;
namespace osu.Game
@@ -562,6 +563,12 @@ namespace osu.Game
{
base.LoadComplete();
+ foreach (var language in Enum.GetValues(typeof(Language)).OfType())
+ {
+ var cultureCode = language.ToString();
+ Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode));
+ }
+
// The next time this is updated is in UpdateAfterChildren, which occurs too late and results
// in the cursor being shown for a few frames during the intro.
// This prevents the cursor from showing until we have a screen with CursorVisible = true
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
index 1576431d40..97ccb66599 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Overlays.BeatmapListing
{
RelativeSizeAxes = Axes.Both,
Masking = true,
- Child = beatmapCover = new UpdateableBeatmapSetCover
+ Child = beatmapCover = new TopSearchBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
@@ -184,5 +184,10 @@ namespace osu.Game.Overlays.BeatmapListing
return true;
}
}
+
+ private class TopSearchBeatmapSetCover : UpdateableBeatmapSetCover
+ {
+ protected override bool TransformImmediately => true;
+ }
}
}
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 28f2287514..285041800a 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -24,6 +24,8 @@ using osu.Game.Overlays.Chat.Tabs;
using osuTK.Input;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Online;
namespace osu.Game.Overlays
@@ -31,8 +33,8 @@ namespace osu.Game.Overlays
public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{
public string IconTexture => "Icons/Hexacons/messaging";
- public string Title => "chat";
- public string Description => "join the real-time discussion";
+ public LocalisableString Title => ChatStrings.HeaderTitle;
+ public LocalisableString Description => ChatStrings.HeaderDescription;
private const float textbox_height = 60;
private const float channel_selection_min_height = 0.3f;
diff --git a/osu.Game/Overlays/Dialog/ConfirmDialog.cs b/osu.Game/Overlays/Dialog/ConfirmDialog.cs
index a87c06ffdf..d1c0d746d1 100644
--- a/osu.Game/Overlays/Dialog/ConfirmDialog.cs
+++ b/osu.Game/Overlays/Dialog/ConfirmDialog.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Dialog
},
new PopupDialogCancelButton
{
- Text = @"Cancel",
+ Text = Localisation.CommonStrings.Cancel,
Action = onCancel
},
};
diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs
index 735f0bcbd4..58c41c4a4b 100644
--- a/osu.Game/Overlays/FullscreenOverlay.cs
+++ b/osu.Game/Overlays/FullscreenOverlay.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osuTK.Graphics;
@@ -18,8 +19,8 @@ namespace osu.Game.Overlays
where T : OverlayHeader
{
public virtual string IconTexture => Header.Title.IconTexture ?? string.Empty;
- public virtual string Title => Header.Title.Title ?? string.Empty;
- public virtual string Description => Header.Title.Description ?? string.Empty;
+ public virtual LocalisableString Title => Header.Title.Title;
+ public virtual LocalisableString Description => Header.Title.Description;
public T Header { get; }
diff --git a/osu.Game/Overlays/INamedOverlayComponent.cs b/osu.Game/Overlays/INamedOverlayComponent.cs
index 38fb8679a0..ca0aea041e 100644
--- a/osu.Game/Overlays/INamedOverlayComponent.cs
+++ b/osu.Game/Overlays/INamedOverlayComponent.cs
@@ -1,14 +1,16 @@
// 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.Localisation;
+
namespace osu.Game.Overlays
{
public interface INamedOverlayComponent
{
string IconTexture { get; }
- string Title { get; }
+ LocalisableString Title { get; }
- string Description { get; }
+ LocalisableString Description { get; }
}
}
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 43942d2d52..9c09b6e7d0 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -16,6 +16,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
+using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@@ -445,6 +446,9 @@ namespace osu.Game.Overlays.KeyBinding
public void UpdateKeyCombination(KeyCombination newCombination)
{
+ if ((KeyBinding as DatabasedKeyBinding)?.RulesetID != null && !KeyBindingStore.CheckValidForGameplay(newCombination))
+ return;
+
KeyBinding.KeyCombination = newCombination;
Text.Text = KeyBinding.KeyCombination.ReadableString();
}
diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs
index d51d964fc4..b26e17b34c 100644
--- a/osu.Game/Overlays/NotificationOverlay.cs
+++ b/osu.Game/Overlays/NotificationOverlay.cs
@@ -11,16 +11,18 @@ using osu.Game.Graphics.Containers;
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Graphics;
+using osu.Game.Localisation;
namespace osu.Game.Overlays
{
public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{
public string IconTexture => "Icons/Hexacons/notification";
- public string Title => "notifications";
- public string Description => "waiting for 'ya";
+ public LocalisableString Title => NotificationsStrings.HeaderTitle;
+ public LocalisableString Description => NotificationsStrings.HeaderDescription;
private const float width = 320;
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index 81bf71cdec..f88be91c01 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -19,6 +19,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Overlays.Music;
using osuTK;
using osuTK.Graphics;
@@ -28,8 +29,8 @@ namespace osu.Game.Overlays
public class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{
public string IconTexture => "Icons/Hexacons/music";
- public string Title => "now playing";
- public string Description => "manage the currently playing track";
+ public LocalisableString Title => NowPlayingStrings.HeaderTitle;
+ public LocalisableString Description => NowPlayingStrings.HeaderDescription;
private const float player_height = 130;
private const float transition_length = 800;
diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs
index c3ea35adfc..d92979e8d4 100644
--- a/osu.Game/Overlays/OverlayTitle.cs
+++ b/osu.Game/Overlays/OverlayTitle.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@@ -19,15 +20,15 @@ namespace osu.Game.Overlays
private readonly OsuSpriteText titleText;
private readonly Container icon;
- private string title;
+ private LocalisableString title;
- public string Title
+ public LocalisableString Title
{
get => title;
protected set => titleText.Text = title = value;
}
- public string Description { get; protected set; }
+ public LocalisableString Description { get; protected set; }
private string iconTexture;
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
index 20e40569e8..6d6ff32aac 100644
--- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
+++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
@@ -41,12 +41,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
{
AddRangeInternal(new Drawable[]
{
- new UpdateableBeatmapSetCover
+ new UpdateableBeatmapSetCover(BeatmapSetCoverType.List)
{
RelativeSizeAxes = Axes.Y,
Width = cover_width,
BeatmapSet = beatmap.BeatmapSet,
- CoverType = BeatmapSetCoverType.List,
},
new Container
{
diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
index 44e42ecbfe..c2767f61b4 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
@@ -1,27 +1,45 @@
// 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.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.General
{
public class LanguageSettings : SettingsSubsection
{
+ private SettingsDropdown languageSelection;
+ private Bindable frameworkLocale;
+
protected override string Header => "Language";
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig)
{
+ frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale);
+
Children = new Drawable[]
{
+ languageSelection = new SettingsEnumDropdown
+ {
+ LabelText = "Language",
+ },
new SettingsCheckbox
{
LabelText = "Prefer metadata in original language",
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode)
},
};
+
+ if (!Enum.TryParse(frameworkLocale.Value, out var locale))
+ locale = Language.en;
+ languageSelection.Current.Value = locale;
+
+ languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToString());
}
}
}
diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs
index d8ec00bd99..a7f1cef74c 100644
--- a/osu.Game/Overlays/Settings/SettingsHeader.cs
+++ b/osu.Game/Overlays/Settings/SettingsHeader.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -11,10 +12,10 @@ namespace osu.Game.Overlays.Settings
{
public class SettingsHeader : Container
{
- private readonly string heading;
- private readonly string subheading;
+ private readonly LocalisableString heading;
+ private readonly LocalisableString subheading;
- public SettingsHeader(string heading, string subheading)
+ public SettingsHeader(LocalisableString heading, LocalisableString subheading)
{
this.heading = heading;
this.subheading = subheading;
diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs
index 7bd84dbc6c..8c21880cc6 100644
--- a/osu.Game/Overlays/SettingsOverlay.cs
+++ b/osu.Game/Overlays/SettingsOverlay.cs
@@ -10,14 +10,16 @@ using osuTK.Graphics;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
namespace osu.Game.Overlays
{
public class SettingsOverlay : SettingsPanel, INamedOverlayComponent
{
public string IconTexture => "Icons/Hexacons/settings";
- public string Title => "settings";
- public string Description => "change the way osu! behaves";
+ public LocalisableString Title => SettingsStrings.HeaderTitle;
+ public LocalisableString Description => SettingsStrings.HeaderDescription;
protected override IEnumerable CreateSections() => new SettingsSection[]
{
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index 8f3274b2b5..f0a11d67b7 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -191,7 +191,7 @@ namespace osu.Game.Overlays
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
- protected class SettingsSectionsContainer : SectionsContainer
+ public class SettingsSectionsContainer : SectionsContainer
{
public SearchContainer SearchContainer;
diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs
index 62e2539c2a..8166e6b8ce 100644
--- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs
+++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Edit
}
}
- private void updateReplay() => drawableRuleset.RegenerateAutoplay();
+ private void updateReplay() => Scheduler.AddOnce(drawableRuleset.RegenerateAutoplay);
private void addHitObject(HitObject hitObject)
{
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 86c733c392..cc663c37af 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -172,7 +172,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
base.AddInternal(Samples = new PausableSkinnableSound());
CurrentSkin = skinSource;
- CurrentSkin.SourceChanged += onSkinSourceChanged;
+ CurrentSkin.SourceChanged += skinSourceChanged;
+ }
+
+ protected override void LoadAsyncComplete()
+ {
+ base.LoadAsyncComplete();
+ skinChanged();
}
protected override void LoadComplete()
@@ -495,7 +501,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected ISkinSource CurrentSkin { get; private set; }
- private void onSkinSourceChanged() => Scheduler.AddOnce(() =>
+ private void skinSourceChanged() => Scheduler.AddOnce(skinChanged);
+
+ private void skinChanged()
{
UpdateComboColour();
@@ -503,7 +511,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (IsLoaded)
updateState(State.Value, true);
- });
+ }
protected void UpdateComboColour()
{
@@ -747,7 +755,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (HitObject != null)
HitObject.DefaultsApplied -= onDefaultsApplied;
- CurrentSkin.SourceChanged -= onSkinSourceChanged;
+ CurrentSkin.SourceChanged -= skinSourceChanged;
}
}
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index d6f002ea2c..75c3a4661c 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -13,6 +13,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges.Events;
using osu.Framework.Input.States;
using osu.Game.Configuration;
+using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Screens.Play;
@@ -169,6 +170,13 @@ namespace osu.Game.Rulesets.UI
: base(ruleset, variant, unique)
{
}
+
+ protected override void ReloadMappings()
+ {
+ base.ReloadMappings();
+
+ KeyBindings = KeyBindings.Where(b => KeyBindingStore.CheckValidForGameplay(b.KeyCombination)).ToList();
+ }
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index e231f7f648..3e97e15cca 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -61,6 +61,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
inputManager = GetContainingInputManager();
+ Beatmap.HitObjectAdded += hitObjectAdded;
+
// updates to selected are handled for us by SelectionHandler.
NewCombo.BindTo(SelectionHandler.SelectionNewComboState);
@@ -259,10 +261,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null;
- protected override void OnBlueprintAdded(HitObject item)
+ private void hitObjectAdded(HitObject obj)
{
- base.OnBlueprintAdded(item);
-
+ // refresh the tool to handle the case of placement completing.
refreshTool();
// on successful placement, the new combo button should be reset as this is the most common user interaction.
diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs
index d956394ebb..26f26d1304 100644
--- a/osu.Game/Screens/Menu/Button.cs
+++ b/osu.Game/Screens/Menu/Button.cs
@@ -19,6 +19,7 @@ using osu.Game.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Screens.Menu
@@ -50,7 +51,7 @@ namespace osu.Game.Screens.Menu
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
- public Button(string text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
+ public Button(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
{
this.sampleName = sampleName;
this.clickAction = clickAction;
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 81b1cb0bf1..a836f7bf09 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Threading;
@@ -22,6 +23,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
@@ -97,8 +99,8 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(new Drawable[]
{
- new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
- backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
+ new Button(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
+ backButton = new Button(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
{
VisibleState = ButtonSystemState.Play,
},
@@ -121,19 +123,19 @@ namespace osu.Game.Screens.Menu
private LoginOverlay loginOverlay { get; set; }
[BackgroundDependencyLoader(true)]
- private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
+ private void load(AudioManager audio, IdleTracker idleTracker, GameHost host, LocalisationManager strings)
{
- buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
- buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
- buttonsPlay.Add(new Button(@"playlists", @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
+ buttonsPlay.Add(new Button(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
+ buttonsPlay.Add(new Button(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
+ buttonsPlay.Add(new Button(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
- buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
- buttonsTopLevel.Add(new Button(@"edit", @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
- buttonsTopLevel.Add(new Button(@"browse", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
+ buttonsTopLevel.Add(new Button(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
+ buttonsTopLevel.Add(new Button(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
+ buttonsTopLevel.Add(new Button(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
if (host.CanExit)
- buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
+ buttonsTopLevel.Add(new Button(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
buttonArea.AddRange(buttonsPlay);
buttonArea.AddRange(buttonsTopLevel);
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
index 0eb2367f73..e9ccbcdae2 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
InternalChild = judgementsFlow = new JudgementFlow();
}
- protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset)));
+ protected override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(judgement.Type));
private class JudgementFlow : FillFlowContainer
{
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index ab5b01cab6..ffe03815f5 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -176,10 +176,7 @@ namespace osu.Game.Screens.Play
foreach (var element in mainComponents.Components.Cast())
{
// for now align top-right components with the bottom-edge of the lowest top-anchored hud element.
- if (!element.RelativeSizeAxes.HasFlagFast(Axes.X))
- continue;
-
- if (element.Anchor.HasFlagFast(Anchor.TopRight))
+ if (element.Anchor.HasFlagFast(Anchor.TopRight) || (element.Anchor.HasFlagFast(Anchor.y0) && element.RelativeSizeAxes == Axes.X))
{
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
if (element is LegacyHealthDisplay)
@@ -189,7 +186,8 @@ namespace osu.Game.Screens.Play
if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y)
lowestTopScreenSpace = bottomRight;
}
- else if (element.Anchor.HasFlagFast(Anchor.y2))
+ // and align bottom-right components with the top-edge of the highest bottom-anchored hud element.
+ else if (element.Anchor.HasFlagFast(Anchor.BottomRight) || (element.Anchor.HasFlagFast(Anchor.y2) && element.RelativeSizeAxes == Axes.X))
{
var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
@@ -198,12 +196,12 @@ namespace osu.Game.Screens.Play
}
if (lowestTopScreenSpace.HasValue)
- topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y;
+ topRightElements.Y = TopScoringElementsHeight = MathHelper.Clamp(ToLocalSpace(lowestTopScreenSpace.Value).Y, 0, DrawHeight - topRightElements.DrawHeight);
else
topRightElements.Y = 0;
if (highestBottomScreenSpace.HasValue)
- bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y);
+ bottomRightElements.Y = BottomScoringElementsHeight = -MathHelper.Clamp(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y, 0, DrawHeight - bottomRightElements.DrawHeight);
else
bottomRightElements.Y = 0;
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 7a64f38840..fb9cf47cb7 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -338,6 +338,7 @@ namespace osu.Game.Skinning
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
+ var combo = container.OfType().FirstOrDefault();
if (score != null && accuracy != null)
{
@@ -353,9 +354,12 @@ namespace osu.Game.Skinning
hitError.Anchor = Anchor.BottomCentre;
hitError.Origin = Anchor.CentreLeft;
hitError.Rotation = -90;
+ }
- if (songProgress != null)
- hitError.Y -= SongProgress.MAX_HEIGHT;
+ if (songProgress != null)
+ {
+ if (hitError != null) hitError.Y -= SongProgress.MAX_HEIGHT;
+ if (combo != null) combo.Y -= SongProgress.MAX_HEIGHT;
}
})
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1e3b77cd70..fa2945db6a 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -29,7 +29,11 @@
-
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index a2a9ac35fc..e35b1b5c42 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 4ac796ccd0..62751cebb1 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -120,6 +120,7 @@
WARNING
WARNING
HINT
+ HINT
WARNING
HINT
HINT