mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 21:43:04 +08:00
Merge branch 'master' into perform-from-subscreen-support
This commit is contained in:
commit
8037b101cb
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.220.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -17,7 +17,7 @@ using osu.Game.Database;
|
||||
|
||||
namespace osu.Android
|
||||
{
|
||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
|
||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]
|
||||
|
@ -105,7 +105,7 @@ namespace osu.Desktop
|
||||
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
|
||||
presence.Assets.LargeImageText = string.Empty;
|
||||
else
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
||||
|
||||
// update ruleset
|
||||
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
|
||||
|
@ -4,6 +4,7 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(5.0565038923984691d, "diffcalc-test")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new CatchModDoubleTime());
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(2.7646128945056723d, "diffcalc-test")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new ManiaModDoubleTime());
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
@ -19,6 +20,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(8.6228371119393064d, "diffcalc-test")]
|
||||
[TestCase(1.2864585434597433d, "zero-length-sliders")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new OsuModDoubleTime());
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
[TestCase(3.1473940254109078d, "diffcalc-test")]
|
||||
[TestCase(3.1473940254109078d, "diffcalc-test-strong")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new TaikoModDoubleTime());
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
|
@ -118,9 +118,9 @@ namespace osu.Game.Tests.Rulesets
|
||||
public BindableNumber<double> Frequency => throw new NotImplementedException();
|
||||
public BindableNumber<double> Tempo => throw new NotImplementedException();
|
||||
|
||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
|
||||
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotImplementedException();
|
||||
|
||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
|
||||
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotImplementedException();
|
||||
|
||||
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException();
|
||||
|
||||
|
@ -56,9 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
pauseAndConfirm();
|
||||
resume();
|
||||
|
||||
confirmClockRunning(false);
|
||||
confirmPauseOverlayShown(false);
|
||||
|
||||
confirmPausedWithNoOverlay();
|
||||
AddStep("click to resume", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
confirmClockRunning(true);
|
||||
@ -71,15 +69,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
|
||||
|
||||
pauseAndConfirm();
|
||||
|
||||
resume();
|
||||
confirmClockRunning(false);
|
||||
confirmPauseOverlayShown(false);
|
||||
|
||||
confirmPausedWithNoOverlay();
|
||||
pauseAndConfirm();
|
||||
|
||||
AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden);
|
||||
confirmPaused();
|
||||
confirmNotExited();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -94,33 +91,54 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseTooSoon()
|
||||
public void TestUserPauseWhenPauseNotAllowed()
|
||||
{
|
||||
AddStep("disable pause support", () => Player.Configuration.AllowPause = false);
|
||||
|
||||
pauseFromUserExitKey();
|
||||
confirmExited();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserPauseDuringCooldownTooSoon()
|
||||
{
|
||||
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
|
||||
|
||||
pauseAndConfirm();
|
||||
|
||||
resume();
|
||||
pause();
|
||||
pauseFromUserExitKey();
|
||||
|
||||
confirmClockRunning(true);
|
||||
confirmPauseOverlayShown(false);
|
||||
confirmResumed();
|
||||
confirmNotExited();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitTooSoon()
|
||||
public void TestQuickExitDuringCooldownTooSoon()
|
||||
{
|
||||
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
|
||||
|
||||
pauseAndConfirm();
|
||||
|
||||
resume();
|
||||
AddStep("pause via exit key", () => Player.ExitViaQuickExit());
|
||||
|
||||
confirmResumed();
|
||||
AddAssert("exited", () => !Player.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitSoonAfterResumeSucceeds()
|
||||
{
|
||||
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
|
||||
|
||||
pauseAndConfirm();
|
||||
resume();
|
||||
|
||||
AddStep("exit too soon", () => Player.Exit());
|
||||
AddStep("exit quick", () => Player.Exit());
|
||||
|
||||
confirmClockRunning(true);
|
||||
confirmPauseOverlayShown(false);
|
||||
|
||||
AddAssert("not exited", () => Player.IsCurrentScreen());
|
||||
confirmResumed();
|
||||
AddAssert("exited", () => !Player.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -131,22 +149,37 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
confirmClockRunning(false);
|
||||
|
||||
pause();
|
||||
|
||||
confirmClockRunning(false);
|
||||
confirmPauseOverlayShown(false);
|
||||
AddStep("pause via forced pause", () => Player.Pause());
|
||||
|
||||
confirmPausedWithNoOverlay();
|
||||
AddAssert("fail overlay still shown", () => Player.FailOverlayVisible);
|
||||
|
||||
exitAndConfirm();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitFromFailedGameplay()
|
||||
public void TestExitFromFailedGameplayAfterFailAnimation()
|
||||
{
|
||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||
AddStep("exit", () => Player.Exit());
|
||||
AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible);
|
||||
|
||||
confirmClockRunning(false);
|
||||
|
||||
AddStep("exit via user pause", () => Player.ExitViaPause());
|
||||
confirmExited();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitFromFailedGameplayDuringFailAnimation()
|
||||
{
|
||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||
|
||||
// will finish the fail animation and show the fail/pause screen.
|
||||
AddStep("attempt exit via pause key", () => Player.ExitViaPause());
|
||||
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
|
||||
|
||||
// will actually exit.
|
||||
AddStep("exit via pause key", () => Player.ExitViaPause());
|
||||
confirmExited();
|
||||
}
|
||||
|
||||
@ -245,7 +278,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void pauseAndConfirm()
|
||||
{
|
||||
pause();
|
||||
pauseFromUserExitKey();
|
||||
confirmPaused();
|
||||
}
|
||||
|
||||
@ -257,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void exitAndConfirm()
|
||||
{
|
||||
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
|
||||
confirmNotExited();
|
||||
AddStep("exit", () => Player.Exit());
|
||||
confirmExited();
|
||||
confirmNoTrackAdjustments();
|
||||
@ -266,7 +299,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private void confirmPaused()
|
||||
{
|
||||
confirmClockRunning(false);
|
||||
AddAssert("player not exited", () => Player.IsCurrentScreen());
|
||||
confirmNotExited();
|
||||
AddAssert("player not failed", () => !Player.HasFailed);
|
||||
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
|
||||
}
|
||||
@ -277,18 +310,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
confirmPauseOverlayShown(false);
|
||||
}
|
||||
|
||||
private void confirmExited()
|
||||
private void confirmPausedWithNoOverlay()
|
||||
{
|
||||
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
|
||||
confirmClockRunning(false);
|
||||
confirmPauseOverlayShown(false);
|
||||
}
|
||||
|
||||
private void confirmExited() => AddUntilStep("player exited", () => !Player.IsCurrentScreen());
|
||||
private void confirmNotExited() => AddAssert("player not exited", () => Player.IsCurrentScreen());
|
||||
|
||||
private void confirmNoTrackAdjustments()
|
||||
{
|
||||
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
|
||||
}
|
||||
|
||||
private void restart() => AddStep("restart", () => Player.Restart());
|
||||
private void pause() => AddStep("pause", () => Player.Pause());
|
||||
private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause());
|
||||
private void resume() => AddStep("resume", () => Player.Resume());
|
||||
|
||||
private void confirmPauseOverlayShown(bool isShown) =>
|
||||
@ -307,6 +344,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
|
||||
|
||||
public void ExitViaPause() => PerformExit(true);
|
||||
|
||||
public void ExitViaQuickExit() => PerformExit(false);
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
@ -50,6 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result);
|
||||
|
||||
AddStep("create leaderboard", () =>
|
||||
{
|
||||
leaderboard?.Expire();
|
||||
@ -85,6 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public void TestScoreUpdates()
|
||||
{
|
||||
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100);
|
||||
AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@ -155,7 +156,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Id = i,
|
||||
Username = $"User {i}",
|
||||
CurrentModeRank = RNG.Next(1, 100000),
|
||||
RulesetsStatistics = new Dictionary<string, UserStatistics>
|
||||
{
|
||||
{
|
||||
Ruleset.Value.ShortName,
|
||||
new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
|
||||
}
|
||||
},
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
});
|
||||
|
||||
@ -193,7 +200,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Id = 0,
|
||||
Username = "User 0",
|
||||
CurrentModeRank = RNG.Next(1, 100000),
|
||||
RulesetsStatistics = new Dictionary<string, UserStatistics>
|
||||
{
|
||||
{
|
||||
Ruleset.Value.ShortName,
|
||||
new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
|
||||
}
|
||||
},
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
});
|
||||
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
@ -115,6 +116,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
|
||||
|
||||
public new Bindable<IReadOnlyList<Mod>> SelectedMods => base.SelectedMods;
|
||||
|
||||
// if we don't do this, when running under nUnit the version that gets populated is that of nUnit.
|
||||
public override string Version => "test game";
|
||||
|
||||
|
@ -11,7 +11,9 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
@ -41,6 +43,30 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
exitViaEscapeAndConfirm();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetryFromResults()
|
||||
{
|
||||
Player player = null;
|
||||
ResultsScreen results = null;
|
||||
|
||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
PushAndConfirm(() => new TestSongSelect());
|
||||
|
||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("set autoplay", () => Game.SelectedMods.Value = new[] { new OsuModAutoplay() });
|
||||
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
|
||||
AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length));
|
||||
AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded);
|
||||
AddStep("attempt to retry", () => results.ChildrenOfType<HotkeyRetryOverlay>().First().Action());
|
||||
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestSongContinuesAfterExitPlayer(bool withUserPause)
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 123456 },
|
||||
GlobalRank = 123456,
|
||||
PP = 12345,
|
||||
};
|
||||
});
|
||||
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 89000 },
|
||||
GlobalRank = 89000,
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 89000 },
|
||||
GlobalRank = 89000,
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 12000 },
|
||||
GlobalRank = 12000,
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
graph.Statistics.Value = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 12000 },
|
||||
GlobalRank = 12000,
|
||||
PP = 12345,
|
||||
RankHistory = new User.RankHistoryData
|
||||
{
|
||||
|
@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
ProfileOrder = new[] { "me" },
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 },
|
||||
GlobalRank = 2148,
|
||||
CountryRank = 1,
|
||||
PP = 4567.89m,
|
||||
Level = new UserStatistics.LevelInfo
|
||||
{
|
||||
|
@ -113,11 +113,11 @@ namespace osu.Game.Tournament.Tests
|
||||
},
|
||||
Players =
|
||||
{
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 12 } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 16 } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 20 } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 24 } },
|
||||
new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 30 } },
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Models
|
||||
{
|
||||
get
|
||||
{
|
||||
var ranks = Players.Select(p => p.Statistics?.Ranks.Global)
|
||||
var ranks = Players.Select(p => p.Statistics?.GlobalRank)
|
||||
.Where(i => i.HasValue)
|
||||
.Select(i => i.Value)
|
||||
.ToArray();
|
||||
|
@ -250,7 +250,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
||||
};
|
||||
|
||||
foreach (var p in team.Players)
|
||||
fill.Add(new RowDisplay(p.Username, p.Statistics?.Ranks.Global?.ToString("\\##,0") ?? "-"));
|
||||
fill.Add(new RowDisplay(p.Username, p.Statistics?.GlobalRank?.ToString("\\##,0") ?? "-"));
|
||||
}
|
||||
|
||||
internal class RowDisplay : CompositeDrawable
|
||||
|
@ -150,9 +150,9 @@ namespace osu.Game.Tournament
|
||||
{
|
||||
foreach (var p in t.Players)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p.Username) || p.Statistics == null)
|
||||
if (string.IsNullOrEmpty(p.Username) || p.Statistics?.GlobalRank == null)
|
||||
{
|
||||
PopulateUser(p);
|
||||
PopulateUser(p, immediate: true);
|
||||
addedInfo = true;
|
||||
}
|
||||
}
|
||||
@ -211,12 +211,14 @@ namespace osu.Game.Tournament
|
||||
return addedInfo;
|
||||
}
|
||||
|
||||
public void PopulateUser(User user, Action success = null, Action failure = null)
|
||||
public void PopulateUser(User user, Action success = null, Action failure = null, bool immediate = false)
|
||||
{
|
||||
var req = new GetUserRequest(user.Id, Ruleset.Value);
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
user.Id = res.Id;
|
||||
|
||||
user.Username = res.Username;
|
||||
user.Statistics = res.Statistics;
|
||||
user.Country = res.Country;
|
||||
@ -231,7 +233,10 @@ namespace osu.Game.Tournament
|
||||
failure?.Invoke();
|
||||
};
|
||||
|
||||
API.Queue(req);
|
||||
if (immediate)
|
||||
API.Perform(req);
|
||||
else
|
||||
API.Queue(req);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -29,6 +29,14 @@ namespace osu.Game.Collections
|
||||
/// </summary>
|
||||
protected virtual bool ShowManageCollectionsItem => true;
|
||||
|
||||
private readonly BindableWithCurrent<CollectionFilterMenuItem> current = new BindableWithCurrent<CollectionFilterMenuItem>();
|
||||
|
||||
public new Bindable<CollectionFilterMenuItem> Current
|
||||
{
|
||||
get => current.Current;
|
||||
set => current.Current = value;
|
||||
}
|
||||
|
||||
private readonly IBindableList<BeatmapCollection> collections = new BindableList<BeatmapCollection>();
|
||||
private readonly IBindableList<BeatmapInfo> beatmaps = new BindableList<BeatmapInfo>();
|
||||
private readonly BindableList<CollectionFilterMenuItem> filters = new BindableList<CollectionFilterMenuItem>();
|
||||
@ -36,25 +44,28 @@ namespace osu.Game.Collections
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private CollectionManager collectionManager { get; set; }
|
||||
|
||||
public CollectionFilterDropdown()
|
||||
{
|
||||
ItemSource = filters;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load([CanBeNull] CollectionManager collectionManager)
|
||||
{
|
||||
if (collectionManager != null)
|
||||
collections.BindTo(collectionManager.Collections);
|
||||
|
||||
collections.CollectionChanged += (_, __) => collectionsChanged();
|
||||
collectionsChanged();
|
||||
Current.Value = new AllBeatmapsCollectionFilterMenuItem();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (collectionManager != null)
|
||||
collections.BindTo(collectionManager.Collections);
|
||||
|
||||
// Dropdown has logic which triggers a change on the bindable with every change to the contained items.
|
||||
// This is not desirable here, as it leads to multiple filter operations running even though nothing has changed.
|
||||
// An extra bindable is enough to subvert this behaviour.
|
||||
base.Current = Current;
|
||||
|
||||
collections.BindCollectionChanged((_, __) => collectionsChanged(), true);
|
||||
Current.BindValueChanged(filterChanged, true);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
@ -9,7 +10,7 @@ namespace osu.Game.Collections
|
||||
/// <summary>
|
||||
/// A <see cref="BeatmapCollection"/> filter.
|
||||
/// </summary>
|
||||
public class CollectionFilterMenuItem
|
||||
public class CollectionFilterMenuItem : IEquatable<CollectionFilterMenuItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// The collection to filter beatmaps from.
|
||||
@ -33,6 +34,11 @@ namespace osu.Game.Collections
|
||||
Collection = collection;
|
||||
CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable<string>("All beatmaps");
|
||||
}
|
||||
|
||||
public bool Equals(CollectionFilterMenuItem other)
|
||||
=> other != null && CollectionName.Value == other.CollectionName.Value;
|
||||
|
||||
public override int GetHashCode() => CollectionName.Value.GetHashCode();
|
||||
}
|
||||
|
||||
public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem
|
||||
|
@ -139,35 +139,43 @@ namespace osu.Game.Collections
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
var collection = readCollections(stream, notification);
|
||||
bool importCompleted = false;
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
importCollections(collection);
|
||||
importCompleted = true;
|
||||
});
|
||||
|
||||
while (!IsDisposed && !importCompleted)
|
||||
await Task.Delay(10);
|
||||
await importCollections(collection);
|
||||
|
||||
notification.CompletionText = $"Imported {collection.Count} collections";
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
||||
private void importCollections(List<BeatmapCollection> newCollections)
|
||||
private Task importCollections(List<BeatmapCollection> newCollections)
|
||||
{
|
||||
foreach (var newCol in newCollections)
|
||||
{
|
||||
var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
|
||||
if (existing == null)
|
||||
Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
foreach (var newBeatmap in newCol.Beatmaps)
|
||||
Schedule(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!existing.Beatmaps.Contains(newBeatmap))
|
||||
existing.Beatmaps.Add(newBeatmap);
|
||||
foreach (var newCol in newCollections)
|
||||
{
|
||||
var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
|
||||
if (existing == null)
|
||||
Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });
|
||||
|
||||
foreach (var newBeatmap in newCol.Beatmaps)
|
||||
{
|
||||
if (!existing.Beatmaps.Contains(newBeatmap))
|
||||
existing.Beatmaps.Add(newBeatmap);
|
||||
}
|
||||
}
|
||||
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Failed to import collection.");
|
||||
tcs.SetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private List<BeatmapCollection> readCollections(Stream stream, ProgressNotification notification = null)
|
||||
|
@ -16,12 +16,12 @@ namespace osu.Game.Configuration
|
||||
[Description("Hit Error (right)")]
|
||||
HitErrorRight,
|
||||
|
||||
[Description("Hit Error (bottom)")]
|
||||
HitErrorBottom,
|
||||
|
||||
[Description("Hit Error (left+right)")]
|
||||
HitErrorBoth,
|
||||
|
||||
[Description("Hit Error (bottom)")]
|
||||
HitErrorBottom,
|
||||
|
||||
[Description("Colour (left)")]
|
||||
ColourLeft,
|
||||
|
||||
|
@ -36,18 +36,23 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(5)]
|
||||
public IEnumerable<APIMod> AllowedMods { get; set; } = Enumerable.Empty<APIMod>();
|
||||
|
||||
[Key(6)]
|
||||
public long PlaylistItemId { get; set; }
|
||||
|
||||
public bool Equals(MultiplayerRoomSettings other)
|
||||
=> BeatmapID == other.BeatmapID
|
||||
&& BeatmapChecksum == other.BeatmapChecksum
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||
&& RulesetID == other.RulesetID
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal);
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||
&& PlaylistItemId == other.PlaylistItemId;
|
||||
|
||||
public override string ToString() => $"Name:{Name}"
|
||||
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
|
||||
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
|
||||
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
|
||||
+ $" Ruleset:{RulesetID}";
|
||||
+ $" Ruleset:{RulesetID}"
|
||||
+ $" Item:{PlaylistItemId}";
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
|
||||
|
||||
public readonly Bindable<PlaylistItem?> CurrentMatchPlayingItem = new Bindable<PlaylistItem?>();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
|
||||
/// </summary>
|
||||
@ -92,10 +94,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
private Room? apiRoom;
|
||||
// Only exists for compatibility with old osu-server-spectator build.
|
||||
// Todo: Can be removed on 2021/02/26.
|
||||
private long defaultPlaylistItemId;
|
||||
|
||||
// Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise.
|
||||
private long playlistItemId;
|
||||
private Room? apiRoom;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -142,7 +145,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
Room = joinedRoom;
|
||||
apiRoom = room;
|
||||
playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
|
||||
defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0;
|
||||
}, cancellationSource.Token);
|
||||
|
||||
// Update room settings.
|
||||
@ -218,7 +221,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
||||
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
||||
RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods,
|
||||
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods
|
||||
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods,
|
||||
});
|
||||
}
|
||||
|
||||
@ -506,14 +509,13 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Settings = settings;
|
||||
apiRoom.Name.Value = Room.Settings.Name;
|
||||
|
||||
// The playlist update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here.
|
||||
apiRoom.Playlist.Clear();
|
||||
// The current item update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here.
|
||||
CurrentMatchPlayingItem.Value = null;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
@ -540,18 +542,30 @@ namespace osu.Game.Online.Multiplayer
|
||||
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
|
||||
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
|
||||
|
||||
PlaylistItem playlistItem = new PlaylistItem
|
||||
// Try to retrieve the existing playlist item from the API room.
|
||||
var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId);
|
||||
|
||||
if (playlistItem != null)
|
||||
updateItem(playlistItem);
|
||||
else
|
||||
{
|
||||
ID = playlistItemId,
|
||||
Beatmap = { Value = beatmap },
|
||||
Ruleset = { Value = ruleset.RulesetInfo },
|
||||
};
|
||||
// An existing playlist item does not exist, so append a new one.
|
||||
updateItem(playlistItem = new PlaylistItem());
|
||||
apiRoom.Playlist.Add(playlistItem);
|
||||
}
|
||||
|
||||
playlistItem.RequiredMods.AddRange(mods);
|
||||
playlistItem.AllowedMods.AddRange(allowedMods);
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
|
||||
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
|
||||
apiRoom.Playlist.Add(playlistItem);
|
||||
void updateItem(PlaylistItem item)
|
||||
{
|
||||
item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId;
|
||||
item.Beatmap.Value = beatmap;
|
||||
item.Ruleset.Value = ruleset.RulesetInfo;
|
||||
item.RequiredMods.Clear();
|
||||
item.RequiredMods.AddRange(mods);
|
||||
item.AllowedMods.Clear();
|
||||
item.AllowedMods.AddRange(allowedMods);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Online
|
||||
/// A <see cref="Container"/> for displaying online content which require a local user to be logged in.
|
||||
/// Shows its children only when the local user is logged in and supports displaying a placeholder if not.
|
||||
/// </summary>
|
||||
public abstract class OnlineViewContainer : Container
|
||||
public class OnlineViewContainer : Container
|
||||
{
|
||||
protected LoadingSpinner LoadingSpinner { get; private set; }
|
||||
|
||||
@ -30,7 +30,7 @@ namespace osu.Game.Online
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
protected OnlineViewContainer(string placeholderMessage)
|
||||
public OnlineViewContainer(string placeholderMessage)
|
||||
{
|
||||
this.placeholderMessage = placeholderMessage;
|
||||
}
|
||||
|
@ -23,6 +23,12 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="PlaylistItem"/> is still a valid selection for the <see cref="Room"/>.
|
||||
/// </summary>
|
||||
[JsonProperty("expired")]
|
||||
public bool Expired { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
|
||||
|
||||
|
@ -153,6 +153,12 @@ namespace osu.Game.Online.Rooms
|
||||
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
|
||||
Status.Value = new RoomStatusEnded();
|
||||
|
||||
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
|
||||
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
|
||||
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
|
||||
if (!(Status.Value is RoomStatusEnded))
|
||||
other.Playlist.RemoveAll(i => i.Expired);
|
||||
|
||||
if (!Playlist.SequenceEqual(other.Playlist))
|
||||
{
|
||||
Playlist.Clear();
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Overlays
|
||||
protected List<APIUpdateStream> Streams;
|
||||
|
||||
public ChangelogOverlay()
|
||||
: base(OverlayColourScheme.Purple)
|
||||
: base(OverlayColourScheme.Purple, false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ using osu.Game.Overlays.Chat.Tabs;
|
||||
using osuTK.Input;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -118,40 +119,47 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
currentChannelContainer = new Container<DrawableChannel>
|
||||
new OnlineViewContainer("Sign in to chat")
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Bottom = textbox_height
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = textbox_height,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = padding * 2,
|
||||
Bottom = padding * 2,
|
||||
Left = ChatLine.LEFT_PADDING + padding * 2,
|
||||
Right = padding * 2,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textbox = new FocusedTextBox
|
||||
currentChannelContainer = new Container<DrawableChannel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 1,
|
||||
PlaceholderText = "type your message",
|
||||
ReleaseFocusOnCommit = false,
|
||||
HoldFocus = true,
|
||||
}
|
||||
}
|
||||
},
|
||||
loading = new LoadingSpinner(),
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Bottom = textbox_height
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = textbox_height,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = padding * 2,
|
||||
Bottom = padding * 2,
|
||||
Left = ChatLine.LEFT_PADDING + padding * 2,
|
||||
Right = padding * 2,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textbox = new FocusedTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 1,
|
||||
PlaceholderText = "type your message",
|
||||
ReleaseFocusOnCommit = false,
|
||||
HoldFocus = true,
|
||||
}
|
||||
}
|
||||
},
|
||||
loading = new LoadingSpinner(),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
tabsArea = new TabsArea
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Overlays
|
||||
private readonly Bindable<string> article = new Bindable<string>(null);
|
||||
|
||||
public NewsOverlay()
|
||||
: base(OverlayColourScheme.Purple)
|
||||
: base(OverlayColourScheme.Purple, false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -16,10 +17,16 @@ namespace osu.Game.Overlays
|
||||
protected readonly LoadingLayer Loading;
|
||||
private readonly Container content;
|
||||
|
||||
protected OnlineOverlay(OverlayColourScheme colourScheme)
|
||||
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
|
||||
: base(colourScheme)
|
||||
{
|
||||
base.Content.AddRange(new Drawable[]
|
||||
var mainContent = requiresSignIn
|
||||
? new OnlineViewContainer($"Sign in to view the {Header.Title.Title}")
|
||||
: new Container();
|
||||
|
||||
mainContent.RelativeSizeAxes = Axes.Both;
|
||||
|
||||
mainContent.AddRange(new Drawable[]
|
||||
{
|
||||
ScrollFlow = new OverlayScrollContainer
|
||||
{
|
||||
@ -43,6 +50,8 @@ namespace osu.Game.Overlays
|
||||
},
|
||||
Loading = new LoadingLayer(true)
|
||||
});
|
||||
|
||||
base.Content.Add(mainContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,8 +144,8 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
|
||||
private void updateDisplay(User user)
|
||||
{
|
||||
hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-";
|
||||
hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-";
|
||||
hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-";
|
||||
hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,8 +176,8 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
foreach (var scoreRankInfo in scoreRankInfos)
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-";
|
||||
detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-";
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-";
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-";
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
}
|
||||
|
@ -61,8 +61,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
LoadComponentAsync(display, loaded =>
|
||||
{
|
||||
if (API.IsLoggedIn)
|
||||
Loading.Hide();
|
||||
Loading.Hide();
|
||||
|
||||
Child = loaded;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
|
@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public IEnumerable<string> GetAvailableResources() => throw new NotSupportedException();
|
||||
|
||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotSupportedException();
|
||||
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotSupportedException();
|
||||
|
||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotSupportedException();
|
||||
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => throw new NotSupportedException();
|
||||
|
||||
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException();
|
||||
|
||||
|
@ -8,11 +8,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
@ -21,14 +24,20 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
[Cached(typeof(IPreviewTrackOwner))]
|
||||
public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
|
||||
{
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
private Sample sampleStart;
|
||||
private readonly ModSelectOverlay userModsSelectOverlay;
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
/// <summary>
|
||||
/// A container that provides controls for selection of user mods.
|
||||
/// This will be shown/hidden automatically when applicable.
|
||||
/// </summary>
|
||||
protected Drawable UserModsSection;
|
||||
|
||||
private Sample sampleStart;
|
||||
|
||||
/// <summary>
|
||||
/// Any mods applied by/to the local user.
|
||||
@ -53,9 +62,26 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
protected RoomSubScreen()
|
||||
{
|
||||
AddInternal(BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
SelectedItem = { BindTarget = SelectedItem }
|
||||
BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = SelectedItem }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Depth = float.MinValue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
|
||||
Child = userModsSelectOverlay = new UserModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = UserMods },
|
||||
IsValidMod = _ => false
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -73,7 +99,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
|
||||
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||
|
||||
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(beatmapUpdated);
|
||||
@ -81,6 +106,19 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
|
||||
}
|
||||
|
||||
public override bool OnBackButton()
|
||||
{
|
||||
if (userModsSelectOverlay.State.Value == Visibility.Visible)
|
||||
{
|
||||
userModsSelectOverlay.Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButton();
|
||||
}
|
||||
|
||||
protected void ShowUserModSelect() => userModsSelectOverlay.Show();
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
@ -120,17 +158,31 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
{
|
||||
updateWorkingBeatmap();
|
||||
|
||||
if (SelectedItem.Value == null)
|
||||
var selected = SelectedItem.Value;
|
||||
|
||||
if (selected == null)
|
||||
return;
|
||||
|
||||
// Remove any user mods that are no longer allowed.
|
||||
UserMods.Value = UserMods.Value
|
||||
.Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType()))
|
||||
.Where(m => selected.AllowedMods.Any(a => m.GetType() == a.GetType()))
|
||||
.ToList();
|
||||
|
||||
UpdateMods();
|
||||
|
||||
Ruleset.Value = SelectedItem.Value.Ruleset.Value;
|
||||
Ruleset.Value = selected.Ruleset.Value;
|
||||
|
||||
if (!selected.AllowedMods.Any())
|
||||
{
|
||||
UserModsSection?.Hide();
|
||||
userModsSelectOverlay.Hide();
|
||||
userModsSelectOverlay.IsValidMod = _ => false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserModsSection?.Show();
|
||||
userModsSelectOverlay.IsValidMod = m => selected.AllowedMods.Any(a => a.GetType() == m.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet) => Schedule(updateWorkingBeatmap);
|
||||
@ -185,5 +237,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
if (track != null)
|
||||
track.Looping = false;
|
||||
}
|
||||
|
||||
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -13,7 +11,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class BeatmapSelectionControl : OnlinePlayComposite
|
||||
public class BeatmapSelectionControl : RoomSubScreenComposite
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerMatchSubScreen matchSubScreen { get; set; }
|
||||
@ -60,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||
SelectedItem.BindValueChanged(_ => updateBeatmap(), true);
|
||||
Host.BindValueChanged(host =>
|
||||
{
|
||||
if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true)
|
||||
@ -70,12 +68,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
private void updateBeatmap()
|
||||
{
|
||||
if (Playlist.Any())
|
||||
beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(Playlist.Single(), false, false);
|
||||
else
|
||||
if (SelectedItem.Value == null)
|
||||
beatmapPanelContainer.Clear();
|
||||
else
|
||||
beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@ -16,7 +15,6 @@ using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
@ -43,11 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
private ModSelectOverlay userModsSelectOverlay;
|
||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||
private Drawable userModsSection;
|
||||
|
||||
private IBindable<bool> isConnected;
|
||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable readyClickOperation;
|
||||
@ -155,7 +151,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
new BeatmapSelectionControl { RelativeSizeAxes = Axes.X }
|
||||
}
|
||||
},
|
||||
userModsSection = new FillFlowContainer
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@ -176,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = () => userModsSelectOverlay.Show()
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
@ -231,19 +227,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
|
||||
Child = userModsSelectOverlay = new UserModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = UserMods },
|
||||
IsValidMod = _ => false
|
||||
}
|
||||
},
|
||||
settingsOverlay = new MultiplayerMatchSettingsOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -269,14 +252,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||
SelectedItem.BindTo(client.CurrentMatchPlayingItem);
|
||||
|
||||
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
|
||||
UserMods.BindValueChanged(onUserModsChanged);
|
||||
|
||||
client.LoadRequested += onLoadRequested;
|
||||
client.RoomUpdated += onRoomUpdated;
|
||||
|
||||
isConnected = client.IsConnected.GetBoundCopy();
|
||||
isConnected.BindTo(client.IsConnected);
|
||||
isConnected.BindValueChanged(connected =>
|
||||
{
|
||||
if (!connected.NewValue)
|
||||
@ -303,32 +287,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userModsSelectOverlay.State.Value == Visibility.Visible)
|
||||
{
|
||||
userModsSelectOverlay.Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBackButton();
|
||||
}
|
||||
|
||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||
|
||||
if (SelectedItem.Value?.AllowedMods.Any() != true)
|
||||
{
|
||||
userModsSection.Hide();
|
||||
userModsSelectOverlay.Hide();
|
||||
userModsSelectOverlay.IsValidMod = _ => false;
|
||||
}
|
||||
else
|
||||
{
|
||||
userModsSection.Show();
|
||||
userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
private ModSettingChangeTracker modSettingChangeTracker;
|
||||
private ScheduledDelegate debouncedModSettingsUpdate;
|
||||
|
||||
@ -433,9 +394,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
modSettingChangeTracker?.Dispose();
|
||||
}
|
||||
|
||||
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Debug.Assert(client.Room != null);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
((IBindable<bool>)leaderboard.Expanded).BindTo(IsBreakTime);
|
||||
}
|
||||
|
||||
protected override void StartGameplay()
|
||||
{
|
||||
// block base call, but let the server know we are ready to start.
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -35,9 +36,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private SpriteIcon crown;
|
||||
private OsuSpriteText userRankText;
|
||||
private ModDisplay userModsDisplay;
|
||||
private StateDisplay userStateDisplay;
|
||||
private SpriteIcon crown;
|
||||
|
||||
public ParticipantPanel(MultiplayerRoomUser user)
|
||||
{
|
||||
@ -119,12 +121,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
|
||||
Text = user?.Username
|
||||
},
|
||||
new OsuSpriteText
|
||||
userRankText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
Text = user?.CurrentModeRank != null ? $"#{user.CurrentModeRank}" : string.Empty
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -162,6 +163,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
const double fade_time = 50;
|
||||
|
||||
var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
|
||||
|
||||
var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank;
|
||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||
|
||||
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
||||
|
||||
if (Room.Host?.Equals(User) == true)
|
||||
@ -171,11 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
||||
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
||||
Schedule(() =>
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
|
||||
userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList();
|
||||
});
|
||||
Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList());
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
|
@ -2,14 +2,19 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="CompositeDrawable"/> that exposes bindables for <see cref="Room"/> properties.
|
||||
/// </summary>
|
||||
public class OnlinePlayComposite : CompositeDrawable
|
||||
{
|
||||
[Resolved(typeof(Room))]
|
||||
@ -53,5 +58,23 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<TimeSpan?> Duration { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the first item from <see cref="Playlist"/>
|
||||
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
|
||||
/// </summary>
|
||||
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true);
|
||||
}
|
||||
|
||||
protected virtual void UpdateSelectedItem()
|
||||
{
|
||||
SelectedItem.Value = Playlist.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -31,7 +32,12 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
private readonly Bindable<IReadOnlyList<Mod>> freeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
[CanBeNull]
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IBindable<PlaylistItem> selectedItem { get; set; }
|
||||
|
||||
private readonly FreeModSelectOverlay freeModSelectOverlay;
|
||||
|
||||
private WorkingBeatmap initialBeatmap;
|
||||
@ -45,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
freeModSelectOverlay = new FreeModSelectOverlay
|
||||
{
|
||||
SelectedMods = { BindTarget = freeMods },
|
||||
SelectedMods = { BindTarget = FreeMods },
|
||||
IsValidMod = IsValidFreeMod,
|
||||
};
|
||||
}
|
||||
@ -66,15 +72,15 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
|
||||
// Similarly, freeMods is currently empty but should only contain the allowed mods.
|
||||
Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
|
||||
|
||||
Ruleset.BindValueChanged(onRulesetChanged);
|
||||
}
|
||||
|
||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
|
||||
{
|
||||
freeMods.Value = Array.Empty<Mod>();
|
||||
FreeMods.Value = Array.Empty<Mod>();
|
||||
}
|
||||
|
||||
protected sealed override bool OnStart()
|
||||
@ -90,7 +96,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
|
||||
|
||||
item.AllowedMods.Clear();
|
||||
item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy()));
|
||||
item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy()));
|
||||
|
||||
SelectItem(item);
|
||||
return true;
|
||||
@ -133,7 +139,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons()
|
||||
{
|
||||
var buttons = base.CreateFooterButtons().ToList();
|
||||
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay));
|
||||
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay));
|
||||
return buttons;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,9 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
@ -27,6 +29,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
[Resolved(typeof(Room), nameof(Room.RoomID))]
|
||||
private Bindable<long?> roomId { get; set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
private BindableList<PlaylistItem> playlist { get; set; }
|
||||
|
||||
private MatchSettingsOverlay settingsOverlay;
|
||||
private MatchLeaderboard leaderboard;
|
||||
|
||||
@ -117,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new DrawableRoomPlaylistWithResults
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Playlist },
|
||||
Items = { BindTarget = playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
RequestShowResults = item =>
|
||||
{
|
||||
@ -140,13 +145,55 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Leaderboard"), },
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PurpleTriangleButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
DisplayUnrankedText = false,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Leaderboard")
|
||||
},
|
||||
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
|
||||
new Drawable[] { new OverlinedHeader("Chat"), },
|
||||
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
@ -222,7 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
// Set the first playlist item.
|
||||
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
|
||||
Schedule(() => SelectedItem.Value = Playlist.FirstOrDefault());
|
||||
Schedule(() => SelectedItem.Value = playlist.FirstOrDefault());
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
@ -56,6 +56,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
|
||||
item.RequiredMods.Clear();
|
||||
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
|
||||
|
||||
item.AllowedMods.Clear();
|
||||
item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs
Normal file
38
osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.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.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="OnlinePlayComposite"/> with additional logic tracking the currently-selected <see cref="PlaylistItem"/> inside a <see cref="RoomSubScreen"/>.
|
||||
/// </summary>
|
||||
public class RoomSubScreenComposite : OnlinePlayComposite
|
||||
{
|
||||
[Resolved]
|
||||
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
subScreenSelectedItem.BindValueChanged(_ => UpdateSelectedItem(), true);
|
||||
}
|
||||
|
||||
protected override void UpdateSelectedItem()
|
||||
{
|
||||
if (RoomID.Value == null)
|
||||
{
|
||||
// If the room hasn't been created yet, fall-back to the base logic.
|
||||
base.UpdateSelectedItem();
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedItem.Value = subScreenSelectedItem.Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
private readonly Cached sorting = new Cached();
|
||||
|
||||
public Bindable<bool> Expanded = new Bindable<bool>();
|
||||
|
||||
public GameplayLeaderboard()
|
||||
{
|
||||
Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH;
|
||||
@ -47,8 +50,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
var drawable = new GameplayLeaderboardScore(user, isTracked)
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Expanded = { BindTarget = Expanded },
|
||||
};
|
||||
|
||||
base.Add(drawable);
|
||||
|
@ -20,16 +20,33 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public class GameplayLeaderboardScore : CompositeDrawable, ILeaderboardScore
|
||||
{
|
||||
public const float EXTENDED_WIDTH = 255f;
|
||||
public const float EXTENDED_WIDTH = regular_width + top_player_left_width_extension;
|
||||
|
||||
private const float regular_width = 235f;
|
||||
|
||||
// a bit hand-wavy, but there's a lot of hard-coded paddings in each of the grid's internals.
|
||||
private const float compact_width = 77.5f;
|
||||
|
||||
private const float top_player_left_width_extension = 20f;
|
||||
|
||||
public const float PANEL_HEIGHT = 35f;
|
||||
|
||||
public const float SHEAR_WIDTH = PANEL_HEIGHT * panel_shear;
|
||||
|
||||
private const float panel_shear = 0.15f;
|
||||
|
||||
private const float rank_text_width = 35f;
|
||||
|
||||
private const float score_components_width = 85f;
|
||||
|
||||
private const float avatar_size = 25f;
|
||||
|
||||
private const double panel_transition_duration = 500;
|
||||
|
||||
private const double text_transition_duration = 200;
|
||||
|
||||
public Bindable<bool> Expanded = new Bindable<bool>();
|
||||
|
||||
private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText;
|
||||
|
||||
public BindableDouble TotalScore { get; } = new BindableDouble();
|
||||
@ -63,8 +80,15 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private readonly bool trackedPlayer;
|
||||
|
||||
private Container mainFillContainer;
|
||||
|
||||
private Box centralFill;
|
||||
|
||||
private Container backgroundPaddingAdjustContainer;
|
||||
|
||||
private GridContainer gridContainer;
|
||||
|
||||
private Container scoreComponents;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="GameplayLeaderboardScore"/>.
|
||||
/// </summary>
|
||||
@ -75,7 +99,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
User = user;
|
||||
this.trackedPlayer = trackedPlayer;
|
||||
|
||||
Size = new Vector2(EXTENDED_WIDTH, PANEL_HEIGHT);
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = PANEL_HEIGHT;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -85,147 +110,167 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
mainFillContainer = new Container
|
||||
new Container
|
||||
{
|
||||
Width = regular_width,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Masking = true,
|
||||
CornerRadius = 5f,
|
||||
Shear = new Vector2(panel_shear, 0f),
|
||||
Child = new Box
|
||||
Margin = new MarginPadding { Left = top_player_left_width_extension },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Alpha = 0.5f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
Width = regular_width,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 35f),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 85f),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
backgroundPaddingAdjustContainer = new Container
|
||||
{
|
||||
positionText = new OsuSpriteText
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = Color4.White,
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold),
|
||||
Shadow = false,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
mainFillContainer = new Container
|
||||
{
|
||||
new Container
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5f,
|
||||
Shear = new Vector2(panel_shear, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = 5f,
|
||||
Shear = new Vector2(panel_shear, 0f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
new Box
|
||||
{
|
||||
centralFill = new Box
|
||||
{
|
||||
Alpha = 0.5f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("3399cc"),
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Left = SHEAR_WIDTH },
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
avatarContainer = new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(25f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Name = "Placeholder while avatar loads",
|
||||
Alpha = 0.3f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray4,
|
||||
}
|
||||
}
|
||||
},
|
||||
usernameText = new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.6f,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.White,
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||
Text = User?.Username,
|
||||
Truncate = true,
|
||||
Shadow = false,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.White,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
scoreText = new OsuSpriteText
|
||||
{
|
||||
Spacing = new Vector2(-1f, 0f),
|
||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Shadow = false,
|
||||
},
|
||||
accuracyText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Spacing = new Vector2(-1f, 0f),
|
||||
Shadow = false,
|
||||
},
|
||||
comboText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Spacing = new Vector2(-1f, 0f),
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Shadow = false,
|
||||
Alpha = 0.5f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
gridContainer = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = compact_width, // will be updated by expanded state.
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, rank_text_width),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize, maxSize: score_components_width),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
positionText = new OsuSpriteText
|
||||
{
|
||||
Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = Color4.White,
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold),
|
||||
Shadow = false,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = 5f,
|
||||
Shear = new Vector2(panel_shear, 0f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
centralFill = new Box
|
||||
{
|
||||
Alpha = 0.5f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("3399cc"),
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Left = SHEAR_WIDTH },
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
avatarContainer = new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(avatar_size),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Name = "Placeholder while avatar loads",
|
||||
Alpha = 0.3f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray4,
|
||||
}
|
||||
}
|
||||
},
|
||||
usernameText = new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.6f,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.White,
|
||||
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||
Text = User?.Username,
|
||||
Truncate = true,
|
||||
Shadow = false,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
scoreComponents = new Container
|
||||
{
|
||||
Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f },
|
||||
AlwaysPresent = true, // required to smoothly animate autosize after hidden early.
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.White,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
scoreText = new OsuSpriteText
|
||||
{
|
||||
Spacing = new Vector2(-1f, 0f),
|
||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Shadow = false,
|
||||
},
|
||||
accuracyText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Spacing = new Vector2(-1f, 0f),
|
||||
Shadow = false,
|
||||
},
|
||||
comboText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Spacing = new Vector2(-1f, 0f),
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Shadow = false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add);
|
||||
@ -241,18 +286,43 @@ namespace osu.Game.Screens.Play.HUD
|
||||
base.LoadComplete();
|
||||
|
||||
updateState();
|
||||
Expanded.BindValueChanged(changeExpandedState, true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private const double panel_transition_duration = 500;
|
||||
private void changeExpandedState(ValueChangedEvent<bool> expanded)
|
||||
{
|
||||
scoreComponents.ClearTransforms();
|
||||
|
||||
if (expanded.NewValue)
|
||||
{
|
||||
gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint);
|
||||
|
||||
scoreComponents.ResizeWidthTo(score_components_width, panel_transition_duration, Easing.OutQuint);
|
||||
scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint);
|
||||
|
||||
usernameText.FadeIn(panel_transition_duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint);
|
||||
|
||||
scoreComponents.ResizeWidthTo(0, panel_transition_duration, Easing.OutQuint);
|
||||
scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint);
|
||||
|
||||
usernameText.FadeOut(text_transition_duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
bool widthExtension = false;
|
||||
|
||||
if (HasQuit.Value)
|
||||
{
|
||||
// we will probably want to display this in a better way once we have a design.
|
||||
// and also show states other than quit.
|
||||
mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic);
|
||||
panelColour = Color4.Gray;
|
||||
textColour = Color4.White;
|
||||
return;
|
||||
@ -260,22 +330,29 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
if (scorePosition == 1)
|
||||
{
|
||||
mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
|
||||
widthExtension = true;
|
||||
panelColour = Color4Extensions.FromHex("7fcc33");
|
||||
textColour = Color4.White;
|
||||
}
|
||||
else if (trackedPlayer)
|
||||
{
|
||||
mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
|
||||
widthExtension = true;
|
||||
panelColour = Color4Extensions.FromHex("ffd966");
|
||||
textColour = Color4Extensions.FromHex("2e576b");
|
||||
}
|
||||
else
|
||||
{
|
||||
mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic);
|
||||
panelColour = Color4Extensions.FromHex("3399cc");
|
||||
textColour = Color4.White;
|
||||
}
|
||||
|
||||
this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic);
|
||||
}
|
||||
|
||||
public float SizeContainerLeftPadding
|
||||
{
|
||||
get => backgroundPaddingAdjustContainer.Padding.Left;
|
||||
set => backgroundPaddingAdjustContainer.Padding = new MarginPadding { Left = value };
|
||||
}
|
||||
|
||||
private Color4 panelColour
|
||||
@ -287,8 +364,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
}
|
||||
}
|
||||
|
||||
private const double text_transition_duration = 200;
|
||||
|
||||
private Color4 textColour
|
||||
{
|
||||
set
|
||||
|
@ -339,7 +339,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
HoldToQuit =
|
||||
{
|
||||
Action = performUserRequestedExit,
|
||||
Action = () => PerformExit(true),
|
||||
IsPaused = { BindTarget = GameplayClockContainer.IsPaused }
|
||||
},
|
||||
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } },
|
||||
@ -363,14 +363,14 @@ namespace osu.Game.Screens.Play
|
||||
FailOverlay = new FailOverlay
|
||||
{
|
||||
OnRetry = Restart,
|
||||
OnQuit = performUserRequestedExit,
|
||||
OnQuit = () => PerformExit(true),
|
||||
},
|
||||
PauseOverlay = new PauseOverlay
|
||||
{
|
||||
OnResume = Resume,
|
||||
Retries = RestartCount,
|
||||
OnRetry = Restart,
|
||||
OnQuit = performUserRequestedExit,
|
||||
OnQuit = () => PerformExit(true),
|
||||
},
|
||||
new HotkeyExitOverlay
|
||||
{
|
||||
@ -379,7 +379,7 @@ namespace osu.Game.Screens.Play
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
fadeOut(true);
|
||||
PerformExit(true);
|
||||
PerformExit(false);
|
||||
},
|
||||
},
|
||||
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
|
||||
@ -478,23 +478,47 @@ namespace osu.Game.Screens.Play
|
||||
/// <summary>
|
||||
/// Exits the <see cref="Player"/>.
|
||||
/// </summary>
|
||||
/// <param name="userRequested">
|
||||
/// Whether the exit is requested by the user, or a higher-level game component.
|
||||
/// Pausing is allowed only in the former case.
|
||||
/// <param name="showDialogFirst">
|
||||
/// Whether the pause or fail dialog should be shown before performing an exit.
|
||||
/// If true and a dialog is not yet displayed, the exit will be blocked the the relevant dialog will display instead.
|
||||
/// </param>
|
||||
protected void PerformExit(bool userRequested)
|
||||
protected void PerformExit(bool showDialogFirst)
|
||||
{
|
||||
// if a restart has been requested, cancel any pending completion (user has shown intent to restart).
|
||||
completionProgressDelegate?.Cancel();
|
||||
|
||||
ValidForResume = false;
|
||||
// there is a chance that the exit was performed after the transition to results has started.
|
||||
// we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process).
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
ValidForResume = false;
|
||||
this.MakeCurrent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
bool pauseOrFailDialogVisible =
|
||||
PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible;
|
||||
|
||||
if (userRequested)
|
||||
performUserRequestedExit();
|
||||
else
|
||||
this.Exit();
|
||||
if (showDialogFirst && !pauseOrFailDialogVisible)
|
||||
{
|
||||
// if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion).
|
||||
if (ValidForResume && HasFailed)
|
||||
{
|
||||
failAnimation.FinishTransforms(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// there's a chance the pausing is not supported in the current state, at which point immediate exit should be preferred.
|
||||
if (pausingSupportedByCurrentState)
|
||||
{
|
||||
// in the case a dialog needs to be shown, attempt to pause and show it.
|
||||
// this may fail (see internal checks in Pause()) but the fail cases are temporary, so don't fall through to Exit().
|
||||
Pause();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
private void performUserRequestedSkip()
|
||||
@ -508,20 +532,6 @@ namespace osu.Game.Screens.Play
|
||||
updateSampleDisabledState();
|
||||
}
|
||||
|
||||
private void performUserRequestedExit()
|
||||
{
|
||||
if (ValidForResume && HasFailed && !FailOverlay.IsPresent)
|
||||
{
|
||||
failAnimation.FinishTransforms(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (canPause)
|
||||
Pause();
|
||||
else
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart gameplay via a parent <see cref="PlayerLoader"/>.
|
||||
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
|
||||
@ -538,10 +548,7 @@ namespace osu.Game.Screens.Play
|
||||
sampleRestart?.Play();
|
||||
RestartRequested?.Invoke();
|
||||
|
||||
if (this.IsCurrentScreen())
|
||||
PerformExit(true);
|
||||
else
|
||||
this.MakeCurrent();
|
||||
PerformExit(false);
|
||||
}
|
||||
|
||||
private ScheduledDelegate completionProgressDelegate;
|
||||
@ -667,15 +674,17 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private double? lastPauseActionTime;
|
||||
|
||||
private bool canPause =>
|
||||
/// <summary>
|
||||
/// A set of conditionals which defines whether the current game state and configuration allows for
|
||||
/// pausing to be attempted via <see cref="Pause"/>. If false, the game should generally exit if a user pause
|
||||
/// is attempted.
|
||||
/// </summary>
|
||||
private bool pausingSupportedByCurrentState =>
|
||||
// must pass basic screen conditions (beatmap loaded, instance allows pause)
|
||||
LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume
|
||||
// replays cannot be paused and exit immediately
|
||||
&& !DrawableRuleset.HasReplayLoaded.Value
|
||||
// cannot pause if we are already in a fail state
|
||||
&& !HasFailed
|
||||
// cannot pause if already paused (or in a cooldown state) unless we are in a resuming state.
|
||||
&& (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive));
|
||||
&& !HasFailed;
|
||||
|
||||
private bool pauseCooldownActive =>
|
||||
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
|
||||
@ -690,7 +699,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (!canPause) return;
|
||||
if (!pausingSupportedByCurrentState) return;
|
||||
|
||||
if (!IsResuming && pauseCooldownActive)
|
||||
return;
|
||||
|
||||
if (IsResuming)
|
||||
{
|
||||
@ -809,14 +821,6 @@ namespace osu.Game.Screens.Play
|
||||
return true;
|
||||
}
|
||||
|
||||
// ValidForResume is false when restarting
|
||||
if (ValidForResume)
|
||||
{
|
||||
if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value)
|
||||
// still want to block if we are within the cooldown period and not already paused.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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.
|
||||
GameplayClockContainer?.StopUsingBeatmapClock();
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select
|
||||
Sort = sortMode.Value,
|
||||
AllowConvertedBeatmaps = showConverted.Value,
|
||||
Ruleset = ruleset.Value,
|
||||
Collection = collectionDropdown?.Current.Value.Collection
|
||||
Collection = collectionDropdown?.Current.Value?.Collection
|
||||
};
|
||||
|
||||
if (!minimumStars.IsDefault)
|
||||
|
@ -165,9 +165,9 @@ namespace osu.Game.Skinning
|
||||
|
||||
public BindableNumber<double> Tempo => sampleContainer.Tempo;
|
||||
|
||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable);
|
||||
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable);
|
||||
|
||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
|
||||
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
|
||||
|
||||
public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type);
|
||||
|
||||
|
@ -176,10 +176,10 @@ namespace osu.Game.Skinning
|
||||
|
||||
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
||||
|
||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
|
||||
=> samplesContainer.AddAdjustment(type, adjustBindable);
|
||||
|
||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
|
||||
=> samplesContainer.RemoveAdjustment(type, adjustBindable);
|
||||
|
||||
public void RemoveAllAdjustments(AdjustableProperty type)
|
||||
|
@ -2,10 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API.Requests;
|
||||
|
||||
namespace osu.Game.Users
|
||||
{
|
||||
@ -178,6 +181,10 @@ namespace osu.Game.Users
|
||||
|
||||
private UserStatistics statistics;
|
||||
|
||||
/// <summary>
|
||||
/// User statistics for the requested ruleset (in the case of a <see cref="GetUserRequest"/> response).
|
||||
/// Otherwise empty.
|
||||
/// </summary>
|
||||
[JsonProperty(@"statistics")]
|
||||
public UserStatistics Statistics
|
||||
{
|
||||
@ -228,14 +235,14 @@ namespace osu.Game.Users
|
||||
[JsonProperty("replays_watched_counts")]
|
||||
public UserHistoryCount[] ReplaysWatchedCounts;
|
||||
|
||||
public class UserHistoryCount
|
||||
{
|
||||
[JsonProperty("start_date")]
|
||||
public DateTime Date;
|
||||
|
||||
[JsonProperty("count")]
|
||||
public long Count;
|
||||
}
|
||||
/// <summary>
|
||||
/// All user statistics per ruleset's short name (in the case of a <see cref="GetUsersRequest"/> response).
|
||||
/// Otherwise empty. Can be altered for testing purposes.
|
||||
/// </summary>
|
||||
// todo: this should likely be moved to a separate UserCompact class at some point.
|
||||
[JsonProperty("statistics_rulesets")]
|
||||
[CanBeNull]
|
||||
public Dictionary<string, UserStatistics> RulesetsStatistics { get; set; }
|
||||
|
||||
public override string ToString() => Username;
|
||||
|
||||
@ -249,6 +256,14 @@ namespace osu.Game.Users
|
||||
Id = 0
|
||||
};
|
||||
|
||||
public bool Equals(User other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
return Id == other.Id;
|
||||
}
|
||||
|
||||
public enum PlayStyle
|
||||
{
|
||||
[Description("Keyboard")]
|
||||
@ -264,12 +279,13 @@ namespace osu.Game.Users
|
||||
Touch,
|
||||
}
|
||||
|
||||
public bool Equals(User other)
|
||||
public class UserHistoryCount
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
[JsonProperty("start_date")]
|
||||
public DateTime Date;
|
||||
|
||||
return Id == other.Id;
|
||||
[JsonProperty("count")]
|
||||
public long Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,17 +26,24 @@ namespace osu.Game.Users
|
||||
public int Progress;
|
||||
}
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public decimal? PP;
|
||||
[JsonProperty(@"global_rank")]
|
||||
public int? GlobalRank;
|
||||
|
||||
[JsonProperty(@"pp_rank")] // the API sometimes only returns this value in condensed user responses
|
||||
private int? rank
|
||||
{
|
||||
set => Ranks.Global = value;
|
||||
}
|
||||
public int? CountryRank;
|
||||
|
||||
[JsonProperty(@"rank")]
|
||||
public UserRanks Ranks;
|
||||
private UserRanks ranks
|
||||
{
|
||||
// eventually that will also become an own json property instead of reading from a `rank` object.
|
||||
// see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53.
|
||||
set => CountryRank = value.Country;
|
||||
}
|
||||
|
||||
// populated via User model, as that's where the data currently lives.
|
||||
public RankHistoryData RankHistory;
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public decimal? PP;
|
||||
|
||||
[JsonProperty(@"ranked_score")]
|
||||
public long RankedScore;
|
||||
@ -113,15 +120,12 @@ namespace osu.Game.Users
|
||||
}
|
||||
}
|
||||
|
||||
public struct UserRanks
|
||||
#pragma warning disable 649
|
||||
private struct UserRanks
|
||||
{
|
||||
[JsonProperty(@"global")]
|
||||
public int? Global;
|
||||
|
||||
[JsonProperty(@"country")]
|
||||
public int? Country;
|
||||
}
|
||||
|
||||
public RankHistoryData RankHistory;
|
||||
#pragma warning restore 649
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.220.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||
<PackageReference Include="Sentry" Version="3.0.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.27.1" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.220.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||
@ -91,7 +91,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.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.220.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.27.1" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user