diff --git a/osu.Android.props b/osu.Android.props
index a406cdf08a..69f897128c 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index b3dd392202..c63e30e98a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -7,6 +7,7 @@ using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
@@ -49,6 +50,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override void OnMouseUp(MouseUpEvent e)
{
+ if (e.Button != MouseButton.Left)
+ return;
+
base.OnMouseUp(e);
EndPlacement(true);
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index 400abb6380..3fb03d642f 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
@@ -46,6 +47,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e)
{
+ if (e.Button != MouseButton.Left)
+ return false;
+
if (Column == null)
return base.OnMouseDown(e);
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
index 2b7b383dbe..a4c0791253 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
+using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
@@ -30,6 +31,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e)
{
+ if (e.Button != MouseButton.Left)
+ return false;
+
base.OnMouseDown(e);
// Place the note immediately.
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index cf6677a55d..e0577dd464 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -28,8 +28,11 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- foreach (var point in slider.Path.ControlPoints)
+ var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
+ foreach (var point in controlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
+
+ slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index caf645d5a2..d324441285 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -167,13 +167,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
default:
{
- bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
+ bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
+
+ bool isRim = samples.Any(isRimDefinition);
yield return new Hit
{
StartTime = obj.StartTime,
Type = isRim ? HitType.Rim : HitType.Centre,
- Samples = obj.Samples,
+ Samples = samples,
IsStrong = strong
};
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index d2671eadda..d332f90cd4 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
+using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
@@ -47,6 +49,37 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit)
: new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
+ protected override IEnumerable GetSamples()
+ {
+ // normal and claps are always handled by the drum (see DrumSampleMapping).
+ var samples = HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
+
+ if (HitObject.Type == HitType.Rim && HitObject.IsStrong)
+ {
+ // strong + rim always maps to whistle.
+ // TODO: this should really be in the legacy decoder, but can't be because legacy encoding parity would be broken.
+ // when we add a taiko editor, this is probably not going to play nice.
+
+ var corrected = samples.ToList();
+
+ for (var i = 0; i < corrected.Count; i++)
+ {
+ var s = corrected[i];
+
+ if (s.Name != HitSampleInfo.HIT_FINISH)
+ continue;
+
+ var sClone = s.Clone();
+ sClone.Name = HitSampleInfo.HIT_WHISTLE;
+ corrected[i] = sClone;
+ }
+
+ return corrected;
+ }
+
+ return samples;
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 1be04f1760..90daf3950c 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -165,8 +165,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return base.CreateNestedHitObject(hitObject);
}
- // Normal and clap samples are handled by the drum
- protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
+ // Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
+ protected override IEnumerable GetSamples() => Enumerable.Empty();
protected abstract SkinnableDrawable CreateMainPiece();
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index bcc873b0b7..30331e98d2 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
@@ -28,14 +29,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))]
- public void TestBeatmap(string name)
+ public void TestEncodeDecodeStability(string name)
{
- var decoded = decode(name, out var encoded);
+ var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name));
+ var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded));
sort(decoded);
- sort(encoded);
+ sort(decodedAfterEncode);
- Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
+ Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize()));
}
private void sort(IBeatmap beatmap)
@@ -48,27 +50,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
- private IBeatmap decode(string filename, out IBeatmap encoded)
+ private IBeatmap decodeFromLegacy(Stream stream)
{
- using (var stream = TestResources.GetStore().GetStream(filename))
- using (var sr = new LineBufferedReader(stream))
- {
- var legacyDecoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr));
+ using (var reader = new LineBufferedReader(stream))
+ return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader));
+ }
- using (var ms = new MemoryStream())
- using (var sw = new StreamWriter(ms))
- using (var sr2 = new LineBufferedReader(ms, true))
- {
- new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
+ private Stream encodeToLegacy(IBeatmap beatmap)
+ {
+ var stream = new MemoryStream();
- sw.Flush();
- ms.Position = 0;
+ using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+ new LegacyBeatmapEncoder(beatmap).Encode(writer);
- encoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2));
+ stream.Position = 0;
- return legacyDecoded;
- }
- }
+ return stream;
}
private IBeatmap convert(IBeatmap beatmap)
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 685decf097..8deed75a56 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -152,11 +152,12 @@ namespace osu.Game.Tests.Skins
}
[Test]
- public void TestSetBeatmapVersionNoFallback()
+ public void TestSetBeatmapVersionFallsBackToUserSkin()
{
+ // completely ignoring beatmap versions for simplicity.
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m);
- AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m);
+ AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
}
[Test]
@@ -172,7 +173,6 @@ namespace osu.Game.Tests.Skins
public void TestIniWithNoVersionFallsBackTo1()
{
AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream())));
- AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
}
diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs
index 64f1ebeb1a..f98fa05821 100644
--- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs
+++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs
@@ -9,12 +9,12 @@ using Newtonsoft.Json.Linq;
namespace osu.Game.IO.Serialization.Converters
{
///
- /// A type of that serializes a alongside
+ /// A type of that serializes an alongside
/// a lookup table for the types contained. The lookup table is used in deserialization to
/// reconstruct the objects with their original types.
///
- /// The type of objects contained in the this attribute is attached to.
- public class TypedListConverter : JsonConverter
+ /// The type of objects contained in the this attribute is attached to.
+ public class TypedListConverter : JsonConverter>
{
private readonly bool requiresTypeVersion;
@@ -36,9 +36,7 @@ namespace osu.Game.IO.Serialization.Converters
this.requiresTypeVersion = requiresTypeVersion;
}
- public override bool CanConvert(Type objectType) => objectType == typeof(List);
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override IReadOnlyList ReadJson(JsonReader reader, Type objectType, IReadOnlyList existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var list = new List();
@@ -59,14 +57,12 @@ namespace osu.Game.IO.Serialization.Converters
return list;
}
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, IReadOnlyList value, JsonSerializer serializer)
{
- var list = (IEnumerable)value;
-
var lookupTable = new List();
var objects = new List();
- foreach (var item in list)
+ foreach (var item in value)
{
var type = item.GetType();
var assemblyName = type.Assembly.GetName();
diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs
index bf5edeef94..46447b607b 100644
--- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs
+++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs
@@ -11,26 +11,22 @@ namespace osu.Game.IO.Serialization.Converters
///
/// A type of that serializes only the X and Y coordinates of a .
///
- public class Vector2Converter : JsonConverter
+ public class Vector2Converter : JsonConverter
{
- public override bool CanConvert(Type objectType) => objectType == typeof(Vector2);
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
return new Vector2((float)obj["x"], (float)obj["y"]);
}
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer)
{
- var vector = (Vector2)value;
-
writer.WriteStartObject();
writer.WritePropertyName("x");
- writer.WriteValue(vector.X);
+ writer.WriteValue(value.X);
writer.WritePropertyName("y");
- writer.WriteValue(vector.Y);
+ writer.WriteValue(value.Y);
writer.WriteEndObject();
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 3caffb6db5..7ecd7851d7 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -91,7 +91,7 @@ namespace osu.Game
protected BackButton BackButton;
- protected SettingsPanel Settings;
+ protected SettingsOverlay Settings;
private VolumeOverlay volume;
private OsuLogo osuLogo;
@@ -767,13 +767,20 @@ namespace osu.Game
private Task asyncLoadStream;
- private T loadComponentSingleFile(T d, Action add, bool cache = false)
+ ///
+ /// Queues loading the provided component in sequential fashion.
+ /// This operation is limited to a single thread to avoid saturating all cores.
+ ///
+ /// The component to load.
+ /// An action to invoke on load completion (generally to add the component to the hierarchy).
+ /// Whether to cache the component as type into the game dependencies before any scheduling.
+ private T loadComponentSingleFile(T component, Action loadCompleteAction, bool cache = false)
where T : Drawable
{
if (cache)
- dependencies.Cache(d);
+ dependencies.CacheAs(component);
- if (d is OverlayContainer overlay)
+ if (component is OverlayContainer overlay)
overlays.Add(overlay);
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
@@ -791,12 +798,12 @@ namespace osu.Game
try
{
- Logger.Log($"Loading {d}...", level: LogLevel.Debug);
+ Logger.Log($"Loading {component}...", level: LogLevel.Debug);
// Since this is running in a separate thread, it is possible for OsuGame to be disposed after LoadComponentAsync has been called
// throwing an exception. To avoid this, the call is scheduled on the update thread, which does not run if IsDisposed = true
Task task = null;
- var del = new ScheduledDelegate(() => task = LoadComponentAsync(d, add));
+ var del = new ScheduledDelegate(() => task = LoadComponentAsync(component, loadCompleteAction));
Scheduler.Add(del);
// The delegate won't complete if OsuGame has been disposed in the meantime
@@ -811,7 +818,7 @@ namespace osu.Game
await task;
- Logger.Log($"Loaded {d}!", level: LogLevel.Debug);
+ Logger.Log($"Loaded {component}!", level: LogLevel.Debug);
}
catch (OperationCanceledException)
{
@@ -819,7 +826,7 @@ namespace osu.Game
});
});
- return d;
+ return component;
}
protected override bool OnScroll(ScrollEvent e)
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 58ca2143f9..01d5991d3e 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -250,6 +250,28 @@ namespace osu.Game.Overlays.KeyBinding
finalise();
}
+ protected override bool OnMidiDown(MidiDownEvent e)
+ {
+ if (!HasFocus)
+ return false;
+
+ bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
+ finalise();
+
+ return true;
+ }
+
+ protected override void OnMidiUp(MidiUpEvent e)
+ {
+ if (!HasFocus)
+ {
+ base.OnMidiUp(e);
+ return;
+ }
+
+ finalise();
+ }
+
private void clear()
{
bindTarget.UpdateKeyCombination(InputKey.None);
diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs
index 20cdca858a..0dd3341a93 100644
--- a/osu.Game/Screens/Select/DifficultyRecommender.cs
+++ b/osu.Game/Screens/Select/DifficultyRecommender.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
- api.Unregister(this);
+ api?.Unregister(this);
}
}
}
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index 21533e58cd..87bca856a3 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -27,9 +27,11 @@ namespace osu.Game.Skinning
switch (lookup)
{
case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version:
- if (Configuration.LegacyVersion is decimal version)
- return SkinUtils.As(new Bindable(version));
+ // For lookup simplicity, ignore beatmap-level versioning completely.
+ // If it is decided that we need this due to beatmaps somehow using it, the default (1.0 specified in LegacySkinDecoder.CreateTemplateObject)
+ // needs to be removed else it will cause incorrect skin behaviours. This is due to the config lookup having no context of which skin
+ // it should be returning the version for.
return null;
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 5ccfaaac9e..c6dba8da13 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index dc83d937f7..f78fd2e4ff 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -80,7 +80,7 @@
-
+