diff --git a/osu.Android.props b/osu.Android.props
index 6aebae665d..c88bea8265 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -11,7 +11,7 @@
manifestmerger.jar
-
+
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index 99a80ef28d..b2155968ea 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -21,18 +21,29 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
+ SetDefault(ManiaRulesetSetting.ScrollSpeed, 8, 1, 40);
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
+
+#pragma warning disable CS0618
+ // Although obsolete, this is still required to populate the bindable from the database in case migration is required.
+ SetDefault(ManiaRulesetSetting.ScrollTime, null);
+
+ if (Get(ManiaRulesetSetting.ScrollTime) is double scrollTime)
+ {
+ SetValue(ManiaRulesetSetting.ScrollSpeed, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime));
+ SetValue(ManiaRulesetSetting.ScrollTime, null);
+ }
+#pragma warning restore CS0618
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
- new TrackedSetting(ManiaRulesetSetting.ScrollTime,
- scrollTime => new SettingDescription(
- rawValue: scrollTime,
+ new TrackedSetting(ManiaRulesetSetting.ScrollSpeed,
+ speed => new SettingDescription(
+ rawValue: speed,
name: RulesetSettingsStrings.ScrollSpeed,
- value: RulesetSettingsStrings.ScrollSpeedTooltip(scrollTime, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime))
+ value: RulesetSettingsStrings.ScrollSpeedTooltip(DrawableManiaRuleset.ComputeScrollTime(speed), speed)
)
)
};
@@ -40,7 +51,9 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaRulesetSetting
{
+ [Obsolete("Use ScrollSpeed instead.")] // Can be removed 2023-11-30
ScrollTime,
+ ScrollSpeed,
ScrollDirection,
TimingBasedNoteColouring
}
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
index fc0b4a9ed9..a5434a36ab 100644
--- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
@@ -34,10 +33,10 @@ namespace osu.Game.Rulesets.Mania
LabelText = RulesetSettingsStrings.ScrollingDirection,
Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection)
},
- new SettingsSlider
+ new SettingsSlider
{
LabelText = RulesetSettingsStrings.ScrollSpeed,
- Current = config.GetBindable(ManiaRulesetSetting.ScrollTime),
+ Current = config.GetBindable(ManiaRulesetSetting.ScrollSpeed),
KeyboardStep = 5
},
new SettingsCheckbox
@@ -48,9 +47,9 @@ namespace osu.Game.Rulesets.Mania
};
}
- private partial class ManiaScrollSlider : RoundedSliderBar
+ private partial class ManiaScrollSlider : RoundedSliderBar
{
- public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(Current.Value, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value));
+ public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index ce34addeff..3f91328128 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -245,7 +245,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// As the note is being held, adjust the size of the sizing container. This has two effects:
// 1. The contained masking container will mask the body and ticks.
// 2. The head note will move along with the new "head position" in the container.
- if (Head.IsHit && releaseTime == null && DrawHeight > 0)
+ //
+ // As per stable, this should not apply for early hits, waiting until the object starts to touch the
+ // judgement area first.
+ if (Head.IsHit && releaseTime == null && DrawHeight > 0 && Time.Current >= HitObject.StartTime)
{
// How far past the hit target this hold note is.
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index af8758fb5e..2d373c0471 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -33,12 +33,12 @@ namespace osu.Game.Rulesets.Mania.UI
public partial class DrawableManiaRuleset : DrawableScrollingRuleset
{
///
- /// The minimum time range. This occurs at a of 40.
+ /// The minimum time range. This occurs at a of 40.
///
public const double MIN_TIME_RANGE = 290;
///
- /// The maximum time range. This occurs at a of 1.
+ /// The maximum time range. This occurs with a of 1.
///
public const double MAX_TIME_RANGE = 11485;
@@ -69,7 +69,8 @@ namespace osu.Game.Rulesets.Mania.UI
protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod;
private readonly Bindable configDirection = new Bindable();
- private readonly BindableDouble configTimeRange = new BindableDouble();
+ private readonly BindableInt configScrollSpeed = new BindableInt();
+ private double smoothTimeRange;
// Stores the current speed adjustment active in gameplay.
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
@@ -78,6 +79,9 @@ namespace osu.Game.Rulesets.Mania.UI
: base(ruleset, beatmap, mods)
{
BarLines = new BarLineGenerator(Beatmap).BarLines;
+
+ TimeRange.MinValue = 1;
+ TimeRange.MaxValue = MAX_TIME_RANGE;
}
[BackgroundDependencyLoader]
@@ -104,30 +108,28 @@ namespace osu.Game.Rulesets.Mania.UI
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
- Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange);
- TimeRange.MinValue = configTimeRange.MinValue;
- TimeRange.MaxValue = configTimeRange.MaxValue;
+ Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
+ configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
+
+ TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
}
- protected override void AdjustScrollSpeed(int amount)
- {
- this.TransformTo(nameof(relativeTimeRange), relativeTimeRange + amount, 200, Easing.OutQuint);
- }
-
- private double relativeTimeRange
- {
- get => MAX_TIME_RANGE / configTimeRange.Value;
- set => configTimeRange.Value = MAX_TIME_RANGE / value;
- }
+ protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount;
protected override void Update()
{
base.Update();
-
updateTimeRange();
}
- private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
+ private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
+
+ ///
+ /// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40.
+ ///
+ /// The scroll speed.
+ /// The scroll time.
+ public static double ComputeScrollTime(int scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
index 8fdab9f1f9..616a9c362d 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs
@@ -17,6 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModAutoplay : OsuModTestScene
{
+ [Test]
+ public void TestCursorPositionStoredToJudgement()
+ {
+ CreateModTest(new ModTestData
+ {
+ Autoplay = true,
+ PassCondition = () =>
+ Player.ScoreProcessor.JudgedHits >= 1
+ && Player.ScoreProcessor.HitEvents.Any(e => e.Position != null)
+ });
+ }
+
[Test]
public void TestSpmUnaffectedByRateAdjust()
=> runSpmTest(new OsuModDaycore
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
index 12e2090f89..b74b722bad 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
@@ -42,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Mods
private PlayfieldAdjustmentContainer bubbleContainer = null!;
+ private DrawablePool bubblePool = null!;
+
private readonly Bindable currentCombo = new BindableInt();
private float maxSize;
private float bubbleSize;
private double bubbleFade;
- private readonly DrawablePool bubblePool = new DrawablePool(100);
-
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
@@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods
bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer();
drawableRuleset.Overlays.Add(bubbleContainer);
+ drawableRuleset.Overlays.Add(bubblePool = new DrawablePool(100));
}
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index ab07ac3e9d..f97be0d7ff 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
@@ -13,6 +15,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
}
+ protected override HitEvent CreateHitEvent(JudgementResult result)
+ => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit);
+
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
return 700000 * comboProgress
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
index 3efc7fbd30..6d309078e6 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
@@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Playlist =
{
- new MultiplayerPlaylistItem(playlistItem),
+ TestMultiplayerClient.CreateMultiplayerPlaylistItem(playlistItem),
},
Users = { localUser },
Host = localUser,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index d747d23229..09624f63b7 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -906,7 +906,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
- AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
+ AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
@@ -938,7 +938,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
- AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
+ AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
index d7578b4114..2100f82886 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
@@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
///
private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () =>
{
- MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
+ MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
{
Expired = expired,
PlayedAt = DateTimeOffset.Now
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
index bb37f1a5a7..47fb4e06ea 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add playlist item", () =>
{
- MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
+ MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index d799e82bc9..82f89d6889 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -19,6 +19,7 @@ using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
+using osu.Game.Online.Multiplayer;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using SixLabors.ImageSharp;
@@ -69,7 +70,7 @@ namespace osu.Game.Graphics
{
case GlobalAction.TakeScreenshot:
shutter.Play();
- TakeScreenshotAsync();
+ TakeScreenshotAsync().FireAndForget();
return true;
}
@@ -86,70 +87,75 @@ namespace osu.Game.Graphics
{
Interlocked.Increment(ref screenShotTasks);
- if (!captureMenuCursor.Value)
+ try
{
- cursorVisibility.Value = false;
-
- // We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
- const int frames_to_wait = 3;
-
- int framesWaited = 0;
-
- using (var framesWaitedEvent = new ManualResetEventSlim(false))
+ if (!captureMenuCursor.Value)
{
- ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
+ cursorVisibility.Value = false;
+
+ // We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
+ const int frames_to_wait = 3;
+
+ int framesWaited = 0;
+
+ using (var framesWaitedEvent = new ManualResetEventSlim(false))
{
- if (framesWaited++ >= frames_to_wait)
- // ReSharper disable once AccessToDisposedClosure
- framesWaitedEvent.Set();
- }, 10, true);
+ ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
+ {
+ if (framesWaited++ >= frames_to_wait)
+ // ReSharper disable once AccessToDisposedClosure
+ framesWaitedEvent.Set();
+ }, 10, true);
- if (!framesWaitedEvent.Wait(1000))
- throw new TimeoutException("Screenshot data did not arrive in a timely fashion");
+ if (!framesWaitedEvent.Wait(1000))
+ throw new TimeoutException("Screenshot data did not arrive in a timely fashion");
- waitDelegate.Cancel();
+ waitDelegate.Cancel();
+ }
+ }
+
+ using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
+ {
+ host.GetClipboard()?.SetImage(image);
+
+ (string filename, var stream) = getWritableStream();
+
+ if (filename == null) return;
+
+ using (stream)
+ {
+ switch (screenshotFormat.Value)
+ {
+ case ScreenshotFormat.Png:
+ await image.SaveAsPngAsync(stream).ConfigureAwait(false);
+ break;
+
+ case ScreenshotFormat.Jpg:
+ const int jpeg_quality = 92;
+
+ await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false);
+ break;
+
+ default:
+ throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
+ }
+ }
+
+ notificationOverlay.Post(new SimpleNotification
+ {
+ Text = $"Screenshot {filename} saved!",
+ Activated = () =>
+ {
+ storage.PresentFileExternally(filename);
+ return true;
+ }
+ });
}
}
-
- using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
+ finally
{
- if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false)
+ if (Interlocked.Decrement(ref screenShotTasks) == 0)
cursorVisibility.Value = true;
-
- host.GetClipboard()?.SetImage(image);
-
- (string filename, var stream) = getWritableStream();
-
- if (filename == null) return;
-
- using (stream)
- {
- switch (screenshotFormat.Value)
- {
- case ScreenshotFormat.Png:
- await image.SaveAsPngAsync(stream).ConfigureAwait(false);
- break;
-
- case ScreenshotFormat.Jpg:
- const int jpeg_quality = 92;
-
- await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false);
- break;
-
- default:
- throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
- }
- }
-
- notificationOverlay.Post(new SimpleNotification
- {
- Text = $"Screenshot {filename} saved!",
- Activated = () =>
- {
- storage.PresentFileExternally(filename);
- return true;
- }
- });
}
});
diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs
index 52e6a5eaac..91bbece004 100644
--- a/osu.Game/Localisation/RulesetSettingsStrings.cs
+++ b/osu.Game/Localisation/RulesetSettingsStrings.cs
@@ -82,7 +82,7 @@ namespace osu.Game.Localisation
///
/// "{0}ms (speed {1})"
///
- public static LocalisableString ScrollSpeedTooltip(double arg0, int arg1) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", arg0, arg1);
+ public static LocalisableString ScrollSpeedTooltip(double scrollTime, int scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0:0}ms (speed {1})", scrollTime, scrollSpeed);
private static string getKey(string key) => $@"{prefix}:{key}";
}
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 2be7327234..5716b7ad3b 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -783,7 +783,7 @@ namespace osu.Game.Online.Multiplayer
RoomUpdated?.Invoke();
}
- private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID })
+ private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID, StarRating = item.StarRating })
{
ID = item.ID,
OwnerID = item.OwnerID,
diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
index c45f703b05..8be703e620 100644
--- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
+++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs
@@ -53,22 +53,12 @@ namespace osu.Game.Online.Rooms
[Key(9)]
public DateTimeOffset? PlayedAt { get; set; }
+ [Key(10)]
+ public double StarRating { get; set; }
+
+ [SerializationConstructor]
public MultiplayerPlaylistItem()
{
}
-
- public MultiplayerPlaylistItem(PlaylistItem item)
- {
- ID = item.ID;
- OwnerID = item.OwnerID;
- BeatmapID = item.Beatmap.OnlineID;
- BeatmapChecksum = item.Beatmap.MD5Hash;
- RulesetID = item.RulesetID;
- RequiredMods = item.RequiredMods.ToArray();
- AllowedMods = item.AllowedMods.ToArray();
- Expired = item.Expired;
- PlaylistOrder = item.PlaylistOrder ?? 0;
- PlayedAt = item.PlayedAt;
- }
}
}
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index 2213311c67..a900d8f3d7 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Online.Rooms
}
public PlaylistItem(MultiplayerPlaylistItem item)
- : this(new APIBeatmap { OnlineID = item.BeatmapID })
+ : this(new APIBeatmap { OnlineID = item.BeatmapID, StarRating = item.StarRating })
{
ID = item.ID;
OwnerID = item.OwnerID;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index fe6e479d19..3768dad370 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -1136,12 +1136,22 @@ namespace osu.Game
if (entry.Level == LogLevel.Error)
{
- Schedule(() => Notifications.Post(new SimpleNotification
+ Schedule(() =>
{
- Text = $"Encountered tablet error: \"{message}\"",
- Icon = FontAwesome.Solid.PenSquare,
- IconColour = Colours.RedDark,
- }));
+ Notifications.Post(new SimpleNotification
+ {
+ Text = $"Disabling tablet support due to error: \"{message}\"",
+ Icon = FontAwesome.Solid.PenSquare,
+ IconColour = Colours.RedDark,
+ });
+
+ // We only have one tablet handler currently.
+ // The loop here is weakly guarding against a future where more than one is added.
+ // If this is ever the case, this logic needs adjustment as it should probably only
+ // disable the relevant tablet handler rather than all.
+ foreach (var tabletHandler in Host.AvailableInputHandlers.OfType())
+ tabletHandler.Enabled.Value = false;
+ });
}
else if (notifyOnWarning)
{
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
index 8dd1b51cae..00c90bd317 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
@@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Difficulty
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
{
- Mod classicMod = rulesetInstance.CreateAllMods().SingleOrDefault(m => m is ModClassic);
+ Mod classicMod = rulesetInstance.CreateMod();
var finalCombination = ModUtils.FlattenMod(combination);
if (classicMod != null)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
index ea063e9216..55f122669d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
@@ -50,6 +50,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly Container colouredComponents;
private readonly OsuSpriteText comboIndexText;
+ private readonly SamplePointPiece samplePointPiece;
+ private readonly DifficultyPointPiece? difficultyPointPiece;
[Resolved]
private ISkinSource skin { get; set; } = null!;
@@ -101,7 +103,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
},
}
},
- new SamplePointPiece(Item)
+ samplePointPiece = new SamplePointPiece(Item)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopCentre
@@ -118,7 +120,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (item is IHasSliderVelocity)
{
- AddInternal(new DifficultyPointPiece(Item)
+ AddInternal(difficultyPointPiece = new DifficultyPointPiece(Item)
{
Anchor = Anchor.TopLeft,
Origin = Anchor.BottomCentre
@@ -244,6 +246,25 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft;
+ protected override bool ComputeIsMaskedAway(RectangleF maskingBounds)
+ {
+ // Since children are exceeding the component size, we need to use a custom quad to compute whether it should be masked away.
+
+ // If the component isn't considered masked away by itself, there's no need to apply custom logic.
+ if (!base.ComputeIsMaskedAway(maskingBounds))
+ return false;
+
+ // If the component is considered masked away, we'll use children to create an extended quad that encapsulates all parts of this blueprint
+ // to ensure it doesn't pop in and out of existence abruptly when scrolling the timeline.
+ var rect = RectangleF.Union(ScreenSpaceDrawQuad.AABBFloat, circle.ScreenSpaceDrawQuad.AABBFloat);
+ rect = RectangleF.Union(rect, samplePointPiece.ScreenSpaceDrawQuad.AABBFloat);
+
+ if (difficultyPointPiece != null)
+ rect = RectangleF.Union(rect, difficultyPointPiece.ScreenSpaceDrawQuad.AABBFloat);
+
+ return !Precision.AlmostIntersects(maskingBounds, rect);
+ }
+
private partial class Tick : Circle
{
public Tick()
diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
index 93c8faf0b0..2ee3bb30dd 100644
--- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
@@ -85,15 +85,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
StarDifficulty minDifficulty;
StarDifficulty maxDifficulty;
- if (DifficultyRange.Value != null)
+ if (DifficultyRange.Value != null && Playlist.Count == 0)
{
+ // When Playlist is empty (in lounge) we take retrieved range
minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0);
maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0);
}
else
{
- // In multiplayer rooms, the beatmaps of playlist items will not be populated to a point this can be correct.
- // Either populating them via BeatmapLookupCache or polling the API for the room's DifficultyRange will be required.
+ // When Playlist is not empty (in room) we compute actual range
var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index ad5e3f6c4d..c27e30d5bb 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -108,7 +108,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
// simulate the server's automatic assignment of users to teams on join.
// the "best" team is the one with the least users on it.
int bestTeam = teamVersus.Teams
- .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))).MinBy(pair => pair.userCount).teamID;
+ .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID)))
+ .MinBy(pair => pair.userCount).teamID;
user.MatchState = new TeamVersusUserState { TeamID = bestTeam };
((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).WaitSafely();
@@ -232,7 +233,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = ServerAPIRoom.QueueMode.Value,
AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value
},
- Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(),
+ Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(),
Users = { localUser },
Host = localUser
};
@@ -637,5 +638,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
byte[]? serialized = MessagePackSerializer.Serialize(typeof(T), incoming, SignalRUnionWorkaroundResolver.OPTIONS);
return MessagePackSerializer.Deserialize(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
}
+
+ public static MultiplayerPlaylistItem CreateMultiplayerPlaylistItem(PlaylistItem item) => new MultiplayerPlaylistItem
+ {
+ ID = item.ID,
+ OwnerID = item.OwnerID,
+ BeatmapID = item.Beatmap.OnlineID,
+ BeatmapChecksum = item.Beatmap.MD5Hash,
+ RulesetID = item.RulesetID,
+ RequiredMods = item.RequiredMods.ToArray(),
+ AllowedMods = item.AllowedMods.ToArray(),
+ Expired = item.Expired,
+ PlaylistOrder = item.PlaylistOrder ?? 0,
+ PlayedAt = item.PlayedAt,
+ StarRating = item.Beatmap.StarRating,
+ };
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 0fd2b0c2c5..8a941ca6c1 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index e4a169f8e5..1dcece7741 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -16,6 +16,6 @@
iossimulator-x64
-
+