mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 15:22:55 +08:00
Merge branch 'master' into net6
This commit is contained in:
commit
5b30921cbf
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.204.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.204.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.204.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
|
@ -87,8 +87,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
|
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSpinner _:
|
case DrawableSpinner spinner:
|
||||||
requiresHold = true;
|
requiresHold |= spinner.HitObject.SpinsRequired > 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
Player.ScoreProcessor.NewJudgement += b => judged = true;
|
Player.ScoreProcessor.NewJudgement += b => judged = true;
|
||||||
});
|
});
|
||||||
AddUntilStep("swell judged", () => judged);
|
AddUntilStep("swell judged", () => judged);
|
||||||
AddAssert("failed", () => Player.HasFailed);
|
AddAssert("failed", () => Player.GameplayState.HasFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
// The pause screen and fail animation both ramp frequency.
|
// The pause screen and fail animation both ramp frequency.
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1);
|
AddUntilStep("wait for multiple judgements", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits > 1);
|
||||||
AddAssert("total number of results == 1", () =>
|
AddAssert("total number of results == 1", () =>
|
||||||
{
|
{
|
||||||
|
@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPauseAfterFail()
|
public void TestPauseAfterFail()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
|
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
|
||||||
|
|
||||||
confirmClockRunning(false);
|
confirmClockRunning(false);
|
||||||
@ -201,7 +201,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitFromFailedGameplayAfterFailAnimation()
|
public void TestExitFromFailedGameplayAfterFailAnimation()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible);
|
AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible);
|
||||||
|
|
||||||
confirmClockRunning(false);
|
confirmClockRunning(false);
|
||||||
@ -213,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitFromFailedGameplayDuringFailAnimation()
|
public void TestExitFromFailedGameplayDuringFailAnimation()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
|
|
||||||
// will finish the fail animation and show the fail/pause screen.
|
// will finish the fail animation and show the fail/pause screen.
|
||||||
AddStep("attempt exit via pause key", () => Player.ExitViaPause());
|
AddStep("attempt exit via pause key", () => Player.ExitViaPause());
|
||||||
@ -227,7 +227,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestQuickRetryFromFailedGameplay()
|
public void TestQuickRetryFromFailedGameplay()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyRetryOverlay>().First().Action?.Invoke());
|
AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyRetryOverlay>().First().Action?.Invoke());
|
||||||
|
|
||||||
confirmExited();
|
confirmExited();
|
||||||
@ -236,7 +236,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestQuickExitFromFailedGameplay()
|
public void TestQuickExitFromFailedGameplay()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
|
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
|
||||||
|
|
||||||
confirmExited();
|
confirmExited();
|
||||||
@ -341,7 +341,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
confirmClockRunning(false);
|
confirmClockRunning(false);
|
||||||
confirmNotExited();
|
confirmNotExited();
|
||||||
AddAssert("player not failed", () => !Player.HasFailed);
|
AddAssert("player not failed", () => !Player.GameplayState.HasFailed);
|
||||||
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
|
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddStep("exit", () => Player.Exit());
|
AddStep("exit", () => Player.Exit());
|
||||||
|
|
||||||
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
|
||||||
@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
addFakeHit();
|
addFakeHit();
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddStep("exit", () => Player.Exit());
|
AddStep("exit", () => Player.Exit());
|
||||||
|
|
||||||
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
|
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
|
||||||
|
@ -155,11 +155,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
waitForPlayer();
|
waitForPlayer();
|
||||||
checkPaused(true);
|
checkPaused(true);
|
||||||
|
sendFrames();
|
||||||
|
|
||||||
finish();
|
finish(SpectatedUserState.Failed);
|
||||||
|
|
||||||
checkPaused(false);
|
checkPaused(false); // Should continue playing until out of frames
|
||||||
// TODO: should replay until running out of frames then fail
|
checkPaused(true); // And eventually stop after running out of frames and fail.
|
||||||
|
// Todo: Should check for + display a failed message.
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -211,7 +213,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("send frames and finish play", () =>
|
AddStep("send frames and finish play", () =>
|
||||||
{
|
{
|
||||||
spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero));
|
spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero));
|
||||||
spectatorClient.EndPlaying();
|
spectatorClient.EndPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()) { HasPassed = true });
|
||||||
});
|
});
|
||||||
|
|
||||||
// We can't access API because we're an "online" test.
|
// We can't access API because we're an "online" test.
|
||||||
@ -234,6 +236,71 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null);
|
AddAssert("last frame has header", () => lastBundle.Frames[^1].Header != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlayingState()
|
||||||
|
{
|
||||||
|
loadSpectatingScreen();
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPassedState()
|
||||||
|
{
|
||||||
|
loadSpectatingScreen();
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
|
||||||
|
AddStep("send passed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Passed));
|
||||||
|
AddUntilStep("state is passed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Passed);
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestQuitState()
|
||||||
|
{
|
||||||
|
loadSpectatingScreen();
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
|
||||||
|
AddStep("send quit", () => spectatorClient.EndPlay(streamingUser.Id));
|
||||||
|
AddUntilStep("state is quit", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Quit);
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFailedState()
|
||||||
|
{
|
||||||
|
loadSpectatingScreen();
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
|
||||||
|
AddStep("send failed", () => spectatorClient.EndPlay(streamingUser.Id, SpectatedUserState.Failed));
|
||||||
|
AddUntilStep("state is failed", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Failed);
|
||||||
|
|
||||||
|
start();
|
||||||
|
sendFrames();
|
||||||
|
waitForPlayer();
|
||||||
|
AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing);
|
||||||
|
}
|
||||||
|
|
||||||
private OsuFramedReplayInputHandler replayHandler =>
|
private OsuFramedReplayInputHandler replayHandler =>
|
||||||
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;
|
(OsuFramedReplayInputHandler)Stack.ChildrenOfType<OsuInputManager>().First().ReplayInputHandler;
|
||||||
|
|
||||||
@ -246,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
private void start(int? beatmapId = null) => AddStep("start play", () => spectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||||
|
|
||||||
private void finish() => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id));
|
private void finish(SpectatedUserState state = SpectatedUserState.Quit) => AddStep("end play", () => spectatorClient.EndPlay(streamingUser.Id, state));
|
||||||
|
|
||||||
private void checkPaused(bool state) =>
|
private void checkPaused(bool state) =>
|
||||||
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
||||||
|
@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestClientSendsCorrectRuleset()
|
public void TestClientSendsCorrectRuleset()
|
||||||
{
|
{
|
||||||
AddUntilStep("spectator client sending frames", () => spectatorClient.PlayingUserStates.ContainsKey(dummy_user_id));
|
AddUntilStep("spectator client sending frames", () => spectatorClient.WatchedUserStates.ContainsKey(dummy_user_id));
|
||||||
AddAssert("spectator client sent correct ruleset", () => spectatorClient.PlayingUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
|
AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void TearDownSteps()
|
public override void TearDownSteps()
|
||||||
|
@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600);
|
AddStep("set storyboard duration to 0.6s", () => currentStoryboardDuration = 600);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
public void RandomlyUpdateState()
|
public void RandomlyUpdateState()
|
||||||
{
|
{
|
||||||
foreach (int userId in PlayingUsers)
|
foreach ((int userId, _) in WatchedUserStates)
|
||||||
{
|
{
|
||||||
if (RNG.NextBool())
|
if (RNG.NextBool())
|
||||||
continue;
|
continue;
|
||||||
|
@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
|
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for fail", () => player.HasFailed);
|
AddUntilStep("wait for fail", () => player.GameplayState.HasFailed);
|
||||||
|
|
||||||
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
|
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
|
||||||
AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime);
|
AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime);
|
||||||
|
@ -3,32 +3,69 @@
|
|||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
public class TestSceneMigrationScreens : ScreenTestScene
|
public class TestSceneMigrationScreens : ScreenTestScene
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly NotificationOverlay notifications;
|
||||||
|
|
||||||
public TestSceneMigrationScreens()
|
public TestSceneMigrationScreens()
|
||||||
{
|
{
|
||||||
AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen()));
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
notifications = new NotificationOverlay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeleteSuccess()
|
||||||
|
{
|
||||||
|
AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeleteFails()
|
||||||
|
{
|
||||||
|
AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen(false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestMigrationSelectScreen : MigrationSelectScreen
|
private class TestMigrationSelectScreen : MigrationSelectScreen
|
||||||
{
|
{
|
||||||
protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen());
|
private readonly bool deleteSuccess;
|
||||||
|
|
||||||
|
public TestMigrationSelectScreen(bool deleteSuccess)
|
||||||
|
{
|
||||||
|
this.deleteSuccess = deleteSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen(deleteSuccess));
|
||||||
|
|
||||||
private class TestMigrationRunScreen : MigrationRunScreen
|
private class TestMigrationRunScreen : MigrationRunScreen
|
||||||
{
|
{
|
||||||
protected override void PerformMigration()
|
private readonly bool success;
|
||||||
{
|
|
||||||
Thread.Sleep(3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestMigrationRunScreen()
|
public TestMigrationRunScreen(bool success)
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool PerformMigration()
|
||||||
|
{
|
||||||
|
Thread.Sleep(3000);
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,68 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExternalRulesetChange()
|
||||||
|
{
|
||||||
|
createCarousel(new List<BeatmapSetInfo>());
|
||||||
|
|
||||||
|
AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = rulesets.AvailableRulesets.ElementAt(0),
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
}, false));
|
||||||
|
|
||||||
|
AddStep("add mixed ruleset beatmapset", () =>
|
||||||
|
{
|
||||||
|
var testMixed = TestResources.CreateTestBeatmapSetInfo(3);
|
||||||
|
|
||||||
|
for (int i = 0; i <= 2; i++)
|
||||||
|
{
|
||||||
|
testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
carousel.UpdateBeatmapSet(testMixed);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for filtered difficulties", () =>
|
||||||
|
{
|
||||||
|
var visibleBeatmapPanels = carousel.Items.OfType<DrawableCarouselBeatmap>().Where(p => p.IsPresent).ToArray();
|
||||||
|
|
||||||
|
return visibleBeatmapPanels.Length == 1
|
||||||
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("filter to ruleset 1", () => carousel.Filter(new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = rulesets.AvailableRulesets.ElementAt(1),
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
}, false));
|
||||||
|
|
||||||
|
AddUntilStep("wait for filtered difficulties", () =>
|
||||||
|
{
|
||||||
|
var visibleBeatmapPanels = carousel.Items.OfType<DrawableCarouselBeatmap>().Where(p => p.IsPresent).ToArray();
|
||||||
|
|
||||||
|
return visibleBeatmapPanels.Length == 2
|
||||||
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1
|
||||||
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 1) == 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("filter to ruleset 2", () => carousel.Filter(new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = rulesets.AvailableRulesets.ElementAt(2),
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
}, false));
|
||||||
|
|
||||||
|
AddUntilStep("wait for filtered difficulties", () =>
|
||||||
|
{
|
||||||
|
var visibleBeatmapPanels = carousel.Items.OfType<DrawableCarouselBeatmap>().Where(p => p.IsPresent).ToArray();
|
||||||
|
|
||||||
|
return visibleBeatmapPanels.Length == 2
|
||||||
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1
|
||||||
|
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 2) == 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScrollPositionMaintainedOnAdd()
|
public void TestScrollPositionMaintainedOnAdd()
|
||||||
{
|
{
|
||||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Tournament.IO
|
|||||||
|
|
||||||
public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty);
|
public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty);
|
||||||
|
|
||||||
public override void Migrate(Storage newStorage)
|
public override bool Migrate(Storage newStorage)
|
||||||
{
|
{
|
||||||
// this migration only happens once on moving to the per-tournament storage system.
|
// this migration only happens once on moving to the per-tournament storage system.
|
||||||
// listed files are those known at that point in time.
|
// listed files are those known at that point in time.
|
||||||
@ -94,6 +94,8 @@ namespace osu.Game.Tournament.IO
|
|||||||
ChangeTargetStorage(newStorage);
|
ChangeTargetStorage(newStorage);
|
||||||
storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament);
|
storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament);
|
||||||
storageConfig.Save();
|
storageConfig.Save();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveFileIfExists(string file, DirectoryInfo destination)
|
private void moveFileIfExists(string file, DirectoryInfo destination)
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
bool firstGroup = true;
|
bool firstGroup = true;
|
||||||
|
|
||||||
foreach (var group in beatmapSetInfo.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key))
|
foreach (var group in beatmapSetInfo.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||||
{
|
{
|
||||||
if (!firstGroup)
|
if (!firstGroup)
|
||||||
{
|
{
|
||||||
|
@ -62,10 +62,8 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
||||||
|
|
||||||
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key))
|
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
|
||||||
{
|
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed));
|
||||||
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key, rulesetGrouping, collapsed));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -33,7 +33,8 @@ namespace osu.Game.IO
|
|||||||
/// A general purpose migration method to move the storage to a different location.
|
/// A general purpose migration method to move the storage to a different location.
|
||||||
/// <param name="newStorage">The target storage of the migration.</param>
|
/// <param name="newStorage">The target storage of the migration.</param>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Migrate(Storage newStorage)
|
/// <returns>Whether cleanup could complete.</returns>
|
||||||
|
public virtual bool Migrate(Storage newStorage)
|
||||||
{
|
{
|
||||||
var source = new DirectoryInfo(GetFullPath("."));
|
var source = new DirectoryInfo(GetFullPath("."));
|
||||||
var destination = new DirectoryInfo(newStorage.GetFullPath("."));
|
var destination = new DirectoryInfo(newStorage.GetFullPath("."));
|
||||||
@ -57,17 +58,20 @@ namespace osu.Game.IO
|
|||||||
|
|
||||||
CopyRecursive(source, destination);
|
CopyRecursive(source, destination);
|
||||||
ChangeTargetStorage(newStorage);
|
ChangeTargetStorage(newStorage);
|
||||||
DeleteRecursive(source);
|
|
||||||
|
return DeleteRecursive(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true)
|
protected bool DeleteRecursive(DirectoryInfo target, bool topLevelExcludes = true)
|
||||||
{
|
{
|
||||||
|
bool allFilesDeleted = true;
|
||||||
|
|
||||||
foreach (System.IO.FileInfo fi in target.GetFiles())
|
foreach (System.IO.FileInfo fi in target.GetFiles())
|
||||||
{
|
{
|
||||||
if (topLevelExcludes && IgnoreFiles.Contains(fi.Name))
|
if (topLevelExcludes && IgnoreFiles.Contains(fi.Name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AttemptOperation(() => fi.Delete());
|
allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (DirectoryInfo dir in target.GetDirectories())
|
foreach (DirectoryInfo dir in target.GetDirectories())
|
||||||
@ -75,11 +79,13 @@ namespace osu.Game.IO
|
|||||||
if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name))
|
if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AttemptOperation(() => dir.Delete(true));
|
allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
||||||
AttemptOperation(target.Delete);
|
allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false);
|
||||||
|
|
||||||
|
return allFilesDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
||||||
@ -110,19 +116,25 @@ namespace osu.Game.IO
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to perform.</param>
|
/// <param name="action">The action to perform.</param>
|
||||||
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
||||||
protected static void AttemptOperation(Action action, int attempts = 10)
|
/// <param name="throwOnFailure">Whether to throw an exception on failure. If <c>false</c>, will silently fail.</param>
|
||||||
|
protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
action();
|
action();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
if (attempts-- == 0)
|
if (attempts-- == 0)
|
||||||
|
{
|
||||||
|
if (throwOnFailure)
|
||||||
throw;
|
throw;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(250);
|
Thread.Sleep(250);
|
||||||
|
@ -113,11 +113,14 @@ namespace osu.Game.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Migrate(Storage newStorage)
|
public override bool Migrate(Storage newStorage)
|
||||||
{
|
{
|
||||||
base.Migrate(newStorage);
|
bool cleanupSucceeded = base.Migrate(newStorage);
|
||||||
|
|
||||||
storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath("."));
|
storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath("."));
|
||||||
storageConfig.Save();
|
storageConfig.Save();
|
||||||
|
|
||||||
|
return cleanupSucceeded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
Best,
|
Best,
|
||||||
Firsts,
|
Firsts,
|
||||||
Recent
|
Recent,
|
||||||
|
Pinned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
|
|
||||||
public string MD5Hash => Checksum;
|
public string MD5Hash => Checksum;
|
||||||
|
|
||||||
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
|
public IRulesetInfo Ruleset => new APIRuleset { OnlineID = RulesetID };
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string Hash => throw new NotImplementedException();
|
public string Hash => throw new NotImplementedException();
|
||||||
@ -106,5 +106,29 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public bool Equals(IBeatmapInfo? other) => other is APIBeatmap b && this.MatchesOnlineID(b);
|
public bool Equals(IBeatmapInfo? other) => other is APIBeatmap b && this.MatchesOnlineID(b);
|
||||||
|
|
||||||
|
private class APIRuleset : IRulesetInfo
|
||||||
|
{
|
||||||
|
public int OnlineID { get; set; } = -1;
|
||||||
|
|
||||||
|
public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})";
|
||||||
|
public string ShortName => nameof(APIRuleset);
|
||||||
|
public string InstantiationInfo => string.Empty;
|
||||||
|
|
||||||
|
public Ruleset CreateInstance() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public bool Equals(IRulesetInfo? other) => other is APIRuleset r && this.MatchesOnlineID(r);
|
||||||
|
|
||||||
|
public int CompareTo(IRulesetInfo other)
|
||||||
|
{
|
||||||
|
if (!(other is APIRuleset ruleset))
|
||||||
|
throw new ArgumentException($@"Object is not of type {nameof(APIRuleset)}.", nameof(other));
|
||||||
|
|
||||||
|
return OnlineID.CompareTo(ruleset.OnlineID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||||
|
public override int GetHashCode() => OnlineID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"scores_recent_count")]
|
[JsonProperty(@"scores_recent_count")]
|
||||||
public int ScoresRecentCount;
|
public int ScoresRecentCount;
|
||||||
|
|
||||||
|
[JsonProperty(@"scores_pinned_count")]
|
||||||
|
public int ScoresPinnedCount;
|
||||||
|
|
||||||
[JsonProperty(@"beatmap_playcounts_count")]
|
[JsonProperty(@"beatmap_playcounts_count")]
|
||||||
public int BeatmapPlayCountsCount;
|
public int BeatmapPlayCountsCount;
|
||||||
|
|
||||||
|
@ -1,46 +1,22 @@
|
|||||||
// 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.Net.Http;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using osu.Framework.IO.Network;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.Solo;
|
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Online.Rooms
|
namespace osu.Game.Online.Rooms
|
||||||
{
|
{
|
||||||
public class SubmitRoomScoreRequest : APIRequest<MultiplayerScore>
|
public class SubmitRoomScoreRequest : SubmitScoreRequest
|
||||||
{
|
{
|
||||||
private readonly long scoreId;
|
|
||||||
private readonly long roomId;
|
private readonly long roomId;
|
||||||
private readonly long playlistItemId;
|
private readonly long playlistItemId;
|
||||||
private readonly SubmittableScore score;
|
|
||||||
|
|
||||||
public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo)
|
public SubmitRoomScoreRequest(ScoreInfo scoreInfo, long scoreId, long roomId, long playlistItemId)
|
||||||
|
: base(scoreInfo, scoreId)
|
||||||
{
|
{
|
||||||
this.scoreId = scoreId;
|
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.playlistItemId = playlistItemId;
|
this.playlistItemId = playlistItemId;
|
||||||
score = new SubmittableScore(scoreInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{ScoreId}";
|
||||||
{
|
|
||||||
var req = base.CreateWebRequest();
|
|
||||||
|
|
||||||
req.ContentType = "application/json";
|
|
||||||
req.Method = HttpMethod.Put;
|
|
||||||
req.Timeout = 30000;
|
|
||||||
|
|
||||||
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
|
|
||||||
{
|
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
|
||||||
}));
|
|
||||||
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
osu.Game/Online/Rooms/SubmitScoreRequest.cs
Normal file
41
osu.Game/Online/Rooms/SubmitScoreRequest.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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.Net.Http;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Rooms
|
||||||
|
{
|
||||||
|
public abstract class SubmitScoreRequest : APIRequest<MultiplayerScore>
|
||||||
|
{
|
||||||
|
public readonly SubmittableScore Score;
|
||||||
|
|
||||||
|
protected readonly long ScoreId;
|
||||||
|
|
||||||
|
protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId)
|
||||||
|
{
|
||||||
|
Score = new SubmittableScore(scoreInfo);
|
||||||
|
ScoreId = scoreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var req = base.CreateWebRequest();
|
||||||
|
|
||||||
|
req.ContentType = "application/json";
|
||||||
|
req.Method = HttpMethod.Put;
|
||||||
|
req.Timeout = 30000;
|
||||||
|
|
||||||
|
req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||||
|
}));
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +1,21 @@
|
|||||||
// 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.Net.Http;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using osu.Framework.IO.Network;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Online.Solo
|
namespace osu.Game.Online.Solo
|
||||||
{
|
{
|
||||||
public class SubmitSoloScoreRequest : APIRequest<MultiplayerScore>
|
public class SubmitSoloScoreRequest : SubmitScoreRequest
|
||||||
{
|
{
|
||||||
public readonly SubmittableScore Score;
|
|
||||||
|
|
||||||
private readonly long scoreId;
|
|
||||||
|
|
||||||
private readonly int beatmapId;
|
private readonly int beatmapId;
|
||||||
|
|
||||||
public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo)
|
public SubmitSoloScoreRequest(ScoreInfo scoreInfo, long scoreId, int beatmapId)
|
||||||
|
: base(scoreInfo, scoreId)
|
||||||
{
|
{
|
||||||
this.beatmapId = beatmapId;
|
this.beatmapId = beatmapId;
|
||||||
this.scoreId = scoreId;
|
|
||||||
Score = new SubmittableScore(scoreInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{ScoreId}";
|
||||||
{
|
|
||||||
var req = base.CreateWebRequest();
|
|
||||||
|
|
||||||
req.ContentType = "application/json";
|
|
||||||
req.Method = HttpMethod.Put;
|
|
||||||
req.Timeout = 30000;
|
|
||||||
|
|
||||||
req.AddRaw(JsonConvert.SerializeObject(Score, new JsonSerializerSettings
|
|
||||||
{
|
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
|
||||||
}));
|
|
||||||
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{scoreId}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
osu.Game/Online/Spectator/SpectatedUserState.cs
Normal file
38
osu.Game/Online/Spectator/SpectatedUserState.cs
Normal file
@ -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.
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Spectator
|
||||||
|
{
|
||||||
|
public enum SpectatedUserState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The spectated user is not yet playing.
|
||||||
|
/// </summary>
|
||||||
|
Idle,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectated user is currently playing.
|
||||||
|
/// </summary>
|
||||||
|
Playing,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectated user is currently paused. Unused for the time being.
|
||||||
|
/// </summary>
|
||||||
|
Paused,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectated user has passed gameplay.
|
||||||
|
/// </summary>
|
||||||
|
Passed,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectated user has failed gameplay.
|
||||||
|
/// </summary>
|
||||||
|
Failed,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectated user has quit gameplay.
|
||||||
|
/// </summary>
|
||||||
|
Quit
|
||||||
|
}
|
||||||
|
}
|
@ -35,19 +35,28 @@ namespace osu.Game.Online.Spectator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IBindable<bool> IsConnected { get; }
|
public abstract IBindable<bool> IsConnected { get; }
|
||||||
|
|
||||||
private readonly List<int> watchingUsers = new List<int>();
|
/// <summary>
|
||||||
|
/// The states of all users currently being watched.
|
||||||
|
/// </summary>
|
||||||
|
public IBindableDictionary<int, SpectatorState> WatchedUserStates => watchedUserStates;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A global list of all players currently playing.
|
||||||
|
/// </summary>
|
||||||
public IBindableList<int> PlayingUsers => playingUsers;
|
public IBindableList<int> PlayingUsers => playingUsers;
|
||||||
private readonly BindableList<int> playingUsers = new BindableList<int>();
|
|
||||||
|
|
||||||
public IBindableDictionary<int, SpectatorState> PlayingUserStates => playingUserStates;
|
/// <summary>
|
||||||
private readonly BindableDictionary<int, SpectatorState> playingUserStates = new BindableDictionary<int, SpectatorState>();
|
/// All users currently being watched.
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<int> watchedUsers = new List<int>();
|
||||||
|
|
||||||
|
private readonly BindableDictionary<int, SpectatorState> watchedUserStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
private readonly BindableList<int> playingUsers = new BindableList<int>();
|
||||||
|
private readonly SpectatorState currentState = new SpectatorState();
|
||||||
|
|
||||||
private IBeatmap? currentBeatmap;
|
private IBeatmap? currentBeatmap;
|
||||||
private Score? currentScore;
|
private Score? currentScore;
|
||||||
|
|
||||||
private readonly SpectatorState currentState = new SpectatorState();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the local user is playing.
|
/// Whether the local user is playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -76,8 +85,8 @@ namespace osu.Game.Online.Spectator
|
|||||||
if (connected.NewValue)
|
if (connected.NewValue)
|
||||||
{
|
{
|
||||||
// get all the users that were previously being watched
|
// get all the users that were previously being watched
|
||||||
int[] users = watchingUsers.ToArray();
|
int[] users = watchedUsers.ToArray();
|
||||||
watchingUsers.Clear();
|
watchedUsers.Clear();
|
||||||
|
|
||||||
// resubscribe to watched users.
|
// resubscribe to watched users.
|
||||||
foreach (int userId in users)
|
foreach (int userId in users)
|
||||||
@ -90,7 +99,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
playingUsers.Clear();
|
playingUsers.Clear();
|
||||||
playingUserStates.Clear();
|
watchedUserStates.Clear();
|
||||||
}
|
}
|
||||||
}), true);
|
}), true);
|
||||||
}
|
}
|
||||||
@ -102,11 +111,8 @@ namespace osu.Game.Online.Spectator
|
|||||||
if (!playingUsers.Contains(userId))
|
if (!playingUsers.Contains(userId))
|
||||||
playingUsers.Add(userId);
|
playingUsers.Add(userId);
|
||||||
|
|
||||||
// UserBeganPlaying() is called by the server regardless of whether the local user is watching the remote user, and is called a further time when the remote user is watched.
|
if (watchedUsers.Contains(userId))
|
||||||
// This may be a temporary thing (see: https://github.com/ppy/osu-server-spectator/blob/2273778e02cfdb4a9c6a934f2a46a8459cb5d29c/osu.Server.Spectator/Hubs/SpectatorHub.cs#L28-L29).
|
watchedUserStates[userId] = state;
|
||||||
// We don't want the user states to update unless the player is being watched, otherwise calling BindUserBeganPlaying() can lead to double invocations.
|
|
||||||
if (watchingUsers.Contains(userId))
|
|
||||||
playingUserStates[userId] = state;
|
|
||||||
|
|
||||||
OnUserBeganPlaying?.Invoke(userId, state);
|
OnUserBeganPlaying?.Invoke(userId, state);
|
||||||
});
|
});
|
||||||
@ -119,7 +125,9 @@ namespace osu.Game.Online.Spectator
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
playingUsers.Remove(userId);
|
playingUsers.Remove(userId);
|
||||||
playingUserStates.Remove(userId);
|
|
||||||
|
if (watchedUsers.Contains(userId))
|
||||||
|
watchedUserStates[userId] = state;
|
||||||
|
|
||||||
OnUserFinishedPlaying?.Invoke(userId, state);
|
OnUserFinishedPlaying?.Invoke(userId, state);
|
||||||
});
|
});
|
||||||
@ -151,6 +159,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID;
|
currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID;
|
||||||
currentState.RulesetID = score.ScoreInfo.RulesetID;
|
currentState.RulesetID = score.ScoreInfo.RulesetID;
|
||||||
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
|
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
|
||||||
|
currentState.State = SpectatedUserState.Playing;
|
||||||
|
|
||||||
currentBeatmap = state.Beatmap;
|
currentBeatmap = state.Beatmap;
|
||||||
currentScore = score;
|
currentScore = score;
|
||||||
@ -161,7 +170,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
|
|
||||||
public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data);
|
public void SendFrames(FrameDataBundle data) => lastSend = SendFramesInternal(data);
|
||||||
|
|
||||||
public void EndPlaying()
|
public void EndPlaying(GameplayState state)
|
||||||
{
|
{
|
||||||
// This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue).
|
// This method is most commonly called via Dispose(), which is can be asynchronous (via the AsyncDisposalQueue).
|
||||||
// We probably need to find a better way to handle this...
|
// We probably need to find a better way to handle this...
|
||||||
@ -176,6 +185,13 @@ namespace osu.Game.Online.Spectator
|
|||||||
IsPlaying = false;
|
IsPlaying = false;
|
||||||
currentBeatmap = null;
|
currentBeatmap = null;
|
||||||
|
|
||||||
|
if (state.HasPassed)
|
||||||
|
currentState.State = SpectatedUserState.Passed;
|
||||||
|
else if (state.HasFailed)
|
||||||
|
currentState.State = SpectatedUserState.Failed;
|
||||||
|
else
|
||||||
|
currentState.State = SpectatedUserState.Quit;
|
||||||
|
|
||||||
EndPlayingInternal(currentState);
|
EndPlayingInternal(currentState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -184,10 +200,10 @@ namespace osu.Game.Online.Spectator
|
|||||||
{
|
{
|
||||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||||
|
|
||||||
if (watchingUsers.Contains(userId))
|
if (watchedUsers.Contains(userId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
watchingUsers.Add(userId);
|
watchedUsers.Add(userId);
|
||||||
|
|
||||||
WatchUserInternal(userId);
|
WatchUserInternal(userId);
|
||||||
}
|
}
|
||||||
@ -198,8 +214,8 @@ namespace osu.Game.Online.Spectator
|
|||||||
// Todo: This should not be a thing, but requires framework changes.
|
// Todo: This should not be a thing, but requires framework changes.
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
watchingUsers.Remove(userId);
|
watchedUsers.Remove(userId);
|
||||||
playingUserStates.Remove(userId);
|
watchedUserStates.Remove(userId);
|
||||||
StopWatchingUserInternal(userId);
|
StopWatchingUserInternal(userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,17 @@ namespace osu.Game.Online.Spectator
|
|||||||
[Key(2)]
|
[Key(2)]
|
||||||
public IEnumerable<APIMod> Mods { get; set; } = Enumerable.Empty<APIMod>();
|
public IEnumerable<APIMod> Mods { get; set; } = Enumerable.Empty<APIMod>();
|
||||||
|
|
||||||
|
[Key(3)]
|
||||||
|
public SpectatedUserState State { get; set; }
|
||||||
|
|
||||||
public bool Equals(SpectatorState other)
|
public bool Equals(SpectatorState other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(null, other)) return false;
|
if (ReferenceEquals(null, other)) return false;
|
||||||
if (ReferenceEquals(this, other)) return true;
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
|
||||||
return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID;
|
return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}";
|
public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,7 @@ namespace osu.Game
|
|||||||
Scheduler.AddDelayed(GracefullyExit, 2000);
|
Scheduler.AddDelayed(GracefullyExit, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Migrate(string path)
|
public bool Migrate(string path)
|
||||||
{
|
{
|
||||||
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
|
Logger.Log($@"Migrating osu! data from ""{Storage.GetFullPath(string.Empty)}"" to ""{path}""...");
|
||||||
|
|
||||||
@ -432,14 +432,15 @@ namespace osu.Game
|
|||||||
|
|
||||||
readyToRun.Wait();
|
readyToRun.Wait();
|
||||||
|
|
||||||
(Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
|
||||||
|
|
||||||
|
Logger.Log(@"Migration complete!");
|
||||||
|
return cleanupSucceded != false;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
realmBlocker?.Dispose();
|
realmBlocker?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log(@"Migration complete!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -52,21 +53,24 @@ namespace osu.Game.Overlays.Dashboard
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
playingUsers.BindTo(spectatorClient.PlayingUsers);
|
playingUsers.BindTo(spectatorClient.PlayingUsers);
|
||||||
playingUsers.BindCollectionChanged(onUsersChanged, true);
|
playingUsers.BindCollectionChanged(onPlayingUsersChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() =>
|
private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() =>
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add:
|
case NotifyCollectionChangedAction.Add:
|
||||||
foreach (int id in e.NewItems.OfType<int>().ToArray())
|
Debug.Assert(e.NewItems != null);
|
||||||
|
|
||||||
|
foreach (int userId in e.NewItems)
|
||||||
{
|
{
|
||||||
users.GetUserAsync(id).ContinueWith(task =>
|
users.GetUserAsync(userId).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
var user = task.GetResultSafely();
|
var user = task.GetResultSafely();
|
||||||
|
|
||||||
if (user == null) return;
|
if (user == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -82,12 +86,10 @@ namespace osu.Game.Overlays.Dashboard
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NotifyCollectionChangedAction.Remove:
|
case NotifyCollectionChangedAction.Remove:
|
||||||
foreach (int u in e.OldItems.OfType<int>())
|
Debug.Assert(e.OldItems != null);
|
||||||
userFlow.FirstOrDefault(card => card.User.Id == u)?.Expire();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NotifyCollectionChangedAction.Reset:
|
foreach (int userId in e.OldItems)
|
||||||
userFlow.Clear();
|
userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -46,6 +46,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
|||||||
case ScoreType.Recent:
|
case ScoreType.Recent:
|
||||||
return user.ScoresRecentCount;
|
return user.ScoresRecentCount;
|
||||||
|
|
||||||
|
case ScoreType.Pinned:
|
||||||
|
return user.ScoresPinnedCount;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections
|
|||||||
{
|
{
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
|
new PaginatedScoreContainer(ScoreType.Pinned, User, UsersStrings.ShowExtraTopRanksPinnedTitle),
|
||||||
new PaginatedScoreContainer(ScoreType.Best, User, UsersStrings.ShowExtraTopRanksBestTitle),
|
new PaginatedScoreContainer(ScoreType.Best, User, UsersStrings.ShowExtraTopRanksBestTitle),
|
||||||
new PaginatedScoreContainer(ScoreType.Firsts, User, UsersStrings.ShowExtraTopRanksFirstTitle)
|
new PaginatedScoreContainer(ScoreType.Firsts, User, UsersStrings.ShowExtraTopRanksFirstTitle)
|
||||||
};
|
};
|
||||||
|
@ -4,13 +4,16 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -23,6 +26,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private OsuGame game { get; set; }
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private NotificationOverlay notifications { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Storage storage { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
public override bool AllowBackButton => false;
|
public override bool AllowBackButton => false;
|
||||||
|
|
||||||
public override bool AllowExternalScreenChange => false;
|
public override bool AllowExternalScreenChange => false;
|
||||||
@ -84,17 +96,33 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
|
|
||||||
Beatmap.Value = Beatmap.Default;
|
Beatmap.Value = Beatmap.Default;
|
||||||
|
|
||||||
|
var originalStorage = new NativeStorage(storage.GetFullPath(string.Empty), host);
|
||||||
|
|
||||||
migrationTask = Task.Run(PerformMigration)
|
migrationTask = Task.Run(PerformMigration)
|
||||||
.ContinueWith(t =>
|
.ContinueWith(task =>
|
||||||
{
|
{
|
||||||
if (t.IsFaulted)
|
if (task.IsFaulted)
|
||||||
Logger.Log($"Error during migration: {t.Exception?.Message}", level: LogLevel.Error);
|
{
|
||||||
|
Logger.Error(task.Exception, $"Error during migration: {task.Exception?.Message}");
|
||||||
|
}
|
||||||
|
else if (!task.GetResultSafely())
|
||||||
|
{
|
||||||
|
notifications.Post(new SimpleNotification
|
||||||
|
{
|
||||||
|
Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.",
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
originalStorage.PresentExternally();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schedule(this.Exit);
|
Schedule(this.Exit);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void PerformMigration() => game?.Migrate(destination.FullName);
|
protected virtual bool PerformMigration() => game?.Migrate(destination.FullName) != false;
|
||||||
|
|
||||||
public override void OnEntering(IScreen last)
|
public override void OnEntering(IScreen last)
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets
|
|||||||
{
|
{
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
[Table(@"RulesetInfo")]
|
[Table(@"RulesetInfo")]
|
||||||
public sealed class EFRulesetInfo : IEquatable<EFRulesetInfo>, IRulesetInfo
|
public sealed class EFRulesetInfo : IEquatable<EFRulesetInfo>, IComparable<EFRulesetInfo>, IRulesetInfo
|
||||||
{
|
{
|
||||||
public int? ID { get; set; }
|
public int? ID { get; set; }
|
||||||
|
|
||||||
@ -42,7 +42,15 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
|
public bool Equals(EFRulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
|
||||||
|
|
||||||
public int CompareTo(RulesetInfo other) => OnlineID.CompareTo(other.OnlineID);
|
public int CompareTo(EFRulesetInfo other) => OnlineID.CompareTo(other.OnlineID);
|
||||||
|
|
||||||
|
public int CompareTo(IRulesetInfo other)
|
||||||
|
{
|
||||||
|
if (!(other is EFRulesetInfo ruleset))
|
||||||
|
throw new ArgumentException($@"Object is not of type {nameof(EFRulesetInfo)}.", nameof(other));
|
||||||
|
|
||||||
|
return CompareTo(ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo);
|
public override bool Equals(object obj) => obj is EFRulesetInfo rulesetInfo && Equals(rulesetInfo);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A representation of a ruleset's metadata.
|
/// A representation of a ruleset's metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRulesetInfo : IHasOnlineID<int>, IEquatable<IRulesetInfo>, IComparable<RulesetInfo>
|
public interface IRulesetInfo : IHasOnlineID<int>, IEquatable<IRulesetInfo>, IComparable<IRulesetInfo>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user-exposed name of this ruleset.
|
/// The user-exposed name of this ruleset.
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets
|
|||||||
{
|
{
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
[MapTo("Ruleset")]
|
[MapTo("Ruleset")]
|
||||||
public class RulesetInfo : RealmObject, IEquatable<RulesetInfo>, IRulesetInfo
|
public class RulesetInfo : RealmObject, IEquatable<RulesetInfo>, IComparable<RulesetInfo>, IRulesetInfo
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string ShortName { get; set; } = string.Empty;
|
public string ShortName { get; set; } = string.Empty;
|
||||||
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets
|
|||||||
return ShortName == other.ShortName;
|
return ShortName == other.ShortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(IRulesetInfo? other) => other is RulesetInfo b && Equals(b);
|
public bool Equals(IRulesetInfo? other) => other is RulesetInfo r && Equals(r);
|
||||||
|
|
||||||
public int CompareTo(RulesetInfo other)
|
public int CompareTo(RulesetInfo other)
|
||||||
{
|
{
|
||||||
@ -63,6 +63,14 @@ namespace osu.Game.Rulesets
|
|||||||
return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal);
|
return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CompareTo(IRulesetInfo other)
|
||||||
|
{
|
||||||
|
if (!(other is RulesetInfo ruleset))
|
||||||
|
throw new ArgumentException($@"Object is not of type {nameof(RulesetInfo)}.", nameof(other));
|
||||||
|
|
||||||
|
return CompareTo(ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
// Importantly, ignore the underlying realm hash code, as it will usually not match.
|
// Importantly, ignore the underlying realm hash code, as it will usually not match.
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public int RecordFrameRate = 60;
|
public int RecordFrameRate = 60;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -48,14 +48,13 @@ namespace osu.Game.Rulesets.UI
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
|
spectatorClient.BeginPlaying(gameplayState, target);
|
||||||
spectatorClient?.BeginPlaying(gameplayState, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
spectatorClient?.EndPlaying();
|
spectatorClient?.EndPlaying(gameplayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -851,7 +851,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
var difficultyItems = new List<MenuItem>();
|
var difficultyItems = new List<MenuItem>();
|
||||||
|
|
||||||
foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset.ShortName).OrderBy(group => group.Key))
|
foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.Ruleset).OrderBy(group => group.Key))
|
||||||
{
|
{
|
||||||
if (difficultyItems.Count > 0)
|
if (difficultyItems.Count > 0)
|
||||||
difficultyItems.Add(new EditorMenuItemSpacer());
|
difficultyItems.Add(new EditorMenuItemSpacer());
|
||||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
public class MultiplayerPlayerLoader : PlayerLoader
|
public class MultiplayerPlayerLoader : PlayerLoader
|
||||||
{
|
{
|
||||||
public bool GameplayPassed => player?.GameplayPassed == true;
|
public bool GameplayPassed => player?.GameplayState.HasPassed == true;
|
||||||
|
|
||||||
private Player player;
|
private Player player;
|
||||||
|
|
||||||
|
@ -207,15 +207,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserStateChanged(int userId, SpectatorState spectatorState)
|
protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState)
|
protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState)
|
||||||
=> instances.Single(i => i.UserId == userId).LoadScore(spectatorGameplayState.Score);
|
=> instances.Single(i => i.UserId == userId).LoadScore(spectatorGameplayState.Score);
|
||||||
|
|
||||||
protected override void EndGameplay(int userId)
|
protected override void EndGameplay(int userId, SpectatorState state)
|
||||||
{
|
{
|
||||||
|
// Allowed passed/failed users to complete their remaining replay frames.
|
||||||
|
// The failed state isn't really possible in multiplayer (yet?) but is added here just for safety in case it starts being used.
|
||||||
|
if (state.State == SpectatedUserState.Passed || state.State == SpectatedUserState.Failed)
|
||||||
|
return;
|
||||||
|
|
||||||
RemoveUser(userId);
|
RemoveUser(userId);
|
||||||
|
|
||||||
var instance = instances.Single(i => i.UserId == userId);
|
var instance = instances.Single(i => i.UserId == userId);
|
||||||
|
@ -39,6 +39,21 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Score Score;
|
public readonly Score Score;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether gameplay completed without the user failing.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasPassed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the user failed during gameplay.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasFailed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the user quit gameplay without having either passed or failed.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasQuit { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A bindable tracking the last judgement result applied to any hit object.
|
/// A bindable tracking the last judgement result applied to any hit object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -72,15 +72,8 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual bool PauseOnFocusLost => true;
|
protected virtual bool PauseOnFocusLost => true;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether gameplay has completed without the user having failed.
|
|
||||||
/// </summary>
|
|
||||||
public bool GameplayPassed { get; private set; }
|
|
||||||
|
|
||||||
public Action RestartRequested;
|
public Action RestartRequested;
|
||||||
|
|
||||||
public bool HasFailed { get; private set; }
|
|
||||||
|
|
||||||
private Bindable<bool> mouseWheelDisabled;
|
private Bindable<bool> mouseWheelDisabled;
|
||||||
|
|
||||||
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
||||||
@ -560,7 +553,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (showDialogFirst && !pauseOrFailDialogVisible)
|
if (showDialogFirst && !pauseOrFailDialogVisible)
|
||||||
{
|
{
|
||||||
// if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion).
|
// if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion).
|
||||||
if (ValidForResume && HasFailed)
|
if (ValidForResume && GameplayState.HasFailed)
|
||||||
{
|
{
|
||||||
failAnimationLayer.FinishTransforms(true);
|
failAnimationLayer.FinishTransforms(true);
|
||||||
return;
|
return;
|
||||||
@ -679,7 +672,7 @@ namespace osu.Game.Screens.Play
|
|||||||
resultsDisplayDelegate?.Cancel();
|
resultsDisplayDelegate?.Cancel();
|
||||||
resultsDisplayDelegate = null;
|
resultsDisplayDelegate = null;
|
||||||
|
|
||||||
GameplayPassed = false;
|
GameplayState.HasPassed = false;
|
||||||
ValidForResume = true;
|
ValidForResume = true;
|
||||||
skipOutroOverlay.Hide();
|
skipOutroOverlay.Hide();
|
||||||
return;
|
return;
|
||||||
@ -689,7 +682,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (HealthProcessor.HasFailed)
|
if (HealthProcessor.HasFailed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GameplayPassed = true;
|
GameplayState.HasPassed = true;
|
||||||
|
|
||||||
// Setting this early in the process means that even if something were to go wrong in the order of events following, there
|
// Setting this early in the process means that even if something were to go wrong in the order of events following, there
|
||||||
// is no chance that a user could return to the (already completed) Player instance from a child screen.
|
// is no chance that a user could return to the (already completed) Player instance from a child screen.
|
||||||
@ -805,7 +798,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!CheckModsAllowFailure())
|
if (!CheckModsAllowFailure())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
HasFailed = true;
|
GameplayState.HasFailed = true;
|
||||||
Score.ScoreInfo.Passed = false;
|
Score.ScoreInfo.Passed = false;
|
||||||
|
|
||||||
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
|
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
|
||||||
@ -860,13 +853,13 @@ namespace osu.Game.Screens.Play
|
|||||||
// replays cannot be paused and exit immediately
|
// replays cannot be paused and exit immediately
|
||||||
&& !DrawableRuleset.HasReplayLoaded.Value
|
&& !DrawableRuleset.HasReplayLoaded.Value
|
||||||
// cannot pause if we are already in a fail state
|
// cannot pause if we are already in a fail state
|
||||||
&& !HasFailed;
|
&& !GameplayState.HasFailed;
|
||||||
|
|
||||||
private bool canResume =>
|
private bool canResume =>
|
||||||
// cannot resume from a non-paused state
|
// cannot resume from a non-paused state
|
||||||
GameplayClockContainer.IsPaused.Value
|
GameplayClockContainer.IsPaused.Value
|
||||||
// cannot resume if we are already in a fail state
|
// cannot resume if we are already in a fail state
|
||||||
&& !HasFailed
|
&& !GameplayState.HasFailed
|
||||||
// already resuming
|
// already resuming
|
||||||
&& !IsResuming;
|
&& !IsResuming;
|
||||||
|
|
||||||
@ -991,6 +984,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
|
if (!GameplayState.HasPassed && !GameplayState.HasFailed)
|
||||||
|
GameplayState.HasQuit = true;
|
||||||
|
|
||||||
screenSuspension?.RemoveAndDisposeImmediately();
|
screenSuspension?.RemoveAndDisposeImmediately();
|
||||||
failAnimationLayer?.RemoveFilters();
|
failAnimationLayer?.RemoveFilters();
|
||||||
|
|
||||||
@ -1005,7 +1001,7 @@ namespace osu.Game.Screens.Play
|
|||||||
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
|
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
|
||||||
// To resolve test failures, forcefully end playing synchronously when this screen exits.
|
// To resolve test failures, forcefully end playing synchronously when this screen exits.
|
||||||
// Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method.
|
// Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method.
|
||||||
spectatorClient.EndPlaying();
|
spectatorClient.EndPlaying(GameplayState);
|
||||||
|
|
||||||
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
|
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
|
||||||
// as we are no longer the current screen, we cannot guarantee the track is still usable.
|
// as we are no longer the current screen, we cannot guarantee the track is still usable.
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play
|
|||||||
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token)
|
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token)
|
||||||
{
|
{
|
||||||
Debug.Assert(Room.RoomID.Value != null);
|
Debug.Assert(Room.RoomID.Value != null);
|
||||||
return new SubmitRoomScoreRequest(token, Room.RoomID.Value.Value, PlaylistItem.ID, score.ScoreInfo);
|
return new SubmitRoomScoreRequest(score.ScoreInfo, token, Room.RoomID.Value.Value, PlaylistItem.ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
Debug.Assert(beatmap.OnlineID > 0);
|
Debug.Assert(beatmap.OnlineID > 0);
|
||||||
|
|
||||||
return new SubmitSoloScoreRequest(beatmap.OnlineID, token, score.ScoreInfo);
|
return new SubmitSoloScoreRequest(score.ScoreInfo, token, beatmap.OnlineID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play
|
|||||||
automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload());
|
automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserStateChanged(int userId, SpectatorState spectatorState)
|
protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState)
|
||||||
{
|
{
|
||||||
clearDisplay();
|
clearDisplay();
|
||||||
showBeatmapPanel(spectatorState);
|
showBeatmapPanel(spectatorState);
|
||||||
@ -180,7 +180,7 @@ namespace osu.Game.Screens.Play
|
|||||||
scheduleStart(spectatorGameplayState);
|
scheduleStart(spectatorGameplayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void EndGameplay(int userId)
|
protected override void EndGameplay(int userId, SpectatorState state)
|
||||||
{
|
{
|
||||||
scheduledStart?.Cancel();
|
scheduledStart?.Cancel();
|
||||||
immediateSpectatorGameplayState = null;
|
immediateSpectatorGameplayState = null;
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
var beatmaps = carouselSet.Beatmaps.ToList();
|
var beatmaps = carouselSet.Beatmaps.ToList();
|
||||||
|
|
||||||
return beatmaps.Count > maximum_difficulty_icons
|
return beatmaps.Count > maximum_difficulty_icons
|
||||||
? (IEnumerable<DifficultyIcon>)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset.ShortName)
|
? (IEnumerable<DifficultyIcon>)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset)
|
||||||
.Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset))
|
.Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset))
|
||||||
: beatmaps.Select(b => new FilterableDifficultyIcon(b));
|
: beatmaps.Select(b => new FilterableDifficultyIcon(b));
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ 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 IBindableDictionary<int, SpectatorState> userStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
|
||||||
private readonly Dictionary<int, APIUser> userMap = new Dictionary<int, APIUser>();
|
private readonly Dictionary<int, APIUser> userMap = new Dictionary<int, APIUser>();
|
||||||
private readonly Dictionary<int, SpectatorGameplayState> gameplayStates = new Dictionary<int, SpectatorGameplayState>();
|
private readonly Dictionary<int, SpectatorGameplayState> gameplayStates = new Dictionary<int, SpectatorGameplayState>();
|
||||||
@ -77,8 +77,8 @@ namespace osu.Game.Screens.Spectate
|
|||||||
userMap[u.Id] = u;
|
userMap[u.Id] = u;
|
||||||
}
|
}
|
||||||
|
|
||||||
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
|
userStates.BindTo(spectatorClient.WatchedUserStates);
|
||||||
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
|
userStates.BindCollectionChanged(onUserStatesChanged, true);
|
||||||
|
|
||||||
realmSubscription = realm.RegisterForNotifications(
|
realmSubscription = realm.RegisterForNotifications(
|
||||||
realm => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
|
realm => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
|
||||||
@ -99,51 +99,55 @@ namespace osu.Game.Screens.Spectate
|
|||||||
{
|
{
|
||||||
foreach ((int userId, _) in userMap)
|
foreach ((int userId, _) in userMap)
|
||||||
{
|
{
|
||||||
if (!playingUserStates.TryGetValue(userId, out var userState))
|
if (!userStates.TryGetValue(userId, out var userState))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID))
|
if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID))
|
||||||
updateGameplayState(userId);
|
startGameplay(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPlayingUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
|
private void onUserStatesChanged(object sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case NotifyDictionaryChangedAction.Add:
|
case NotifyDictionaryChangedAction.Add:
|
||||||
|
case NotifyDictionaryChangedAction.Replace:
|
||||||
foreach ((int userId, var state) in e.NewItems.AsNonNull())
|
foreach ((int userId, var state) in e.NewItems.AsNonNull())
|
||||||
onUserStateAdded(userId, state);
|
onUserStateChanged(userId, state);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NotifyDictionaryChangedAction.Remove:
|
case NotifyDictionaryChangedAction.Remove:
|
||||||
foreach ((int userId, var _) in e.OldItems.AsNonNull())
|
foreach ((int userId, SpectatorState state) in e.OldItems.AsNonNull())
|
||||||
onUserStateRemoved(userId);
|
onUserStateRemoved(userId, state);
|
||||||
break;
|
|
||||||
|
|
||||||
case NotifyDictionaryChangedAction.Replace:
|
|
||||||
foreach ((int userId, var _) in e.OldItems.AsNonNull())
|
|
||||||
onUserStateRemoved(userId);
|
|
||||||
|
|
||||||
foreach ((int userId, var state) in e.NewItems.AsNonNull())
|
|
||||||
onUserStateAdded(userId, state);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUserStateAdded(int userId, SpectatorState state)
|
private void onUserStateChanged(int userId, SpectatorState newState)
|
||||||
{
|
{
|
||||||
if (state.RulesetID == null || state.BeatmapID == null)
|
if (newState.RulesetID == null || newState.BeatmapID == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!userMap.ContainsKey(userId))
|
if (!userMap.ContainsKey(userId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Schedule(() => OnUserStateChanged(userId, state));
|
switch (newState.State)
|
||||||
updateGameplayState(userId);
|
{
|
||||||
|
case SpectatedUserState.Passed:
|
||||||
|
// Make sure that gameplay completes to the end.
|
||||||
|
if (gameplayStates.TryGetValue(userId, out var gameplayState))
|
||||||
|
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SpectatedUserState.Playing:
|
||||||
|
Schedule(() => OnNewPlayingUserState(userId, newState));
|
||||||
|
startGameplay(userId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUserStateRemoved(int userId)
|
private void onUserStateRemoved(int userId, SpectatorState state)
|
||||||
{
|
{
|
||||||
if (!userMap.ContainsKey(userId))
|
if (!userMap.ContainsKey(userId))
|
||||||
return;
|
return;
|
||||||
@ -154,15 +158,15 @@ namespace osu.Game.Screens.Spectate
|
|||||||
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
gameplayState.Score.Replay.HasReceivedAllFrames = true;
|
||||||
|
|
||||||
gameplayStates.Remove(userId);
|
gameplayStates.Remove(userId);
|
||||||
Schedule(() => EndGameplay(userId));
|
Schedule(() => EndGameplay(userId, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGameplayState(int userId)
|
private void startGameplay(int userId)
|
||||||
{
|
{
|
||||||
Debug.Assert(userMap.ContainsKey(userId));
|
Debug.Assert(userMap.ContainsKey(userId));
|
||||||
|
|
||||||
var user = userMap[userId];
|
var user = userMap[userId];
|
||||||
var spectatorState = playingUserStates[userId];
|
var spectatorState = userStates[userId];
|
||||||
|
|
||||||
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == spectatorState.RulesetID)?.CreateInstance();
|
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.OnlineID == spectatorState.RulesetID)?.CreateInstance();
|
||||||
if (resolvedRuleset == null)
|
if (resolvedRuleset == null)
|
||||||
@ -191,11 +195,11 @@ namespace osu.Game.Screens.Spectate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a spectated user's state has changed.
|
/// Invoked when a spectated user's state has changed to a new state indicating the player is currently playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user whose state has changed.</param>
|
/// <param name="userId">The user whose state has changed.</param>
|
||||||
/// <param name="spectatorState">The new state.</param>
|
/// <param name="spectatorState">The new state.</param>
|
||||||
protected abstract void OnUserStateChanged(int userId, [NotNull] SpectatorState spectatorState);
|
protected abstract void OnNewPlayingUserState(int userId, [NotNull] SpectatorState spectatorState);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts gameplay for a user.
|
/// Starts gameplay for a user.
|
||||||
@ -208,7 +212,8 @@ namespace osu.Game.Screens.Spectate
|
|||||||
/// Ends gameplay for a user.
|
/// Ends gameplay for a user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user to end gameplay for.</param>
|
/// <param name="userId">The user to end gameplay for.</param>
|
||||||
protected abstract void EndGameplay(int userId);
|
/// <param name="state">The final user state.</param>
|
||||||
|
protected abstract void EndGameplay(int userId, SpectatorState state);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops spectating a user.
|
/// Stops spectating a user.
|
||||||
@ -216,7 +221,10 @@ 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)
|
||||||
{
|
{
|
||||||
onUserStateRemoved(userId);
|
if (!userStates.TryGetValue(userId, out var state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
onUserStateRemoved(userId, state);
|
||||||
|
|
||||||
users.Remove(userId);
|
users.Remove(userId);
|
||||||
userMap.Remove(userId);
|
userMap.Remove(userId);
|
||||||
|
@ -58,7 +58,8 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
/// Ends play for an arbitrary user.
|
/// Ends play for an arbitrary user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user to end play for.</param>
|
/// <param name="userId">The user to end play for.</param>
|
||||||
public void EndPlay(int userId)
|
/// <param name="state">The spectator state to end play with.</param>
|
||||||
|
public void EndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit)
|
||||||
{
|
{
|
||||||
if (!userBeatmapDictionary.ContainsKey(userId))
|
if (!userBeatmapDictionary.ContainsKey(userId))
|
||||||
return;
|
return;
|
||||||
@ -67,6 +68,7 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
{
|
{
|
||||||
BeatmapID = userBeatmapDictionary[userId],
|
BeatmapID = userBeatmapDictionary[userId],
|
||||||
RulesetID = 0,
|
RulesetID = 0,
|
||||||
|
State = state
|
||||||
});
|
});
|
||||||
|
|
||||||
userBeatmapDictionary.Remove(userId);
|
userBeatmapDictionary.Remove(userId);
|
||||||
@ -142,6 +144,7 @@ namespace osu.Game.Tests.Visual.Spectator
|
|||||||
{
|
{
|
||||||
BeatmapID = userBeatmapDictionary[userId],
|
BeatmapID = userBeatmapDictionary[userId],
|
||||||
RulesetID = 0,
|
RulesetID = 0,
|
||||||
|
State = SpectatedUserState.Playing
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.8.0" />
|
<PackageReference Include="Realm" Version="10.8.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2022.204.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2022.204.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.204.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.13.0" />
|
<PackageReference Include="Sentry" Version="3.13.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.204.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.204.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.204.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user