1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 08:52:55 +08:00

Merge branch 'master' into add-mod-search-option

This commit is contained in:
Cootz 2023-06-12 13:09:09 +03:00 committed by GitHub
commit aece548db1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 148 additions and 92 deletions

View File

@ -1,22 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModAutoplay : OsuModTestScene
{
protected override bool AllowFail => true;
[Test]
public void TestCursorPositionStoredToJudgement()
{
@ -44,6 +51,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
FinalRate = { Value = 1.3 }
});
[Test]
public void TestPerfectScoreOnShortSliderWithRepeat()
{
AddStep("set score to standardised", () => LocalConfig.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
CreateModTest(new ModTestData
{
Autoplay = true,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Slider
{
StartTime = 500,
Position = new Vector2(256, 192),
Path = new SliderPath(new[]
{
new PathControlPoint(),
new PathControlPoint(new Vector2(0, 6.25f))
}),
RepeatCount = 1,
SliderVelocity = 10
}
}
},
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 1_000_000
});
}
private void runSpmTest(Mod mod)
{
SpinnerSpmCalculator? spmCalculator = null;

View File

@ -75,18 +75,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both };
AddRangeInternal(new Drawable[]
{
shakeContainer = new ShakeContainer
{
ShakeDuration = 30,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
Children = new[]
{
Body = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
// proxied here so that the tail is drawn under repeats/ticks - legacy skins rely on this
tailContainer.CreateProxy(),
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
// actual tail container is placed here to ensure that tail hitobjects are processed after ticks/repeats.
// this is required for the correct operation of Score V2.
tailContainer,
}
},
// slider head is not included in shake as it handles hit detection, and handles its own shaking.

View File

@ -48,21 +48,26 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
private Bindable<bool> configHitLighting = null!;
private static readonly Vector2 circle_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
[Resolved]
private DrawableHitObject drawableObject { get; set; } = null!;
public ArgonMainCirclePiece(bool withOuterFill)
{
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Size = circle_size;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
outerFill = new Circle // renders white outer border and dark fill
outerFill = new Circle // renders dark fill
{
Size = Size,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
// Slightly inset to prevent bleeding outside the ring
Size = circle_size - new Vector2(1),
Alpha = withOuterFill ? 1 : 0,
},
outerGradient = new Circle // renders the outer bright gradient
@ -88,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
Masking = true,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = Size,
Size = circle_size,
Child = new KiaiFlash
{
RelativeSizeAxes = Axes.Both,

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
private const double pre_beat_transition_time = 80;
private const float flash_opacity = 0.3f;
private const float kiai_flash_opacity = 0.15f;
private ColourInfo accentColour;
@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
if (drawableHitObject.State.Value == ArmedState.Idle)
{
flash
.FadeTo(flash_opacity)
.FadeTo(kiai_flash_opacity)
.Then()
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
}

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
private const double pre_beat_transition_time = 80;
private const float flash_opacity = 0.3f;
private const float kiai_flash_opacity = 0.15f;
[Resolved]
private DrawableHitObject drawableHitObject { get; set; } = null!;
@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
if (drawableHitObject.State.Value == ArmedState.Idle)
{
flashBox
.FadeTo(flash_opacity)
.FadeTo(kiai_flash_opacity)
.Then()
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using System.Threading;
@ -24,8 +22,8 @@ namespace osu.Game.Tests.Visual.Online
{
private readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" };
private TestSpectatorClient spectatorClient;
private CurrentlyPlayingDisplay currentlyPlaying;
private TestSpectatorClient spectatorClient = null!;
private CurrentlyPlayingDisplay currentlyPlaying = null!;
[SetUpSteps]
public void SetUpSteps()
@ -88,13 +86,13 @@ namespace osu.Game.Tests.Visual.Online
"pishifat"
};
protected override Task<APIUser> ComputeValueAsync(int lookup, CancellationToken token = default)
protected override Task<APIUser?> ComputeValueAsync(int lookup, CancellationToken token = default)
{
// tests against failed lookups
if (lookup == 13)
return Task.FromResult<APIUser>(null);
return Task.FromResult<APIUser?>(null);
return Task.FromResult(new APIUser
return Task.FromResult<APIUser?>(new APIUser
{
Id = lookup,
Username = usernames[lookup % usernames.Length],

View File

@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -24,10 +23,10 @@ namespace osu.Game.Tests.Visual.SongSelect
{
public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene
{
private BeatmapMetadataDisplay display;
private BeatmapMetadataDisplay display = null!;
[Resolved]
private BeatmapManager manager { get; set; }
private BeatmapManager manager { get; set; } = null!;
[Cached(typeof(BeatmapDifficultyCache))]
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
@ -121,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
{
private TaskCompletionSource<bool> calculationBlocker;
private TaskCompletionSource<bool>? calculationBlocker;
private bool blockCalculation;
@ -142,10 +141,13 @@ namespace osu.Game.Tests.Visual.SongSelect
}
}
public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, IEnumerable<Mod>? mods = null, CancellationToken cancellationToken = default)
{
if (blockCalculation)
{
Debug.Assert(calculationBlocker != null);
await calculationBlocker.Task.ConfigureAwait(false);
}
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
}

View File

@ -17,9 +17,8 @@ namespace osu.Game.Beatmaps
// If issues are found it's worth checking to make sure similar issues exist there.
public class BeatmapPanelBackgroundTextureLoaderStore : IResourceStore<TextureUpload>
{
// These numbers are taken from the draw visualiser size requirements for song select panel textures at extreme aspect ratios.
private const int max_height = 130;
private const int max_width = 1280;
// The aspect ratio of SetPanelBackground at its maximum size (very tall window).
private const float minimum_display_ratio = 512 / 80f;
private readonly IResourceStore<TextureUpload>? textureStore;
@ -66,14 +65,21 @@ namespace osu.Game.Beatmaps
textureUpload.Dispose();
Size size = image.Size();
int usableWidth = Math.Min(max_width, size.Width);
int usableHeight = Math.Min(max_height, size.Height);
// Assume that panel backgrounds are always displayed using `FillMode.Fill`.
// Also assume that all backgrounds are wider than they are tall, so the
// fill is always going to be based on width.
//
// We need to include enough height to make this work for all ratio panels are displayed at.
int usableHeight = (int)Math.Ceiling(size.Width * 1 / minimum_display_ratio);
usableHeight = Math.Min(size.Height, usableHeight);
// Crop the centre region of the background for now.
Rectangle cropRectangle = new Rectangle(
(size.Width - usableWidth) / 2,
0,
(size.Height - usableHeight) / 2,
usableWidth,
size.Width,
usableHeight
);

View File

@ -6,6 +6,7 @@
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
namespace osu.Game.Configuration
{
@ -21,6 +22,7 @@ namespace osu.Game.Configuration
SetDefault(Static.LowBatteryNotificationShownOnce, false);
SetDefault(Static.FeaturedArtistDisclaimerShownOnce, false);
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null);
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
}
@ -56,5 +58,11 @@ namespace osu.Game.Configuration
/// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like <see cref="SettingsOverlay"/>.
/// </summary>
LastHoverSoundPlaybackTime,
/// <summary>
/// The last playback time in milliseconds of an on/off sample (from <see cref="ModSelectPanel"/>).
/// Used to debounce <see cref="ModSelectPanel"/> on/off sounds game-wide to avoid volume saturation, especially in activating mod presets with many mods.
/// </summary>
LastModSelectPanelSamplePlaybackTime
}
}

View File

@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@ -21,8 +18,7 @@ namespace osu.Game.Database
/// <param name="beatmapId">The beatmap to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated beatmap, or null if the beatmap does not exist or the request could not be satisfied.</returns>
[ItemCanBeNull]
public Task<APIBeatmap> GetBeatmapAsync(int beatmapId, CancellationToken token = default) => LookupAsync(beatmapId, token);
public Task<APIBeatmap?> GetBeatmapAsync(int beatmapId, CancellationToken token = default) => LookupAsync(beatmapId, token);
/// <summary>
/// Perform an API lookup on the specified beatmaps, populating a <see cref="APIBeatmap"/> model.
@ -30,10 +26,10 @@ namespace osu.Game.Database
/// <param name="beatmapIds">The beatmaps to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated beatmaps. May include null results for failed retrievals.</returns>
public Task<APIBeatmap[]> GetBeatmapsAsync(int[] beatmapIds, CancellationToken token = default) => LookupAsync(beatmapIds, token);
public Task<APIBeatmap?[]> GetBeatmapsAsync(int[] beatmapIds, CancellationToken token = default) => LookupAsync(beatmapIds, token);
protected override GetBeatmapsRequest CreateRequest(IEnumerable<int> ids) => new GetBeatmapsRequest(ids.ToArray());
protected override IEnumerable<APIBeatmap> RetrieveResults(GetBeatmapsRequest request) => request.Response?.Beatmaps;
protected override IEnumerable<APIBeatmap>? RetrieveResults(GetBeatmapsRequest request) => request.Response?.Beatmaps;
}
}

View File

@ -1,13 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Statistics;
@ -19,8 +17,9 @@ namespace osu.Game.Database
/// Currently not persisted between game sessions.
/// </summary>
public abstract partial class MemoryCachingComponent<TLookup, TValue> : Component
where TLookup : notnull
{
private readonly ConcurrentDictionary<TLookup, TValue> cache = new ConcurrentDictionary<TLookup, TValue>();
private readonly ConcurrentDictionary<TLookup, TValue?> cache = new ConcurrentDictionary<TLookup, TValue?>();
private readonly GlobalStatistic<MemoryCachingStatistics> statistics;
@ -37,12 +36,12 @@ namespace osu.Game.Database
/// </summary>
/// <param name="lookup">The lookup to retrieve.</param>
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
protected async Task<TValue> GetAsync([NotNull] TLookup lookup, CancellationToken token = default)
protected async Task<TValue?> GetAsync(TLookup lookup, CancellationToken token = default)
{
if (CheckExists(lookup, out TValue performance))
if (CheckExists(lookup, out TValue? existing))
{
statistics.Value.HitCount++;
return performance;
return existing;
}
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
@ -73,7 +72,7 @@ namespace osu.Game.Database
statistics.Value.Usage = cache.Count;
}
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) =>
protected bool CheckExists(TLookup lookup, [MaybeNullWhen(false)] out TValue value) =>
cache.TryGetValue(lookup, out value);
/// <summary>
@ -82,7 +81,7 @@ namespace osu.Game.Database
/// <param name="lookup">The lookup to retrieve.</param>
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
/// <returns>The computed value.</returns>
protected abstract Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
protected abstract Task<TValue?> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
private class MemoryCachingStatistics
{

View File

@ -1,14 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Game.Online.API;
@ -21,7 +18,7 @@ namespace osu.Game.Database
where TRequest : APIRequest
{
[Resolved]
private IAPIProvider api { get; set; }
private IAPIProvider api { get; set; } = null!;
/// <summary>
/// Creates an <see cref="APIRequest"/> to retrieve the values for a given collection of <typeparamref name="TLookup"/>s.
@ -32,8 +29,7 @@ namespace osu.Game.Database
/// <summary>
/// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>.
/// </summary>
[CanBeNull]
protected abstract IEnumerable<TValue> RetrieveResults(TRequest request);
protected abstract IEnumerable<TValue>? RetrieveResults(TRequest request);
/// <summary>
/// Perform a lookup using the specified <paramref name="id"/>, populating a <typeparamref name="TValue"/>.
@ -41,8 +37,7 @@ namespace osu.Game.Database
/// <param name="id">The ID to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated <typeparamref name="TValue"/>, or null if the value does not exist or the request could not be satisfied.</returns>
[ItemCanBeNull]
protected Task<TValue> LookupAsync(TLookup id, CancellationToken token = default) => GetAsync(id, token);
protected Task<TValue?> LookupAsync(TLookup id, CancellationToken token = default) => GetAsync(id, token);
/// <summary>
/// Perform an API lookup on the specified <paramref name="ids"/>, populating a <typeparamref name="TValue"/>.
@ -50,9 +45,9 @@ namespace osu.Game.Database
/// <param name="ids">The IDs to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated values. May include null results for failed retrievals.</returns>
protected Task<TValue[]> LookupAsync(TLookup[] ids, CancellationToken token = default)
protected Task<TValue?[]> LookupAsync(TLookup[] ids, CancellationToken token = default)
{
var lookupTasks = new List<Task<TValue>>();
var lookupTasks = new List<Task<TValue?>>();
foreach (var id in ids)
{
@ -69,18 +64,18 @@ namespace osu.Game.Database
}
// cannot be sealed due to test usages (see TestUserLookupCache).
protected override async Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default)
protected override async Task<TValue?> ComputeValueAsync(TLookup lookup, CancellationToken token = default)
=> await queryValue(lookup).ConfigureAwait(false);
private readonly Queue<(TLookup id, TaskCompletionSource<TValue>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue>)>();
private Task pendingRequestTask;
private readonly Queue<(TLookup id, TaskCompletionSource<TValue?>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue?>)>();
private Task? pendingRequestTask;
private readonly object taskAssignmentLock = new object();
private Task<TValue> queryValue(TLookup id)
private Task<TValue?> queryValue(TLookup id)
{
lock (taskAssignmentLock)
{
var tcs = new TaskCompletionSource<TValue>();
var tcs = new TaskCompletionSource<TValue?>();
// Add to the queue.
pendingTasks.Enqueue((id, tcs));
@ -96,14 +91,14 @@ namespace osu.Game.Database
private async Task performLookup()
{
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue>>>();
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue?>>>();
// Grab at most 50 unique IDs from the queue.
lock (taskAssignmentLock)
{
while (pendingTasks.Count > 0 && nextTaskBatch.Count < 50)
{
(TLookup id, TaskCompletionSource<TValue> task) next = pendingTasks.Dequeue();
(TLookup id, TaskCompletionSource<TValue?> task) next = pendingTasks.Dequeue();
// Perform a secondary check for existence, in case the value was queried in a previous batch.
if (CheckExists(next.id, out var existing))
@ -113,7 +108,7 @@ namespace osu.Game.Database
if (nextTaskBatch.TryGetValue(next.id, out var tasks))
tasks.Add(next.task);
else
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue>> { next.task };
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue?>> { next.task };
}
}
}

View File

@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@ -21,8 +18,7 @@ namespace osu.Game.Database
/// <param name="userId">The user to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated user, or null if the user does not exist or the request could not be satisfied.</returns>
[ItemCanBeNull]
public Task<APIUser> GetUserAsync(int userId, CancellationToken token = default) => LookupAsync(userId, token);
public Task<APIUser?> GetUserAsync(int userId, CancellationToken token = default) => LookupAsync(userId, token);
/// <summary>
/// Perform an API lookup on the specified users, populating a <see cref="APIUser"/> model.
@ -30,10 +26,10 @@ namespace osu.Game.Database
/// <param name="userIds">The users to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated users. May include null results for failed retrievals.</returns>
public Task<APIUser[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
public Task<APIUser?[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
protected override GetUsersRequest CreateRequest(IEnumerable<int> ids) => new GetUsersRequest(ids.ToArray());
protected override IEnumerable<APIUser> RetrieveResults(GetUsersRequest request) => request.Response?.Users;
protected override IEnumerable<APIUser>? RetrieveResults(GetUsersRequest request) => request.Response?.Users;
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
namespace osu.Game.Online.API.Requests

View File

@ -67,7 +67,7 @@ namespace osu.Game.Online.Rooms
{
var beatmap = task.GetResultSafely();
if (SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID)
if (beatmap != null && SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID)
{
selectedBeatmap = beatmap;
beginTracking();

View File

@ -180,7 +180,7 @@ namespace osu.Game.Overlays.Mods
dequeuedAction();
// each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements).
selectionDelay = Math.Max(30, selectionDelay * 0.8f);
selectionDelay = Math.Max(ModSelectPanel.SAMPLE_PLAYBACK_DELAY, selectionDelay * 0.8f);
lastSelection = Time.Current;
}
else

View File

@ -15,6 +15,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -46,6 +47,8 @@ namespace osu.Game.Overlays.Mods
public const float CORNER_RADIUS = 7;
public const float HEIGHT = 42;
public const double SAMPLE_PLAYBACK_DELAY = 30;
protected virtual float IdleSwitchWidth => 14;
protected virtual float ExpandedSwitchWidth => 30;
protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3;
@ -70,6 +73,8 @@ namespace osu.Game.Overlays.Mods
private Sample? sampleOff;
private Sample? sampleOn;
private Bindable<double?> lastPlaybackTime = null!;
protected ModSelectPanel()
{
RelativeSizeAxes = Axes.X;
@ -164,13 +169,15 @@ namespace osu.Game.Overlays.Mods
protected abstract void Deselect();
[BackgroundDependencyLoader]
private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler)
private void load(AudioManager audio, SessionStatics statics, ISamplePlaybackDisabler? samplePlaybackDisabler)
{
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");
if (samplePlaybackDisabler != null)
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
lastPlaybackTime = statics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime);
}
protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
@ -196,10 +203,17 @@ namespace osu.Game.Overlays.Mods
if (!IsPresent)
return;
if (Active.Value)
sampleOn?.Play();
else
sampleOff?.Play();
bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= SAMPLE_PLAYBACK_DELAY;
if (enoughTimePassedSinceLastPlayback)
{
if (Active.Value)
sampleOn?.Play();
else
sampleOff?.Play();
lastPlaybackTime.Value = Time.Current;
}
}
protected override bool OnHover(HoverEvent e)

View File

@ -28,9 +28,10 @@ namespace osu.Game.Scoring.Legacy
/// <remarks>
/// <list type="bullet">
/// <item><description>30000001: Appends <see cref="LegacyReplaySoloScoreInfo"/> to the end of scores.</description></item>
/// <item><description>30000002: Score stored to replay calculated using the Score V2 algorithm.</description></item>
/// </list>
/// </remarks>
public const int LATEST_VERSION = 30000001;
public const int LATEST_VERSION = 30000002;
/// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.

View File

@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Database;
@ -21,7 +18,7 @@ namespace osu.Game.Scoring
public partial class ScorePerformanceCache : MemoryCachingComponent<ScorePerformanceCache.PerformanceCacheLookup, PerformanceAttributes>
{
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
protected override bool CacheNullValues => false;
@ -30,10 +27,10 @@ namespace osu.Game.Scoring
/// </summary>
/// <param name="score">The score to do the calculation on. </param>
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
public Task<PerformanceAttributes> CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) =>
public Task<PerformanceAttributes?> CalculatePerformanceAsync(ScoreInfo score, CancellationToken token = default) =>
GetAsync(new PerformanceCacheLookup(score), token);
protected override async Task<PerformanceAttributes> ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default)
protected override async Task<PerformanceAttributes?> ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default)
{
var score = lookup.ScoreInfo;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Threading;
using System.Threading.Tasks;
using osu.Game.Database;
@ -18,12 +16,12 @@ namespace osu.Game.Tests.Visual
/// </summary>
public const int UNRESOLVED_USER_ID = -1;
protected override Task<APIUser> ComputeValueAsync(int lookup, CancellationToken token = default)
protected override Task<APIUser?> ComputeValueAsync(int lookup, CancellationToken token = default)
{
if (lookup == UNRESOLVED_USER_ID)
return Task.FromResult((APIUser)null);
return Task.FromResult<APIUser?>(null);
return Task.FromResult(new APIUser
return Task.FromResult<APIUser?>(new APIUser
{
Id = lookup,
Username = $"User {lookup}"