mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 11:42:56 +08:00
Merge branch 'master' into improve-song-select-enter-performance
This commit is contained in:
commit
7269b4807e
@ -11,7 +11,7 @@
|
|||||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.531.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.608.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||||
|
@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
|
|
||||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||||
{
|
{
|
||||||
return 200000 * comboProgress
|
return 10000 * comboProgress
|
||||||
+ 800000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
||||||
+ bonusPortion;
|
+ bonusPortion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
|
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.01f, 0)));
|
||||||
|
|
||||||
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
|
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
|
||||||
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.235f, 0)));
|
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.205f, 0)));
|
||||||
|
|
||||||
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
|
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.01f, 0)));
|
||||||
|
|
||||||
AddAssert("object 3 snapped to 1", () =>
|
AddAssert("object 3 snapped to 1", () =>
|
||||||
{
|
{
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
return Precision.AlmostEquals(first.EndPosition, third.Position);
|
return Precision.AlmostEquals(first.EndPosition, third.Position);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f)));
|
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.21f, playfield.ScreenSpaceDrawQuad.Width * 0.205f)));
|
||||||
|
|
||||||
AddAssert("object 2 snapped to 1", () =>
|
AddAssert("object 2 snapped to 1", () =>
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||||
|
|
||||||
float snapRadius =
|
float snapRadius =
|
||||||
playfield.GamefieldToScreenSpace(new Vector2(OsuHitObject.OBJECT_RADIUS / 5)).X -
|
playfield.GamefieldToScreenSpace(new Vector2(OsuHitObject.OBJECT_RADIUS * 0.10f)).X -
|
||||||
playfield.GamefieldToScreenSpace(Vector2.Zero).X;
|
playfield.GamefieldToScreenSpace(Vector2.Zero).X;
|
||||||
|
|
||||||
foreach (var b in blueprints)
|
foreach (var b in blueprints)
|
||||||
|
@ -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,
|
||||||
|
@ -15,187 +15,175 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestHitAllDrumRoll()
|
public void TestHitAllDrumRoll()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(1001),
|
new TaikoReplayFrame(1001),
|
||||||
|
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1251),
|
||||||
|
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1501),
|
||||||
|
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1751),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(false)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(3);
|
AssertJudgementCount(6);
|
||||||
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<DrumRollTick>(2, HitResult.SmallBonus);
|
||||||
|
AssertResult<DrumRollTick>(3, HitResult.SmallBonus);
|
||||||
|
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitSomeDrumRoll()
|
public void TestHitSomeDrumRoll()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(false)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(3);
|
AssertJudgementCount(6);
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitNoneDrumRoll()
|
public void TestHitNoneDrumRoll()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(false)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(3);
|
AssertJudgementCount(6);
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(4, HitResult.IgnoreMiss);
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitAllStrongDrumRollWithOneKey()
|
public void TestHitAllStrongDrumRollWithOneKey()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(1001),
|
new TaikoReplayFrame(1001),
|
||||||
|
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1251),
|
||||||
|
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1501),
|
||||||
|
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1751),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
|
|
||||||
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
StartTime = hit_time,
|
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
|
||||||
Duration = 1000,
|
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
|
||||||
IsStrong = true
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(6);
|
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
|
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
|
||||||
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
|
||||||
|
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitSomeStrongDrumRollWithOneKey()
|
public void TestHitSomeStrongDrumRollWithOneKey()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000,
|
|
||||||
IsStrong = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(6);
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
|
||||||
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
|
||||||
|
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitAllStrongDrumRollWithBothKeys()
|
public void TestHitAllStrongDrumRollWithBothKeys()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
new TaikoReplayFrame(1001),
|
new TaikoReplayFrame(1001),
|
||||||
|
new TaikoReplayFrame(1250, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
|
new TaikoReplayFrame(1251),
|
||||||
|
new TaikoReplayFrame(1500, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
|
new TaikoReplayFrame(1501),
|
||||||
|
new TaikoReplayFrame(1750, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
|
new TaikoReplayFrame(1751),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
|
|
||||||
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
StartTime = hit_time,
|
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
|
||||||
Duration = 1000,
|
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
|
||||||
IsStrong = true
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(6);
|
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
|
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
|
||||||
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
|
||||||
|
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitSomeStrongDrumRollWithBothKeys()
|
public void TestHitSomeStrongDrumRollWithBothKeys()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000,
|
|
||||||
IsStrong = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(6);
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
|
||||||
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
|
||||||
|
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DrumRoll createDrumRoll(bool strong) => new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Duration = 1000,
|
||||||
|
IsStrong = strong
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: stable makes the last tick of a drumroll non-required when the next object is too close.
|
||||||
|
// This probably needs to be reimplemented:
|
||||||
|
//
|
||||||
|
// List<HitObject> hitobjects = hitObjectManager.hitObjects;
|
||||||
|
// int ind = hitobjects.IndexOf(this);
|
||||||
|
// if (i < hitobjects.Count - 1 && hitobjects[i + 1].HittableStartTime - (EndTime + (int)TickSpacing) <= (int)TickSpacing)
|
||||||
|
// lastTickHittable = false;
|
||||||
|
|
||||||
return converted;
|
return converted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
Duration = taikoDuration,
|
Duration = taikoDuration,
|
||||||
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
|
|
||||||
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
|
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
|
|
||||||
|
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
|
||||||
|
|
||||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
public override Texture GetBackground() => throw new NotImplementedException();
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
|
|
||||||
var mock = new Mock<IWorkingBeatmap>();
|
var mock = new Mock<IWorkingBeatmap>();
|
||||||
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
|
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
|
||||||
mock.SetupGet(w => w.Background).Returns(background);
|
mock.Setup(w => w.GetBackground()).Returns(background);
|
||||||
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
|
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
|
||||||
|
|
||||||
return mock;
|
return mock;
|
||||||
|
@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Texture GetBackground() => renderer.CreateTexture(1, 1);
|
public override Texture GetBackground() => renderer.CreateTexture(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
|
private partial class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -23,8 +21,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
|
public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private BeatDivisorControl beatDivisorControl;
|
private BeatDivisorControl beatDivisorControl = null!;
|
||||||
private BindableBeatDivisor bindableBeatDivisor;
|
private BindableBeatDivisor bindableBeatDivisor = null!;
|
||||||
|
|
||||||
private SliderBar<int> tickSliderBar => beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single();
|
private SliderBar<int> tickSliderBar => beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single();
|
||||||
private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType<Triangle>().Single();
|
private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType<Triangle>().Single();
|
||||||
@ -169,9 +167,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
switchPresets(1);
|
switchPresets(1);
|
||||||
assertPreset(BeatDivisorType.Triplets);
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
|
assertBeatSnap(6);
|
||||||
|
|
||||||
switchPresets(1);
|
switchPresets(1);
|
||||||
assertPreset(BeatDivisorType.Common);
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
assertBeatSnap(4);
|
||||||
|
|
||||||
switchPresets(-1);
|
switchPresets(-1);
|
||||||
assertPreset(BeatDivisorType.Triplets);
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
@ -187,6 +187,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
setDivisorViaInput(15);
|
setDivisorViaInput(15);
|
||||||
assertPreset(BeatDivisorType.Custom, 15);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
|
assertBeatSnap(15);
|
||||||
|
|
||||||
switchBeatSnap(-1);
|
switchBeatSnap(-1);
|
||||||
assertBeatSnap(5);
|
assertBeatSnap(5);
|
||||||
@ -196,12 +197,14 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
setDivisorViaInput(5);
|
setDivisorViaInput(5);
|
||||||
assertPreset(BeatDivisorType.Custom, 15);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
|
assertBeatSnap(5);
|
||||||
|
|
||||||
switchPresets(1);
|
switchPresets(1);
|
||||||
assertPreset(BeatDivisorType.Common);
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
switchPresets(-1);
|
switchPresets(-1);
|
||||||
assertPreset(BeatDivisorType.Triplets);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
|
assertBeatSnap(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
|
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
|
||||||
@ -225,7 +228,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private void assertPreset(BeatDivisorType type, int? maxDivisor = null)
|
private void assertPreset(BeatDivisorType type, int? maxDivisor = null)
|
||||||
{
|
{
|
||||||
AddAssert($"preset is {type}", () => bindableBeatDivisor.ValidDivisors.Value.Type == type);
|
AddAssert($"preset is {type}", () => bindableBeatDivisor.ValidDivisors.Value.Type, () => Is.EqualTo(type));
|
||||||
|
|
||||||
if (type == BeatDivisorType.Custom)
|
if (type == BeatDivisorType.Custom)
|
||||||
{
|
{
|
||||||
@ -243,7 +246,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
BeatDivisorControl.CustomDivisorPopover popover = null;
|
BeatDivisorControl.CustomDivisorPopover? popover = null;
|
||||||
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<BeatDivisorControl.CustomDivisorPopover>().SingleOrDefault()) != null && popover.IsLoaded);
|
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<BeatDivisorControl.CustomDivisorPopover>().SingleOrDefault()) != null && popover.IsLoaded);
|
||||||
AddStep($"set divisor to {divisor}", () =>
|
AddStep($"set divisor to {divisor}", () =>
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
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 readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" };
|
||||||
|
|
||||||
private TestSpectatorClient spectatorClient;
|
private TestSpectatorClient spectatorClient = null!;
|
||||||
private CurrentlyPlayingDisplay currentlyPlaying;
|
private CurrentlyPlayingDisplay currentlyPlaying = null!;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
@ -88,13 +86,13 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
"pishifat"
|
"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
|
// tests against failed lookups
|
||||||
if (lookup == 13)
|
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,
|
Id = lookup,
|
||||||
Username = usernames[lookup % usernames.Length],
|
Username = usernames[lookup % usernames.Length],
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -24,10 +23,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene
|
public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene
|
||||||
{
|
{
|
||||||
private BeatmapMetadataDisplay display;
|
private BeatmapMetadataDisplay display = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager manager { get; set; }
|
private BeatmapManager manager { get; set; } = null!;
|
||||||
|
|
||||||
[Cached(typeof(BeatmapDifficultyCache))]
|
[Cached(typeof(BeatmapDifficultyCache))]
|
||||||
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
|
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
|
||||||
@ -121,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
|
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
|
||||||
{
|
{
|
||||||
private TaskCompletionSource<bool> calculationBlocker;
|
private TaskCompletionSource<bool>? calculationBlocker;
|
||||||
|
|
||||||
private bool blockCalculation;
|
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)
|
if (blockCalculation)
|
||||||
|
{
|
||||||
|
Debug.Assert(calculationBlocker != null);
|
||||||
await calculationBlocker.Task.ConfigureAwait(false);
|
await calculationBlocker.Task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
|
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Tests
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => null;
|
public override Texture GetBackground() => null;
|
||||||
|
|
||||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||||
|
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
// Implementation of this class is based off of `MaxDimensionLimitedTextureLoaderStore`.
|
||||||
|
// If issues are found it's worth checking to make sure similar issues exist there.
|
||||||
|
public class BeatmapPanelBackgroundTextureLoaderStore : IResourceStore<TextureUpload>
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
public BeatmapPanelBackgroundTextureLoaderStore(IResourceStore<TextureUpload>? textureStore)
|
||||||
|
{
|
||||||
|
this.textureStore = textureStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
textureStore?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureUpload Get(string name)
|
||||||
|
{
|
||||||
|
var textureUpload = textureStore?.Get(name);
|
||||||
|
|
||||||
|
// NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp.
|
||||||
|
if (textureUpload == null)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
return limitTextureUploadSize(textureUpload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TextureUpload> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken())
|
||||||
|
{
|
||||||
|
// NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp.
|
||||||
|
if (textureStore == null)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (textureUpload == null)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
return await Task.Run(() => limitTextureUploadSize(textureUpload), cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextureUpload limitTextureUploadSize(TextureUpload textureUpload)
|
||||||
|
{
|
||||||
|
var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height);
|
||||||
|
|
||||||
|
// The original texture upload will no longer be returned or used.
|
||||||
|
textureUpload.Dispose();
|
||||||
|
|
||||||
|
Size size = image.Size();
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
0,
|
||||||
|
(size.Height - usableHeight) / 2,
|
||||||
|
size.Width,
|
||||||
|
usableHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
image.Mutate(i => i.Crop(cropRectangle));
|
||||||
|
|
||||||
|
return new TextureUpload(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream? GetStream(string name) => textureStore?.GetStream(name);
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty<string>();
|
||||||
|
}
|
||||||
|
}
|
@ -23,8 +23,9 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (working.Background != null)
|
var background = working.GetBackground();
|
||||||
Texture = working.Background;
|
if (background != null)
|
||||||
|
Texture = background;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => new Beatmap();
|
protected override IBeatmap GetBeatmap() => new Beatmap();
|
||||||
|
|
||||||
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
|
public override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
public override Texture GetBackground() => throw new NotImplementedException();
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
protected internal override ISkin GetSkin() => throw new NotImplementedException();
|
protected internal override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
|
@ -9,13 +9,18 @@ using osu.Game.IO;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public interface IBeatmapResourceProvider : IStorageResourceProvider
|
internal interface IBeatmapResourceProvider : IStorageResourceProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a global large texture store, used for loading beatmap backgrounds.
|
/// Retrieve a global large texture store, used for loading beatmap backgrounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TextureStore LargeTextureStore { get; }
|
TextureStore LargeTextureStore { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a global large texture store, used specifically for retrieving cropped beatmap panel backgrounds.
|
||||||
|
/// </summary>
|
||||||
|
TextureStore BeatmapPanelTextureStore { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access a global track store for retrieving beatmap tracks from.
|
/// Access a global track store for retrieving beatmap tracks from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -32,12 +32,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the Beatmap has finished loading.
|
/// Whether the Beatmap has finished loading.
|
||||||
///</summary>
|
///</summary>
|
||||||
public bool BeatmapLoaded { get; }
|
bool BeatmapLoaded { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the Track has finished loading.
|
/// Whether the Track has finished loading.
|
||||||
///</summary>
|
///</summary>
|
||||||
public bool TrackLoaded { get; }
|
bool TrackLoaded { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="IWorkingBeatmap"/> represents.
|
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="IWorkingBeatmap"/> represents.
|
||||||
@ -47,7 +47,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
|
/// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Texture Background { get; }
|
Texture GetBackground();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a cropped background for this <see cref="IWorkingBeatmap"/> used for display on panels.
|
||||||
|
/// </summary>
|
||||||
|
Texture GetPanelBackground();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.
|
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.
|
||||||
@ -124,12 +129,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beings loading the contents of this <see cref="IWorkingBeatmap"/> asynchronously.
|
/// Beings loading the contents of this <see cref="IWorkingBeatmap"/> asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void BeginAsyncLoad();
|
void BeginAsyncLoad();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancels the asynchronous loading of the contents of this <see cref="IWorkingBeatmap"/>.
|
/// Cancels the asynchronous loading of the contents of this <see cref="IWorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CancelAsyncLoad();
|
void CancelAsyncLoad();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
||||||
|
@ -34,8 +34,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public Storyboard Storyboard => storyboard.Value;
|
public Storyboard Storyboard => storyboard.Value;
|
||||||
|
|
||||||
public Texture Background => GetBackground(); // Texture uses ref counting, so we want to return a new instance every usage.
|
|
||||||
|
|
||||||
public ISkin Skin => skin.Value;
|
public ISkin Skin => skin.Value;
|
||||||
|
|
||||||
private AudioManager audioManager { get; }
|
private AudioManager audioManager { get; }
|
||||||
@ -67,7 +65,8 @@ namespace osu.Game.Beatmaps
|
|||||||
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
||||||
|
|
||||||
protected abstract IBeatmap GetBeatmap();
|
protected abstract IBeatmap GetBeatmap();
|
||||||
protected abstract Texture GetBackground();
|
public abstract Texture GetBackground();
|
||||||
|
public virtual Texture GetPanelBackground() => GetBackground();
|
||||||
protected abstract Track GetBeatmapTrack();
|
protected abstract Track GetBeatmapTrack();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -42,6 +42,7 @@ namespace osu.Game.Beatmaps
|
|||||||
private readonly AudioManager audioManager;
|
private readonly AudioManager audioManager;
|
||||||
private readonly IResourceStore<byte[]> resources;
|
private readonly IResourceStore<byte[]> resources;
|
||||||
private readonly LargeTextureStore largeTextureStore;
|
private readonly LargeTextureStore largeTextureStore;
|
||||||
|
private readonly LargeTextureStore beatmapPanelTextureStore;
|
||||||
private readonly ITrackStore trackStore;
|
private readonly ITrackStore trackStore;
|
||||||
private readonly IResourceStore<byte[]> files;
|
private readonly IResourceStore<byte[]> files;
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ namespace osu.Game.Beatmaps
|
|||||||
this.host = host;
|
this.host = host;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
|
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
|
||||||
|
beatmapPanelTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), new BeatmapPanelBackgroundTextureLoaderStore(host?.CreateTextureLoaderStore(files)));
|
||||||
this.trackStore = trackStore;
|
this.trackStore = trackStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +112,7 @@ namespace osu.Game.Beatmaps
|
|||||||
#region IResourceStorageProvider
|
#region IResourceStorageProvider
|
||||||
|
|
||||||
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
|
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
|
||||||
|
TextureStore IBeatmapResourceProvider.BeatmapPanelTextureStore => beatmapPanelTextureStore;
|
||||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
||||||
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
|
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
|
||||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
||||||
@ -160,7 +163,11 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Texture GetBackground()
|
public override Texture GetPanelBackground() => getBackgroundFromStore(resources.BeatmapPanelTextureStore);
|
||||||
|
|
||||||
|
public override Texture GetBackground() => getBackgroundFromStore(resources.LargeTextureStore);
|
||||||
|
|
||||||
|
private Texture getBackgroundFromStore(TextureStore store)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
|
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
|
||||||
return null;
|
return null;
|
||||||
@ -168,7 +175,7 @@ namespace osu.Game.Beatmaps
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile);
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile);
|
||||||
var texture = resources.LargeTextureStore.Get(fileStorePath);
|
var texture = store.Get(fileStorePath);
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
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="beatmapId">The beatmap to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified beatmaps, populating a <see cref="APIBeatmap"/> model.
|
/// 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="beatmapIds">The beatmaps to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated beatmaps. May include null results for failed retrievals.</returns>
|
/// <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 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Statistics;
|
using osu.Framework.Statistics;
|
||||||
@ -19,8 +17,9 @@ namespace osu.Game.Database
|
|||||||
/// Currently not persisted between game sessions.
|
/// Currently not persisted between game sessions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class MemoryCachingComponent<TLookup, TValue> : Component
|
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;
|
private readonly GlobalStatistic<MemoryCachingStatistics> statistics;
|
||||||
|
|
||||||
@ -37,12 +36,12 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lookup">The lookup to retrieve.</param>
|
/// <param name="lookup">The lookup to retrieve.</param>
|
||||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</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++;
|
statistics.Value.HitCount++;
|
||||||
return performance;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
||||||
@ -73,7 +72,7 @@ namespace osu.Game.Database
|
|||||||
statistics.Value.Usage = cache.Count;
|
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);
|
cache.TryGetValue(lookup, out value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,7 +81,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="lookup">The lookup to retrieve.</param>
|
/// <param name="lookup">The lookup to retrieve.</param>
|
||||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
||||||
/// <returns>The computed value.</returns>
|
/// <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
|
private class MemoryCachingStatistics
|
||||||
{
|
{
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -21,7 +18,7 @@ namespace osu.Game.Database
|
|||||||
where TRequest : APIRequest
|
where TRequest : APIRequest
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an <see cref="APIRequest"/> to retrieve the values for a given collection of <typeparamref name="TLookup"/>s.
|
/// 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>
|
/// <summary>
|
||||||
/// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>.
|
/// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
protected abstract IEnumerable<TValue>? RetrieveResults(TRequest request);
|
||||||
protected abstract IEnumerable<TValue> RetrieveResults(TRequest request);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform a lookup using the specified <paramref name="id"/>, populating a <typeparamref name="TValue"/>.
|
/// 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="id">The ID to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified <paramref name="ids"/>, populating a <typeparamref name="TValue"/>.
|
/// 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="ids">The IDs to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated values. May include null results for failed retrievals.</returns>
|
/// <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)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
@ -69,18 +64,18 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cannot be sealed due to test usages (see TestUserLookupCache).
|
// 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);
|
=> await queryValue(lookup).ConfigureAwait(false);
|
||||||
|
|
||||||
private readonly Queue<(TLookup id, TaskCompletionSource<TValue>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue>)>();
|
private readonly Queue<(TLookup id, TaskCompletionSource<TValue?>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue?>)>();
|
||||||
private Task pendingRequestTask;
|
private Task? pendingRequestTask;
|
||||||
private readonly object taskAssignmentLock = new object();
|
private readonly object taskAssignmentLock = new object();
|
||||||
|
|
||||||
private Task<TValue> queryValue(TLookup id)
|
private Task<TValue?> queryValue(TLookup id)
|
||||||
{
|
{
|
||||||
lock (taskAssignmentLock)
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<TValue>();
|
var tcs = new TaskCompletionSource<TValue?>();
|
||||||
|
|
||||||
// Add to the queue.
|
// Add to the queue.
|
||||||
pendingTasks.Enqueue((id, tcs));
|
pendingTasks.Enqueue((id, tcs));
|
||||||
@ -96,14 +91,14 @@ namespace osu.Game.Database
|
|||||||
private async Task performLookup()
|
private async Task performLookup()
|
||||||
{
|
{
|
||||||
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
|
// 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.
|
// Grab at most 50 unique IDs from the queue.
|
||||||
lock (taskAssignmentLock)
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
while (pendingTasks.Count > 0 && nextTaskBatch.Count < 50)
|
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.
|
// Perform a secondary check for existence, in case the value was queried in a previous batch.
|
||||||
if (CheckExists(next.id, out var existing))
|
if (CheckExists(next.id, out var existing))
|
||||||
@ -113,7 +108,7 @@ namespace osu.Game.Database
|
|||||||
if (nextTaskBatch.TryGetValue(next.id, out var tasks))
|
if (nextTaskBatch.TryGetValue(next.id, out var tasks))
|
||||||
tasks.Add(next.task);
|
tasks.Add(next.task);
|
||||||
else
|
else
|
||||||
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue>> { next.task };
|
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue?>> { next.task };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,15 @@ using osu.Framework.Statistics;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Scoring.Legacy;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using Realms;
|
using Realms;
|
||||||
using Realms.Exceptions;
|
using Realms.Exceptions;
|
||||||
@ -72,8 +75,9 @@ namespace osu.Game.Database
|
|||||||
/// 25 2022-09-18 Remove skins to add with new naming.
|
/// 25 2022-09-18 Remove skins to add with new naming.
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 27;
|
private const int schema_version = 28;
|
||||||
|
|
||||||
/// <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.
|
||||||
@ -880,6 +884,7 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 26:
|
case 26:
|
||||||
|
{
|
||||||
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
||||||
var scores = migration.NewRealm.All<ScoreInfo>();
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
@ -888,6 +893,44 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 28:
|
||||||
|
{
|
||||||
|
var files = new RealmFileStore(this, storage);
|
||||||
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
{
|
||||||
|
string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath();
|
||||||
|
if (replayFilename == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = files.Store.GetStream(replayFilename))
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Trimmed down logic from LegacyScoreDecoder to extract the version from replays.
|
||||||
|
using (SerializationReader sr = new SerializationReader(stream))
|
||||||
|
{
|
||||||
|
sr.ReadByte(); // Ruleset.
|
||||||
|
int version = sr.ReadInt32();
|
||||||
|
if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION)
|
||||||
|
score.IsLegacyScore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? getRulesetShortNameFromLegacyID(long rulesetId)
|
private string? getRulesetShortNameFromLegacyID(long rulesetId)
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
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="userId">The user to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</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>
|
/// <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>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified users, populating a <see cref="APIUser"/> model.
|
/// 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="userIds">The users to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated users. May include null results for failed retrievals.</returns>
|
/// <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 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(LargeTextureStore textures)
|
private void load(LargeTextureStore textures)
|
||||||
{
|
{
|
||||||
Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName);
|
Sprite.Texture = Beatmap?.GetBackground() ?? textures.Get(fallbackTextureName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(Background other)
|
public override bool Equals(Background other)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
{
|
{
|
||||||
var beatmap = task.GetResultSafely();
|
var beatmap = task.GetResultSafely();
|
||||||
|
|
||||||
if (SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID)
|
if (beatmap != null && SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID)
|
||||||
{
|
{
|
||||||
selectedBeatmap = beatmap;
|
selectedBeatmap = beatmap;
|
||||||
beginTracking();
|
beginTracking();
|
||||||
|
@ -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;
|
||||||
@ -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;
|
||||||
|
|
||||||
|
bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= SAMPLE_PLAYBACK_DELAY;
|
||||||
|
|
||||||
|
if (enoughTimePassedSinceLastPlayback)
|
||||||
|
{
|
||||||
if (Active.Value)
|
if (Active.Value)
|
||||||
sampleOn?.Play();
|
sampleOn?.Play();
|
||||||
else
|
else
|
||||||
sampleOff?.Play();
|
sampleOff?.Play();
|
||||||
|
|
||||||
|
lastPlaybackTime.Value = Time.Current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -415,7 +415,7 @@ namespace osu.Game.Overlays
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(LargeTextureStore textures)
|
private void load(LargeTextureStore textures)
|
||||||
{
|
{
|
||||||
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
|
sprite.Texture = beatmap?.GetBackground() ?? textures.Get(@"Backgrounds/bg4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks
|
|||||||
if (backgroundFile == null)
|
if (backgroundFile == null)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
var texture = context.WorkingBeatmap.Background;
|
var texture = context.WorkingBeatmap.GetBackground();
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
if (currentSnap > DistanceSpacingMultiplier.MinValue)
|
if (currentSnap > DistanceSpacingMultiplier.MinValue)
|
||||||
{
|
{
|
||||||
currentDistanceSpacingButton.Enabled.Value = currentDistanceSpacingButton.Expanded.Value
|
currentDistanceSpacingButton.Enabled.Value = currentDistanceSpacingButton.Expanded.Value
|
||||||
|
&& !DistanceSpacingMultiplier.Disabled
|
||||||
&& !Precision.AlmostEquals(currentSnap, DistanceSpacingMultiplier.Value, DistanceSpacingMultiplier.Precision / 2);
|
&& !Precision.AlmostEquals(currentSnap, DistanceSpacingMultiplier.Value, DistanceSpacingMultiplier.Precision / 2);
|
||||||
currentDistanceSpacingButton.ContractedLabelText = $"current {currentSnap:N2}x";
|
currentDistanceSpacingButton.ContractedLabelText = $"current {currentSnap:N2}x";
|
||||||
currentDistanceSpacingButton.ExpandedLabelText = $"Use current ({currentSnap:N2}x)";
|
currentDistanceSpacingButton.ExpandedLabelText = $"Use current ({currentSnap:N2}x)";
|
||||||
@ -141,8 +142,12 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (!DistanceSpacingMultiplier.Disabled)
|
if (DistanceSpacingMultiplier.Disabled)
|
||||||
{
|
{
|
||||||
|
distanceSpacingSlider.Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DistanceSpacingMultiplier.Value = EditorBeatmap.BeatmapInfo.DistanceSpacing;
|
DistanceSpacingMultiplier.Value = EditorBeatmap.BeatmapInfo.DistanceSpacing;
|
||||||
DistanceSpacingMultiplier.BindValueChanged(multiplier =>
|
DistanceSpacingMultiplier.BindValueChanged(multiplier =>
|
||||||
{
|
{
|
||||||
@ -163,7 +168,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
});
|
});
|
||||||
DistanceSpacingMultiplier.BindValueChanged(spacing => distanceSpacingSlider.Current.Value = spacing.NewValue);
|
DistanceSpacingMultiplier.BindValueChanged(spacing => distanceSpacingSlider.Current.Value = spacing.NewValue);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||||
{
|
{
|
||||||
|
@ -46,6 +46,9 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
score.ScoreInfo = scoreInfo;
|
score.ScoreInfo = scoreInfo;
|
||||||
|
|
||||||
int version = sr.ReadInt32();
|
int version = sr.ReadInt32();
|
||||||
|
|
||||||
|
scoreInfo.IsLegacyScore = version < LegacyScoreEncoder.FIRST_LAZER_VERSION;
|
||||||
|
|
||||||
string beatmapHash = sr.ReadString();
|
string beatmapHash = sr.ReadString();
|
||||||
|
|
||||||
workingBeatmap = GetBeatmap(beatmapHash);
|
workingBeatmap = GetBeatmap(beatmapHash);
|
||||||
|
@ -28,9 +28,10 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><description>30000001: Appends <see cref="LegacyReplaySoloScoreInfo"/> to the end of scores.</description></item>
|
/// <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>
|
/// </list>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public const int LATEST_VERSION = 30000001;
|
public const int LATEST_VERSION = 30000002;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
|
||||||
|
@ -16,7 +16,13 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
=> getDisplayScore(scoreProcessor.Ruleset.RulesetInfo.OnlineID, scoreProcessor.TotalScore.Value, mode, scoreProcessor.MaximumStatistics);
|
=> getDisplayScore(scoreProcessor.Ruleset.RulesetInfo.OnlineID, scoreProcessor.TotalScore.Value, mode, scoreProcessor.MaximumStatistics);
|
||||||
|
|
||||||
public static long GetDisplayScore(this ScoreInfo scoreInfo, ScoringMode mode)
|
public static long GetDisplayScore(this ScoreInfo scoreInfo, ScoringMode mode)
|
||||||
=> getDisplayScore(scoreInfo.Ruleset.OnlineID, scoreInfo.TotalScore, mode, scoreInfo.MaximumStatistics);
|
{
|
||||||
|
// Temporary to not scale stable scores that are already in the XX-millions with the classic scoring mode.
|
||||||
|
if (scoreInfo.IsLegacyScore)
|
||||||
|
return scoreInfo.TotalScore;
|
||||||
|
|
||||||
|
return getDisplayScore(scoreInfo.Ruleset.OnlineID, scoreInfo.TotalScore, mode, scoreInfo.MaximumStatistics);
|
||||||
|
}
|
||||||
|
|
||||||
private static long getDisplayScore(int rulesetId, long score, ScoringMode mode, IReadOnlyDictionary<HitResult, int> maximumStatistics)
|
private static long getDisplayScore(int rulesetId, long score, ScoringMode mode, IReadOnlyDictionary<HitResult, int> maximumStatistics)
|
||||||
{
|
{
|
||||||
|
@ -146,16 +146,48 @@ namespace osu.Game.Scoring
|
|||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Very naive local caching to improve performance of large score imports (where the username is usually the same for most or all scores).
|
||||||
|
private readonly Dictionary<string, APIUser> usernameLookupCache = new Dictionary<string, APIUser>();
|
||||||
|
|
||||||
protected override void PostImport(ScoreInfo model, Realm realm, ImportParameters parameters)
|
protected override void PostImport(ScoreInfo model, Realm realm, ImportParameters parameters)
|
||||||
{
|
{
|
||||||
base.PostImport(model, realm, parameters);
|
base.PostImport(model, realm, parameters);
|
||||||
|
|
||||||
var userRequest = new GetUserRequest(model.RealmUser.Username);
|
populateUserDetails(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy replays only store a username.
|
||||||
|
/// This will populate a user ID during import.
|
||||||
|
/// </summary>
|
||||||
|
private void populateUserDetails(ScoreInfo model)
|
||||||
|
{
|
||||||
|
string username = model.RealmUser.Username;
|
||||||
|
|
||||||
|
if (usernameLookupCache.TryGetValue(username, out var existing))
|
||||||
|
{
|
||||||
|
model.User = existing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userRequest = new GetUserRequest(username);
|
||||||
|
|
||||||
api.Perform(userRequest);
|
api.Perform(userRequest);
|
||||||
|
|
||||||
if (userRequest.Response is APIUser user)
|
if (userRequest.Response is APIUser user)
|
||||||
|
{
|
||||||
|
usernameLookupCache.TryAdd(username, new APIUser
|
||||||
|
{
|
||||||
|
// Because this is a permanent cache, let's only store the pieces we're interested in,
|
||||||
|
// rather than the full API response. If we start to store more than these three fields
|
||||||
|
// in realm, this should be undone.
|
||||||
|
Id = user.Id,
|
||||||
|
Username = user.Username,
|
||||||
|
CountryCode = user.CountryCode,
|
||||||
|
});
|
||||||
|
|
||||||
model.User = user;
|
model.User = user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -181,8 +181,7 @@ namespace osu.Game.Scoring
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
|
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Ignored]
|
public bool IsLegacyScore { get; set; }
|
||||||
public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
|
|
||||||
|
|
||||||
private Dictionary<HitResult, int>? statistics;
|
private Dictionary<HitResult, int>? statistics;
|
||||||
|
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -21,7 +18,7 @@ namespace osu.Game.Scoring
|
|||||||
public partial class ScorePerformanceCache : MemoryCachingComponent<ScorePerformanceCache.PerformanceCacheLookup, PerformanceAttributes>
|
public partial class ScorePerformanceCache : MemoryCachingComponent<ScorePerformanceCache.PerformanceCacheLookup, PerformanceAttributes>
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapDifficultyCache difficultyCache { get; set; }
|
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||||
|
|
||||||
protected override bool CacheNullValues => false;
|
protected override bool CacheNullValues => false;
|
||||||
|
|
||||||
@ -30,10 +27,10 @@ namespace osu.Game.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The score to do the calculation on. </param>
|
/// <param name="score">The score to do the calculation on. </param>
|
||||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</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);
|
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;
|
var score = lookup.ScoreInfo;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -33,6 +31,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
public partial class BeatDivisorControl : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
public partial class BeatDivisorControl : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
|
private int? lastCustomDivisor;
|
||||||
|
|
||||||
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||||
|
|
||||||
public BeatDivisorControl(BindableBeatDivisor beatDivisor)
|
public BeatDivisorControl(BindableBeatDivisor beatDivisor)
|
||||||
@ -186,29 +186,46 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
beatDivisor.ValidDivisors.BindValueChanged(valid =>
|
||||||
|
{
|
||||||
|
if (valid.NewValue.Type == BeatDivisorType.Custom)
|
||||||
|
lastCustomDivisor = valid.NewValue.Presets.Last();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
private void cycleDivisorType(int direction)
|
private void cycleDivisorType(int direction)
|
||||||
{
|
{
|
||||||
Debug.Assert(Math.Abs(direction) == 1);
|
int totalTypes = Enum.GetValues<BeatDivisorType>().Length;
|
||||||
int nextDivisorType = (int)beatDivisor.ValidDivisors.Value.Type + direction;
|
BeatDivisorType currentType = beatDivisor.ValidDivisors.Value.Type;
|
||||||
if (nextDivisorType > (int)BeatDivisorType.Triplets)
|
|
||||||
nextDivisorType = (int)BeatDivisorType.Common;
|
|
||||||
else if (nextDivisorType < (int)BeatDivisorType.Common)
|
|
||||||
nextDivisorType = (int)BeatDivisorType.Triplets;
|
|
||||||
|
|
||||||
switch ((BeatDivisorType)nextDivisorType)
|
Debug.Assert(Math.Abs(direction) == 1);
|
||||||
|
|
||||||
|
cycleOnce();
|
||||||
|
|
||||||
|
if (lastCustomDivisor == null && currentType == BeatDivisorType.Custom)
|
||||||
|
cycleOnce();
|
||||||
|
|
||||||
|
switch (currentType)
|
||||||
{
|
{
|
||||||
case BeatDivisorType.Common:
|
case BeatDivisorType.Common:
|
||||||
beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
beatDivisor.SetArbitraryDivisor(4);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BeatDivisorType.Triplets:
|
case BeatDivisorType.Triplets:
|
||||||
beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
beatDivisor.SetArbitraryDivisor(6);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BeatDivisorType.Custom:
|
case BeatDivisorType.Custom:
|
||||||
beatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.Custom(beatDivisor.ValidDivisors.Value.Presets.Max());
|
Debug.Assert(lastCustomDivisor != null);
|
||||||
|
beatDivisor.SetArbitraryDivisor(lastCustomDivisor.Value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cycleOnce() => currentType = (BeatDivisorType)(((int)currentType + totalTypes + direction) % totalTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
@ -326,12 +343,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
BeatDivisor.BindValueChanged(_ => updateState(), true);
|
BeatDivisor.BindValueChanged(_ => updateState(), true);
|
||||||
divisorTextBox.OnCommit += (_, _) => setPresets();
|
divisorTextBox.OnCommit += (_, _) => setPresetsFromTextBoxEntry();
|
||||||
|
|
||||||
Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox));
|
Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPresets()
|
private void setPresetsFromTextBoxEntry()
|
||||||
{
|
{
|
||||||
if (!int.TryParse(divisorTextBox.Text, out int divisor) || divisor < 1 || divisor > 64)
|
if (!int.TryParse(divisorTextBox.Text, out int divisor) || divisor < 1 || divisor > 64)
|
||||||
{
|
{
|
||||||
@ -394,10 +411,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private partial class TickSliderBar : SliderBar<int>
|
private partial class TickSliderBar : SliderBar<int>
|
||||||
{
|
{
|
||||||
private Marker marker;
|
private Marker marker = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
private readonly BindableBeatDivisor beatDivisor;
|
private readonly BindableBeatDivisor beatDivisor;
|
||||||
|
|
||||||
@ -539,7 +556,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private partial class Marker : CompositeDrawable
|
private partial class Marker : CompositeDrawable
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider colourProvider { get; set; }
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Right = 5 },
|
Padding = new MarginPadding { Right = 5 },
|
||||||
},
|
},
|
||||||
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
|
new BeatDivisorControl(this.beatDivisor) { RelativeSizeAxes = Axes.Both }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
public override Texture GetBackground() => throw new NotImplementedException();
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
if (Beatmap?.BeatmapSet is IBeatmapSetOnlineInfo online)
|
if (Beatmap?.BeatmapSet is IBeatmapSetOnlineInfo online)
|
||||||
texture = textures.Get(online.Covers.Cover);
|
texture = textures.Get(online.Covers.Cover);
|
||||||
|
|
||||||
Sprite.Texture = texture ?? beatmaps.DefaultBeatmap.Background;
|
Sprite.Texture = texture ?? beatmaps.DefaultBeatmap.GetBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(Background? other)
|
public override bool Equals(Background? other)
|
||||||
|
@ -109,7 +109,7 @@ namespace osu.Game.Screens.Play
|
|||||||
new Sprite
|
new Sprite
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Texture = beatmap.Background,
|
Texture = beatmap.GetBackground(),
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
|
@ -226,7 +226,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => gameplayBeatmap;
|
protected override IBeatmap GetBeatmap() => gameplayBeatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
public override Texture GetBackground() => throw new NotImplementedException();
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
//CollectionSettings = new CollectionSettings(),
|
//CollectionSettings = new CollectionSettings(),
|
||||||
//DiscussionSettings = new DiscussionSettings(),
|
//DiscussionSettings = new DiscussionSettings(),
|
||||||
PlaybackSettings = new PlaybackSettings { Expanded = { Value = false } },
|
PlaybackSettings = new PlaybackSettings { Expanded = { Value = false } },
|
||||||
VisualSettings = new VisualSettings { Expanded = { Value = false } }
|
VisualSettings = new VisualSettings { Expanded = { Value = false } },
|
||||||
|
new AudioSettings { Expanded = { Value = false } }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
// 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;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new BeatmapBackgroundSprite(working)
|
new PanelBeatmapBackground(working)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -68,5 +70,23 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public partial class PanelBeatmapBackground : Sprite
|
||||||
|
{
|
||||||
|
private readonly IWorkingBeatmap working;
|
||||||
|
|
||||||
|
public PanelBeatmapBackground(IWorkingBeatmap working)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(working);
|
||||||
|
|
||||||
|
this.working = working;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Texture = working.GetPanelBackground();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => throw new NotImplementedException();
|
public override Texture GetBackground() => throw new NotImplementedException();
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
|
|
||||||
public override Stream? GetStream(string storagePath) => null;
|
public override Stream? GetStream(string storagePath) => null;
|
||||||
|
|
||||||
protected override Texture? GetBackground() => null;
|
public override Texture? GetBackground() => null;
|
||||||
|
|
||||||
protected override Track? GetBeatmapTrack() => null;
|
protected override Track? GetBeatmapTrack() => null;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -18,12 +16,12 @@ namespace osu.Game.Tests.Visual
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const int UNRESOLVED_USER_ID = -1;
|
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)
|
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,
|
Id = lookup,
|
||||||
Username = $"User {lookup}"
|
Username = $"User {lookup}"
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.20.0" />
|
<PackageReference Include="Realm" Version="10.20.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.531.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.510.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" />
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.531.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.608.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user