mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 17:53:15 +08:00
Merge branch 'master' into localisation-proof-of-concept
This commit is contained in:
commit
b13a68592f
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.513.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.521.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -14,10 +14,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
private readonly HitPiece piece;
|
private readonly HitPiece piece;
|
||||||
|
|
||||||
private static Hit hit;
|
public new Hit HitObject => (Hit)base.HitObject;
|
||||||
|
|
||||||
public HitPlacementBlueprint()
|
public HitPlacementBlueprint()
|
||||||
: base(hit = new Hit())
|
: base(new Hit())
|
||||||
{
|
{
|
||||||
InternalChild = piece = new HitPiece
|
InternalChild = piece = new HitPiece
|
||||||
{
|
{
|
||||||
@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Left:
|
case MouseButton.Left:
|
||||||
hit.Type = HitType.Centre;
|
HitObject.Type = HitType.Centre;
|
||||||
EndPlacement(true);
|
EndPlacement(true);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MouseButton.Right:
|
case MouseButton.Right:
|
||||||
hit.Type = HitType.Rim;
|
HitObject.Type = HitType.Rim;
|
||||||
EndPlacement(true);
|
EndPlacement(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -52,23 +52,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
type.BindTo(HitObject.TypeBindable);
|
type.BindTo(HitObject.TypeBindable);
|
||||||
type.BindValueChanged(_ =>
|
// this doesn't need to be run inline as RecreatePieces is called by the base call below.
|
||||||
{
|
type.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces));
|
||||||
updateActionsFromType();
|
|
||||||
|
|
||||||
// will overwrite samples, should only be called on subsequent changes
|
|
||||||
// after the initial application.
|
|
||||||
updateSamplesFromTypeChange();
|
|
||||||
|
|
||||||
RecreatePieces();
|
|
||||||
});
|
|
||||||
|
|
||||||
// action update also has to happen immediately on application.
|
|
||||||
updateActionsFromType();
|
|
||||||
|
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void RecreatePieces()
|
||||||
|
{
|
||||||
|
updateActionsFromType();
|
||||||
|
base.RecreatePieces();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnFree()
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
@ -83,33 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
validActionPressed = pressHandledThisFrame = false;
|
validActionPressed = pressHandledThisFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
|
||||||
|
|
||||||
protected override void LoadSamples()
|
|
||||||
{
|
|
||||||
base.LoadSamples();
|
|
||||||
|
|
||||||
type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSamplesFromTypeChange()
|
|
||||||
{
|
|
||||||
var rimSamples = getRimSamples();
|
|
||||||
|
|
||||||
bool isRimType = HitObject.Type == HitType.Rim;
|
|
||||||
|
|
||||||
if (isRimType != rimSamples.Any())
|
|
||||||
{
|
|
||||||
if (isRimType)
|
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var sample in rimSamples)
|
|
||||||
HitObject.Samples.Remove(sample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateActionsFromType()
|
private void updateActionsFromType()
|
||||||
{
|
{
|
||||||
HitActions =
|
HitActions =
|
||||||
|
@ -137,7 +137,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE);
|
Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
MainPiece?.Expire();
|
if (MainPiece != null)
|
||||||
|
Content.Remove(MainPiece);
|
||||||
|
|
||||||
Content.Add(MainPiece = CreateMainPiece());
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +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.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -29,14 +27,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
isStrong.BindTo(HitObject.IsStrongBindable);
|
isStrong.BindTo(HitObject.IsStrongBindable);
|
||||||
isStrong.BindValueChanged(_ =>
|
// this doesn't need to be run inline as RecreatePieces is called by the base call below.
|
||||||
{
|
isStrong.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces));
|
||||||
// will overwrite samples, should only be called on subsequent changes
|
|
||||||
// after the initial application.
|
|
||||||
updateSamplesFromStrong();
|
|
||||||
|
|
||||||
RecreatePieces();
|
|
||||||
});
|
|
||||||
|
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
}
|
}
|
||||||
@ -50,30 +42,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
isStrong.UnbindEvents();
|
isStrong.UnbindEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray();
|
|
||||||
|
|
||||||
protected override void LoadSamples()
|
|
||||||
{
|
|
||||||
base.LoadSamples();
|
|
||||||
isStrong.Value = getStrongSamples().Any();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSamplesFromStrong()
|
|
||||||
{
|
|
||||||
var strongSamples = getStrongSamples();
|
|
||||||
|
|
||||||
if (isStrong.Value != strongSamples.Any())
|
|
||||||
{
|
|
||||||
if (isStrong.Value)
|
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var sample in strongSamples)
|
|
||||||
HitObject.Samples.Remove(sample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void RecreatePieces()
|
protected override void RecreatePieces()
|
||||||
{
|
{
|
||||||
base.RecreatePieces();
|
base.RecreatePieces();
|
||||||
|
@ -1,7 +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.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
@ -15,9 +17,36 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
public HitType Type
|
public HitType Type
|
||||||
{
|
{
|
||||||
get => TypeBindable.Value;
|
get => TypeBindable.Value;
|
||||||
set => TypeBindable.Value = value;
|
set
|
||||||
|
{
|
||||||
|
TypeBindable.Value = value;
|
||||||
|
updateSamplesFromType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateSamplesFromType()
|
||||||
|
{
|
||||||
|
var rimSamples = getRimSamples();
|
||||||
|
|
||||||
|
bool isRimType = Type == HitType.Rim;
|
||||||
|
|
||||||
|
if (isRimType != rimSamples.Any())
|
||||||
|
{
|
||||||
|
if (isRimType)
|
||||||
|
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var sample in rimSamples)
|
||||||
|
Samples.Remove(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||||
|
/// </summary>
|
||||||
|
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||||
|
|
||||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||||
|
|
||||||
public class StrongNestedHit : StrongNestedHitObject
|
public class StrongNestedHit : StrongNestedHitObject
|
||||||
|
@ -1,8 +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.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
@ -31,9 +33,31 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
public bool IsStrong
|
public bool IsStrong
|
||||||
{
|
{
|
||||||
get => IsStrongBindable.Value;
|
get => IsStrongBindable.Value;
|
||||||
set => IsStrongBindable.Value = value;
|
set
|
||||||
|
{
|
||||||
|
IsStrongBindable.Value = value;
|
||||||
|
updateSamplesFromStrong();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateSamplesFromStrong()
|
||||||
|
{
|
||||||
|
var strongSamples = getStrongSamples();
|
||||||
|
|
||||||
|
if (IsStrongBindable.Value != strongSamples.Any())
|
||||||
|
{
|
||||||
|
if (IsStrongBindable.Value)
|
||||||
|
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var sample in strongSamples)
|
||||||
|
Samples.Remove(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HitSampleInfo[] getStrongSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray();
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
85
osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs
Normal file
85
osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Gameplay
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneProxyContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
private HitObjectContainer hitObjectContainer;
|
||||||
|
private ProxyContainer proxyContainer;
|
||||||
|
private readonly ManualClock clock = new ManualClock();
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hitObjectContainer = new HitObjectContainer(),
|
||||||
|
proxyContainer = new ProxyContainer()
|
||||||
|
},
|
||||||
|
Clock = new FramedClock(clock)
|
||||||
|
};
|
||||||
|
clock.CurrentTime = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestProxyLifetimeManagement()
|
||||||
|
{
|
||||||
|
AddStep("Add proxy drawables", () =>
|
||||||
|
{
|
||||||
|
addProxy(new TestDrawableHitObject(1000));
|
||||||
|
addProxy(new TestDrawableHitObject(3000));
|
||||||
|
addProxy(new TestDrawableHitObject(5000));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("time = 1000", () => clock.CurrentTime = 1000);
|
||||||
|
AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1);
|
||||||
|
AddStep("time = 5000", () => clock.CurrentTime = 5000);
|
||||||
|
AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1);
|
||||||
|
AddStep("time = 6000", () => clock.CurrentTime = 6000);
|
||||||
|
AddAssert("No proxy is alive", () => proxyContainer.AliveChildren.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addProxy(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
hitObjectContainer.Add(drawableHitObject);
|
||||||
|
proxyContainer.AddProxy(drawableHitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProxyContainer : LifetimeManagementContainer
|
||||||
|
{
|
||||||
|
public IReadOnlyList<Drawable> AliveChildren => AliveInternalChildren;
|
||||||
|
|
||||||
|
public void AddProxy(Drawable d) => AddInternal(d.CreateProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
|
{
|
||||||
|
protected override double InitialLifetimeOffset => 100;
|
||||||
|
|
||||||
|
public TestDrawableHitObject(double startTime)
|
||||||
|
: base(new HitObject { StartTime = startTime })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
LifetimeEnd = LifetimeStart + 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,8 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Select;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
@ -37,17 +37,17 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtSongSelect()
|
public void TestPerformAtSongSelect()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtMenuFromSongSelect()
|
public void TestPerformAtMenuFromSongSelect()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
@ -57,18 +57,18 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtSongSelectFromPlayerLoader()
|
public void TestPerformAtSongSelectFromPlayerLoader()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
||||||
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtMenuFromPlayerLoader()
|
public void TestPerformAtMenuFromPlayerLoader()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
||||||
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
|
@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitSongSelectWithEscape()
|
public void TestExitSongSelectWithEscape()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
pushEscape();
|
pushEscape();
|
||||||
@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestOpenModSelectOverlayUsingAction()
|
public void TestOpenModSelectOverlayUsingAction()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
Player player = null;
|
Player player = null;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestSongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestSongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestSongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
||||||
|
|
||||||
@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMenuMakesMusic()
|
public void TestMenuMakesMusic()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
|
||||||
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
||||||
|
|
||||||
@ -153,9 +153,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitSongSelectWithClick()
|
public void TestExitSongSelectWithClick()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||||
@ -213,9 +213,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestModSelectInput()
|
public void TestModSelectInput()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
|
|
||||||
@ -234,9 +234,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBeatmapOptionsInput()
|
public void TestBeatmapOptionsInput()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
||||||
|
|
||||||
@ -312,11 +312,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
ConfirmAtMainMenu();
|
ConfirmAtMainMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSongSelect : PlaySongSelect
|
public class TestPlaySongSelect : PlaySongSelect
|
||||||
{
|
{
|
||||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||||
|
|
||||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||||
|
|
||||||
|
protected override bool DisplayStableImportPrompt => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,82 +22,17 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
public class TestSceneAccuracyCircle : OsuTestScene
|
public class TestSceneAccuracyCircle : OsuTestScene
|
||||||
{
|
{
|
||||||
[Test]
|
[TestCase(0.2, ScoreRank.D)]
|
||||||
public void TestLowDRank()
|
[TestCase(0.5, ScoreRank.D)]
|
||||||
|
[TestCase(0.75, ScoreRank.C)]
|
||||||
|
[TestCase(0.85, ScoreRank.B)]
|
||||||
|
[TestCase(0.925, ScoreRank.A)]
|
||||||
|
[TestCase(0.975, ScoreRank.S)]
|
||||||
|
[TestCase(0.9999, ScoreRank.S)]
|
||||||
|
[TestCase(1, ScoreRank.X)]
|
||||||
|
public void TestRank(double accuracy, ScoreRank rank)
|
||||||
{
|
{
|
||||||
var score = createScore();
|
var score = createScore(accuracy, rank);
|
||||||
score.Accuracy = 0.2;
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.5;
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestCRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.75;
|
|
||||||
score.Rank = ScoreRank.C;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestBRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.85;
|
|
||||||
score.Rank = ScoreRank.B;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestARank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.925;
|
|
||||||
score.Rank = ScoreRank.A;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.975;
|
|
||||||
score.Rank = ScoreRank.S;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestAlmostSSRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.9999;
|
|
||||||
score.Rank = ScoreRank.S;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSSRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 1;
|
|
||||||
score.Rank = ScoreRank.X;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
addCircleStep(score);
|
||||||
}
|
}
|
||||||
@ -120,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new AccuracyCircle(score, true)
|
new AccuracyCircle(score)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -129,7 +64,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
private ScoreInfo createScore() => new ScoreInfo
|
private ScoreInfo createScore(double accuracy, ScoreRank rank) => new ScoreInfo
|
||||||
{
|
{
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
@ -139,9 +74,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
Accuracy = 0.95,
|
Accuracy = accuracy,
|
||||||
MaxCombo = 999,
|
MaxCombo = 999,
|
||||||
Rank = ScoreRank.S,
|
Rank = rank,
|
||||||
Date = DateTimeOffset.Now,
|
Date = DateTimeOffset.Now,
|
||||||
Statistics =
|
Statistics =
|
||||||
{
|
{
|
||||||
|
@ -29,13 +29,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneResultsScreen : OsuManualInputManagerTestScene
|
public class TestSceneResultsScreen : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private BeatmapManager beatmaps;
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(BeatmapManager beatmaps)
|
|
||||||
{
|
|
||||||
this.beatmaps = beatmaps;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
@ -46,10 +41,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
|
||||||
|
|
||||||
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestResultsWithoutPlayer()
|
public void TestResultsWithoutPlayer()
|
||||||
{
|
{
|
||||||
@ -69,12 +60,25 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(0.2, ScoreRank.D)]
|
||||||
public void TestResultsWithPlayer()
|
[TestCase(0.5, ScoreRank.D)]
|
||||||
|
[TestCase(0.75, ScoreRank.C)]
|
||||||
|
[TestCase(0.85, ScoreRank.B)]
|
||||||
|
[TestCase(0.925, ScoreRank.A)]
|
||||||
|
[TestCase(0.975, ScoreRank.S)]
|
||||||
|
[TestCase(0.9999, ScoreRank.S)]
|
||||||
|
[TestCase(1, ScoreRank.X)]
|
||||||
|
public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
|
||||||
{
|
{
|
||||||
TestResultsScreen screen = null;
|
TestResultsScreen screen = null;
|
||||||
|
|
||||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
Accuracy = accuracy,
|
||||||
|
Rank = rank
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score)));
|
||||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||||
}
|
}
|
||||||
@ -232,6 +236,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
private class TestResultsContainer : Container
|
private class TestResultsContainer : Container
|
||||||
{
|
{
|
||||||
[Cached(typeof(Player))]
|
[Cached(typeof(Player))]
|
||||||
|
@ -8,13 +8,13 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Legacy;
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
@ -38,8 +38,6 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
public readonly BindableList<BeatmapCollection> Collections = new BindableList<BeatmapCollection>();
|
public readonly BindableList<BeatmapCollection> Collections = new BindableList<BeatmapCollection>();
|
||||||
|
|
||||||
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
@ -96,25 +94,12 @@ namespace osu.Game.Collections
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<Notification> PostNotification { protected get; set; }
|
public Action<Notification> PostNotification { protected get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set a storage with access to an osu-stable install for import purposes.
|
|
||||||
/// </summary>
|
|
||||||
public Func<Storage> GetStableStorage { private get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task ImportFromStableAsync()
|
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||||
{
|
{
|
||||||
var stable = GetStableStorage?.Invoke();
|
if (!stableStorage.Exists(database_name))
|
||||||
|
|
||||||
if (stable == null)
|
|
||||||
{
|
|
||||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stable.Exists(database_name))
|
|
||||||
{
|
{
|
||||||
// This handles situations like when the user does not have a collections.db file
|
// This handles situations like when the user does not have a collections.db file
|
||||||
Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
||||||
@ -123,7 +108,7 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
return Task.Run(async () =>
|
return Task.Run(async () =>
|
||||||
{
|
{
|
||||||
using (var stream = stable.GetStream(database_name))
|
using (var stream = stableStorage.GetStream(database_name))
|
||||||
await Import(stream).ConfigureAwait(false);
|
await Import(stream).ConfigureAwait(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ using System.Threading.Tasks;
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -81,8 +80,6 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public virtual IEnumerable<string> HandledExtensions => new[] { ".zip" };
|
public virtual IEnumerable<string> HandledExtensions => new[] { ".zip" };
|
||||||
|
|
||||||
public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
|
||||||
|
|
||||||
protected readonly FileStore Files;
|
protected readonly FileStore Files;
|
||||||
|
|
||||||
protected readonly IDatabaseContextFactory ContextFactory;
|
protected readonly IDatabaseContextFactory ContextFactory;
|
||||||
@ -669,16 +666,6 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
#region osu-stable import
|
#region osu-stable import
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set a storage with access to an osu-stable install for import purposes.
|
|
||||||
/// </summary>
|
|
||||||
public Func<StableStorage> GetStableStorage { private get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Denotes whether an osu-stable installation is present to perform automated imports from.
|
|
||||||
/// </summary>
|
|
||||||
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The relative path from osu-stable's data directory to import items from.
|
/// The relative path from osu-stable's data directory to import items from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -700,22 +687,16 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task ImportFromStableAsync()
|
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||||
{
|
{
|
||||||
var stableStorage = GetStableStorage?.Invoke();
|
|
||||||
|
|
||||||
if (stableStorage == null)
|
|
||||||
{
|
|
||||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
var storage = PrepareStableStorage(stableStorage);
|
var storage = PrepareStableStorage(stableStorage);
|
||||||
|
|
||||||
|
// Handle situations like when the user does not have a Skins folder.
|
||||||
if (!storage.ExistsDirectory(ImportFromStablePath))
|
if (!storage.ExistsDirectory(ImportFromStablePath))
|
||||||
{
|
{
|
||||||
// This handles situations like when the user does not have a Skins folder
|
string fullPath = storage.GetFullPath(ImportFromStablePath);
|
||||||
Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
|
Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
96
osu.Game/Database/StableImportManager.cs
Normal file
96
osu.Game/Database/StableImportManager.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class StableImportManager : Component
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skins { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scores { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private CollectionManager collections { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private DesktopGameHost desktopGameHost { get; set; }
|
||||||
|
|
||||||
|
private StableStorage cachedStorage;
|
||||||
|
|
||||||
|
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||||
|
|
||||||
|
public async Task ImportFromStableAsync(StableContent content)
|
||||||
|
{
|
||||||
|
var stableStorage = await getStableStorage().ConfigureAwait(false);
|
||||||
|
var importTasks = new List<Task>();
|
||||||
|
|
||||||
|
Task beatmapImportTask = Task.CompletedTask;
|
||||||
|
if (content.HasFlagFast(StableContent.Beatmaps))
|
||||||
|
importTasks.Add(beatmapImportTask = beatmaps.ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
|
if (content.HasFlagFast(StableContent.Skins))
|
||||||
|
importTasks.Add(skins.ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
|
if (content.HasFlagFast(StableContent.Collections))
|
||||||
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
|
||||||
|
if (content.HasFlagFast(StableContent.Scores))
|
||||||
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
|
||||||
|
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<StableStorage> getStableStorage()
|
||||||
|
{
|
||||||
|
if (cachedStorage != null)
|
||||||
|
return cachedStorage;
|
||||||
|
|
||||||
|
var stableStorage = game.GetStorageForStableInstall();
|
||||||
|
if (stableStorage != null)
|
||||||
|
return cachedStorage = stableStorage;
|
||||||
|
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource)));
|
||||||
|
var stablePath = await taskCompletionSource.Task.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return cachedStorage = new StableStorage(stablePath, desktopGameHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum StableContent
|
||||||
|
{
|
||||||
|
Beatmaps = 1 << 0,
|
||||||
|
Scores = 1 << 1,
|
||||||
|
Skins = 1 << 2,
|
||||||
|
Collections = 1 << 3,
|
||||||
|
All = Beatmaps | Scores | Skins | Collections
|
||||||
|
}
|
||||||
|
}
|
@ -40,10 +40,10 @@ namespace osu.Game.Online.Spectator
|
|||||||
private readonly List<int> watchingUsers = new List<int>();
|
private readonly List<int> watchingUsers = new List<int>();
|
||||||
|
|
||||||
public IBindableList<int> PlayingUsers => playingUsers;
|
public IBindableList<int> PlayingUsers => playingUsers;
|
||||||
|
|
||||||
private readonly BindableList<int> playingUsers = new BindableList<int>();
|
private readonly BindableList<int> playingUsers = new BindableList<int>();
|
||||||
|
|
||||||
private readonly Dictionary<int, SpectatorState> playingUserStates = new Dictionary<int, SpectatorState>();
|
public IBindableDictionary<int, SpectatorState> PlayingUserStates => playingUserStates;
|
||||||
|
private readonly BindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
|
||||||
private IBeatmap? currentBeatmap;
|
private IBeatmap? currentBeatmap;
|
||||||
|
|
||||||
@ -200,6 +200,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
watchingUsers.Remove(userId);
|
watchingUsers.Remove(userId);
|
||||||
|
playingUserStates.Remove(userId);
|
||||||
StopWatchingUserInternal(userId);
|
StopWatchingUserInternal(userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -256,33 +257,5 @@ namespace osu.Game.Online.Spectator
|
|||||||
|
|
||||||
lastSendTime = Time.Current;
|
lastSendTime = Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to retrieve the <see cref="SpectatorState"/> for a currently-playing user.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="userId">The user.</param>
|
|
||||||
/// <param name="state">The current <see cref="SpectatorState"/> for the user, if they're playing. <c>null</c> if the user is not playing.</param>
|
|
||||||
/// <returns><c>true</c> if successful (the user is playing), <c>false</c> otherwise.</returns>
|
|
||||||
public bool TryGetPlayingUserState(int userId, out SpectatorState state)
|
|
||||||
{
|
|
||||||
return playingUserStates.TryGetValue(userId, out state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bind an action to <see cref="OnUserBeganPlaying"/> with the option of running the bound action once immediately.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="callback">The action to perform when a user begins playing.</param>
|
|
||||||
/// <param name="runOnceImmediately">Whether the action provided in <paramref name="callback"/> should be run once immediately for all users currently playing.</param>
|
|
||||||
public void BindUserBeganPlaying(Action<int, SpectatorState> callback, bool runOnceImmediately = false)
|
|
||||||
{
|
|
||||||
// The lock is taken before the event is subscribed to to prevent doubling of events.
|
|
||||||
OnUserBeganPlaying += callback;
|
|
||||||
|
|
||||||
if (!runOnceImmediately)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (userId, state) in playingUserStates)
|
|
||||||
callback(userId, state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,9 @@ namespace osu.Game
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly StableImportManager stableImportManager = new StableImportManager();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
||||||
|
|
||||||
@ -573,14 +576,11 @@ namespace osu.Game
|
|||||||
|
|
||||||
// todo: all archive managers should be able to be looped here.
|
// todo: all archive managers should be able to be looped here.
|
||||||
SkinManager.PostNotification = n => notifications.Post(n);
|
SkinManager.PostNotification = n => notifications.Post(n);
|
||||||
SkinManager.GetStableStorage = GetStorageForStableInstall;
|
|
||||||
|
|
||||||
BeatmapManager.PostNotification = n => notifications.Post(n);
|
BeatmapManager.PostNotification = n => notifications.Post(n);
|
||||||
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
|
||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => notifications.Post(n);
|
ScoreManager.PostNotification = n => notifications.Post(n);
|
||||||
ScoreManager.GetStableStorage = GetStorageForStableInstall;
|
|
||||||
ScoreManager.PresentImport = items => PresentScore(items.First());
|
ScoreManager.PresentImport = items => PresentScore(items.First());
|
||||||
|
|
||||||
// make config aware of how to lookup skins for on-screen display purposes.
|
// make config aware of how to lookup skins for on-screen display purposes.
|
||||||
@ -697,10 +697,10 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(new CollectionManager(Storage)
|
loadComponentSingleFile(new CollectionManager(Storage)
|
||||||
{
|
{
|
||||||
PostNotification = n => notifications.Post(n),
|
PostNotification = n => notifications.Post(n),
|
||||||
GetStableStorage = GetStorageForStableInstall
|
|
||||||
}, Add, true);
|
}, Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(difficultyRecommender, Add);
|
loadComponentSingleFile(difficultyRecommender, Add);
|
||||||
|
loadComponentSingleFile(stableImportManager, Add);
|
||||||
|
|
||||||
loadComponentSingleFile(screenshotManager, Add);
|
loadComponentSingleFile(screenshotManager, Add);
|
||||||
|
|
||||||
|
@ -96,7 +96,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774");
|
Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774");
|
||||||
Waves.FourthWaveColour = Color4Extensions.FromHex(@"003a4e");
|
Waves.FourthWaveColour = Color4Extensions.FromHex(@"003a4e");
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = HEIGHT;
|
||||||
|
|
||||||
Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING };
|
Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING };
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ using osuTK;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
{
|
{
|
||||||
@ -69,20 +69,24 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.Relative, 0.8f),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension(),
|
|
||||||
},
|
},
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new OsuTextFlowContainer(cp =>
|
||||||
{
|
{
|
||||||
Text = HeaderText,
|
cp.Font = OsuFont.Default.With(size: 24);
|
||||||
Font = OsuFont.Default.With(size: 40),
|
})
|
||||||
Origin = Anchor.Centre,
|
{
|
||||||
Anchor = Anchor.Centre,
|
Text = HeaderText.ToString(),
|
||||||
|
TextAnchor = Anchor.TopCentre,
|
||||||
|
Margin = new MarginPadding(10),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
@ -99,6 +103,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Width = 300,
|
Width = 300,
|
||||||
|
Margin = new MarginPadding(10),
|
||||||
Text = "Select directory",
|
Text = "Select directory",
|
||||||
Action = () => OnSelection(directorySelector.CurrentPath.Value)
|
Action = () => OnSelection(directorySelector.CurrentPath.Value)
|
||||||
},
|
},
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -29,9 +30,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
private TriangleButton undeleteButton;
|
private TriangleButton undeleteButton;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, DialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] StableImportManager stableImportManager, DialogOverlay dialogOverlay)
|
||||||
{
|
{
|
||||||
if (beatmaps.SupportsImportFromStable)
|
if (stableImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importBeatmapsButton = new SettingsButton
|
Add(importBeatmapsButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importBeatmapsButton.Enabled.Value = false;
|
importBeatmapsButton.Enabled.Value = false;
|
||||||
beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true));
|
stableImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (scores.SupportsImportFromStable)
|
if (stableImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importScoresButton = new SettingsButton
|
Add(importScoresButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -65,7 +66,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importScoresButton.Enabled.Value = false;
|
importScoresButton.Enabled.Value = false;
|
||||||
scores.ImportFromStableAsync().ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true));
|
stableImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -83,7 +84,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (skins.SupportsImportFromStable)
|
if (stableImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importSkinsButton = new SettingsButton
|
Add(importSkinsButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -91,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importSkinsButton.Enabled.Value = false;
|
importSkinsButton.Enabled.Value = false;
|
||||||
skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true));
|
stableImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -111,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
|
|
||||||
if (collectionManager != null)
|
if (collectionManager != null)
|
||||||
{
|
{
|
||||||
if (collectionManager.SupportsImportFromStable)
|
if (stableImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importCollectionsButton = new SettingsButton
|
Add(importCollectionsButton = new SettingsButton
|
||||||
{
|
{
|
||||||
@ -119,7 +120,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
importCollectionsButton.Enabled.Value = false;
|
importCollectionsButton.Enabled.Value = false;
|
||||||
collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true));
|
stableImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
|
{
|
||||||
|
public class StableDirectoryLocationDialog : PopupDialog
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
public StableDirectoryLocationDialog(TaskCompletionSource<string> taskCompletionSource)
|
||||||
|
{
|
||||||
|
HeaderText = "Failed to automatically locate an osu!stable installation.";
|
||||||
|
BodyText = "An existing install could not be located. If you know where it is, you can help locate it.";
|
||||||
|
Icon = FontAwesome.Solid.QuestionCircle;
|
||||||
|
|
||||||
|
Buttons = new PopupDialogButton[]
|
||||||
|
{
|
||||||
|
new PopupDialogOkButton
|
||||||
|
{
|
||||||
|
Text = "Sure! I know where it is located!",
|
||||||
|
Action = () => Schedule(() => game.PerformFromScreen(screen => screen.Push(new StableDirectorySelectScreen(taskCompletionSource))))
|
||||||
|
},
|
||||||
|
new PopupDialogCancelButton
|
||||||
|
{
|
||||||
|
Text = "Actually I don't have osu!stable installed.",
|
||||||
|
Action = () => taskCompletionSource.TrySetCanceled()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
|
{
|
||||||
|
public class StableDirectorySelectScreen : DirectorySelectScreen
|
||||||
|
{
|
||||||
|
private readonly TaskCompletionSource<string> taskCompletionSource;
|
||||||
|
|
||||||
|
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled;
|
||||||
|
|
||||||
|
protected override bool IsValidDirectory(DirectoryInfo info) => info?.GetFiles("osu!.*.cfg").Any() ?? false;
|
||||||
|
|
||||||
|
public override LocalisableString HeaderText => "Please select your osu!stable install location";
|
||||||
|
|
||||||
|
public StableDirectorySelectScreen(TaskCompletionSource<string> taskCompletionSource)
|
||||||
|
{
|
||||||
|
this.taskCompletionSource = taskCompletionSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSelection(DirectoryInfo directory)
|
||||||
|
{
|
||||||
|
taskCompletionSource.TrySetResult(directory.FullName);
|
||||||
|
this.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnExiting(IScreen next)
|
||||||
|
{
|
||||||
|
taskCompletionSource.TrySetCanceled();
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
@ -27,13 +26,14 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool HasEntryApplied { get; private set; }
|
protected bool HasEntryApplied { get; private set; }
|
||||||
|
|
||||||
|
// Drawable's lifetime gets out of sync with entry's lifetime if entry's lifetime is modified.
|
||||||
|
// We cannot delegate getter to `Entry.LifetimeStart` because it is incompatible with `LifetimeManagementContainer` due to how lifetime change is detected.
|
||||||
public override double LifetimeStart
|
public override double LifetimeStart
|
||||||
{
|
{
|
||||||
get => Entry?.LifetimeStart ?? double.MinValue;
|
get => base.LifetimeStart;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (Entry == null && LifetimeStart != value)
|
base.LifetimeStart = value;
|
||||||
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
|
||||||
|
|
||||||
if (Entry != null)
|
if (Entry != null)
|
||||||
Entry.LifetimeStart = value;
|
Entry.LifetimeStart = value;
|
||||||
@ -42,11 +42,10 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
|
|
||||||
public override double LifetimeEnd
|
public override double LifetimeEnd
|
||||||
{
|
{
|
||||||
get => Entry?.LifetimeEnd ?? double.MaxValue;
|
get => base.LifetimeEnd;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (Entry == null && LifetimeEnd != value)
|
base.LifetimeEnd = value;
|
||||||
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
|
||||||
|
|
||||||
if (Entry != null)
|
if (Entry != null)
|
||||||
Entry.LifetimeEnd = value;
|
Entry.LifetimeEnd = value;
|
||||||
@ -80,7 +79,12 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
free();
|
free();
|
||||||
|
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
||||||
|
base.LifetimeStart = entry.LifetimeStart;
|
||||||
|
base.LifetimeEnd = entry.LifetimeEnd;
|
||||||
|
|
||||||
OnApply(entry);
|
OnApply(entry);
|
||||||
|
|
||||||
HasEntryApplied = true;
|
HasEntryApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +116,11 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
Debug.Assert(Entry != null && HasEntryApplied);
|
Debug.Assert(Entry != null && HasEntryApplied);
|
||||||
|
|
||||||
OnFree(Entry);
|
OnFree(Entry);
|
||||||
|
|
||||||
Entry = null;
|
Entry = null;
|
||||||
|
base.LifetimeStart = double.MinValue;
|
||||||
|
base.LifetimeEnd = double.MaxValue;
|
||||||
|
|
||||||
HasEntryApplied = false;
|
HasEntryApplied = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Depth = float.MinValue,
|
Depth = float.MinValue,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 0.5f,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
|
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
|
||||||
Child = userModsSelectOverlay = new UserModSelectOverlay
|
Child = userModsSelectOverlay = new UserModSelectOverlay
|
||||||
{
|
{
|
||||||
|
@ -10,11 +10,9 @@ 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.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||||
@ -76,19 +74,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
|
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
|
|
||||||
private readonly bool withFlair;
|
|
||||||
|
|
||||||
private SmoothCircularProgress accuracyCircle;
|
private SmoothCircularProgress accuracyCircle;
|
||||||
private SmoothCircularProgress innerMask;
|
private SmoothCircularProgress innerMask;
|
||||||
private Container<RankBadge> badges;
|
private Container<RankBadge> badges;
|
||||||
private RankText rankText;
|
private RankText rankText;
|
||||||
|
|
||||||
private SkinnableSound applauseSound;
|
public AccuracyCircle(ScoreInfo score)
|
||||||
|
|
||||||
public AccuracyCircle(ScoreInfo score, bool withFlair)
|
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
this.withFlair = withFlair;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -211,13 +204,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
},
|
},
|
||||||
rankText = new RankText(score.Rank)
|
rankText = new RankText(score.Rank)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (withFlair)
|
|
||||||
{
|
|
||||||
AddInternal(applauseSound = score.Rank >= ScoreRank.A
|
|
||||||
? new SkinnableSound(new SampleInfo("Results/rankpass", "applause"))
|
|
||||||
: new SkinnableSound(new SampleInfo("Results/rankfail")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreRank getRank(ScoreRank rank)
|
private ScoreRank getRank(ScoreRank rank)
|
||||||
@ -256,7 +242,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
|
|
||||||
using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
|
using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
|
||||||
{
|
{
|
||||||
this.Delay(-1440).Schedule(() => applauseSound?.Play());
|
|
||||||
rankText.Appear();
|
rankText.Appear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
Margin = new MarginPadding { Top = 40 },
|
Margin = new MarginPadding { Top = 40 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 230,
|
Height = 230,
|
||||||
Child = new AccuracyCircle(score, withFlair)
|
Child = new AccuracyCircle(score)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -19,13 +20,20 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking
|
namespace osu.Game.Screens.Ranking
|
||||||
{
|
{
|
||||||
public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>
|
public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delay before the default applause sound should be played, in order to match the grade display timing in <see cref="AccuracyCircle"/>.
|
||||||
|
/// </summary>
|
||||||
|
public const double APPLAUSE_DELAY = AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY + ScorePanel.RESIZE_DURATION + ScorePanel.TOP_LAYER_EXPAND_DELAY - 1440;
|
||||||
|
|
||||||
protected const float BACKGROUND_BLUR = 20;
|
protected const float BACKGROUND_BLUR = 20;
|
||||||
private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y;
|
private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y;
|
||||||
|
|
||||||
@ -56,6 +64,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
private readonly bool allowRetry;
|
private readonly bool allowRetry;
|
||||||
private readonly bool allowWatchingReplay;
|
private readonly bool allowWatchingReplay;
|
||||||
|
|
||||||
|
private SkinnableSound applauseSound;
|
||||||
|
|
||||||
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
||||||
{
|
{
|
||||||
Score = score;
|
Score = score;
|
||||||
@ -146,6 +156,13 @@ namespace osu.Game.Screens.Ranking
|
|||||||
bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay);
|
bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay);
|
||||||
|
|
||||||
ScorePanelList.AddScore(Score, shouldFlair);
|
ScorePanelList.AddScore(Score, shouldFlair);
|
||||||
|
|
||||||
|
if (shouldFlair)
|
||||||
|
{
|
||||||
|
AddInternal(applauseSound = Score.Rank >= ScoreRank.A
|
||||||
|
? new SkinnableSound(new SampleInfo("Results/rankpass", "applause"))
|
||||||
|
: new SkinnableSound(new SampleInfo("Results/rankfail")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowWatchingReplay)
|
if (allowWatchingReplay)
|
||||||
@ -183,6 +200,9 @@ namespace osu.Game.Screens.Ranking
|
|||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
|
|
||||||
statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true);
|
statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(APPLAUSE_DELAY))
|
||||||
|
Schedule(() => applauseSound?.Play());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -54,12 +54,12 @@ namespace osu.Game.Screens.Ranking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duration for the panel to resize into its expanded/contracted size.
|
/// Duration for the panel to resize into its expanded/contracted size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const double resize_duration = 200;
|
public const double RESIZE_DURATION = 200;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delay after <see cref="resize_duration"/> before the top layer is expanded.
|
/// Delay after <see cref="RESIZE_DURATION"/> before the top layer is expanded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const double top_layer_expand_delay = 100;
|
public const double TOP_LAYER_EXPAND_DELAY = 100;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duration for the top layer expansion.
|
/// Duration for the top layer expansion.
|
||||||
@ -208,8 +208,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
case PanelState.Expanded:
|
case PanelState.Expanded:
|
||||||
Size = new Vector2(EXPANDED_WIDTH, expanded_height);
|
Size = new Vector2(EXPANDED_WIDTH, expanded_height);
|
||||||
|
|
||||||
topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint);
|
topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||||
middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint);
|
middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0));
|
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0));
|
||||||
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0));
|
middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0));
|
||||||
@ -221,20 +221,20 @@ namespace osu.Game.Screens.Ranking
|
|||||||
case PanelState.Contracted:
|
case PanelState.Contracted:
|
||||||
Size = new Vector2(CONTRACTED_WIDTH, contracted_height);
|
Size = new Vector2(CONTRACTED_WIDTH, contracted_height);
|
||||||
|
|
||||||
topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint);
|
topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||||
middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint);
|
middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0));
|
topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0));
|
||||||
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0));
|
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
content.ResizeTo(Size, resize_duration, Easing.OutQuint);
|
content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
bool topLayerExpanded = topLayerContainer.Y < 0;
|
bool topLayerExpanded = topLayerContainer.Y < 0;
|
||||||
|
|
||||||
// If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state.
|
// If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state.
|
||||||
using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true))
|
using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY, true))
|
||||||
{
|
{
|
||||||
topLayerContainer.FadeIn();
|
topLayerContainer.FadeIn();
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public ImportFromStablePopup(Action importFromStable)
|
public ImportFromStablePopup(Action importFromStable)
|
||||||
{
|
{
|
||||||
HeaderText = @"You have no beatmaps!";
|
HeaderText = @"You have no beatmaps!";
|
||||||
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins, collections and scores?\nThis will create a second copy of all files on disk.";
|
BodyText = "Would you like to import your beatmaps, skins, collections and scores from an existing osu!stable installation?\nThis will create a second copy of all files on disk.";
|
||||||
|
|
||||||
Icon = FontAwesome.Solid.Plane;
|
Icon = FontAwesome.Solid.Plane;
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Select.Options;
|
using osu.Game.Screens.Select.Options;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -35,9 +34,9 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Scoring;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
@ -52,6 +51,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected virtual bool ShowFooter => true;
|
protected virtual bool ShowFooter => true;
|
||||||
|
|
||||||
|
protected virtual bool DisplayStableImportPrompt => stableImportManager?.SupportsImportFromStable == true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be null if <see cref="ShowFooter"/> is false.
|
/// Can be null if <see cref="ShowFooter"/> is false.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -84,6 +85,9 @@ namespace osu.Game.Screens.Select
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private StableImportManager stableImportManager { get; set; }
|
||||||
|
|
||||||
protected ModSelectOverlay ModSelect { get; private set; }
|
protected ModSelectOverlay ModSelect { get; private set; }
|
||||||
|
|
||||||
protected Sample SampleConfirm { get; private set; }
|
protected Sample SampleConfirm { get; private set; }
|
||||||
@ -101,7 +105,7 @@ namespace osu.Game.Screens.Select
|
|||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores, CollectionManager collections, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
|
private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
|
||||||
{
|
{
|
||||||
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
|
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
|
||||||
transferRulesetValue();
|
transferRulesetValue();
|
||||||
@ -282,18 +286,12 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
// if we have no beatmaps but osu-stable is found, let's prompt the user to import.
|
// if we have no beatmaps, let's prompt the user to import from over a stable install if he has one.
|
||||||
if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && beatmaps.StableInstallationAvailable)
|
if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && DisplayStableImportPrompt)
|
||||||
{
|
{
|
||||||
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
||||||
{
|
{
|
||||||
Task.Run(beatmaps.ImportFromStableAsync)
|
Task.Run(() => stableImportManager.ImportFromStableAsync(StableContent.All));
|
||||||
.ContinueWith(_ =>
|
|
||||||
{
|
|
||||||
Task.Run(scores.ImportFromStableAsync);
|
|
||||||
Task.Run(collections.ImportFromStableAsync);
|
|
||||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
|
||||||
Task.Run(skins.ImportFromStableAsync);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.Spectate
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache userLookupCache { get; set; }
|
private UserLookupCache userLookupCache { get; set; }
|
||||||
|
|
||||||
|
private readonly IBindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
|
||||||
private readonly Dictionary<int, User> userMap = new Dictionary<int, User>();
|
private readonly Dictionary<int, User> userMap = new Dictionary<int, User>();
|
||||||
private readonly Dictionary<int, GameplayState> gameplayStates = new Dictionary<int, GameplayState>();
|
private readonly Dictionary<int, GameplayState> gameplayStates = new Dictionary<int, GameplayState>();
|
||||||
|
|
||||||
@ -65,8 +68,9 @@ namespace osu.Game.Screens.Spectate
|
|||||||
foreach (var u in users.Result)
|
foreach (var u in users.Result)
|
||||||
userMap[u.Id] = u;
|
userMap[u.Id] = u;
|
||||||
|
|
||||||
spectatorClient.BindUserBeganPlaying(userBeganPlaying, true);
|
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
|
||||||
spectatorClient.OnUserFinishedPlaying += userFinishedPlaying;
|
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
|
||||||
|
|
||||||
spectatorClient.OnNewFrames += userSentFrames;
|
spectatorClient.OnNewFrames += userSentFrames;
|
||||||
|
|
||||||
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
||||||
@ -102,7 +106,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
|
|
||||||
foreach (var (userId, _) in userMap)
|
foreach (var (userId, _) in userMap)
|
||||||
{
|
{
|
||||||
if (!spectatorClient.TryGetPlayingUserState(userId, out var userState))
|
if (!playingUserStates.TryGetValue(userId, out var userState))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID))
|
if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID))
|
||||||
@ -110,7 +114,31 @@ namespace osu.Game.Screens.Spectate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void userBeganPlaying(int userId, SpectatorState state)
|
private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyDictionaryChangedAction.Add:
|
||||||
|
foreach (var (userId, state) in e.NewItems.AsNonNull())
|
||||||
|
onUserStateAdded(userId, state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyDictionaryChangedAction.Remove:
|
||||||
|
foreach (var (userId, _) in e.OldItems.AsNonNull())
|
||||||
|
onUserStateRemoved(userId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyDictionaryChangedAction.Replace:
|
||||||
|
foreach (var (userId, _) in e.OldItems.AsNonNull())
|
||||||
|
onUserStateRemoved(userId);
|
||||||
|
|
||||||
|
foreach (var (userId, state) in e.NewItems.AsNonNull())
|
||||||
|
onUserStateAdded(userId, state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserStateAdded(int userId, SpectatorState state)
|
||||||
{
|
{
|
||||||
if (state.RulesetID == null || state.BeatmapID == null)
|
if (state.RulesetID == null || state.BeatmapID == null)
|
||||||
return;
|
return;
|
||||||
@ -118,24 +146,30 @@ namespace osu.Game.Screens.Spectate
|
|||||||
if (!userMap.ContainsKey(userId))
|
if (!userMap.ContainsKey(userId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// The user may have stopped playing.
|
Schedule(() => OnUserStateChanged(userId, state));
|
||||||
if (!spectatorClient.TryGetPlayingUserState(userId, out _))
|
updateGameplayState(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserStateRemoved(int userId)
|
||||||
|
{
|
||||||
|
if (!userMap.ContainsKey(userId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Schedule(() => OnUserStateChanged(userId, state));
|
if (!gameplayStates.TryGetValue(userId, out var gameplayState))
|
||||||
|
return;
|
||||||
|
|
||||||
updateGameplayState(userId);
|
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
||||||
|
|
||||||
|
gameplayStates.Remove(userId);
|
||||||
|
Schedule(() => EndGameplay(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGameplayState(int userId)
|
private void updateGameplayState(int userId)
|
||||||
{
|
{
|
||||||
Debug.Assert(userMap.ContainsKey(userId));
|
Debug.Assert(userMap.ContainsKey(userId));
|
||||||
|
|
||||||
// The user may have stopped playing.
|
|
||||||
if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var user = userMap[userId];
|
var user = userMap[userId];
|
||||||
|
var spectatorState = playingUserStates[userId];
|
||||||
|
|
||||||
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance();
|
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance();
|
||||||
if (resolvedRuleset == null)
|
if (resolvedRuleset == null)
|
||||||
@ -186,20 +220,6 @@ namespace osu.Game.Screens.Spectate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void userFinishedPlaying(int userId, SpectatorState state)
|
|
||||||
{
|
|
||||||
if (!userMap.ContainsKey(userId))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!gameplayStates.TryGetValue(userId, out var gameplayState))
|
|
||||||
return;
|
|
||||||
|
|
||||||
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
|
||||||
|
|
||||||
gameplayStates.Remove(userId);
|
|
||||||
Schedule(() => EndGameplay(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a spectated user's state has changed.
|
/// Invoked when a spectated user's state has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -226,7 +246,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
/// <param name="userId">The user to stop spectating.</param>
|
/// <param name="userId">The user to stop spectating.</param>
|
||||||
protected void RemoveUser(int userId)
|
protected void RemoveUser(int userId)
|
||||||
{
|
{
|
||||||
userFinishedPlaying(userId, null);
|
onUserStateRemoved(userId);
|
||||||
|
|
||||||
userIds.Remove(userId);
|
userIds.Remove(userId);
|
||||||
userMap.Remove(userId);
|
userMap.Remove(userId);
|
||||||
@ -240,8 +260,6 @@ namespace osu.Game.Screens.Spectate
|
|||||||
|
|
||||||
if (spectatorClient != null)
|
if (spectatorClient != null)
|
||||||
{
|
{
|
||||||
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
|
|
||||||
spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying;
|
|
||||||
spectatorClient.OnNewFrames -= userSentFrames;
|
spectatorClient.OnNewFrames -= userSentFrames;
|
||||||
|
|
||||||
foreach (var (userId, _) in userMap)
|
foreach (var (userId, _) in userMap)
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.513.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.521.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.3.4" />
|
<PackageReference Include="Sentry" Version="3.3.4" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.513.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.521.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<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="2021.513.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.521.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user