mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 15:12:57 +08:00
Merge branch 'master' into refactor-selection-rotate-logic
This commit is contained in:
commit
34a5b22b79
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
</Project>
|
||||
|
@ -14,10 +14,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
{
|
||||
private readonly HitPiece piece;
|
||||
|
||||
private static Hit hit;
|
||||
public new Hit HitObject => (Hit)base.HitObject;
|
||||
|
||||
public HitPlacementBlueprint()
|
||||
: base(hit = new Hit())
|
||||
: base(new Hit())
|
||||
{
|
||||
InternalChild = piece = new HitPiece
|
||||
{
|
||||
@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
switch (e.Button)
|
||||
{
|
||||
case MouseButton.Left:
|
||||
hit.Type = HitType.Centre;
|
||||
HitObject.Type = HitType.Centre;
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
|
||||
case MouseButton.Right:
|
||||
hit.Type = HitType.Rim;
|
||||
HitObject.Type = HitType.Rim;
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
@ -52,23 +52,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override void OnApply()
|
||||
{
|
||||
type.BindTo(HitObject.TypeBindable);
|
||||
type.BindValueChanged(_ =>
|
||||
{
|
||||
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();
|
||||
// this doesn't need to be run inline as RecreatePieces is called by the base call below.
|
||||
type.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces));
|
||||
|
||||
base.OnApply();
|
||||
}
|
||||
|
||||
protected override void RecreatePieces()
|
||||
{
|
||||
updateActionsFromType();
|
||||
base.RecreatePieces();
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
@ -83,33 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
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()
|
||||
{
|
||||
HitActions =
|
||||
|
@ -137,7 +137,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE);
|
||||
|
||||
MainPiece?.Expire();
|
||||
if (MainPiece != null)
|
||||
Content.Remove(MainPiece);
|
||||
|
||||
Content.Add(MainPiece = CreateMainPiece());
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
@ -29,14 +27,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override void OnApply()
|
||||
{
|
||||
isStrong.BindTo(HitObject.IsStrongBindable);
|
||||
isStrong.BindValueChanged(_ =>
|
||||
{
|
||||
// will overwrite samples, should only be called on subsequent changes
|
||||
// after the initial application.
|
||||
updateSamplesFromStrong();
|
||||
|
||||
RecreatePieces();
|
||||
});
|
||||
// this doesn't need to be run inline as RecreatePieces is called by the base call below.
|
||||
isStrong.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces));
|
||||
|
||||
base.OnApply();
|
||||
}
|
||||
@ -50,30 +42,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
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()
|
||||
{
|
||||
base.RecreatePieces();
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Audio;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
@ -15,9 +17,36 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public HitType Type
|
||||
{
|
||||
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 };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
|
@ -1,8 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
@ -31,9 +33,31 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public bool IsStrong
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK.Input;
|
||||
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
@ -37,17 +37,17 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
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("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerformAtMenuFromSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => new PlaySongSelect());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||
@ -57,18 +57,18 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestPerformAtSongSelectFromPlayerLoader()
|
||||
{
|
||||
PushAndConfirm(() => new PlaySongSelect());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
||||
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||
AddAssert("did perform", () => actionPerformed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerformAtMenuFromPlayerLoader()
|
||||
{
|
||||
PushAndConfirm(() => new PlaySongSelect());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
||||
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||
|
@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitSongSelectWithEscape()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
TestPlaySongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
pushEscape();
|
||||
@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
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));
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
Player player = null;
|
||||
|
||||
PushAndConfirm(() => new TestSongSelect());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||
|
||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
PushAndConfirm(() => new TestSongSelect());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||
|
||||
@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
PushAndConfirm(() => new TestSongSelect());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
||||
|
||||
@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
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);
|
||||
|
||||
@ -153,9 +153,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitSongSelectWithClick()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
TestPlaySongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||
@ -213,9 +213,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestModSelectInput()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
TestPlaySongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
|
||||
@ -234,9 +234,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestBeatmapOptionsInput()
|
||||
{
|
||||
TestSongSelect songSelect = null;
|
||||
TestPlaySongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
|
||||
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
||||
|
||||
@ -312,11 +312,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
ConfirmAtMainMenu();
|
||||
}
|
||||
|
||||
private class TestSongSelect : PlaySongSelect
|
||||
public class TestPlaySongSelect : PlaySongSelect
|
||||
{
|
||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||
|
||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||
|
||||
protected override bool DisplayStableImportPrompt => false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,82 +22,17 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public class TestSceneAccuracyCircle : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestLowDRank()
|
||||
[TestCase(0.2, ScoreRank.D)]
|
||||
[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();
|
||||
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;
|
||||
var score = createScore(accuracy, rank);
|
||||
|
||||
addCircleStep(score);
|
||||
}
|
||||
@ -120,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
}
|
||||
}
|
||||
},
|
||||
new AccuracyCircle(score, true)
|
||||
new AccuracyCircle(score)
|
||||
{
|
||||
Anchor = 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
|
||||
{
|
||||
@ -139,9 +74,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
Accuracy = 0.95,
|
||||
Accuracy = accuracy,
|
||||
MaxCombo = 999,
|
||||
Rank = ScoreRank.S,
|
||||
Rank = rank,
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics =
|
||||
{
|
||||
|
@ -29,13 +29,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[TestFixture]
|
||||
public class TestSceneResultsScreen : OsuManualInputManagerTestScene
|
||||
{
|
||||
private BeatmapManager beatmaps;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapManager beatmaps)
|
||||
{
|
||||
this.beatmaps = beatmaps;
|
||||
}
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
@ -46,10 +41,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
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]
|
||||
public void TestResultsWithoutPlayer()
|
||||
{
|
||||
@ -69,12 +60,25 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResultsWithPlayer()
|
||||
[TestCase(0.2, ScoreRank.D)]
|
||||
[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;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
[Cached(typeof(Player))]
|
||||
|
@ -8,13 +8,13 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
@ -38,8 +38,6 @@ namespace osu.Game.Collections
|
||||
|
||||
public readonly BindableList<BeatmapCollection> Collections = new BindableList<BeatmapCollection>();
|
||||
|
||||
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||
|
||||
[Resolved]
|
||||
private GameHost host { get; set; }
|
||||
|
||||
@ -96,25 +94,12 @@ namespace osu.Game.Collections
|
||||
/// </summary>
|
||||
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>
|
||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||
/// </summary>
|
||||
public Task ImportFromStableAsync()
|
||||
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||
{
|
||||
var stable = GetStableStorage?.Invoke();
|
||||
|
||||
if (stable == null)
|
||||
{
|
||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!stable.Exists(database_name))
|
||||
if (!stableStorage.Exists(database_name))
|
||||
{
|
||||
// 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);
|
||||
@ -123,7 +108,7 @@ namespace osu.Game.Collections
|
||||
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
using (var stream = stable.GetStream(database_name))
|
||||
using (var stream = stableStorage.GetStream(database_name))
|
||||
await Import(stream).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
@ -81,8 +80,6 @@ namespace osu.Game.Database
|
||||
|
||||
public virtual IEnumerable<string> HandledExtensions => new[] { ".zip" };
|
||||
|
||||
public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||
|
||||
protected readonly FileStore Files;
|
||||
|
||||
protected readonly IDatabaseContextFactory ContextFactory;
|
||||
@ -669,16 +666,6 @@ namespace osu.Game.Database
|
||||
|
||||
#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>
|
||||
/// The relative path from osu-stable's data directory to import items from.
|
||||
/// </summary>
|
||||
@ -700,22 +687,16 @@ namespace osu.Game.Database
|
||||
/// <summary>
|
||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||
/// </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);
|
||||
|
||||
// Handle situations like when the user does not have a Skins folder.
|
||||
if (!storage.ExistsDirectory(ImportFromStablePath))
|
||||
{
|
||||
// This handles situations like when the user does not have a Skins folder
|
||||
Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
||||
string fullPath = storage.GetFullPath(ImportFromStablePath);
|
||||
|
||||
Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
|
||||
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>();
|
||||
|
||||
public IBindableList<int> PlayingUsers => playingUsers;
|
||||
|
||||
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;
|
||||
|
||||
@ -200,6 +200,7 @@ namespace osu.Game.Online.Spectator
|
||||
Schedule(() =>
|
||||
{
|
||||
watchingUsers.Remove(userId);
|
||||
playingUserStates.Remove(userId);
|
||||
StopWatchingUserInternal(userId);
|
||||
});
|
||||
}
|
||||
@ -256,33 +257,5 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,9 @@ namespace osu.Game
|
||||
[Cached]
|
||||
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
|
||||
|
||||
[Cached]
|
||||
private readonly StableImportManager stableImportManager = new StableImportManager();
|
||||
|
||||
[Cached]
|
||||
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
||||
|
||||
@ -566,14 +569,11 @@ namespace osu.Game
|
||||
|
||||
// todo: all archive managers should be able to be looped here.
|
||||
SkinManager.PostNotification = n => notifications.Post(n);
|
||||
SkinManager.GetStableStorage = GetStorageForStableInstall;
|
||||
|
||||
BeatmapManager.PostNotification = n => notifications.Post(n);
|
||||
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
||||
|
||||
ScoreManager.PostNotification = n => notifications.Post(n);
|
||||
ScoreManager.GetStableStorage = GetStorageForStableInstall;
|
||||
ScoreManager.PresentImport = items => PresentScore(items.First());
|
||||
|
||||
// make config aware of how to lookup skins for on-screen display purposes.
|
||||
@ -690,10 +690,10 @@ namespace osu.Game
|
||||
loadComponentSingleFile(new CollectionManager(Storage)
|
||||
{
|
||||
PostNotification = n => notifications.Post(n),
|
||||
GetStableStorage = GetStorageForStableInstall
|
||||
}, Add, true);
|
||||
|
||||
loadComponentSingleFile(difficultyRecommender, Add);
|
||||
loadComponentSingleFile(stableImportManager, Add);
|
||||
|
||||
loadComponentSingleFile(screenshotManager, Add);
|
||||
|
||||
|
@ -96,7 +96,8 @@ namespace osu.Game.Overlays.Mods
|
||||
Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774");
|
||||
Waves.FourthWaveColour = Color4Extensions.FromHex(@"003a4e");
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = HEIGHT;
|
||||
|
||||
Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING };
|
||||
|
||||
|
@ -11,9 +11,9 @@ using osuTK;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
@ -69,20 +69,24 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Relative, 0.8f),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
new OsuTextFlowContainer(cp =>
|
||||
{
|
||||
Text = HeaderText,
|
||||
Font = OsuFont.Default.With(size: 40),
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
cp.Font = OsuFont.Default.With(size: 24);
|
||||
})
|
||||
{
|
||||
Text = HeaderText.ToString(),
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
Margin = new MarginPadding(10),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
@ -99,6 +103,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 300,
|
||||
Margin = new MarginPadding(10),
|
||||
Text = "Select directory",
|
||||
Action = () => OnSelection(directorySelector.CurrentPath.Value)
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
@ -29,9 +30,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
private TriangleButton undeleteButton;
|
||||
|
||||
[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
|
||||
{
|
||||
@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Action = () =>
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -65,7 +66,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Action = () =>
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -91,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Action = () =>
|
||||
{
|
||||
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.SupportsImportFromStable)
|
||||
if (stableImportManager?.SupportsImportFromStable == true)
|
||||
{
|
||||
Add(importCollectionsButton = new SettingsButton
|
||||
{
|
||||
@ -119,7 +120,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Action = () =>
|
||||
{
|
||||
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
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
@ -27,13 +26,14 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
get => Entry?.LifetimeStart ?? double.MinValue;
|
||||
get => base.LifetimeStart;
|
||||
set
|
||||
{
|
||||
if (Entry == null && LifetimeStart != value)
|
||||
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||
base.LifetimeStart = value;
|
||||
|
||||
if (Entry != null)
|
||||
Entry.LifetimeStart = value;
|
||||
@ -42,11 +42,10 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
|
||||
public override double LifetimeEnd
|
||||
{
|
||||
get => Entry?.LifetimeEnd ?? double.MaxValue;
|
||||
get => base.LifetimeEnd;
|
||||
set
|
||||
{
|
||||
if (Entry == null && LifetimeEnd != value)
|
||||
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||
base.LifetimeEnd = value;
|
||||
|
||||
if (Entry != null)
|
||||
Entry.LifetimeEnd = value;
|
||||
@ -80,7 +79,12 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
free();
|
||||
|
||||
Entry = entry;
|
||||
|
||||
base.LifetimeStart = entry.LifetimeStart;
|
||||
base.LifetimeEnd = entry.LifetimeEnd;
|
||||
|
||||
OnApply(entry);
|
||||
|
||||
HasEntryApplied = true;
|
||||
}
|
||||
|
||||
@ -112,7 +116,11 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
||||
Debug.Assert(Entry != null && HasEntryApplied);
|
||||
|
||||
OnFree(Entry);
|
||||
|
||||
Entry = null;
|
||||
base.LifetimeStart = double.MinValue;
|
||||
base.LifetimeEnd = double.MaxValue;
|
||||
|
||||
HasEntryApplied = false;
|
||||
}
|
||||
}
|
||||
|
@ -72,8 +72,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Depth = float.MinValue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
|
||||
Child = userModsSelectOverlay = new UserModSelectOverlay
|
||||
{
|
||||
|
@ -10,11 +10,9 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
@ -76,19 +74,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
|
||||
private readonly ScoreInfo score;
|
||||
|
||||
private readonly bool withFlair;
|
||||
|
||||
private SmoothCircularProgress accuracyCircle;
|
||||
private SmoothCircularProgress innerMask;
|
||||
private Container<RankBadge> badges;
|
||||
private RankText rankText;
|
||||
|
||||
private SkinnableSound applauseSound;
|
||||
|
||||
public AccuracyCircle(ScoreInfo score, bool withFlair)
|
||||
public AccuracyCircle(ScoreInfo score)
|
||||
{
|
||||
this.score = score;
|
||||
this.withFlair = withFlair;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -211,13 +204,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
},
|
||||
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)
|
||||
@ -256,7 +242,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
|
||||
using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
|
||||
{
|
||||
this.Delay(-1440).Schedule(() => applauseSound?.Play());
|
||||
rankText.Appear();
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
Margin = new MarginPadding { Top = 40 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 230,
|
||||
Child = new AccuracyCircle(score, withFlair)
|
||||
Child = new AccuracyCircle(score)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
@ -19,13 +20,20 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
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;
|
||||
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 allowWatchingReplay;
|
||||
|
||||
private SkinnableSound applauseSound;
|
||||
|
||||
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
||||
{
|
||||
Score = score;
|
||||
@ -146,6 +156,13 @@ namespace osu.Game.Screens.Ranking
|
||||
bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay);
|
||||
|
||||
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)
|
||||
@ -183,6 +200,9 @@ namespace osu.Game.Screens.Ranking
|
||||
api.Queue(req);
|
||||
|
||||
statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true);
|
||||
|
||||
using (BeginDelayedSequence(APPLAUSE_DELAY))
|
||||
Schedule(() => applauseSound?.Play());
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -54,12 +54,12 @@ namespace osu.Game.Screens.Ranking
|
||||
/// <summary>
|
||||
/// Duration for the panel to resize into its expanded/contracted size.
|
||||
/// </summary>
|
||||
private const double resize_duration = 200;
|
||||
public const double RESIZE_DURATION = 200;
|
||||
|
||||
/// <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>
|
||||
private const double top_layer_expand_delay = 100;
|
||||
public const double TOP_LAYER_EXPAND_DELAY = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Duration for the top layer expansion.
|
||||
@ -208,8 +208,8 @@ namespace osu.Game.Screens.Ranking
|
||||
case PanelState.Expanded:
|
||||
Size = new Vector2(EXPANDED_WIDTH, expanded_height);
|
||||
|
||||
topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(expanded_middle_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);
|
||||
|
||||
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).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:
|
||||
Size = new Vector2(CONTRACTED_WIDTH, contracted_height);
|
||||
|
||||
topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(contracted_middle_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);
|
||||
|
||||
topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0));
|
||||
middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0));
|
||||
break;
|
||||
}
|
||||
|
||||
content.ResizeTo(Size, resize_duration, Easing.OutQuint);
|
||||
content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint);
|
||||
|
||||
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.
|
||||
using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true))
|
||||
using (BeginDelayedSequence(topLayerExpanded ? 0 : RESIZE_DURATION + TOP_LAYER_EXPAND_DELAY, true))
|
||||
{
|
||||
topLayerContainer.FadeIn();
|
||||
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select
|
||||
public ImportFromStablePopup(Action importFromStable)
|
||||
{
|
||||
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;
|
||||
|
||||
|
@ -22,7 +22,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
@ -35,9 +34,9 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Scoring;
|
||||
using System.Diagnostics;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -52,6 +51,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
protected virtual bool ShowFooter => true;
|
||||
|
||||
protected virtual bool DisplayStableImportPrompt => stableImportManager?.SupportsImportFromStable == true;
|
||||
|
||||
/// <summary>
|
||||
/// Can be null if <see cref="ShowFooter"/> is false.
|
||||
/// </summary>
|
||||
@ -84,6 +85,9 @@ namespace osu.Game.Screens.Select
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private StableImportManager stableImportManager { get; set; }
|
||||
|
||||
protected ModSelectOverlay ModSelect { get; private set; }
|
||||
|
||||
protected Sample SampleConfirm { get; private set; }
|
||||
@ -101,7 +105,7 @@ namespace osu.Game.Screens.Select
|
||||
private MusicController music { get; set; }
|
||||
|
||||
[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).
|
||||
transferRulesetValue();
|
||||
@ -282,18 +286,12 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
// if we have no beatmaps but osu-stable is found, let's prompt the user to import.
|
||||
if (!beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.Minimal).Any() && beatmaps.StableInstallationAvailable)
|
||||
// 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() && DisplayStableImportPrompt)
|
||||
{
|
||||
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
||||
{
|
||||
Task.Run(beatmaps.ImportFromStableAsync)
|
||||
.ContinueWith(_ =>
|
||||
{
|
||||
Task.Run(scores.ImportFromStableAsync);
|
||||
Task.Run(collections.ImportFromStableAsync);
|
||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
Task.Run(skins.ImportFromStableAsync);
|
||||
Task.Run(() => stableImportManager.ImportFromStableAsync(StableContent.All));
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.Spectator;
|
||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.Spectate
|
||||
[Resolved]
|
||||
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, GameplayState> gameplayStates = new Dictionary<int, GameplayState>();
|
||||
|
||||
@ -65,8 +68,9 @@ namespace osu.Game.Screens.Spectate
|
||||
foreach (var u in users.Result)
|
||||
userMap[u.Id] = u;
|
||||
|
||||
spectatorClient.BindUserBeganPlaying(userBeganPlaying, true);
|
||||
spectatorClient.OnUserFinishedPlaying += userFinishedPlaying;
|
||||
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
|
||||
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
|
||||
|
||||
spectatorClient.OnNewFrames += userSentFrames;
|
||||
|
||||
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
||||
@ -102,7 +106,7 @@ namespace osu.Game.Screens.Spectate
|
||||
|
||||
foreach (var (userId, _) in userMap)
|
||||
{
|
||||
if (!spectatorClient.TryGetPlayingUserState(userId, out var userState))
|
||||
if (!playingUserStates.TryGetValue(userId, out var userState))
|
||||
continue;
|
||||
|
||||
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)
|
||||
return;
|
||||
@ -118,24 +146,30 @@ namespace osu.Game.Screens.Spectate
|
||||
if (!userMap.ContainsKey(userId))
|
||||
return;
|
||||
|
||||
// The user may have stopped playing.
|
||||
if (!spectatorClient.TryGetPlayingUserState(userId, out _))
|
||||
Schedule(() => OnUserStateChanged(userId, state));
|
||||
updateGameplayState(userId);
|
||||
}
|
||||
|
||||
private void onUserStateRemoved(int userId)
|
||||
{
|
||||
if (!userMap.ContainsKey(userId))
|
||||
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)
|
||||
{
|
||||
Debug.Assert(userMap.ContainsKey(userId));
|
||||
|
||||
// The user may have stopped playing.
|
||||
if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState))
|
||||
return;
|
||||
|
||||
var user = userMap[userId];
|
||||
var spectatorState = playingUserStates[userId];
|
||||
|
||||
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance();
|
||||
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>
|
||||
/// Invoked when a spectated user's state has changed.
|
||||
/// </summary>
|
||||
@ -226,7 +246,7 @@ namespace osu.Game.Screens.Spectate
|
||||
/// <param name="userId">The user to stop spectating.</param>
|
||||
protected void RemoveUser(int userId)
|
||||
{
|
||||
userFinishedPlaying(userId, null);
|
||||
onUserStateRemoved(userId);
|
||||
|
||||
userIds.Remove(userId);
|
||||
userMap.Remove(userId);
|
||||
@ -240,8 +260,6 @@ namespace osu.Game.Screens.Spectate
|
||||
|
||||
if (spectatorClient != null)
|
||||
{
|
||||
spectatorClient.OnUserBeganPlaying -= userBeganPlaying;
|
||||
spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying;
|
||||
spectatorClient.OnNewFrames -= userSentFrames;
|
||||
|
||||
foreach (var (userId, _) in userMap)
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -63,39 +64,43 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
adjustScaleFromAnchor(ref scale, anchor);
|
||||
|
||||
var selectionQuad = getSelectionQuad();
|
||||
// the selection quad is always upright, so use an AABB rect to make mutating the values easier.
|
||||
var selectionRect = getSelectionQuad().AABBFloat;
|
||||
|
||||
// the selection quad is always upright, so use a rect to make mutating the values easier.
|
||||
var adjustedRect = selectionQuad.AABBFloat;
|
||||
// copy to mutate, as we will need to compare to the original later on.
|
||||
var adjustedRect = selectionRect;
|
||||
|
||||
// for now aspect lock scale adjustments that occur at corners.
|
||||
if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1))
|
||||
scale.Y = scale.X / selectionQuad.Width * selectionQuad.Height;
|
||||
// first, remove any scale axis we are not interested in.
|
||||
if (anchor.HasFlagFast(Anchor.x1)) scale.X = 0;
|
||||
if (anchor.HasFlagFast(Anchor.y1)) scale.Y = 0;
|
||||
|
||||
if (anchor.HasFlagFast(Anchor.x0))
|
||||
bool shouldAspectLock =
|
||||
// for now aspect lock scale adjustments that occur at corners..
|
||||
(!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1))
|
||||
// ..or if any of the selection have been rotated.
|
||||
// this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway).
|
||||
|| SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0));
|
||||
|
||||
if (shouldAspectLock)
|
||||
{
|
||||
adjustedRect.X -= scale.X;
|
||||
adjustedRect.Width += scale.X;
|
||||
}
|
||||
else if (anchor.HasFlagFast(Anchor.x2))
|
||||
{
|
||||
adjustedRect.Width += scale.X;
|
||||
if (anchor.HasFlagFast(Anchor.x1))
|
||||
// if dragging from the horizontal centre, only a vertical component is available.
|
||||
scale.X = scale.Y / selectionRect.Height * selectionRect.Width;
|
||||
else
|
||||
// in all other cases (arbitrarily) use the horizontal component for aspect lock.
|
||||
scale.Y = scale.X / selectionRect.Width * selectionRect.Height;
|
||||
}
|
||||
|
||||
if (anchor.HasFlagFast(Anchor.y0))
|
||||
{
|
||||
adjustedRect.Y -= scale.Y;
|
||||
adjustedRect.Height += scale.Y;
|
||||
}
|
||||
else if (anchor.HasFlagFast(Anchor.y2))
|
||||
{
|
||||
adjustedRect.Height += scale.Y;
|
||||
}
|
||||
if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X;
|
||||
if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y;
|
||||
|
||||
// scale adjust should match that of the quad itself.
|
||||
adjustedRect.Width += scale.X;
|
||||
adjustedRect.Height += scale.Y;
|
||||
|
||||
// scale adjust applied to each individual item should match that of the quad itself.
|
||||
var scaledDelta = new Vector2(
|
||||
adjustedRect.Width / selectionQuad.Width,
|
||||
adjustedRect.Height / selectionQuad.Height
|
||||
adjustedRect.Width / selectionRect.Width,
|
||||
adjustedRect.Height / selectionRect.Height
|
||||
);
|
||||
|
||||
foreach (var b in SelectedBlueprints)
|
||||
@ -107,8 +112,8 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
var relativePositionInOriginal =
|
||||
new Vector2(
|
||||
(screenPosition.X - selectionQuad.TopLeft.X) / selectionQuad.Width,
|
||||
(screenPosition.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height
|
||||
(screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width,
|
||||
(screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height
|
||||
);
|
||||
|
||||
var newPositionInAdjusted = new Vector2(
|
||||
|
@ -29,7 +29,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<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="Sentry" Version="3.3.4" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<!-- 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.Core" Version="2.2.6" />
|
||||
<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="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user