mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 10:12:54 +08:00
Merge branch 'master' into tournament-mappool-linebreak
This commit is contained in:
commit
0c622ec895
@ -1,22 +1,29 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
{
|
{
|
||||||
public partial class TestSceneOsuModAutoplay : OsuModTestScene
|
public partial class TestSceneOsuModAutoplay : OsuModTestScene
|
||||||
{
|
{
|
||||||
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCursorPositionStoredToJudgement()
|
public void TestCursorPositionStoredToJudgement()
|
||||||
{
|
{
|
||||||
@ -44,6 +51,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
FinalRate = { Value = 1.3 }
|
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)
|
private void runSpmTest(Mod mod)
|
||||||
{
|
{
|
||||||
SpinnerSpmCalculator? spmCalculator = null;
|
SpinnerSpmCalculator? spmCalculator = null;
|
||||||
|
@ -75,18 +75,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
shakeContainer = new ShakeContainer
|
shakeContainer = new ShakeContainer
|
||||||
{
|
{
|
||||||
ShakeDuration = 30,
|
ShakeDuration = 30,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
Body = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
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 },
|
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
repeatContainer = new Container<DrawableSliderRepeat> { 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.
|
// slider head is not included in shake as it handles hit detection, and handles its own shaking.
|
||||||
|
@ -48,21 +48,26 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
|
|
||||||
private Bindable<bool> configHitLighting = null!;
|
private Bindable<bool> configHitLighting = null!;
|
||||||
|
|
||||||
|
private static readonly Vector2 circle_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
public ArgonMainCirclePiece(bool withOuterFill)
|
public ArgonMainCirclePiece(bool withOuterFill)
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = circle_size;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
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,
|
Alpha = withOuterFill ? 1 : 0,
|
||||||
},
|
},
|
||||||
outerGradient = new Circle // renders the outer bright gradient
|
outerGradient = new Circle // renders the outer bright gradient
|
||||||
@ -88,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
Masking = true,
|
Masking = true,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = Size,
|
Size = circle_size,
|
||||||
Child = new KiaiFlash
|
Child = new KiaiFlash
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
|
|
||||||
private const double pre_beat_transition_time = 80;
|
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;
|
private ColourInfo accentColour;
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
if (drawableHitObject.State.Value == ArmedState.Idle)
|
if (drawableHitObject.State.Value == ArmedState.Idle)
|
||||||
{
|
{
|
||||||
flash
|
flash
|
||||||
.FadeTo(flash_opacity)
|
.FadeTo(kiai_flash_opacity)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
|
|
||||||
private const double pre_beat_transition_time = 80;
|
private const double pre_beat_transition_time = 80;
|
||||||
|
|
||||||
private const float flash_opacity = 0.3f;
|
private const float kiai_flash_opacity = 0.15f;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableHitObject { get; set; } = null!;
|
private DrawableHitObject drawableHitObject { get; set; } = null!;
|
||||||
@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
if (drawableHitObject.State.Value == ArmedState.Idle)
|
if (drawableHitObject.State.Value == ArmedState.Idle)
|
||||||
{
|
{
|
||||||
flashBox
|
flashBox
|
||||||
.FadeTo(flash_opacity)
|
.FadeTo(kiai_flash_opacity)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,8 @@ namespace osu.Game.Beatmaps
|
|||||||
// If issues are found it's worth checking to make sure similar issues exist there.
|
// If issues are found it's worth checking to make sure similar issues exist there.
|
||||||
public class BeatmapPanelBackgroundTextureLoaderStore : IResourceStore<TextureUpload>
|
public class BeatmapPanelBackgroundTextureLoaderStore : IResourceStore<TextureUpload>
|
||||||
{
|
{
|
||||||
// These numbers are taken from the draw visualiser size requirements for song select panel textures at extreme aspect ratios.
|
// The aspect ratio of SetPanelBackground at its maximum size (very tall window).
|
||||||
private const int max_height = 130;
|
private const float minimum_display_ratio = 512 / 80f;
|
||||||
private const int max_width = 1280;
|
|
||||||
|
|
||||||
private readonly IResourceStore<TextureUpload>? textureStore;
|
private readonly IResourceStore<TextureUpload>? textureStore;
|
||||||
|
|
||||||
@ -66,14 +65,21 @@ namespace osu.Game.Beatmaps
|
|||||||
textureUpload.Dispose();
|
textureUpload.Dispose();
|
||||||
|
|
||||||
Size size = image.Size();
|
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.
|
// Crop the centre region of the background for now.
|
||||||
Rectangle cropRectangle = new Rectangle(
|
Rectangle cropRectangle = new Rectangle(
|
||||||
(size.Width - usableWidth) / 2,
|
0,
|
||||||
(size.Height - usableHeight) / 2,
|
(size.Height - usableHeight) / 2,
|
||||||
usableWidth,
|
size.Width,
|
||||||
usableHeight
|
usableHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -106,12 +106,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
||||||
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
titleBadgeArea = new FillFlowContainer
|
titleBadgeArea = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -140,21 +139,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = createArtistText(),
|
Text = createArtistText(),
|
||||||
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
Empty()
|
Empty()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
Text = BeatmapSet.Source,
|
Text = BeatmapSet.Source,
|
||||||
Shadow = false,
|
Shadow = false,
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
||||||
|
@ -107,12 +107,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
||||||
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
titleBadgeArea = new FillFlowContainer
|
titleBadgeArea = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -141,12 +140,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = createArtistText(),
|
Text = createArtistText(),
|
||||||
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
Empty()
|
Empty()
|
||||||
},
|
},
|
||||||
|
@ -264,7 +264,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapFileStream == null)
|
if (beatmapFileStream == null)
|
||||||
{
|
{
|
||||||
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error);
|
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error);
|
||||||
return null;
|
return new Storyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var reader = new LineBufferedReader(beatmapFileStream))
|
using (var reader = new LineBufferedReader(beatmapFileStream))
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
@ -21,6 +22,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(Static.LowBatteryNotificationShownOnce, false);
|
SetDefault(Static.LowBatteryNotificationShownOnce, false);
|
||||||
SetDefault(Static.FeaturedArtistDisclaimerShownOnce, false);
|
SetDefault(Static.FeaturedArtistDisclaimerShownOnce, false);
|
||||||
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
||||||
|
SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null);
|
||||||
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, 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"/>.
|
/// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like <see cref="SettingsOverlay"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LastHoverSoundPlaybackTime,
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,9 @@ namespace osu.Game.Database
|
|||||||
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
||||||
/// 27 2023-06-06 Added EditorTimestamp to BeatmapInfo.
|
/// 27 2023-06-06 Added EditorTimestamp to BeatmapInfo.
|
||||||
/// 28 2023-06-08 Added IsLegacyScore to ScoreInfo, parsed from replay files.
|
/// 28 2023-06-08 Added IsLegacyScore to ScoreInfo, parsed from replay files.
|
||||||
|
/// 29 2023-06-12 Run migration of old lazer scores to be best-effort in the new scoring number space. No actual realm changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 28;
|
private const int schema_version = 29;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -724,6 +725,11 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
private void applyMigrationsForVersion(Migration migration, ulong targetVersion)
|
private void applyMigrationsForVersion(Migration migration, ulong targetVersion)
|
||||||
{
|
{
|
||||||
|
Logger.Log($"Running realm migration to version {targetVersion}...");
|
||||||
|
Stopwatch stopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
stopwatch.Start();
|
||||||
|
|
||||||
switch (targetVersion)
|
switch (targetVersion)
|
||||||
{
|
{
|
||||||
case 7:
|
case 7:
|
||||||
@ -930,7 +936,38 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 29:
|
||||||
|
{
|
||||||
|
var scores = migration.NewRealm
|
||||||
|
.All<ScoreInfo>()
|
||||||
|
.Where(s => !s.IsLegacyScore);
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
{
|
||||||
|
// Recalculate the old-style standardised score to see if this was an old lazer score.
|
||||||
|
bool oldScoreMatchesExpectations = StandardisedScoreMigrationTools.GetOldStandardised(score) == score.TotalScore;
|
||||||
|
// Some older scores don't have correct statistics populated, so let's give them benefit of doubt.
|
||||||
|
bool scoreIsVeryOld = score.Date < new DateTime(2023, 1, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
if (oldScoreMatchesExpectations || scoreIsVeryOld)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long calculatedNew = StandardisedScoreMigrationTools.GetNewStandardised(score);
|
||||||
|
score.TotalScore = calculatedNew;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? getRulesetShortNameFromLegacyID(long rulesetId)
|
private string? getRulesetShortNameFromLegacyID(long rulesetId)
|
||||||
|
196
osu.Game/Database/StandardisedScoreMigrationTools.cs
Normal file
196
osu.Game/Database/StandardisedScoreMigrationTools.cs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public static class StandardisedScoreMigrationTools
|
||||||
|
{
|
||||||
|
public static long GetNewStandardised(ScoreInfo score)
|
||||||
|
{
|
||||||
|
int maxJudgementIndex = 0;
|
||||||
|
|
||||||
|
// Avoid retrieving from realm inside loops.
|
||||||
|
int maxCombo = score.MaxCombo;
|
||||||
|
|
||||||
|
var ruleset = score.Ruleset.CreateInstance();
|
||||||
|
var processor = ruleset.CreateScoreProcessor();
|
||||||
|
|
||||||
|
processor.TrackHitEvents = false;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
|
||||||
|
HitResult maxRulesetJudgement = ruleset.GetHitResults().First().result;
|
||||||
|
|
||||||
|
// This is a list of all results, ordered from best to worst.
|
||||||
|
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
|
||||||
|
List<HitResult> sortedHits = score.Statistics
|
||||||
|
.Where(kvp => kvp.Key.AffectsCombo())
|
||||||
|
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Attempt to use maximum statistics from the database.
|
||||||
|
var maximumJudgements = score.MaximumStatistics
|
||||||
|
.Where(kvp => kvp.Key.AffectsCombo())
|
||||||
|
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Some older scores may not have maximum statistics populated correctly.
|
||||||
|
// In this case we need to fill them with best-known-defaults.
|
||||||
|
if (maximumJudgements.Count != sortedHits.Count)
|
||||||
|
{
|
||||||
|
maximumJudgements = sortedHits
|
||||||
|
.Select(r => new FakeJudgement(getMaxJudgementFor(r, maxRulesetJudgement)))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is required to get the correct maximum combo portion.
|
||||||
|
foreach (var judgement in maximumJudgements)
|
||||||
|
beatmap.HitObjects.Add(new FakeHit(judgement));
|
||||||
|
processor.ApplyBeatmap(beatmap);
|
||||||
|
processor.Mods.Value = score.Mods;
|
||||||
|
|
||||||
|
// Insert all misses into a queue.
|
||||||
|
// These will be nibbled at whenever we need to reset the combo.
|
||||||
|
Queue<HitResult> misses = new Queue<HitResult>(score.Statistics
|
||||||
|
.Where(kvp => kvp.Key == HitResult.Miss || kvp.Key == HitResult.LargeTickMiss)
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value)));
|
||||||
|
|
||||||
|
foreach (var result in sortedHits)
|
||||||
|
{
|
||||||
|
// For the main part of this loop, ignore all misses, as they will be inserted from the queue.
|
||||||
|
if (result == HitResult.Miss || result == HitResult.LargeTickMiss)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Reset combo if required.
|
||||||
|
if (processor.Combo.Value == maxCombo)
|
||||||
|
insertMiss();
|
||||||
|
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, maximumJudgements[maxJudgementIndex++])
|
||||||
|
{
|
||||||
|
Type = result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we haven't forgotten any misses.
|
||||||
|
while (misses.Count > 0)
|
||||||
|
insertMiss();
|
||||||
|
|
||||||
|
var bonusHits = score.Statistics
|
||||||
|
.Where(kvp => kvp.Key.IsBonus())
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value));
|
||||||
|
|
||||||
|
foreach (var result in bonusHits)
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, new FakeJudgement(result)) { Type = result });
|
||||||
|
|
||||||
|
// Not true for all scores for whatever reason. Oh well.
|
||||||
|
// Debug.Assert(processor.HighestCombo.Value == score.MaxCombo);
|
||||||
|
|
||||||
|
return processor.TotalScore.Value;
|
||||||
|
|
||||||
|
void insertMiss()
|
||||||
|
{
|
||||||
|
if (misses.Count > 0)
|
||||||
|
{
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, maximumJudgements[maxJudgementIndex++])
|
||||||
|
{
|
||||||
|
Type = misses.Dequeue(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We ran out of misses. But we can't let max combo increase beyond the known value,
|
||||||
|
// so let's forge a miss.
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, new FakeJudgement(getMaxJudgementFor(HitResult.Miss, maxRulesetJudgement)))
|
||||||
|
{
|
||||||
|
Type = HitResult.Miss,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HitResult getMaxJudgementFor(HitResult hitResult, HitResult max)
|
||||||
|
{
|
||||||
|
switch (hitResult)
|
||||||
|
{
|
||||||
|
case HitResult.Miss:
|
||||||
|
case HitResult.Meh:
|
||||||
|
case HitResult.Ok:
|
||||||
|
case HitResult.Good:
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.Perfect:
|
||||||
|
return max;
|
||||||
|
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return HitResult.SmallTickHit;
|
||||||
|
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return HitResult.LargeTickHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HitResult.IgnoreHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long GetOldStandardised(ScoreInfo score)
|
||||||
|
{
|
||||||
|
double accuracyScore =
|
||||||
|
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value)
|
||||||
|
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
|
||||||
|
double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
|
||||||
|
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
|
||||||
|
|
||||||
|
double accuracyPortion = 0.3;
|
||||||
|
|
||||||
|
switch (score.RulesetID)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
accuracyPortion = 0.75;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
accuracyPortion = 0.99;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double modMultiplier = 1;
|
||||||
|
|
||||||
|
foreach (var mod in score.Mods)
|
||||||
|
modMultiplier *= mod.ScoreMultiplier;
|
||||||
|
|
||||||
|
return (long)((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeHit : HitObject
|
||||||
|
{
|
||||||
|
private readonly Judgement judgement;
|
||||||
|
|
||||||
|
public override Judgement CreateJudgement() => judgement;
|
||||||
|
|
||||||
|
public FakeHit(Judgement judgement)
|
||||||
|
{
|
||||||
|
this.judgement = judgement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeJudgement : Judgement
|
||||||
|
{
|
||||||
|
public override HitResult MaxResult { get; }
|
||||||
|
|
||||||
|
public FakeJudgement(HitResult maxResult)
|
||||||
|
{
|
||||||
|
MaxResult = maxResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,19 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Sprites
|
namespace osu.Game.Graphics.Sprites
|
||||||
{
|
{
|
||||||
public partial class OsuSpriteText : SpriteText
|
public partial class OsuSpriteText : SpriteText
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use TruncatingSpriteText instead.")]
|
||||||
|
public new bool Truncate
|
||||||
|
{
|
||||||
|
set => throw new InvalidOperationException($"Use {nameof(TruncatingSpriteText)} instead.");
|
||||||
|
}
|
||||||
|
|
||||||
public OsuSpriteText()
|
public OsuSpriteText()
|
||||||
{
|
{
|
||||||
Shadow = true;
|
Shadow = true;
|
||||||
|
29
osu.Game/Graphics/Sprites/TruncatingSpriteText.cs
Normal file
29
osu.Game/Graphics/Sprites/TruncatingSpriteText.cs
Normal file
@ -0,0 +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 osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Sprites
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A derived version of <see cref="OsuSpriteText"/> which automatically shows non-truncated text in tooltip when required.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class TruncatingSpriteText : OsuSpriteText, IHasTooltip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a tooltip should be shown with non-truncated text on hover.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowTooltip { get; init; } = true;
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => Text;
|
||||||
|
|
||||||
|
public override bool HandlePositionalInput => IsTruncated && ShowTooltip;
|
||||||
|
|
||||||
|
public TruncatingSpriteText()
|
||||||
|
{
|
||||||
|
((SpriteText)this).Truncate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -335,12 +335,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
Text = new OsuSpriteText
|
Text = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
Icon = new SpriteIcon
|
Icon = new SpriteIcon
|
||||||
{
|
{
|
||||||
|
@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
new Drawable?[]
|
new Drawable?[]
|
||||||
{
|
{
|
||||||
createIcon(),
|
createIcon(),
|
||||||
text = new OsuSpriteText
|
text = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -94,7 +94,6 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
Colour = colourProvider.Light3,
|
Colour = colourProvider.Light3,
|
||||||
Margin = new MarginPadding { Bottom = 2 },
|
Margin = new MarginPadding { Bottom = 2 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
createMentionPill(),
|
createMentionPill(),
|
||||||
close = createCloseButton(),
|
close = createCloseButton(),
|
||||||
|
@ -73,14 +73,13 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Width = chatting_text_width,
|
Width = chatting_text_width,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Padding = new MarginPadding { Horizontal = padding },
|
Padding = new MarginPadding { Horizontal = padding },
|
||||||
Child = chattingText = new OsuSpriteText
|
Child = chattingText = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
MaxWidth = chatting_text_width - padding * 2,
|
MaxWidth = chatting_text_width - padding * 2,
|
||||||
Font = OsuFont.Torus.With(size: 20),
|
Font = OsuFont.Torus.With(size: 20),
|
||||||
Colour = colourProvider.Background1,
|
Colour = colourProvider.Background1,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
searchIconContainer = new Container
|
searchIconContainer = new Container
|
||||||
|
@ -83,11 +83,9 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
Action = openUserProfile;
|
Action = openUserProfile;
|
||||||
|
|
||||||
drawableText = new OsuSpriteText
|
drawableText = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Shadow = false,
|
Shadow = false,
|
||||||
Truncate = true,
|
|
||||||
EllipsisString = "…",
|
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
};
|
};
|
||||||
|
@ -100,17 +100,15 @@ namespace osu.Game.Overlays.Dashboard.Home
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
||||||
Text = BeatmapSet.Title
|
Text = BeatmapSet.Title
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
|
||||||
Text = BeatmapSet.Artist
|
Text = BeatmapSet.Artist
|
||||||
},
|
},
|
||||||
|
@ -176,7 +176,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
dequeuedAction();
|
dequeuedAction();
|
||||||
|
|
||||||
// each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements).
|
// 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;
|
lastSelection = Time.Current;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -45,6 +46,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
public const float CORNER_RADIUS = 7;
|
public const float CORNER_RADIUS = 7;
|
||||||
public const float HEIGHT = 42;
|
public const float HEIGHT = 42;
|
||||||
|
|
||||||
|
public const double SAMPLE_PLAYBACK_DELAY = 30;
|
||||||
|
|
||||||
protected virtual float IdleSwitchWidth => 14;
|
protected virtual float IdleSwitchWidth => 14;
|
||||||
protected virtual float ExpandedSwitchWidth => 30;
|
protected virtual float ExpandedSwitchWidth => 30;
|
||||||
protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3;
|
protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3;
|
||||||
@ -69,6 +72,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private Sample? sampleOff;
|
private Sample? sampleOff;
|
||||||
private Sample? sampleOn;
|
private Sample? sampleOn;
|
||||||
|
|
||||||
|
private Bindable<double?> lastPlaybackTime = null!;
|
||||||
|
|
||||||
protected ModSelectPanel()
|
protected ModSelectPanel()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@ -118,23 +123,23 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
titleText = new OsuSpriteText
|
titleText = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
Margin = new MarginPadding
|
Margin = new MarginPadding
|
||||||
{
|
{
|
||||||
Left = -18 * ShearedOverlayContainer.SHEAR
|
Left = -18 * ShearedOverlayContainer.SHEAR
|
||||||
}
|
},
|
||||||
|
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||||
},
|
},
|
||||||
descriptionText = new OsuSpriteText
|
descriptionText = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Font = OsuFont.Default.With(size: 12),
|
Font = OsuFont.Default.With(size: 12),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0)
|
ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,13 +168,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
protected abstract void Deselect();
|
protected abstract void Deselect();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
private void load(AudioManager audio, SessionStatics statics, ISamplePlaybackDisabler? samplePlaybackDisabler)
|
||||||
{
|
{
|
||||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||||
|
|
||||||
if (samplePlaybackDisabler != null)
|
if (samplePlaybackDisabler != null)
|
||||||
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
((IBindable<bool>)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled);
|
||||||
|
|
||||||
|
lastPlaybackTime = statics.GetBindable<double?>(Static.LastHoverSoundPlaybackTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||||
@ -192,10 +199,17 @@ namespace osu.Game.Overlays.Mods
|
|||||||
if (samplePlaybackDisabled.Value)
|
if (samplePlaybackDisabled.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Active.Value)
|
bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= SAMPLE_PLAYBACK_DELAY;
|
||||||
sampleOn?.Play();
|
|
||||||
else
|
if (enoughTimePassedSinceLastPlayback)
|
||||||
sampleOff?.Play();
|
{
|
||||||
|
if (Active.Value)
|
||||||
|
sampleOn?.Play();
|
||||||
|
else
|
||||||
|
sampleOff?.Play();
|
||||||
|
|
||||||
|
lastPlaybackTime.Value = Time.Current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
{
|
{
|
||||||
protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
|
protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
|
||||||
|
|
||||||
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing" });
|
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency" });
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
|
@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected int MaxHits { get; private set; }
|
protected int MaxHits { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether <see cref="SimulateAutoplay"/> is currently running.
|
||||||
|
/// </summary>
|
||||||
|
protected bool IsSimulating { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total number of judged <see cref="HitObject"/>s at the current point in time.
|
/// The total number of judged <see cref="HitObject"/>s at the current point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -146,6 +151,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to simulate.</param>
|
/// <param name="beatmap">The <see cref="IBeatmap"/> to simulate.</param>
|
||||||
protected virtual void SimulateAutoplay(IBeatmap beatmap)
|
protected virtual void SimulateAutoplay(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
IsSimulating = true;
|
||||||
|
|
||||||
foreach (var obj in beatmap.HitObjects)
|
foreach (var obj in beatmap.HitObjects)
|
||||||
simulate(obj);
|
simulate(obj);
|
||||||
|
|
||||||
@ -163,6 +170,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
result.Type = GetSimulatedHitResult(judgement);
|
result.Type = GetSimulatedHitResult(judgement);
|
||||||
ApplyResult(result);
|
ApplyResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsSimulating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -30,6 +30,14 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
private const double accuracy_cutoff_c = 0.7;
|
private const double accuracy_cutoff_c = 0.7;
|
||||||
private const double accuracy_cutoff_d = 0;
|
private const double accuracy_cutoff_d = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether <see cref="HitEvents"/> should be populated during application of results.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Should only be disabled for special cases.
|
||||||
|
/// When disabled, <see cref="JudgementProcessor.RevertResult"/> cannot be used.</remarks>
|
||||||
|
internal bool TrackHitEvents = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when this <see cref="ScoreProcessor"/> was reset from a replay frame.
|
/// Invoked when this <see cref="ScoreProcessor"/> was reset from a replay frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -226,10 +234,16 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
ApplyScoreChange(result);
|
ApplyScoreChange(result);
|
||||||
|
|
||||||
hitEvents.Add(CreateHitEvent(result));
|
if (!IsSimulating)
|
||||||
lastHitObject = result.HitObject;
|
{
|
||||||
|
if (TrackHitEvents)
|
||||||
|
{
|
||||||
|
hitEvents.Add(CreateHitEvent(result));
|
||||||
|
lastHitObject = result.HitObject;
|
||||||
|
}
|
||||||
|
|
||||||
updateScore();
|
updateScore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -242,6 +256,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
protected sealed override void RevertResultInternal(JudgementResult result)
|
protected sealed override void RevertResultInternal(JudgementResult result)
|
||||||
{
|
{
|
||||||
|
if (!TrackHitEvents)
|
||||||
|
throw new InvalidOperationException(@$"Rewind is not supported when {nameof(TrackHitEvents)} is disabled.");
|
||||||
|
|
||||||
Combo.Value = result.ComboAtJudgement;
|
Combo.Value = result.ComboAtJudgement;
|
||||||
HighestCombo.Value = result.HighestComboAtJudgement;
|
HighestCombo.Value = result.HighestComboAtJudgement;
|
||||||
|
|
||||||
@ -311,6 +328,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="storeResults">Whether to store the current state of the <see cref="ScoreProcessor"/> for future use.</param>
|
/// <param name="storeResults">Whether to store the current state of the <see cref="ScoreProcessor"/> for future use.</param>
|
||||||
protected override void Reset(bool storeResults)
|
protected override void Reset(bool storeResults)
|
||||||
{
|
{
|
||||||
|
// Run one last time to store max values.
|
||||||
|
updateScore();
|
||||||
|
|
||||||
base.Reset(storeResults);
|
base.Reset(storeResults);
|
||||||
|
|
||||||
hitEvents.Clear();
|
hitEvents.Clear();
|
||||||
|
@ -76,6 +76,9 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.Space:
|
case Key.Space:
|
||||||
|
@ -533,6 +533,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
// Track traversal keys.
|
// Track traversal keys.
|
||||||
// Matching osu-stable implementations.
|
// Matching osu-stable implementations.
|
||||||
case Key.Z:
|
case Key.Z:
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Seek to first object time, or track start if already there.
|
// Seek to first object time, or track start if already there.
|
||||||
double? firstObjectTime = editorBeatmap.HitObjects.FirstOrDefault()?.StartTime;
|
double? firstObjectTime = editorBeatmap.HitObjects.FirstOrDefault()?.StartTime;
|
||||||
|
|
||||||
@ -543,12 +546,18 @@ namespace osu.Game.Screens.Edit
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Key.X:
|
case Key.X:
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Restart playback from beginning of track.
|
// Restart playback from beginning of track.
|
||||||
clock.Seek(0);
|
clock.Seek(0);
|
||||||
clock.Start();
|
clock.Start();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Key.C:
|
case Key.C:
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Pause or resume.
|
// Pause or resume.
|
||||||
if (clock.IsRunning)
|
if (clock.IsRunning)
|
||||||
clock.Stop();
|
clock.Stop();
|
||||||
@ -557,6 +566,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Key.V:
|
case Key.V:
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Seek to last object time, or track end if already there.
|
// Seek to last object time, or track end if already there.
|
||||||
// Note that in osu-stable subsequent presses when at track end won't return to last object.
|
// Note that in osu-stable subsequent presses when at track end won't return to last object.
|
||||||
// This has intentionally been changed to make it more useful.
|
// This has intentionally been changed to make it more useful.
|
||||||
|
@ -103,29 +103,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
CornerRadius = CORNER_RADIUS,
|
CornerRadius = CORNER_RADIUS,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new GridContainer
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ColumnDimensions = new[]
|
Colour = colours.Background5,
|
||||||
{
|
Width = 0.2f,
|
||||||
new Dimension(GridSizeMode.Relative, 0.2f)
|
},
|
||||||
},
|
new Box
|
||||||
Content = new[]
|
{
|
||||||
{
|
Anchor = Anchor.TopRight,
|
||||||
new Drawable[]
|
Origin = Anchor.TopRight,
|
||||||
{
|
RelativeSizeAxes = Axes.Both,
|
||||||
new Box
|
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f)),
|
||||||
{
|
Width = 0.8f,
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = colours.Background5,
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
@ -235,7 +235,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
usernameText = new OsuSpriteText
|
usernameText = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Width = 0.6f,
|
Width = 0.6f,
|
||||||
@ -244,7 +244,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Colour = Color4.White,
|
Colour = Color4.White,
|
||||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||||
Text = User?.Username ?? string.Empty,
|
Text = User?.Username ?? string.Empty,
|
||||||
Truncate = true,
|
|
||||||
Shadow = false,
|
Shadow = false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,23 +101,21 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
|
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
|
||||||
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
|
||||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
|
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
|
||||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -156,14 +154,13 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Text = beatmap.DifficultyName,
|
Text = beatmap.DifficultyName,
|
||||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
|
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
|
||||||
{
|
{
|
||||||
|
@ -233,12 +233,11 @@ namespace osu.Game.Screens.Select
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
VersionLabel = new OsuSpriteText
|
VersionLabel = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = beatmapInfo.DifficultyName,
|
Text = beatmapInfo.DifficultyName,
|
||||||
Font = OsuFont.GetFont(size: 24, italics: true),
|
Font = OsuFont.GetFont(size: 24, italics: true),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -286,19 +285,17 @@ namespace osu.Game.Screens.Select
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
TitleLabel = new OsuSpriteText
|
TitleLabel = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Current = { BindTarget = titleBinding },
|
Current = { BindTarget = titleBinding },
|
||||||
Font = OsuFont.GetFont(size: 28, italics: true),
|
Font = OsuFont.GetFont(size: 28, italics: true),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
ArtistLabel = new OsuSpriteText
|
ArtistLabel = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Current = { BindTarget = artistBinding },
|
Current = { BindTarget = artistBinding },
|
||||||
Font = OsuFont.GetFont(size: 17, italics: true),
|
Font = OsuFont.GetFont(size: 17, italics: true),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
MapperContainer = new FillFlowContainer
|
MapperContainer = new FillFlowContainer
|
||||||
{
|
{
|
||||||
|
@ -112,11 +112,11 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID)))
|
background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID)))
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
}, 300)
|
}, 200)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100)
|
mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 50)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -126,7 +126,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
mainFlow.DelayedLoadComplete += fadeContentIn;
|
mainFlow.DelayedLoadComplete += fadeContentIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint);
|
private void fadeContentIn(Drawable d) => d.FadeInFromZero(150);
|
||||||
|
|
||||||
protected override void Deselected()
|
protected override void Deselected()
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.20.0" />
|
<PackageReference Include="Realm" Version="10.20.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.608.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2023.608.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.510.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.605.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.28.1" />
|
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
Loading…
Reference in New Issue
Block a user