mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 10:12:54 +08:00
Merge branch 'master' into remove-requiredtypes
This commit is contained in:
commit
be3a0a3c1d
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.511.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.511.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
|
||||||
|
|
||||||
public bool AllowFail => false;
|
public bool PerformFail() => false;
|
||||||
|
|
||||||
public bool RestartOnFail => false;
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
private OsuInputManager inputManager;
|
private OsuInputManager inputManager;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Skinning;
|
using osu.Game.Rulesets.Taiko.Skinning;
|
||||||
@ -12,11 +13,30 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
{
|
{
|
||||||
public class TestSceneTaikoScroller : TaikoSkinnableTestScene
|
public class TestSceneTaikoScroller : TaikoSkinnableTestScene
|
||||||
{
|
{
|
||||||
|
private readonly ManualClock clock = new ManualClock();
|
||||||
|
|
||||||
|
private bool reversed;
|
||||||
|
|
||||||
public TestSceneTaikoScroller()
|
public TestSceneTaikoScroller()
|
||||||
{
|
{
|
||||||
AddStep("Load scroller", () => SetContents(() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())));
|
AddStep("Load scroller", () => SetContents(() =>
|
||||||
|
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||||
|
{
|
||||||
|
Clock = new FramedClock(clock),
|
||||||
|
Height = 0.4f,
|
||||||
|
}));
|
||||||
|
|
||||||
AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value =
|
AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value =
|
||||||
new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss }));
|
new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss }));
|
||||||
|
|
||||||
|
AddToggleStep("toggle playback direction", reversed => this.reversed = reversed);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
clock.CurrentTime += (reversed ? -1 : 1) * Clock.ElapsedFrameTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
|||||||
{
|
{
|
||||||
public class LegacyTaikoScroller : CompositeDrawable
|
public class LegacyTaikoScroller : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
public Bindable<JudgementResult> LastResult = new Bindable<JudgementResult>();
|
||||||
|
|
||||||
public LegacyTaikoScroller()
|
public LegacyTaikoScroller()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -50,37 +52,38 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bindable<JudgementResult> LastResult = new Bindable<JudgementResult>();
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
while (true)
|
// store X before checking wide enough so if we perform layout there is no positional discrepancy.
|
||||||
|
float currentX = (InternalChildren?.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f;
|
||||||
|
|
||||||
|
// ensure we have enough sprites
|
||||||
|
if (!InternalChildren.Any()
|
||||||
|
|| InternalChildren.First().ScreenSpaceDrawQuad.Width * InternalChildren.Count < ScreenSpaceDrawQuad.Width * 2)
|
||||||
|
AddInternal(new ScrollerSprite { Passing = passing });
|
||||||
|
|
||||||
|
var first = InternalChildren.First();
|
||||||
|
var last = InternalChildren.Last();
|
||||||
|
|
||||||
|
foreach (var sprite in InternalChildren)
|
||||||
{
|
{
|
||||||
float? additiveX = null;
|
// add the x coordinates and perform re-layout on all sprites as spacing may change with gameplay scale.
|
||||||
|
sprite.X = currentX;
|
||||||
|
currentX += sprite.DrawWidth;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var sprite in InternalChildren)
|
if (first.ScreenSpaceDrawQuad.TopLeft.X >= ScreenSpaceDrawQuad.TopLeft.X)
|
||||||
{
|
{
|
||||||
// add the x coordinates and perform re-layout on all sprites as spacing may change with gameplay scale.
|
foreach (var internalChild in InternalChildren)
|
||||||
sprite.X = additiveX ??= sprite.X - (float)Time.Elapsed * 0.1f;
|
internalChild.X -= first.DrawWidth;
|
||||||
|
}
|
||||||
|
|
||||||
additiveX += sprite.DrawWidth - 1;
|
if (last.ScreenSpaceDrawQuad.TopRight.X <= ScreenSpaceDrawQuad.TopRight.X)
|
||||||
|
{
|
||||||
if (sprite.X + sprite.DrawWidth < 0)
|
foreach (var internalChild in InternalChildren)
|
||||||
sprite.Expire();
|
internalChild.X += first.DrawWidth;
|
||||||
}
|
|
||||||
|
|
||||||
var last = InternalChildren.LastOrDefault();
|
|
||||||
|
|
||||||
// only break from this loop once we have saturated horizontal space completely.
|
|
||||||
if (last != null && last.ScreenSpaceDrawQuad.TopRight.X >= ScreenSpaceDrawQuad.TopRight.X)
|
|
||||||
break;
|
|
||||||
|
|
||||||
AddInternal(new ScrollerSprite
|
|
||||||
{
|
|
||||||
Passing = passing
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
|
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
|
||||||
|
|
||||||
AddInternal(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Depth = float.MaxValue
|
Depth = float.MaxValue
|
||||||
|
@ -13,18 +13,16 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
||||||
private const float default_aspect = 16f / 9f;
|
private const float default_aspect = 16f / 9f;
|
||||||
|
|
||||||
public TaikoPlayfieldAdjustmentContainer()
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft;
|
|
||||||
Origin = Anchor.CentreLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
|
float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
|
||||||
Size = new Vector2(1, default_relative_height * aspectAdjust);
|
Size = new Vector2(1, default_relative_height * aspectAdjust);
|
||||||
|
|
||||||
|
// Position the taiko playfield exactly one playfield from the top of the screen.
|
||||||
|
RelativePositionAxes = Axes.Y;
|
||||||
|
Y = Size.Y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,61 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
Assert.Throws<InvalidOperationException>(() => osu.Migrate(customPath));
|
Assert.Throws<ArgumentException>(() => osu.Migrate(customPath));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMigrationToNestedTargetFails()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToNestedTargetFails)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
|
|
||||||
|
string subFolder = Path.Combine(customPath, "sub");
|
||||||
|
|
||||||
|
if (Directory.Exists(subFolder))
|
||||||
|
Directory.Delete(subFolder, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(subFolder);
|
||||||
|
|
||||||
|
Assert.Throws<ArgumentException>(() => osu.Migrate(subFolder));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMigrationToSeeminglyNestedTarget()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
|
|
||||||
|
string seeminglySubFolder = customPath + "sub";
|
||||||
|
|
||||||
|
if (Directory.Exists(seeminglySubFolder))
|
||||||
|
Directory.Delete(seeminglySubFolder, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(seeminglySubFolder);
|
||||||
|
|
||||||
|
osu.Migrate(seeminglySubFolder);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
public new bool AllowFail => base.AllowFail;
|
|
||||||
|
public bool AllowFail => base.CheckModsAllowFailure();
|
||||||
|
|
||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
36
osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs
Normal file
36
osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
|
{
|
||||||
|
public class TestSceneMigrationScreens : ScreenTestScene
|
||||||
|
{
|
||||||
|
public TestSceneMigrationScreens()
|
||||||
|
{
|
||||||
|
AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMigrationSelectScreen : MigrationSelectScreen
|
||||||
|
{
|
||||||
|
protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen());
|
||||||
|
|
||||||
|
private class TestMigrationRunScreen : MigrationRunScreen
|
||||||
|
{
|
||||||
|
protected override void PerformMigration()
|
||||||
|
{
|
||||||
|
Thread.Sleep(3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestMigrationRunScreen()
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Editors
|
namespace osu.Game.Tournament.Screens.Editors
|
||||||
{
|
{
|
||||||
public class SeedingEditorScreen : TournamentEditorScreen<SeedingEditorScreen.SeeingResultRow, SeedingResult>
|
public class SeedingEditorScreen : TournamentEditorScreen<SeedingEditorScreen.SeedingResultRow, SeedingResult>
|
||||||
{
|
{
|
||||||
private readonly TournamentTeam team;
|
private readonly TournamentTeam team;
|
||||||
|
|
||||||
@ -30,14 +30,14 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
this.team = team;
|
this.team = team;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeeingResultRow : CompositeDrawable, IModelBacked<SeedingResult>
|
public class SeedingResultRow : CompositeDrawable, IModelBacked<SeedingResult>
|
||||||
{
|
{
|
||||||
public SeedingResult Model { get; }
|
public SeedingResult Model { get; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private LadderInfo ladderInfo { get; set; }
|
private LadderInfo ladderInfo { get; set; }
|
||||||
|
|
||||||
public SeeingResultRow(TournamentTeam team, SeedingResult round)
|
public SeedingResultRow(TournamentTeam team, SeedingResult round)
|
||||||
{
|
{
|
||||||
Model = round;
|
Model = round;
|
||||||
|
|
||||||
@ -281,6 +281,6 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SeeingResultRow CreateDrawable(SeedingResult model) => new SeeingResultRow(team, model);
|
protected override SeedingResultRow CreateDrawable(SeedingResult model) => new SeedingResultRow(team, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Screens.MapPool
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new TourneyVideo("gameplay")
|
new TourneyVideo("mappool")
|
||||||
{
|
{
|
||||||
Loop = true,
|
Loop = true,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
23
osu.Game/Extensions/WebRequestExtensions.cs
Normal file
23
osu.Game/Extensions/WebRequestExtensions.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
|
||||||
|
namespace osu.Game.Extensions
|
||||||
|
{
|
||||||
|
public static class WebRequestExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add a pagination cursor to the web request in the format required by osu-web.
|
||||||
|
/// </summary>
|
||||||
|
public static void AddCursor(this WebRequest webRequest, Cursor cursor)
|
||||||
|
{
|
||||||
|
cursor?.Properties.ForEach(x =>
|
||||||
|
{
|
||||||
|
webRequest.AddParameter("cursor[" + x.Key + "]", x.Value.ToString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,11 +28,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly Bindable<DirectoryInfo> currentDirectory = new Bindable<DirectoryInfo>();
|
public readonly Bindable<DirectoryInfo> CurrentDirectory = new Bindable<DirectoryInfo>();
|
||||||
|
|
||||||
public DirectorySelector(string initialPath = null)
|
public DirectorySelector(string initialPath = null)
|
||||||
{
|
{
|
||||||
currentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
|
CurrentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -40,19 +40,25 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
Padding = new MarginPadding(10);
|
Padding = new MarginPadding(10);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChild = new GridContainer
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Dimension(GridSizeMode.Absolute, 50),
|
||||||
Direction = FillDirection.Vertical,
|
new Dimension(),
|
||||||
Children = new Drawable[]
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new CurrentDirectoryDisplay
|
new CurrentDirectoryDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 50,
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
new OsuScrollContainer
|
new OsuScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -65,10 +71,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
currentDirectory.BindValueChanged(updateDisplay, true);
|
CurrentDirectory.BindValueChanged(updateDisplay, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplay(ValueChangedEvent<DirectoryInfo> directory)
|
private void updateDisplay(ValueChangedEvent<DirectoryInfo> directory)
|
||||||
@ -86,9 +92,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
directoryFlow.Add(new ParentDirectoryPiece(currentDirectory.Value.Parent));
|
directoryFlow.Add(new ParentDirectoryPiece(CurrentDirectory.Value.Parent));
|
||||||
|
|
||||||
foreach (var dir in currentDirectory.Value.GetDirectories().OrderBy(d => d.Name))
|
foreach (var dir in CurrentDirectory.Value.GetDirectories().OrderBy(d => d.Name))
|
||||||
{
|
{
|
||||||
if ((dir.Attributes & FileAttributes.Hidden) == 0)
|
if ((dir.Attributes & FileAttributes.Hidden) == 0)
|
||||||
directoryFlow.Add(new DirectoryPiece(dir));
|
directoryFlow.Add(new DirectoryPiece(dir));
|
||||||
@ -97,8 +103,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
currentDirectory.Value = directory.OldValue;
|
CurrentDirectory.Value = directory.OldValue;
|
||||||
|
|
||||||
this.FlashColour(Color4.Red, 300);
|
this.FlashColour(Color4.Red, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,21 @@ namespace osu.Game.IO
|
|||||||
var source = new DirectoryInfo(GetFullPath("."));
|
var source = new DirectoryInfo(GetFullPath("."));
|
||||||
var destination = new DirectoryInfo(newLocation);
|
var destination = new DirectoryInfo(newLocation);
|
||||||
|
|
||||||
|
// using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620)
|
||||||
|
var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar);
|
||||||
|
var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
if (sourceUri == destinationUri)
|
||||||
|
throw new ArgumentException("Destination provided is already the current location", nameof(newLocation));
|
||||||
|
|
||||||
|
if (sourceUri.IsBaseOf(destinationUri))
|
||||||
|
throw new ArgumentException("Destination provided is inside the source", nameof(newLocation));
|
||||||
|
|
||||||
// ensure the new location has no files present, else hard abort
|
// ensure the new location has no files present, else hard abort
|
||||||
if (destination.Exists)
|
if (destination.Exists)
|
||||||
{
|
{
|
||||||
if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0)
|
if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0)
|
||||||
throw new InvalidOperationException("Migration destination already has files present");
|
throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation));
|
||||||
|
|
||||||
deleteRecursive(destination);
|
deleteRecursive(destination);
|
||||||
}
|
}
|
||||||
@ -74,7 +84,7 @@ namespace osu.Game.IO
|
|||||||
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
fi.Delete();
|
attemptOperation(() => fi.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (DirectoryInfo dir in target.GetDirectories())
|
foreach (DirectoryInfo dir in target.GetDirectories())
|
||||||
@ -82,8 +92,11 @@ namespace osu.Game.IO
|
|||||||
if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name))
|
if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
dir.Delete(true);
|
attemptOperation(() => dir.Delete(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
||||||
|
attemptOperation(target.Delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
||||||
@ -96,7 +109,7 @@ namespace osu.Game.IO
|
|||||||
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
attemptCopy(fi, Path.Combine(destination.FullName, fi.Name));
|
attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (DirectoryInfo dir in source.GetDirectories())
|
foreach (DirectoryInfo dir in source.GetDirectories())
|
||||||
@ -108,24 +121,27 @@ namespace osu.Game.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void attemptCopy(System.IO.FileInfo fileInfo, string destination)
|
/// <summary>
|
||||||
|
/// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">The action to perform.</param>
|
||||||
|
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
||||||
|
private static void attemptOperation(Action action, int attempts = 10)
|
||||||
{
|
{
|
||||||
int tries = 5;
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
fileInfo.CopyTo(destination, true);
|
action();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
if (tries-- == 0)
|
if (attempts-- == 0)
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(50);
|
Thread.Sleep(250);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
osu.Game/Online/API/Requests/Cursor.cs
Normal file
20
osu.Game/Online/API/Requests/Cursor.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
|
||||||
|
/// </summary>
|
||||||
|
public class Cursor
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
[JsonExtensionData]
|
||||||
|
public IDictionary<string, JToken> Properties;
|
||||||
|
}
|
||||||
|
}
|
@ -7,10 +7,7 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
public abstract class ResponseWithCursor
|
public abstract class ResponseWithCursor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("cursor")]
|
[JsonProperty("cursor")]
|
||||||
public dynamic CursorJson;
|
public Cursor Cursor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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 osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -10,29 +11,31 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
|
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
|
||||||
{
|
{
|
||||||
public SearchCategory SearchCategory { get; set; }
|
public SearchCategory SearchCategory { get; }
|
||||||
|
|
||||||
public SortCriteria SortCriteria { get; set; }
|
public SortCriteria SortCriteria { get; }
|
||||||
|
|
||||||
public SortDirection SortDirection { get; set; }
|
public SortDirection SortDirection { get; }
|
||||||
|
|
||||||
public SearchGenre Genre { get; set; }
|
public SearchGenre Genre { get; }
|
||||||
|
|
||||||
public SearchLanguage Language { get; set; }
|
public SearchLanguage Language { get; }
|
||||||
|
|
||||||
private readonly string query;
|
private readonly string query;
|
||||||
private readonly RulesetInfo ruleset;
|
private readonly RulesetInfo ruleset;
|
||||||
|
private readonly Cursor cursor;
|
||||||
|
|
||||||
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
|
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
|
||||||
|
|
||||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset)
|
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending)
|
||||||
{
|
{
|
||||||
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
||||||
this.ruleset = ruleset;
|
this.ruleset = ruleset;
|
||||||
|
this.cursor = cursor;
|
||||||
|
|
||||||
SearchCategory = SearchCategory.Any;
|
SearchCategory = searchCategory;
|
||||||
SortCriteria = SortCriteria.Ranked;
|
SortCriteria = sortCriteria;
|
||||||
SortDirection = SortDirection.Descending;
|
SortDirection = sortDirection;
|
||||||
Genre = SearchGenre.Any;
|
Genre = SearchGenre.Any;
|
||||||
Language = SearchLanguage.Any;
|
Language = SearchLanguage.Any;
|
||||||
}
|
}
|
||||||
@ -55,6 +58,8 @@ namespace osu.Game.Online.API.Requests
|
|||||||
|
|
||||||
req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
||||||
|
|
||||||
|
req.AddCursor(cursor);
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,25 +22,46 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
{
|
{
|
||||||
public class BeatmapListingFilterControl : CompositeDrawable
|
public class BeatmapListingFilterControl : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a search finishes. Contains only new items in the case of pagination.
|
||||||
|
/// </summary>
|
||||||
public Action<List<BeatmapSetInfo>> SearchFinished;
|
public Action<List<BeatmapSetInfo>> SearchFinished;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when search criteria change.
|
||||||
|
/// </summary>
|
||||||
public Action SearchStarted;
|
public Action SearchStarted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when pagination has reached the end of available results.
|
||||||
|
/// </summary>
|
||||||
|
private bool noMoreResults;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current page fetched of results (zero index).
|
||||||
|
/// </summary>
|
||||||
|
public int CurrentPage { get; private set; }
|
||||||
|
|
||||||
|
private readonly BeatmapListingSearchControl searchControl;
|
||||||
|
private readonly BeatmapListingSortTabControl sortControl;
|
||||||
|
private readonly Box sortControlBackground;
|
||||||
|
|
||||||
|
private ScheduledDelegate queryChangedDebounce;
|
||||||
|
|
||||||
|
private SearchBeatmapSetsRequest getSetsRequest;
|
||||||
|
private SearchBeatmapSetsResponse lastResponse;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesets { get; set; }
|
private RulesetStore rulesets { get; set; }
|
||||||
|
|
||||||
private readonly BeatmapListingSearchControl searchControl;
|
|
||||||
private readonly BeatmapListingSortTabControl sortControl;
|
|
||||||
private readonly Box sortControlBackground;
|
|
||||||
|
|
||||||
private SearchBeatmapSetsRequest getSetsRequest;
|
|
||||||
|
|
||||||
public BeatmapListingFilterControl()
|
public BeatmapListingFilterControl()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
InternalChild = new FillFlowContainer
|
InternalChild = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -114,51 +135,84 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate queryChangedDebounce;
|
public void TakeFocus() => searchControl.TakeFocus();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch the next page of results. May result in a no-op if a fetch is already in progress, or if there are no results left.
|
||||||
|
/// </summary>
|
||||||
|
public void FetchNextPage()
|
||||||
|
{
|
||||||
|
// there may be no results left.
|
||||||
|
if (noMoreResults)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// there may already be an active request.
|
||||||
|
if (getSetsRequest != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (lastResponse != null)
|
||||||
|
CurrentPage++;
|
||||||
|
|
||||||
|
performRequest();
|
||||||
|
}
|
||||||
|
|
||||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||||
{
|
{
|
||||||
SearchStarted?.Invoke();
|
SearchStarted?.Invoke();
|
||||||
|
|
||||||
getSetsRequest?.Cancel();
|
resetSearch();
|
||||||
|
|
||||||
queryChangedDebounce?.Cancel();
|
queryChangedDebounce = Scheduler.AddDelayed(() =>
|
||||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
{
|
||||||
|
resetSearch();
|
||||||
|
FetchNextPage();
|
||||||
|
}, queryTextChanged ? 500 : 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSearch()
|
private void performRequest()
|
||||||
{
|
{
|
||||||
getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value)
|
getSetsRequest = new SearchBeatmapSetsRequest(
|
||||||
{
|
searchControl.Query.Value,
|
||||||
SearchCategory = searchControl.Category.Value,
|
searchControl.Ruleset.Value,
|
||||||
SortCriteria = sortControl.Current.Value,
|
lastResponse?.Cursor,
|
||||||
SortDirection = sortControl.SortDirection.Value,
|
searchControl.Category.Value,
|
||||||
Genre = searchControl.Genre.Value,
|
sortControl.Current.Value,
|
||||||
Language = searchControl.Language.Value
|
sortControl.SortDirection.Value);
|
||||||
};
|
|
||||||
|
|
||||||
getSetsRequest.Success += response => Schedule(() => onSearchFinished(response));
|
getSetsRequest.Success += response =>
|
||||||
|
{
|
||||||
|
var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList();
|
||||||
|
|
||||||
|
if (sets.Count == 0)
|
||||||
|
noMoreResults = true;
|
||||||
|
|
||||||
|
lastResponse = response;
|
||||||
|
getSetsRequest = null;
|
||||||
|
|
||||||
|
SearchFinished?.Invoke(sets);
|
||||||
|
};
|
||||||
|
|
||||||
api.Queue(getSetsRequest);
|
api.Queue(getSetsRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchFinished(SearchBeatmapSetsResponse response)
|
private void resetSearch()
|
||||||
{
|
{
|
||||||
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
noMoreResults = false;
|
||||||
|
CurrentPage = 0;
|
||||||
|
|
||||||
searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First();
|
lastResponse = null;
|
||||||
|
|
||||||
SearchFinished?.Invoke(beatmaps);
|
getSetsRequest?.Cancel();
|
||||||
|
getSetsRequest = null;
|
||||||
|
|
||||||
|
queryChangedDebounce?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
getSetsRequest?.Cancel();
|
resetSearch();
|
||||||
queryChangedDebounce?.Cancel();
|
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TakeFocus() => searchControl.TakeFocus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -30,6 +32,10 @@ namespace osu.Game.Overlays
|
|||||||
private Drawable currentContent;
|
private Drawable currentContent;
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer;
|
||||||
private Container panelTarget;
|
private Container panelTarget;
|
||||||
|
private FillFlowContainer<BeatmapPanel> foundContent;
|
||||||
|
private NotFoundDrawable notFoundContent;
|
||||||
|
|
||||||
|
private OverlayScrollContainer resultScrollContainer;
|
||||||
|
|
||||||
public BeatmapListingOverlay()
|
public BeatmapListingOverlay()
|
||||||
: base(OverlayColourScheme.Blue)
|
: base(OverlayColourScheme.Blue)
|
||||||
@ -48,7 +54,7 @@ namespace osu.Game.Overlays
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = ColourProvider.Background6
|
Colour = ColourProvider.Background6
|
||||||
},
|
},
|
||||||
new OverlayScrollContainer
|
resultScrollContainer = new OverlayScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ScrollbarVisible = false,
|
ScrollbarVisible = false,
|
||||||
@ -80,9 +86,14 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Padding = new MarginPadding { Horizontal = 20 }
|
Padding = new MarginPadding { Horizontal = 20 },
|
||||||
},
|
Children = new Drawable[]
|
||||||
loadingLayer = new LoadingLayer(panelTarget)
|
{
|
||||||
|
foundContent = new FillFlowContainer<BeatmapPanel>(),
|
||||||
|
notFoundContent = new NotFoundDrawable(),
|
||||||
|
loadingLayer = new LoadingLayer(panelTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -110,34 +121,53 @@ namespace osu.Game.Overlays
|
|||||||
loadingLayer.Show();
|
loadingLayer.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task panelLoadDelegate;
|
||||||
|
|
||||||
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
|
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
|
||||||
{
|
{
|
||||||
if (!beatmaps.Any())
|
var newPanels = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
|
||||||
{
|
{
|
||||||
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
Anchor = Anchor.TopCentre,
|
||||||
return;
|
Origin = Anchor.TopCentre,
|
||||||
}
|
});
|
||||||
|
|
||||||
var newPanels = new FillFlowContainer<BeatmapPanel>
|
if (filterControl.CurrentPage == 0)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
//No matches case
|
||||||
AutoSizeAxes = Axes.Y,
|
if (!newPanels.Any())
|
||||||
Spacing = new Vector2(10),
|
|
||||||
Alpha = 0,
|
|
||||||
Margin = new MarginPadding { Vertical = 15 },
|
|
||||||
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||||
Origin = Anchor.TopCentre,
|
return;
|
||||||
})
|
}
|
||||||
};
|
|
||||||
|
|
||||||
LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
// spawn new children with the contained so we only clear old content at the last moment.
|
||||||
|
var content = new FillFlowContainer<BeatmapPanel>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Alpha = 0,
|
||||||
|
Margin = new MarginPadding { Vertical = 15 },
|
||||||
|
ChildrenEnumerable = newPanels
|
||||||
|
};
|
||||||
|
|
||||||
|
panelLoadDelegate = LoadComponentAsync(foundContent = content, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
panelLoadDelegate = LoadComponentsAsync(newPanels, loaded =>
|
||||||
|
{
|
||||||
|
lastFetchDisplayedTime = Time.Current;
|
||||||
|
foundContent.AddRange(loaded);
|
||||||
|
loaded.ForEach(p => p.FadeIn(200, Easing.OutQuint));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addContentToPlaceholder(Drawable content)
|
private void addContentToPlaceholder(Drawable content)
|
||||||
{
|
{
|
||||||
loadingLayer.Hide();
|
loadingLayer.Hide();
|
||||||
|
lastFetchDisplayedTime = Time.Current;
|
||||||
|
|
||||||
var lastContent = currentContent;
|
var lastContent = currentContent;
|
||||||
|
|
||||||
@ -149,11 +179,14 @@ namespace osu.Game.Overlays
|
|||||||
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
||||||
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
||||||
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
||||||
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
|
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => panelTarget.Remove(lastContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
panelTarget.Add(currentContent = content);
|
if (!content.IsAlive)
|
||||||
currentContent.FadeIn(200, Easing.OutQuint);
|
panelTarget.Add(content);
|
||||||
|
content.FadeIn(200, Easing.OutQuint);
|
||||||
|
|
||||||
|
currentContent = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -203,5 +236,23 @@ namespace osu.Game.Overlays
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const double time_between_fetches = 500;
|
||||||
|
|
||||||
|
private double lastFetchDisplayedTime;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
const int pagination_scroll_distance = 500;
|
||||||
|
|
||||||
|
bool shouldShowMore = panelLoadDelegate?.IsCompleted != false
|
||||||
|
&& Time.Current - lastFetchDisplayedTime > time_between_fetches
|
||||||
|
&& (resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance));
|
||||||
|
|
||||||
|
if (shouldShowMore)
|
||||||
|
filterControl.FetchNextPage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.General
|
namespace osu.Game.Overlays.Settings.Sections.General
|
||||||
{
|
{
|
||||||
@ -12,8 +14,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
{
|
{
|
||||||
protected override string Header => "Updates";
|
protected override string Header => "Updates";
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(Storage storage, OsuConfigManager config)
|
private void load(Storage storage, OsuConfigManager config, OsuGame game)
|
||||||
{
|
{
|
||||||
Add(new SettingsEnumDropdown<ReleaseStream>
|
Add(new SettingsEnumDropdown<ReleaseStream>
|
||||||
{
|
{
|
||||||
@ -28,6 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
Text = "Open osu! folder",
|
Text = "Open osu! folder",
|
||||||
Action = storage.OpenInNativeExplorer,
|
Action = storage.OpenInNativeExplorer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Add(new SettingsButton
|
||||||
|
{
|
||||||
|
Text = "Change folder location...",
|
||||||
|
Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen()))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
|
{
|
||||||
|
public class MigrationRunScreen : OsuScreen
|
||||||
|
{
|
||||||
|
private readonly DirectoryInfo destination;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
public override bool AllowBackButton => false;
|
||||||
|
|
||||||
|
public override bool AllowExternalScreenChange => false;
|
||||||
|
|
||||||
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
|
public override bool HideOverlaysOnEnter => true;
|
||||||
|
|
||||||
|
private Task migrationTask;
|
||||||
|
|
||||||
|
public MigrationRunScreen(DirectoryInfo destination)
|
||||||
|
{
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Migration in progress",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "This could take a few minutes depending on the speed of your disk(s).",
|
||||||
|
Font = OsuFont.Default.With(size: 30)
|
||||||
|
},
|
||||||
|
new LoadingSpinner(true)
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Visible }
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Please avoid interacting with the game!",
|
||||||
|
Font = OsuFont.Default.With(size: 30)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Beatmap.Value = Beatmap.Default;
|
||||||
|
|
||||||
|
migrationTask = Task.Run(PerformMigration)
|
||||||
|
.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (t.IsFaulted)
|
||||||
|
Logger.Log($"Error during migration: {t.Exception?.Message}", level: LogLevel.Error);
|
||||||
|
|
||||||
|
Schedule(this.Exit);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void PerformMigration() => game?.Migrate(destination.FullName);
|
||||||
|
|
||||||
|
public override void OnEntering(IScreen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
|
||||||
|
this.FadeOut().Delay(250).Then().FadeIn(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnExiting(IScreen next)
|
||||||
|
{
|
||||||
|
// block until migration is finished
|
||||||
|
if (migrationTask?.IsCompleted == false)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
|
{
|
||||||
|
public class MigrationSelectScreen : OsuScreen
|
||||||
|
{
|
||||||
|
private DirectorySelector directorySelector;
|
||||||
|
|
||||||
|
public override bool AllowExternalScreenChange => false;
|
||||||
|
|
||||||
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
|
public override bool HideOverlaysOnEnter => true;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(OsuGame game, Storage storage, OsuColour colours)
|
||||||
|
{
|
||||||
|
game?.Toolbar.Hide();
|
||||||
|
|
||||||
|
// begin selection in the parent directory of the current storage location
|
||||||
|
var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 10,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(0.5f, 0.8f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.GreySeafoamDark,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Relative, 0.8f),
|
||||||
|
new Dimension(),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Please select a new location",
|
||||||
|
Font = OsuFont.Default.With(size: 40)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
directorySelector = new DirectorySelector(initialPath)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new TriangleButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 300,
|
||||||
|
Text = "Begin folder migration",
|
||||||
|
Action = start
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSuspending(IScreen next)
|
||||||
|
{
|
||||||
|
base.OnSuspending(next);
|
||||||
|
|
||||||
|
this.FadeOut(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start()
|
||||||
|
{
|
||||||
|
var target = directorySelector.CurrentDirectory.Value;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0)
|
||||||
|
target = target.CreateSubdirectory("osu-lazer");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidForResume = false;
|
||||||
|
BeginMigration(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target));
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,13 @@ namespace osu.Game.Overlays
|
|||||||
Width = 300,
|
Width = 300,
|
||||||
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
||||||
},
|
},
|
||||||
|
muteButton = new MuteButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Margin = new MarginPadding(10),
|
||||||
|
Current = { BindTarget = IsMuted }
|
||||||
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
@ -56,19 +63,11 @@ namespace osu.Game.Overlays
|
|||||||
Margin = new MarginPadding { Left = offset },
|
Margin = new MarginPadding { Left = offset },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker)
|
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker),
|
||||||
{
|
|
||||||
Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } // to counter the mute button and re-center the volume meters
|
|
||||||
},
|
|
||||||
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
|
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
|
||||||
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
||||||
muteButton = new MuteButton
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding { Top = 100 },
|
|
||||||
Current = { BindTarget = IsMuted }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
||||||
|
@ -11,10 +11,11 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether we should allow failing at the current point in time.
|
/// Whether we should allow failing at the current point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool AllowFail { get; }
|
/// <returns>Whether the fail should be allowed to proceed. Return false to block.</returns>
|
||||||
|
bool PerformFail();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether we want to restart on fail. Only used if <see cref="AllowFail"/> is true.
|
/// Whether we want to restart on fail. Only used if <see cref="PerformFail"/> returns true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool RestartOnFail { get; }
|
bool RestartOnFail { get; }
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override string Description => "Watch a perfect automated play through the song.";
|
public override string Description => "Watch a perfect automated play through the song.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public bool AllowFail => false;
|
public bool PerformFail() => false;
|
||||||
|
|
||||||
public bool RestartOnFail => false;
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// We never fail, 'yo.
|
/// We never fail, 'yo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowFail => false;
|
public bool PerformFail() => false;
|
||||||
|
|
||||||
public bool RestartOnFail => false;
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
|
@ -48,17 +48,14 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
retries = Retries.Value;
|
retries = Retries.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AllowFail
|
public bool PerformFail()
|
||||||
{
|
{
|
||||||
get
|
if (retries == 0) return true;
|
||||||
{
|
|
||||||
if (retries == 0) return true;
|
|
||||||
|
|
||||||
health.Value = health.MaxValue;
|
health.Value = health.MaxValue;
|
||||||
retries--;
|
retries--;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RestartOnFail => false;
|
public bool RestartOnFail => false;
|
||||||
|
@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
|
||||||
|
|
||||||
public bool AllowFail => true;
|
public bool PerformFail() => true;
|
||||||
|
|
||||||
public bool RestartOnFail => true;
|
public bool RestartOnFail => true;
|
||||||
|
|
||||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||||
|
@ -78,10 +78,10 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
|
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
|
||||||
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
||||||
platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
|
platformOffsetClock = new HardwareCorrectionOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
|
||||||
|
|
||||||
// the final usable gameplay clock with user-set offsets applied.
|
// the final usable gameplay clock with user-set offsets applied.
|
||||||
userOffsetClock = new FramedOffsetClock(platformOffsetClock);
|
userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock);
|
||||||
|
|
||||||
// the clock to be exposed via DI to children.
|
// the clock to be exposed via DI to children.
|
||||||
GameplayClock = new GameplayClock(userOffsetClock);
|
GameplayClock = new GameplayClock(userOffsetClock);
|
||||||
@ -248,5 +248,16 @@ namespace osu.Game.Screens.Play
|
|||||||
speedAdjustmentsApplied = false;
|
speedAdjustmentsApplied = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class HardwareCorrectionOffsetClock : FramedOffsetClock
|
||||||
|
{
|
||||||
|
// we always want to apply the same real-time offset, so it should be adjusted by the playback rate to achieve this.
|
||||||
|
public override double CurrentTime => SourceTime + Offset * Rate;
|
||||||
|
|
||||||
|
public HardwareCorrectionOffsetClock(IClock source, bool processSource = true)
|
||||||
|
: base(source, processSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Screens.Play
|
|||||||
/// Whether failing should be allowed.
|
/// Whether failing should be allowed.
|
||||||
/// By default, this checks whether all selected mods allow failing.
|
/// By default, this checks whether all selected mods allow failing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual bool AllowFail => Mods.Value.OfType<IApplicableFailOverride>().All(m => m.AllowFail);
|
protected virtual bool CheckModsAllowFailure() => Mods.Value.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
|
||||||
|
|
||||||
private readonly bool allowPause;
|
private readonly bool allowPause;
|
||||||
private readonly bool showResults;
|
private readonly bool showResults;
|
||||||
@ -485,7 +485,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private bool onFail()
|
private bool onFail()
|
||||||
{
|
{
|
||||||
if (!AllowFail)
|
if (!CheckModsAllowFailure())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
HasFailed = true;
|
HasFailed = true;
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play
|
|||||||
private readonly Score score;
|
private readonly Score score;
|
||||||
|
|
||||||
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
||||||
protected override bool AllowFail => false;
|
protected override bool CheckModsAllowFailure() => false;
|
||||||
|
|
||||||
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
|
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
|
||||||
: base(allowPause, showResults)
|
: base(allowPause, showResults)
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool AllowFail => true;
|
protected override bool CheckModsAllowFailure() => true;
|
||||||
|
|
||||||
public bool CheckFailed(bool failed)
|
public bool CheckFailed(bool failed)
|
||||||
{
|
{
|
||||||
|
@ -59,12 +59,14 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected class ModTestPlayer : TestPlayer
|
protected class ModTestPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
protected override bool AllowFail { get; }
|
private readonly bool allowFail;
|
||||||
|
|
||||||
|
protected override bool CheckModsAllowFailure() => allowFail;
|
||||||
|
|
||||||
public ModTestPlayer(bool allowFail)
|
public ModTestPlayer(bool allowFail)
|
||||||
: base(false, false)
|
: base(false, false)
|
||||||
{
|
{
|
||||||
AllowFail = allowFail;
|
this.allowFail = allowFail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.511.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.511.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.1" />
|
<PackageReference Include="Sentry" Version="2.1.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
<PackageReference Include="SharpCompress" Version="0.25.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.511.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.511.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
|
Loading…
Reference in New Issue
Block a user