Compare commits
92 Commits
2025.1111.
...
2025.1118.
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e4b0ff197 | |||
| 499f410c94 | |||
| 616c0d8ecd | |||
| ed889138a0 | |||
| 18075bef29 | |||
| bc7780a870 | |||
| 8930b8fadb | |||
| 2d457f4305 | |||
| a784734f94 | |||
| ecfd4764e7 | |||
| f5ca5083d6 | |||
| 1d7c77d8d6 | |||
| 774e52fbd6 | |||
| a9d7a9d5d5 | |||
| f7069b1009 | |||
| c3ce5dc787 | |||
| 98076e2092 | |||
| b7d1092f90 | |||
| 08db90c278 | |||
| b7e36164c3 | |||
| 0f5f13858d | |||
| 89a0c75156 | |||
|
|
243cd9c073 | ||
|
|
e8db35a5c9 | ||
|
|
0f54608cee | ||
|
|
f8331e0b28 | ||
|
|
6ff2a6225d | ||
|
|
a8020dea7c | ||
|
|
88dd458394 | ||
|
|
23cb7f3b23 | ||
|
|
7da051b144 | ||
|
|
4d706b12ac | ||
|
|
c44f701abe | ||
|
|
4c81d661aa | ||
|
|
f4049c7ec1 | ||
|
|
645d27bb32 | ||
|
|
73f1849365 | ||
|
|
89b443bccc | ||
|
|
be9170832c | ||
|
|
9db200ed41 | ||
|
|
1ab017d4e2 | ||
|
|
2413e98108 | ||
|
|
65fb5311ea | ||
|
|
14cdc40f0f | ||
|
|
9a393f912b | ||
|
|
a9ca4634fc | ||
|
|
bdcc0ee937 | ||
|
|
6f94b1ab6d | ||
|
|
b20a41c1e8 | ||
|
|
d0ce74063d | ||
|
|
373162df02 | ||
|
|
8e0c9281d3 | ||
|
|
25a1a1ba37 | ||
|
|
73e05e3fae | ||
|
|
2a01e3d148 | ||
|
|
ea1798d731 | ||
|
|
a435dfe93e | ||
|
|
5c1171f358 | ||
|
|
3fcc626e29 | ||
|
|
7ff6edeb64 | ||
|
|
657bc31539 | ||
|
|
f9f7740acb | ||
|
|
5e4dd77e64 | ||
|
|
ce96c0b037 | ||
|
|
5af9bb784b | ||
|
|
4c60df21db | ||
|
|
3c6fb14a32 | ||
|
|
3afc7b045c | ||
|
|
2f2847f1dd | ||
|
|
ee7c52465b | ||
|
|
beb977892e | ||
|
|
7203f419a2 | ||
|
|
722cfb72d8 | ||
|
|
5da132cc2f | ||
|
|
9fac96cf07 | ||
|
|
0610781c6c | ||
|
|
e9260de56f | ||
|
|
2d177226fd | ||
|
|
bd912710f1 | ||
|
|
4e76bd0f24 | ||
|
|
9a965a2546 | ||
|
|
7b0121a430 | ||
|
|
627fec2e3a | ||
|
|
c779e142e6 | ||
|
|
89fffa5a1a | ||
|
|
6d597fc815 | ||
|
|
a78b456e20 | ||
|
|
9237c76942 | ||
|
|
378c64b7f8 | ||
|
|
87b66685d6 | ||
|
|
c524bf5432 | ||
|
|
a40230da4b |
12
.github/ISSUE_TEMPLATE/config.yml
vendored
12
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,12 +1,6 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Help
|
||||
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||
about: osu! not working or performing as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: osu!stable issues
|
||||
url: https://github.com/ppy/osu-stable-issues
|
||||
about: For osu!(stable) - ie. the current "live" game version, check out the dedicated repository. Note that this is for serious bug reports only, not tech support.
|
||||
url: https://t.me/jvnkosu_chat
|
||||
about: Your jvnkosu! is not working right? Please contact us using our Telegram chat
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace osu.Desktop
|
||||
|
||||
// While .NET 8 only supports Windows 10 and above, running on Windows 7/8.1 may still work. We are limited by realm currently, as they choose to only support 8.1 and higher.
|
||||
// See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/
|
||||
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
|
||||
if (windowsVersion.Major < 6)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
@@ -53,13 +53,26 @@ namespace osu.Desktop
|
||||
// We could also better detect compatibility mode if required:
|
||||
// https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
|
||||
SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
||||
"Your operating system is too old to run osu!"u8,
|
||||
"This version of osu! requires at least Windows 8.1 to run.\n"u8
|
||||
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8
|
||||
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null);
|
||||
"Your operating system is too old to run this game!"u8,
|
||||
"This version of the game requires at least Windows 8.1 to run reliably.\n"u8
|
||||
+ "You may try to run it on Windows 8 or older, but it's not guaranteed to actually work.\n\n"u8
|
||||
+ "Please upgrade your operating system or consider using an older game version.\n\n"u8
|
||||
+ "If you are running a newer version of Windows, please check you don't have \"Compatibility mode\" turned on for the game's executable."u8, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_WARNING,
|
||||
"Your operating system is too old to run this game!"u8,
|
||||
"While the version of Windows you're using may be able to launch it, it's likely to work unreliably and crash.\n"u8
|
||||
+ "Please upgrade your operating system or consider using an older game version.\n\n"u8
|
||||
+ "If you are running a newer version of Windows, please check you don't have \"Compatibility mode\" turned on for the game's executable."u8, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NVIDIA profiles are based on the executable name of a process.
|
||||
@@ -208,9 +221,10 @@ namespace osu.Desktop
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void configureWindows(VelopackApp app)
|
||||
{
|
||||
app.OnFirstRun(_ => WindowsAssociationManager.InstallAssociations());
|
||||
app.OnAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
|
||||
app.OnBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
|
||||
// we do not want associations here, as that breaks official lazer's associations
|
||||
// app.OnFirstRun(_ => WindowsAssociationManager.InstallAssociations());
|
||||
// app.OnAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
|
||||
// app.OnBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
typeof(ManiaModFadeIn)
|
||||
}).ToArray();
|
||||
|
||||
public override bool Ranked => false;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public override bool ValidForFreestyleAsRequiredMod => false;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => false;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1.4;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public class ManiaModMirror : ModMirror, IApplicableToBeatmap
|
||||
{
|
||||
public override LocalisableString Description => "Notes are flipped horizontally.";
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
|
||||
16
osu.Game.Rulesets.Osu/Mods/OsuModRateAdjustConcrete.cs
Normal file
16
osu.Game.Rulesets.Osu/Mods/OsuModRateAdjustConcrete.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModRateAdjustConcrete : ModRateAdjustConcrete
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => @"Spinners will be automatically completed.";
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) };
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public class OsuModTouchDevice : ModTouchDevice
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModBloom) }).ToArray();
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,14 @@ namespace osu.Game.Rulesets.Osu
|
||||
new ModScoreV2(),
|
||||
};
|
||||
|
||||
case ModType.Special:
|
||||
#if DEBUG
|
||||
return new Mod[]
|
||||
{
|
||||
new OsuModRateAdjustConcrete(),
|
||||
};
|
||||
#endif
|
||||
|
||||
default:
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
|
||||
@@ -29,17 +29,17 @@ namespace osu.Game.Tests.Online.Matchmaking
|
||||
new SoloScoreInfo { UserID = 3, TotalScore = 750 },
|
||||
], placement_points);
|
||||
|
||||
Assert.AreEqual(8, state.Users[1].Points);
|
||||
Assert.AreEqual(1, state.Users[1].Placement);
|
||||
Assert.AreEqual(1, state.Users[1].Rounds[1].Placement);
|
||||
Assert.AreEqual(8, state.Users.GetOrAdd(1).Points);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Rounds.GetOrAdd(1).Placement);
|
||||
|
||||
Assert.AreEqual(6, state.Users[2].Points);
|
||||
Assert.AreEqual(3, state.Users[2].Placement);
|
||||
Assert.AreEqual(3, state.Users[2].Rounds[1].Placement);
|
||||
Assert.AreEqual(6, state.Users.GetOrAdd(2).Points);
|
||||
Assert.AreEqual(3, state.Users.GetOrAdd(2).Placement);
|
||||
Assert.AreEqual(3, state.Users.GetOrAdd(2).Rounds.GetOrAdd(1).Placement);
|
||||
|
||||
Assert.AreEqual(7, state.Users[3].Points);
|
||||
Assert.AreEqual(2, state.Users[3].Placement);
|
||||
Assert.AreEqual(2, state.Users[3].Rounds[1].Placement);
|
||||
Assert.AreEqual(7, state.Users.GetOrAdd(3).Points);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(3).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(3).Rounds.GetOrAdd(1).Placement);
|
||||
|
||||
// 2 -> 1 -> 3
|
||||
|
||||
@@ -51,17 +51,17 @@ namespace osu.Game.Tests.Online.Matchmaking
|
||||
new SoloScoreInfo { UserID = 3, TotalScore = 500 },
|
||||
], placement_points);
|
||||
|
||||
Assert.AreEqual(15, state.Users[1].Points);
|
||||
Assert.AreEqual(1, state.Users[1].Placement);
|
||||
Assert.AreEqual(2, state.Users[1].Rounds[2].Placement);
|
||||
Assert.AreEqual(15, state.Users.GetOrAdd(1).Points);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(1).Rounds.GetOrAdd(2).Placement);
|
||||
|
||||
Assert.AreEqual(14, state.Users[2].Points);
|
||||
Assert.AreEqual(2, state.Users[2].Placement);
|
||||
Assert.AreEqual(1, state.Users[2].Rounds[2].Placement);
|
||||
Assert.AreEqual(14, state.Users.GetOrAdd(2).Points);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(2).Rounds.GetOrAdd(2).Placement);
|
||||
|
||||
Assert.AreEqual(13, state.Users[3].Points);
|
||||
Assert.AreEqual(3, state.Users[3].Placement);
|
||||
Assert.AreEqual(3, state.Users[3].Rounds[2].Placement);
|
||||
Assert.AreEqual(13, state.Users.GetOrAdd(3).Points);
|
||||
Assert.AreEqual(3, state.Users.GetOrAdd(3).Placement);
|
||||
Assert.AreEqual(3, state.Users.GetOrAdd(3).Rounds.GetOrAdd(2).Placement);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -80,21 +80,21 @@ namespace osu.Game.Tests.Online.Matchmaking
|
||||
new SoloScoreInfo { UserID = 4, TotalScore = 500 },
|
||||
], placement_points);
|
||||
|
||||
Assert.AreEqual(7, state.Users[1].Points);
|
||||
Assert.AreEqual(1, state.Users[1].Placement);
|
||||
Assert.AreEqual(2, state.Users[1].Rounds[1].Placement);
|
||||
Assert.AreEqual(7, state.Users.GetOrAdd(1).Points);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(1).Rounds.GetOrAdd(1).Placement);
|
||||
|
||||
Assert.AreEqual(7, state.Users[2].Points);
|
||||
Assert.AreEqual(2, state.Users[2].Placement);
|
||||
Assert.AreEqual(2, state.Users[2].Rounds[1].Placement);
|
||||
Assert.AreEqual(7, state.Users.GetOrAdd(2).Points);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Rounds.GetOrAdd(1).Placement);
|
||||
|
||||
Assert.AreEqual(5, state.Users[3].Points);
|
||||
Assert.AreEqual(3, state.Users[3].Placement);
|
||||
Assert.AreEqual(4, state.Users[3].Rounds[1].Placement);
|
||||
Assert.AreEqual(5, state.Users.GetOrAdd(3).Points);
|
||||
Assert.AreEqual(3, state.Users.GetOrAdd(3).Placement);
|
||||
Assert.AreEqual(4, state.Users.GetOrAdd(3).Rounds.GetOrAdd(1).Placement);
|
||||
|
||||
Assert.AreEqual(5, state.Users[4].Points);
|
||||
Assert.AreEqual(4, state.Users[4].Placement);
|
||||
Assert.AreEqual(4, state.Users[4].Rounds[1].Placement);
|
||||
Assert.AreEqual(5, state.Users.GetOrAdd(4).Points);
|
||||
Assert.AreEqual(4, state.Users.GetOrAdd(4).Placement);
|
||||
Assert.AreEqual(4, state.Users.GetOrAdd(4).Rounds.GetOrAdd(1).Placement);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -120,8 +120,8 @@ namespace osu.Game.Tests.Online.Matchmaking
|
||||
new SoloScoreInfo { UserID = 2, TotalScore = 1000 },
|
||||
], placement_points);
|
||||
|
||||
Assert.AreEqual(1, state.Users[1].Placement);
|
||||
Assert.AreEqual(2, state.Users[2].Placement);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -142,12 +142,12 @@ namespace osu.Game.Tests.Online.Matchmaking
|
||||
new SoloScoreInfo { UserID = 5, TotalScore = 1000 },
|
||||
], placement_points);
|
||||
|
||||
Assert.AreEqual(1, state.Users[1].Placement);
|
||||
Assert.AreEqual(2, state.Users[2].Placement);
|
||||
Assert.AreEqual(3, state.Users[3].Placement);
|
||||
Assert.AreEqual(4, state.Users[4].Placement);
|
||||
Assert.AreEqual(5, state.Users[5].Placement);
|
||||
Assert.AreEqual(6, state.Users[6].Placement);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
|
||||
Assert.AreEqual(3, state.Users.GetOrAdd(3).Placement);
|
||||
Assert.AreEqual(4, state.Users.GetOrAdd(4).Placement);
|
||||
Assert.AreEqual(5, state.Users.GetOrAdd(5).Placement);
|
||||
Assert.AreEqual(6, state.Users.GetOrAdd(6).Placement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public Drawable OverlayContent => InternalChild;
|
||||
|
||||
public Drawable FadingContent => (OverlayContent as Container)?.Child;
|
||||
public new Drawable FadingContent => (OverlayContent as Container)?.Child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
@@ -62,5 +64,41 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
panel.AllowSelection = value;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFailedBeatmapLookup()
|
||||
{
|
||||
AddStep("setup request handle", () =>
|
||||
{
|
||||
var api = (DummyAPIAccess)API;
|
||||
var handler = api.HandleRequest;
|
||||
api.HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetBeatmapRequest:
|
||||
case GetBeatmapsRequest:
|
||||
req.TriggerFailure(new InvalidOperationException());
|
||||
return false;
|
||||
|
||||
default:
|
||||
return handler?.Invoke(req) ?? false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("add panel", () =>
|
||||
{
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("load screen", () => LoadScreen(new IntroScreen()));
|
||||
AddStep("load screen", () => LoadScreen(new ScreenIntro()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -124,11 +124,11 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
foreach (var user in MultiplayerClient.ServerRoom!.Users.OrderBy(_ => RNG.Next()))
|
||||
{
|
||||
state.Users[user.UserID].Placement = i++;
|
||||
state.Users[user.UserID].Points = (8 - i) * 7;
|
||||
state.Users[user.UserID].Rounds[1].Placement = 1;
|
||||
state.Users[user.UserID].Rounds[1].TotalScore = 1;
|
||||
state.Users[user.UserID].Rounds[1].Statistics[HitResult.LargeBonus] = 1;
|
||||
state.Users.GetOrAdd(user.UserID).Placement = i++;
|
||||
state.Users.GetOrAdd(user.UserID).Points = (8 - i) * 7;
|
||||
state.Users.GetOrAdd(user.UserID).Rounds.GetOrAdd(1).Placement = 1;
|
||||
state.Users.GetOrAdd(user.UserID).Rounds.GetOrAdd(1).TotalScore = 1;
|
||||
state.Users.GetOrAdd(user.UserID).Rounds.GetOrAdd(1).Statistics[HitResult.LargeBonus] = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,13 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add panel", () => Child = panel = new PlayerPanel(new MultiplayerRoomUser(1)
|
||||
AddStep("join other player to room", () => MultiplayerClient.AddUser(new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
}));
|
||||
|
||||
AddStep("add panel", () => Child = panel = new PlayerPanel(new MultiplayerRoomUser(2)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@@ -85,9 +91,9 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
UserDictionary =
|
||||
{
|
||||
{
|
||||
1, new MatchmakingUser
|
||||
2, new MatchmakingUser
|
||||
{
|
||||
UserId = 1,
|
||||
UserId = 2,
|
||||
Placement = 1,
|
||||
Points = ++points
|
||||
}
|
||||
@@ -100,7 +106,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
[Test]
|
||||
public void TestJump()
|
||||
{
|
||||
AddStep("jump", () => MultiplayerClient.SendUserMatchRequest(1, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely());
|
||||
AddStep("jump", () => MultiplayerClient.SendUserMatchRequest(2, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -108,5 +114,14 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
AddToggleStep("toggle quit", quit => panel.HasQuit = quit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDownloadProgress()
|
||||
{
|
||||
AddStep("set download progress 20%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.2f)));
|
||||
AddStep("set download progress 50%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.5f)));
|
||||
AddStep("set download progress 90%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.9f)));
|
||||
AddStep("set locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.LocallyAvailable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
MatchmakingRoomState state = new MatchmakingRoomState();
|
||||
|
||||
for (int i = 0; i < room.Users.Count; i++)
|
||||
state.Users[room.Users[i].UserID].Placement = placements[i];
|
||||
state.Users.GetOrAdd(room.Users[i].UserID).Placement = placements[i];
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||
});
|
||||
|
||||
@@ -36,28 +36,28 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
int localUserId = API.LocalUser.Value.OnlineID;
|
||||
|
||||
// Overall state.
|
||||
state.Users[localUserId].Placement = 1;
|
||||
state.Users[localUserId].Points = 8;
|
||||
state.Users.GetOrAdd(localUserId).Placement = 1;
|
||||
state.Users.GetOrAdd(localUserId).Points = 8;
|
||||
for (int round = 1; round <= state.CurrentRound; round++)
|
||||
state.Users[localUserId].Rounds[round].Placement = round;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(round).Placement = round;
|
||||
|
||||
// Highest score.
|
||||
state.Users[localUserId].Rounds[1].TotalScore = 1000;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(1).TotalScore = 1000;
|
||||
|
||||
// Highest accuracy.
|
||||
state.Users[localUserId].Rounds[2].Accuracy = 0.9995;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(2).Accuracy = 0.9995;
|
||||
|
||||
// Highest combo.
|
||||
state.Users[localUserId].Rounds[3].MaxCombo = 100;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(3).MaxCombo = 100;
|
||||
|
||||
// Most bonus score.
|
||||
state.Users[localUserId].Rounds[4].Statistics[HitResult.LargeBonus] = 50;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(4).Statistics[HitResult.LargeBonus] = 50;
|
||||
|
||||
// Smallest score difference.
|
||||
state.Users[localUserId].Rounds[5].TotalScore = 1000;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(5).TotalScore = 1000;
|
||||
|
||||
// Largest score difference.
|
||||
state.Users[localUserId].Rounds[6].TotalScore = 1000;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(6).TotalScore = 1000;
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||
});
|
||||
@@ -103,36 +103,78 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
int localUserId = API.LocalUser.Value.OnlineID;
|
||||
|
||||
// Overall state.
|
||||
state.Users[localUserId].Placement = 1;
|
||||
state.Users[localUserId].Points = 8;
|
||||
state.Users[invalid_user_id].Placement = 2;
|
||||
state.Users[invalid_user_id].Points = 7;
|
||||
state.Users.GetOrAdd(localUserId).Placement = 1;
|
||||
state.Users.GetOrAdd(localUserId).Points = 8;
|
||||
state.Users.GetOrAdd(invalid_user_id).Placement = 2;
|
||||
state.Users.GetOrAdd(invalid_user_id).Points = 7;
|
||||
for (int round = 1; round <= state.CurrentRound; round++)
|
||||
state.Users[localUserId].Rounds[round].Placement = round;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(round).Placement = round;
|
||||
|
||||
// Highest score.
|
||||
state.Users[localUserId].Rounds[1].TotalScore = 1000;
|
||||
state.Users[invalid_user_id].Rounds[1].TotalScore = 990;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(1).TotalScore = 1000;
|
||||
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(1).TotalScore = 990;
|
||||
|
||||
// Highest accuracy.
|
||||
state.Users[localUserId].Rounds[2].Accuracy = 0.9995;
|
||||
state.Users[invalid_user_id].Rounds[2].Accuracy = 0.5;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(2).Accuracy = 0.9995;
|
||||
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(2).Accuracy = 0.5;
|
||||
|
||||
// Highest combo.
|
||||
state.Users[localUserId].Rounds[3].MaxCombo = 100;
|
||||
state.Users[invalid_user_id].Rounds[3].MaxCombo = 10;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(3).MaxCombo = 100;
|
||||
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(3).MaxCombo = 10;
|
||||
|
||||
// Most bonus score.
|
||||
state.Users[localUserId].Rounds[4].Statistics[HitResult.LargeBonus] = 50;
|
||||
state.Users[invalid_user_id].Rounds[4].Statistics[HitResult.LargeBonus] = 25;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(4).Statistics[HitResult.LargeBonus] = 50;
|
||||
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(4).Statistics[HitResult.LargeBonus] = 25;
|
||||
|
||||
// Smallest score difference.
|
||||
state.Users[localUserId].Rounds[5].TotalScore = 1000;
|
||||
state.Users[invalid_user_id].Rounds[5].TotalScore = 999;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(5).TotalScore = 1000;
|
||||
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(5).TotalScore = 999;
|
||||
|
||||
// Largest score difference.
|
||||
state.Users[localUserId].Rounds[6].TotalScore = 1000;
|
||||
state.Users[invalid_user_id].Rounds[6].TotalScore = 0;
|
||||
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(6).TotalScore = 1000;
|
||||
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(6).TotalScore = 0;
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoUsers()
|
||||
{
|
||||
AddStep("show results with no users", () =>
|
||||
{
|
||||
var state = new MatchmakingRoomState
|
||||
{
|
||||
CurrentRound = 6,
|
||||
Stage = MatchmakingStage.Ended
|
||||
};
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserWithNoScore()
|
||||
{
|
||||
AddStep("join another user", () => MultiplayerClient.AddUser(new MultiplayerRoomUser(2)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "Other user"
|
||||
}
|
||||
}));
|
||||
|
||||
AddStep("show results with no score", () =>
|
||||
{
|
||||
var state = new MatchmakingRoomState
|
||||
{
|
||||
CurrentRound = 6,
|
||||
Stage = MatchmakingStage.Ended
|
||||
};
|
||||
|
||||
state.Users.GetOrAdd(API.LocalUser.Value.OnlineID).Rounds.GetOrAdd(1).Placement = 1;
|
||||
state.Users.GetOrAdd(2);
|
||||
|
||||
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayerSkipOverlay : MultiplayerTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add skip overlay", () =>
|
||||
{
|
||||
GameplayClockContainer gameplayClockContainer;
|
||||
|
||||
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
|
||||
|
||||
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MultiplayerSkipOverlay(120000)
|
||||
},
|
||||
};
|
||||
|
||||
gameplayClockContainer.Start();
|
||||
});
|
||||
|
||||
AddStep("set playing state", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Playing));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkip()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int i2 = i;
|
||||
|
||||
AddStep($"join user {i2}", () =>
|
||||
{
|
||||
MultiplayerClient.AddUser(new APIUser
|
||||
{
|
||||
Id = i2,
|
||||
Username = $"User {i2}"
|
||||
});
|
||||
|
||||
MultiplayerClient.ChangeUserState(i2, MultiplayerUserState.Playing);
|
||||
});
|
||||
}
|
||||
|
||||
AddStep("local user votes", () => MultiplayerClient.VoteToSkipIntro().WaitSafely());
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int i2 = i;
|
||||
AddStep($"user {i2} votes", () => MultiplayerClient.UserVoteToSkipIntro(i2).WaitSafely());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
osu.Game.Tests/Visual/Online/TestSceneGlobalRankDisplay.cs
Normal file
67
osu.Game.Tests/Visual/Online/TestSceneGlobalRankDisplay.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public partial class TestSceneGlobalRankDisplay : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneGlobalRankDisplay()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
Padding = new MarginPadding(20),
|
||||
Spacing = new Vector2(40),
|
||||
ChildrenEnumerable = new int?[] { 64, 423, 1453, 3468, 18_367, 48_342, 178_432, 375_231, 897_783, null }.Select(createDisplay)
|
||||
};
|
||||
|
||||
private GlobalRankDisplay createDisplay(int? rank) => new GlobalRankDisplay
|
||||
{
|
||||
UserStatistics =
|
||||
{
|
||||
Value = new UserStatistics
|
||||
{
|
||||
GlobalRank = rank,
|
||||
GlobalRankPercent = rank / 1_000_000f,
|
||||
Variants =
|
||||
[
|
||||
new UserStatistics.Variant
|
||||
{
|
||||
VariantType = UserStatistics.RulesetVariant.FourKey,
|
||||
GlobalRank = rank / 3,
|
||||
},
|
||||
new UserStatistics.Variant
|
||||
{
|
||||
VariantType = UserStatistics.RulesetVariant.SevenKey,
|
||||
GlobalRank = 2 * rank / 3,
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
HighestRank =
|
||||
{
|
||||
Value = rank == null
|
||||
? null
|
||||
: new APIUser.UserRankHighest
|
||||
{
|
||||
Rank = rank.Value / 2,
|
||||
UpdatedAt = DateTimeOffset.Now.AddMonths(-3),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,12 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
@@ -322,5 +325,38 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SelectNextSet();
|
||||
AddUntilStep("no beatmap panels visible", () => GetVisiblePanels<PanelBeatmap>().Count(), () => Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupChangedAfterEngagingArtistGrouping()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
AddStep("add test beatmaps", () =>
|
||||
{
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
var baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3);
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = $"{(char)('A' + i)} artist",
|
||||
Title = $"{(char)('A' + 4 - i)} title",
|
||||
};
|
||||
|
||||
foreach (var b in baseTestBeatmap.Beatmaps)
|
||||
b.Metadata = metadata;
|
||||
|
||||
Realm.Write(r => r.Add(baseTestBeatmap, update: true));
|
||||
BeatmapSets.Add(baseTestBeatmap.Detach());
|
||||
}
|
||||
|
||||
SortAndGroupBy(SortMode.Title, GroupMode.Title);
|
||||
SelectNextSet();
|
||||
SelectNextSet();
|
||||
WaitForExpandedGroup(1);
|
||||
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.Artist);
|
||||
WaitForExpandedGroup(3);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -13,25 +16,35 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneDrawableDate : OsuTestScene
|
||||
{
|
||||
public TestSceneDrawableDate()
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
AddStep("Create 7 dates", () =>
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(60))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(55))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(50))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(60))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(65))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(70))),
|
||||
}
|
||||
};
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(60))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(55))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(50))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(60))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(65))),
|
||||
new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(70))),
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSecondsUpdate()
|
||||
{
|
||||
AddUntilStep("4th date says \"2 seconds ago\"", () => this.ChildrenOfType<DrawableDate>().ElementAt(3).Current.Value == "2 seconds ago");
|
||||
}
|
||||
|
||||
private partial class PokeyDrawableDate : CompositeDrawable
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps
|
||||
LocallyModified = -4,
|
||||
|
||||
[LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.StatusUnknown))]
|
||||
[Description("Unknown")]
|
||||
[Description("Offline")]
|
||||
None = -3,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusGraveyard))]
|
||||
|
||||
@@ -33,7 +33,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
Status = beatmapSet.Status,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
TextSize = 13f
|
||||
TextSize = 13f,
|
||||
ShowUnknownStatus = true
|
||||
},
|
||||
new DifficultySpectrumDisplay
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Utils;
|
||||
@@ -80,7 +81,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
public DateTimeOffset TooltipContent => Date;
|
||||
|
||||
private class HumanisedDate : IEquatable<HumanisedDate>, ILocalisableStringData
|
||||
private class HumanisedDate : ILocalisableStringData
|
||||
{
|
||||
public readonly DateTimeOffset Date;
|
||||
|
||||
@@ -89,11 +90,18 @@ namespace osu.Game.Graphics
|
||||
Date = date;
|
||||
}
|
||||
|
||||
public bool Equals(HumanisedDate? other)
|
||||
=> other?.Date != null && Date.Equals(other.Date);
|
||||
|
||||
public bool Equals(ILocalisableStringData? other)
|
||||
=> other is HumanisedDate otherDate && Equals(otherDate);
|
||||
/// <remarks>
|
||||
/// Humanizer formats the <see cref="Date"/> relative to the local computer time.
|
||||
/// Therefore, replacing a <see cref="HumanisedDate"/> instance with another instance of the class with the same <see cref="Date"/>
|
||||
/// should have the effect of replacing and re-formatting the text.
|
||||
/// Including <see cref="Date"/> in equality members would stop this from happening, as <see cref="SpriteText.Text"/>
|
||||
/// has equality-based early guards to prevent redundant text replaces.
|
||||
/// Thus, instances of these class just compare <see langword="false"/> to any <see cref="ILocalisableStringData"/> to ensure re-formatting happens correctly.
|
||||
/// There are "technically" more "correct" ways to do this (like also including the current time into equality checks),
|
||||
/// but they are simultaneously functionally equivalent to this and overly convoluted.
|
||||
/// This is a private hack-job of a wrapper around humanizer anyway.
|
||||
/// </remarks>
|
||||
public bool Equals(ILocalisableStringData? other) => false;
|
||||
|
||||
public string GetLocalised(LocalisationParameters parameters) => HumanizerUtils.Humanize(Date);
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace osu.Game.Graphics
|
||||
switch (status)
|
||||
{
|
||||
case BeatmapOnlineStatus.None:
|
||||
return Color4.RosyBrown;
|
||||
return Color4.AliceBlue;
|
||||
|
||||
case BeatmapOnlineStatus.LocallyModified:
|
||||
return Color4.OrangeRed;
|
||||
@@ -183,6 +183,9 @@ namespace osu.Game.Graphics
|
||||
case ModType.System:
|
||||
return Yellow;
|
||||
|
||||
case ModType.Special:
|
||||
return Orange2;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type");
|
||||
}
|
||||
|
||||
@@ -84,9 +84,12 @@ Please bear with us as we continue to improve the game for you!");
|
||||
public static LocalisableString GreetingNotification => new TranslatableString(getKey(@"greeting_notification"), @"Welcome to jvnkosu!");
|
||||
|
||||
/// <summary>
|
||||
/// "Failed to load backgrounds!\nCheck your internet connection"
|
||||
/// "Failed to load backgrounds!
|
||||
/// Please check your internet connection"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsFail => new TranslatableString(getKey(@"seasonal_backgrounds_fail"), @"Failed to load backgrounds!\nCheck your internet connection"); // TODO: implement l10n in osu-resources
|
||||
public static LocalisableString SeasonalBackgroundsFail => new TranslatableString(getKey(@"seasonal_backgrounds_fail"),
|
||||
@"Failed to load backgrounds!
|
||||
Please check your internet connection"); // TODO: implement l10n in osu-resources
|
||||
|
||||
/// <summary>
|
||||
/// "Successfully refreshed background categories!"
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"settings");
|
||||
|
||||
/// <summary>
|
||||
/// "change the way game behaves"
|
||||
/// "change the way your game behaves"
|
||||
/// </summary>
|
||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way game behaves");
|
||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way your game behaves");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString LocallyModifiedTooltip => new TranslatableString(getKey(@"locally_modified_tooltip"), @"Has been locally modified");
|
||||
|
||||
/// <summary>
|
||||
/// "Unknown"
|
||||
/// "Offline"
|
||||
/// </summary>
|
||||
public static LocalisableString StatusUnknown => new TranslatableString(getKey(@"status_unknown"), @"Unknown");
|
||||
public static LocalisableString StatusUnknown => new TranslatableString(getKey(@"status_unknown"), @"Offline");
|
||||
|
||||
/// <summary>
|
||||
/// "Total Plays"
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace osu.Game.Online.API.Requests
|
||||
Medal,
|
||||
Rank,
|
||||
RankLost,
|
||||
RankRetracted,
|
||||
UserSupportAgain,
|
||||
UserSupportFirst,
|
||||
UserSupportGift,
|
||||
|
||||
@@ -149,5 +149,15 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
/// <param name="item">The changed item.</param>
|
||||
Task PlaylistItemChanged(MultiplayerPlaylistItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a user has requested to skip the beatmap intro.
|
||||
/// </summary>
|
||||
Task UserVotedToSkipIntro(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the vote to skip the beatmap intro has passed.
|
||||
/// </summary>
|
||||
Task VoteToSkipIntroPassed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <param name="playlistItemId">The item to remove.</param>
|
||||
Task RemovePlaylistItem(long playlistItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Votes to skip the beatmap intro.
|
||||
/// </summary>
|
||||
Task VoteToSkipIntro();
|
||||
|
||||
/// <summary>
|
||||
/// Invites a player to the current room.
|
||||
/// </summary>
|
||||
|
||||
@@ -81,10 +81,10 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
|
||||
foreach (var score in scoreGroup)
|
||||
{
|
||||
MatchmakingUser mmUser = Users[score.UserID];
|
||||
MatchmakingUser mmUser = Users.GetOrAdd(score.UserID);
|
||||
mmUser.Points += placementPoints[placement - 1];
|
||||
|
||||
MatchmakingRound mmRound = mmUser.Rounds[CurrentRound];
|
||||
MatchmakingRound mmRound = mmUser.Rounds.GetOrAdd(CurrentRound);
|
||||
mmRound.Placement = placement;
|
||||
mmRound.TotalScore = score.TotalScore;
|
||||
mmRound.Accuracy = score.Accuracy;
|
||||
|
||||
@@ -21,27 +21,24 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
[Key(0)]
|
||||
public IDictionary<int, MatchmakingRound> RoundsDictionary { get; set; } = new Dictionary<int, MatchmakingRound>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates or retrieves the score for the given round.
|
||||
/// </summary>
|
||||
/// <param name="round">The round.</param>
|
||||
public MatchmakingRound this[int round]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RoundsDictionary.TryGetValue(round, out MatchmakingRound? score))
|
||||
return score;
|
||||
|
||||
return RoundsDictionary[round] = new MatchmakingRound { Round = round };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The total number of rounds.
|
||||
/// </summary>
|
||||
[IgnoreMember]
|
||||
public int Count => RoundsDictionary.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves or adds a <see cref="MatchmakingRound"/> entry to this list.
|
||||
/// </summary>
|
||||
/// <param name="round">The round.</param>
|
||||
public MatchmakingRound GetOrAdd(int round)
|
||||
{
|
||||
if (RoundsDictionary.TryGetValue(round, out MatchmakingRound? score))
|
||||
return score;
|
||||
|
||||
return RoundsDictionary[round] = new MatchmakingRound { Round = round };
|
||||
}
|
||||
|
||||
public IEnumerator<MatchmakingRound> GetEnumerator() => RoundsDictionary.Values.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
/// The aggregate room placement (1-based).
|
||||
/// </summary>
|
||||
[Key(1)]
|
||||
public int Placement { get; set; }
|
||||
public int? Placement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The aggregate points.
|
||||
|
||||
@@ -21,27 +21,24 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
[Key(0)]
|
||||
public IDictionary<int, MatchmakingUser> UserDictionary { get; set; } = new Dictionary<int, MatchmakingUser>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates or retrieves the user for the given id.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
public MatchmakingUser this[int userId]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserDictionary.TryGetValue(userId, out MatchmakingUser? user))
|
||||
return user;
|
||||
|
||||
return UserDictionary[userId] = new MatchmakingUser { UserId = userId };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The total number of users.
|
||||
/// </summary>
|
||||
[IgnoreMember]
|
||||
public int Count => UserDictionary.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves or adds a <see cref="MatchmakingUser"/> entry to this list.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID.</param>
|
||||
public MatchmakingUser GetOrAdd(int userId)
|
||||
{
|
||||
if (UserDictionary.TryGetValue(userId, out MatchmakingUser? user))
|
||||
return user;
|
||||
|
||||
return UserDictionary[userId] = new MatchmakingUser { UserId = userId };
|
||||
}
|
||||
|
||||
public IEnumerator<MatchmakingUser> GetEnumerator() => UserDictionary.Values.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
@@ -131,6 +131,11 @@ namespace osu.Game.Online.Multiplayer
|
||||
public event Action<int, long>? MatchmakingItemDeselected;
|
||||
public event Action<MatchRoomState>? MatchRoomStateChanged;
|
||||
|
||||
public event Action<int>? UserVotedToSkipIntro;
|
||||
public event Action? VoteToSkipIntroPassed;
|
||||
|
||||
public event Action<MultiplayerRoomUser, BeatmapAvailability>? BeatmapAvailabilityChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the <see cref="MultiplayerClient"/> is currently connected.
|
||||
/// This is NOT thread safe and usage should be scheduled.
|
||||
@@ -493,6 +498,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task RemovePlaylistItem(long playlistItemId);
|
||||
|
||||
public abstract Task VoteToSkipIntro();
|
||||
|
||||
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
||||
{
|
||||
handleRoomRequest(() =>
|
||||
@@ -770,6 +777,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
user.BeatmapAvailability = beatmapAvailability;
|
||||
|
||||
BeatmapAvailabilityChanged?.Invoke(user, beatmapAvailability);
|
||||
RoomUpdated?.Invoke();
|
||||
});
|
||||
|
||||
@@ -846,6 +854,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
foreach (var user in Room.Users)
|
||||
user.VotedToSkipIntro = false;
|
||||
|
||||
GameplayStarted?.Invoke();
|
||||
});
|
||||
|
||||
@@ -916,6 +928,37 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserVotedToSkipIntro(int userId)
|
||||
{
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
var user = Room.Users.SingleOrDefault(u => u.UserID == userId);
|
||||
|
||||
// TODO: user should NEVER be null here, see https://github.com/ppy/osu/issues/17713.
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
user.VotedToSkipIntro = true;
|
||||
|
||||
UserVotedToSkipIntro?.Invoke(userId);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.VoteToSkipIntroPassed()
|
||||
{
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
VoteToSkipIntroPassed?.Invoke();
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the <see cref="APIUser"/> for a given collection of <see cref="MultiplayerRoomUser"/>s.
|
||||
/// </summary>
|
||||
|
||||
@@ -49,6 +49,12 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(6)]
|
||||
public int? BeatmapId;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this user voted to skip the beatmap intro.
|
||||
/// </summary>
|
||||
[Key(7)]
|
||||
public bool VotedToSkipIntro;
|
||||
|
||||
[IgnoreMember]
|
||||
public APIUser? User { get; set; }
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded);
|
||||
connection.On<long>(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved);
|
||||
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged);
|
||||
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMultiplayerClient)this).DisconnectRequested);
|
||||
connection.On<int>(nameof(IMultiplayerClient.UserVotedToSkipIntro), ((IMultiplayerClient)this).UserVotedToSkipIntro);
|
||||
connection.On(nameof(IMultiplayerClient.VoteToSkipIntroPassed), ((IMultiplayerClient)this).VoteToSkipIntroPassed);
|
||||
|
||||
connection.On(nameof(IMatchmakingClient.MatchmakingQueueJoined), ((IMatchmakingClient)this).MatchmakingQueueJoined);
|
||||
connection.On(nameof(IMatchmakingClient.MatchmakingQueueLeft), ((IMatchmakingClient)this).MatchmakingQueueLeft);
|
||||
@@ -80,6 +81,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<MatchmakingQueueStatus>(nameof(IMatchmakingClient.MatchmakingQueueStatusChanged), ((IMatchmakingClient)this).MatchmakingQueueStatusChanged);
|
||||
connection.On<int, long>(nameof(IMatchmakingClient.MatchmakingItemSelected), ((IMatchmakingClient)this).MatchmakingItemSelected);
|
||||
connection.On<int, long>(nameof(IMatchmakingClient.MatchmakingItemDeselected), ((IMatchmakingClient)this).MatchmakingItemDeselected);
|
||||
|
||||
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMultiplayerClient)this).DisconnectRequested);
|
||||
};
|
||||
|
||||
IsConnected.BindTo(connector.IsConnected);
|
||||
@@ -312,6 +315,16 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId);
|
||||
}
|
||||
|
||||
public override Task VoteToSkipIntro()
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
Debug.Assert(connection != null);
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.VoteToSkipIntro));
|
||||
}
|
||||
|
||||
public override Task DisconnectInternal()
|
||||
{
|
||||
if (connector == null)
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace osu.Game.Online
|
||||
// compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Online/BanchoClient.cs#L539
|
||||
retryDelay = Math.Min(120000, (int)(retryDelay * 1.5));
|
||||
|
||||
Logger.Log($"{ClientName} connect attempt failed: {exception.Message}. Next attempt in {thisDelay / 1000:N0} seconds.", LoggingTarget.Network);
|
||||
Logger.Log($"{ClientName} connect attempt failed. Next attempt in {thisDelay / 1000:N0} seconds.\n{exception}", LoggingTarget.Network);
|
||||
await Task.Delay(thisDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,8 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
TextSize = 14,
|
||||
TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 }
|
||||
TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 },
|
||||
ShowUnknownStatus = true
|
||||
},
|
||||
storyboardIconPill = new StoryboardIconPill
|
||||
{
|
||||
|
||||
@@ -329,6 +329,7 @@ namespace osu.Game.Overlays.Mods
|
||||
yield return createModColumnContent(ModType.Automation);
|
||||
yield return createModColumnContent(ModType.Conversion);
|
||||
yield return createModColumnContent(ModType.Fun);
|
||||
yield return createModColumnContent(ModType.Special);
|
||||
}
|
||||
|
||||
private ColumnDimContainer createModColumnContent(ModType modType)
|
||||
|
||||
@@ -162,16 +162,17 @@ namespace osu.Game.Overlays
|
||||
private int runningDepth;
|
||||
|
||||
private readonly Scheduler postScheduler = new Scheduler();
|
||||
private readonly Scheduler criticalPostScheduler = new Scheduler();
|
||||
|
||||
public override bool IsPresent =>
|
||||
// Delegate presence as we need to consider the toast tray in addition to the main overlay.
|
||||
State.Value == Visibility.Visible || mainContent.IsPresent || toastTray.IsPresent || postScheduler.HasPendingTasks;
|
||||
State.Value == Visibility.Visible || mainContent.IsPresent || toastTray.IsPresent || postScheduler.HasPendingTasks || criticalPostScheduler.HasPendingTasks;
|
||||
|
||||
private bool processingPosts = true;
|
||||
|
||||
private double? lastSamplePlayback;
|
||||
|
||||
public void Post(Notification notification) => postScheduler.Add(() =>
|
||||
public void Post(Notification notification) => (notification.IsCritical ? criticalPostScheduler : postScheduler).Add(() =>
|
||||
{
|
||||
++runningDepth;
|
||||
|
||||
@@ -220,6 +221,8 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.Update();
|
||||
|
||||
criticalPostScheduler.Update();
|
||||
|
||||
if (processingPosts)
|
||||
postScheduler.Update();
|
||||
}
|
||||
|
||||
@@ -91,7 +91,12 @@ namespace osu.Game.Overlays
|
||||
public void FlushAllToasts()
|
||||
{
|
||||
foreach (var notification in toastFlow.ToArray())
|
||||
{
|
||||
if (notification.IsCritical)
|
||||
continue;
|
||||
|
||||
forwardNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
public void Post(Notification notification)
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace osu.Game.Overlays.Notifications
|
||||
/// </summary>
|
||||
public bool IsImportant { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Critical notifications show even during gameplay or other scenarios where notifications would usually be suppressed.
|
||||
/// </summary>
|
||||
public bool IsCritical { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Transient notifications only show as a toast, and do not linger in notification history.
|
||||
/// </summary>
|
||||
|
||||
@@ -78,12 +78,13 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
|
||||
private void updateDisplay(APIUser? user)
|
||||
{
|
||||
var cutoffDate = new DateTime(2025, 8, 25);
|
||||
topLinkContainer.Clear();
|
||||
bottomLinkContainer.Clear();
|
||||
|
||||
if (user == null) return;
|
||||
|
||||
if (user.JoinDate.ToUniversalTime().Year < 2008)
|
||||
if (user.JoinDate.ToUniversalTime().Date < cutoffDate)
|
||||
topLinkContainer.AddText(UsersStrings.ShowFirstMembers);
|
||||
else
|
||||
{
|
||||
|
||||
137
osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs
Normal file
137
osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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.Framework.Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class GlobalRankDisplay : CompositeDrawable
|
||||
{
|
||||
public Bindable<UserStatistics?> UserStatistics = new Bindable<UserStatistics?>();
|
||||
public Bindable<APIUser.UserRankHighest?> HighestRank = new Bindable<APIUser.UserRankHighest?>();
|
||||
|
||||
private ProfileValueDisplay info = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public GlobalRankDisplay()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = info = new ProfileValueDisplay(big: true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankGlobalSimple
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
UserStatistics.BindValueChanged(_ => updateState());
|
||||
HighestRank.BindValueChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
info.Content.Text = UserStatistics.Value?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
info.Content.TooltipText = getGlobalRankTooltipText();
|
||||
|
||||
var tier = getRankingTier();
|
||||
info.Content.Colour = tier == null ? colourProvider.Content2 : OsuColour.ForRankingTier(tier.Value);
|
||||
info.Content.Font = info.Content.Font.With(weight: tier == null || tier == RankingTier.Iron ? FontWeight.Regular : FontWeight.Bold);
|
||||
}
|
||||
|
||||
/// <seealso href="https://github.com/ppy/osu-web/blob/6fcd85eb006ce7699d6f747597435c01344b2d2d/resources/js/profile-page/rank.tsx#L19-L46"/>
|
||||
private RankingTier? getRankingTier()
|
||||
{
|
||||
var stats = UserStatistics.Value;
|
||||
|
||||
int? rank = stats?.GlobalRank;
|
||||
float? percent = stats?.GlobalRankPercent;
|
||||
|
||||
if (rank == null || percent == null)
|
||||
return null;
|
||||
|
||||
if (rank <= 100)
|
||||
return RankingTier.Lustrous;
|
||||
|
||||
if (percent < 0.0005)
|
||||
return RankingTier.Radiant;
|
||||
|
||||
if (percent < 0.0025)
|
||||
return RankingTier.Rhodium;
|
||||
|
||||
if (percent < 0.005)
|
||||
return RankingTier.Platinum;
|
||||
|
||||
if (percent < 0.025)
|
||||
return RankingTier.Gold;
|
||||
|
||||
if (percent < 0.05)
|
||||
return RankingTier.Silver;
|
||||
|
||||
if (percent < 0.25)
|
||||
return RankingTier.Bronze;
|
||||
|
||||
if (percent < 0.5)
|
||||
return RankingTier.Iron;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private LocalisableString getGlobalRankTooltipText()
|
||||
{
|
||||
var rankHighest = HighestRank.Value;
|
||||
var variants = UserStatistics.Value?.Variants;
|
||||
|
||||
LocalisableString? result = null;
|
||||
|
||||
if (variants?.Count > 0)
|
||||
{
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
if (variant.GlobalRank != null)
|
||||
{
|
||||
var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}");
|
||||
|
||||
if (result == null)
|
||||
result = variantText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{variantText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rankHighest != null)
|
||||
{
|
||||
var rankHighestText = UsersStrings.ShowRankHighest(
|
||||
rankHighest.Rank.ToLocalisableString("\\##,##0"),
|
||||
rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"));
|
||||
|
||||
if (result == null)
|
||||
result = rankHighestText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{rankHighestText}");
|
||||
}
|
||||
|
||||
return result ?? default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
|
||||
private ProfileValueDisplay medalInfo = null!;
|
||||
private ProfileValueDisplay ppInfo = null!;
|
||||
private ProfileValueDisplay detailGlobalRank = null!;
|
||||
private GlobalRankDisplay detailGlobalRank = null!;
|
||||
private ProfileValueDisplay detailCountryRank = null!;
|
||||
private RankGraph rankGraph = null!;
|
||||
|
||||
@@ -64,10 +64,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
new[]
|
||||
{
|
||||
detailGlobalRank = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankGlobalSimple,
|
||||
},
|
||||
detailGlobalRank = new GlobalRankDisplay(),
|
||||
Empty(),
|
||||
detailCountryRank = new ProfileValueDisplay(true)
|
||||
{
|
||||
@@ -156,59 +153,22 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
var user = data?.User;
|
||||
|
||||
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
|
||||
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
|
||||
medalInfo.Content.Text = user?.Achievements?.Length.ToString() ?? "0";
|
||||
ppInfo.Content.Text = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
|
||||
ppInfo.Content.TooltipText = getPPInfoTooltipText(user);
|
||||
|
||||
foreach (var scoreRankInfo in scoreRankInfos)
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
detailGlobalRank.ContentTooltipText = getGlobalRankTooltipText(user);
|
||||
detailGlobalRank.HighestRank.Value = user?.RankHighest;
|
||||
detailGlobalRank.UserStatistics.Value = user?.Statistics;
|
||||
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
detailCountryRank.ContentTooltipText = getCountryRankTooltipText(user);
|
||||
detailCountryRank.Content.Text = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
detailCountryRank.Content.TooltipText = getCountryRankTooltipText(user);
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
}
|
||||
|
||||
private static LocalisableString getGlobalRankTooltipText(APIUser? user)
|
||||
{
|
||||
var rankHighest = user?.RankHighest;
|
||||
var variants = user?.Statistics?.Variants;
|
||||
|
||||
LocalisableString? result = null;
|
||||
|
||||
if (variants?.Count > 0)
|
||||
{
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
if (variant.GlobalRank != null)
|
||||
{
|
||||
var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}");
|
||||
|
||||
if (result == null)
|
||||
result = variantText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{variantText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rankHighest != null)
|
||||
{
|
||||
var rankHighestText = UsersStrings.ShowRankHighest(
|
||||
rankHighest.Rank.ToLocalisableString("\\##,##0"),
|
||||
rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"));
|
||||
|
||||
if (result == null)
|
||||
result = rankHighestText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{rankHighestText}");
|
||||
}
|
||||
|
||||
return result ?? default;
|
||||
}
|
||||
|
||||
private static LocalisableString getCountryRankTooltipText(APIUser? user)
|
||||
{
|
||||
var variants = user?.Statistics?.Variants;
|
||||
@@ -234,6 +194,28 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
return result ?? default;
|
||||
}
|
||||
|
||||
private static LocalisableString getPPInfoTooltipText(APIUser? user)
|
||||
{
|
||||
var variants = user?.Statistics?.Variants;
|
||||
|
||||
LocalisableString? result = null;
|
||||
|
||||
if (variants?.Count > 0)
|
||||
{
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.PP.ToLocalisableString("#,##0")}");
|
||||
|
||||
if (result == null)
|
||||
result = variantText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{variantText}");
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? default;
|
||||
}
|
||||
|
||||
private partial class ScoreRankInfo : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText rankCount;
|
||||
|
||||
@@ -14,22 +14,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
public partial class ProfileValueDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText title;
|
||||
private readonly ContentText content;
|
||||
|
||||
public LocalisableString Title
|
||||
{
|
||||
set => title.Text = value;
|
||||
}
|
||||
|
||||
public LocalisableString Content
|
||||
{
|
||||
set => content.Text = value;
|
||||
}
|
||||
|
||||
public LocalisableString ContentTooltipText
|
||||
{
|
||||
set => content.TooltipText = value;
|
||||
}
|
||||
public ContentText Content { get; }
|
||||
|
||||
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
||||
{
|
||||
@@ -44,9 +35,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12)
|
||||
},
|
||||
content = new ContentText
|
||||
Content = new ContentText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light),
|
||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: big ? FontWeight.Regular : FontWeight.Light),
|
||||
},
|
||||
new Container // Add a minimum size to the FillFlowContainer
|
||||
{
|
||||
@@ -60,10 +51,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
title.Colour = colourProvider.Content1;
|
||||
content.Colour = colourProvider.Content2;
|
||||
Content.Colour = colourProvider.Content2;
|
||||
}
|
||||
|
||||
private partial class ContentText : OsuSpriteText, IHasTooltip
|
||||
public partial class ContentText : OsuSpriteText, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
||||
{
|
||||
Title = UsersStrings.ShowStatsPlayTime,
|
||||
ContentTooltipText = "0 hours",
|
||||
Content = { TooltipText = "0 hours", }
|
||||
};
|
||||
|
||||
User.BindValueChanged(updateTime, true);
|
||||
@@ -35,8 +35,8 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
||||
{
|
||||
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
||||
info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.Content = formatTime(playTime);
|
||||
info.Content.TooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.Content.Text = formatTime(playTime);
|
||||
}
|
||||
|
||||
private string formatTime(int? secondsNull)
|
||||
|
||||
@@ -189,6 +189,13 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
|
||||
addText($" ({getRulesetName()})");
|
||||
break;
|
||||
|
||||
case RecentActivityType.RankRetracted:
|
||||
addUserLink();
|
||||
addText("'s score on ");
|
||||
addBeatmapLink();
|
||||
addText($" has been retracted ({getRulesetName()})");
|
||||
break;
|
||||
|
||||
case RecentActivityType.UserSupportAgain:
|
||||
addUserLink();
|
||||
addText(" has once again chosen to support osu! - thanks for your generosity!");
|
||||
|
||||
@@ -75,6 +75,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
|
||||
icon.Colour = Color4.White;
|
||||
break;
|
||||
|
||||
case RecentActivityType.RankRetracted:
|
||||
icon.Icon = FontAwesome.Solid.Ban;
|
||||
icon.Colour = colours.Red1;
|
||||
break;
|
||||
|
||||
case RecentActivityType.UserSupportAgain:
|
||||
icon.Icon = FontAwesome.Solid.Heart;
|
||||
icon.Colour = colours.Pink;
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
|
||||
config.BindWith(OsuSetting.HUDVisibilityMode, configVisibilityMode);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -117,7 +118,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
globallyDisableBeatmapSkinSetting();
|
||||
overrideSkinEditorRelevantSettings();
|
||||
|
||||
if (skinEditor != null)
|
||||
{
|
||||
@@ -159,7 +160,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
globallyReenableBeatmapSkinSetting();
|
||||
restoreSkinEditorRelevantSettings();
|
||||
}
|
||||
|
||||
public void PresentGameplay() => presentGameplay(false);
|
||||
@@ -330,24 +331,33 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
||||
private LeasedBindable<bool>? leasedBeatmapSkins;
|
||||
|
||||
private void globallyDisableBeatmapSkinSetting()
|
||||
{
|
||||
if (beatmapSkins.Disabled)
|
||||
return;
|
||||
private readonly Bindable<HUDVisibilityMode> configVisibilityMode = new Bindable<HUDVisibilityMode>();
|
||||
private LeasedBindable<HUDVisibilityMode>? leasedVisibilityMode;
|
||||
|
||||
// The skin editor doesn't work well if beatmap skins are being applied to the player screen.
|
||||
// To keep things simple, disable the setting game-wide while using the skin editor.
|
||||
//
|
||||
// This causes a full reload of the skin, which is pretty ugly.
|
||||
// TODO: Investigate if we can avoid this when a beatmap skin is not being applied by the current beatmap.
|
||||
leasedBeatmapSkins = beatmapSkins.BeginLease(true);
|
||||
leasedBeatmapSkins.Value = false;
|
||||
private void overrideSkinEditorRelevantSettings()
|
||||
{
|
||||
if (!beatmapSkins.Disabled)
|
||||
{
|
||||
// The skin editor doesn't work well if beatmap skins are being applied to the player screen.
|
||||
// To keep things simple, disable the setting game-wide while using the skin editor.
|
||||
//
|
||||
// This causes a full reload of the skin, which is pretty ugly.
|
||||
// TODO: Investigate if we can avoid this when a beatmap skin is not being applied by the current beatmap.
|
||||
leasedBeatmapSkins = beatmapSkins.BeginLease(true);
|
||||
leasedBeatmapSkins.Value = false;
|
||||
}
|
||||
|
||||
leasedVisibilityMode = configVisibilityMode.BeginLease(true);
|
||||
leasedVisibilityMode.Value = HUDVisibilityMode.Always;
|
||||
}
|
||||
|
||||
private void globallyReenableBeatmapSkinSetting()
|
||||
private void restoreSkinEditorRelevantSettings()
|
||||
{
|
||||
leasedBeatmapSkins?.Return();
|
||||
leasedBeatmapSkins = null;
|
||||
|
||||
leasedVisibilityMode?.Return();
|
||||
leasedVisibilityMode = null;
|
||||
}
|
||||
|
||||
public new void ToggleVisibility()
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki
|
||||
{
|
||||
@@ -19,11 +26,15 @@ namespace osu.Game.Overlays.Wiki
|
||||
{
|
||||
public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex;
|
||||
|
||||
private const string github_wiki_base = @"https://github.com/ppy/osu-wiki/blob/master/wiki";
|
||||
|
||||
public readonly Bindable<APIWikiPage> WikiPageData = new Bindable<APIWikiPage>();
|
||||
|
||||
public Action ShowIndexPage;
|
||||
public Action ShowParentPage;
|
||||
|
||||
private readonly Bindable<string> githubPath = new Bindable<string>();
|
||||
|
||||
public WikiHeader()
|
||||
{
|
||||
TabControl.AddItem(IndexPageString);
|
||||
@@ -35,6 +46,9 @@ namespace osu.Game.Overlays.Wiki
|
||||
|
||||
private void onWikiPageChange(ValueChangedEvent<APIWikiPage> e)
|
||||
{
|
||||
// Clear the path beforehand in case we got an error page.
|
||||
githubPath.Value = null;
|
||||
|
||||
if (e.NewValue == null)
|
||||
return;
|
||||
|
||||
@@ -42,6 +56,7 @@ namespace osu.Game.Overlays.Wiki
|
||||
Current.Value = null;
|
||||
|
||||
TabControl.AddItem(IndexPageString);
|
||||
githubPath.Value = $"{github_wiki_base}/{e.NewValue.Path}/{e.NewValue.Locale}.md";
|
||||
|
||||
if (e.NewValue.Path == WikiOverlay.INDEX_PATH)
|
||||
{
|
||||
@@ -56,6 +71,27 @@ namespace osu.Game.Overlays.Wiki
|
||||
Current.Value = e.NewValue.Title;
|
||||
}
|
||||
|
||||
protected override Drawable CreateTabControlContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Height = 40,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ShowOnGitHubButton
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(32),
|
||||
TargetPath = { BindTarget = githubPath },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private void onCurrentChange(ValueChangedEvent<LocalisableString?> e)
|
||||
{
|
||||
if (e.NewValue == TabControl.Items.LastOrDefault())
|
||||
@@ -83,5 +119,39 @@ namespace osu.Game.Overlays.Wiki
|
||||
Icon = OsuIcon.Wiki;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ShowOnGitHubButton : RoundedButton
|
||||
{
|
||||
public override LocalisableString TooltipText => WikiStrings.ShowEditLink;
|
||||
|
||||
public readonly Bindable<string> TargetPath = new Bindable<string>();
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] ILinkHandler linkHandler)
|
||||
{
|
||||
Width = 42;
|
||||
|
||||
Add(new SpriteIcon
|
||||
{
|
||||
Size = new Vector2(12),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Brands.Github,
|
||||
});
|
||||
|
||||
Action = () => linkHandler?.HandleLink(TargetPath.Value);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
TargetPath.BindValueChanged(e =>
|
||||
{
|
||||
this.FadeTo(e.NewValue != null ? 1 : 0);
|
||||
Enabled.Value = e.NewValue != null;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
osu.Game/Rulesets/Mods/IApplicableFailExit.cs
Normal file
22
osu.Game/Rulesets/Mods/IApplicableFailExit.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a mod which can override a fail and quit the game instead.
|
||||
/// </summary>
|
||||
public interface IApplicableFailExit : IApplicableMod
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether we should allow failing at the current point in time.
|
||||
/// </summary>
|
||||
/// <returns>Whether the fail should be allowed to proceed. Return false to block.</returns>
|
||||
bool PerformFail();
|
||||
|
||||
/// <summary>
|
||||
/// Whether we want to exit the game on fail. Only used if <see cref="PerformFail"/> returns true.
|
||||
/// </summary>
|
||||
bool ExitOnFail { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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.Game;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for mods that apply changes to the <see cref="OsuGameBase"/>.
|
||||
/// This is really stupid and f%%king dangerous, possibly disasterous even.
|
||||
/// </summary>
|
||||
public interface IApplicableToOsuGameBase : IApplicableMod
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide a <see cref="OsuGameBase"/>. Called once on initialisation of a play instance.
|
||||
/// </summary>
|
||||
void ApplyToOsuGameBase(OsuGameBase game);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,9 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// The settings are returned in ascending key order as per <see cref="SettingsMap"/>.
|
||||
/// The ordering is intentionally enforced manually, as ordering of <see cref="Dictionary{TKey,TValue}.Values"/> is unspecified.
|
||||
/// </remarks>
|
||||
internal IEnumerable<IBindable> SettingsBindables => SettingsMap.OrderBy(pair => pair.Key).Select(pair => pair.Value);
|
||||
internal IEnumerable<IBindable> SettingsBindables => SettingsMap.OrderBy(pair => pair.Key)
|
||||
.Select(pair => pair.Value)
|
||||
.Where(x => !x.GetType().GetCustomAttributes(typeof(JsonIgnoreAttribute)).Any());
|
||||
|
||||
/// <summary>
|
||||
/// Provides mapping of names to <see cref="IBindable"/>s of all settings within this mod.
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// - Hit windows differ (https://github.com/ppy/osu/issues/11311).
|
||||
/// - Sliders always gives combo for slider end, even on miss (https://github.com/ppy/osu/issues/11769).
|
||||
/// </summary>
|
||||
public sealed override bool Ranked => false;
|
||||
public sealed override bool Ranked => true;
|
||||
|
||||
public sealed override bool ValidForFreestyleAsRequiredMod => false;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModDaycore;
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override LocalisableString Description => "Whoaaaaa...";
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
|
||||
[SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(0.75)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModDoubleTime;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Zoooooooooom...";
|
||||
public override bool Ranked => SpeedChange.IsDefault;
|
||||
public override bool Ranked => true;
|
||||
|
||||
[SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(1.5)
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) };
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
public override bool ValidForFreestyleAsRequiredMod => true;
|
||||
|
||||
protected const float ADJUST_RATIO = 0.5f;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@@ -9,17 +10,23 @@ using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
|
||||
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride, IApplicableFailExit
|
||||
{
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModCinema) };
|
||||
|
||||
[SettingSource("Restart on fail", "Automatically restarts when failed.")]
|
||||
public BindableBool Restart { get; } = new BindableBool();
|
||||
|
||||
[SettingSource("Exit game on fail", "Automatically exits the game when failed."), JsonIgnore]
|
||||
public BindableBool Exit { get; } = new BindableBool();
|
||||
|
||||
public virtual bool PerformFail() => true;
|
||||
|
||||
public virtual bool RestartOnFail => Restart.Value;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual bool ExitOnFail => Exit.Value;
|
||||
|
||||
private Action? triggerFailureDelegate;
|
||||
|
||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||
@@ -39,7 +46,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
/// <param name="healthProcessor">The loaded <see cref="HealthProcessor"/>.</param>
|
||||
/// <param name="result">The latest <see cref="JudgementResult"/>.</param>
|
||||
/// <returns>Whether the fail condition has been met.</returns>
|
||||
/// <returns>Whether the fail condition has been met.</returns>z
|
||||
/// <remarks>
|
||||
/// This method should only be used to trigger failures based on <paramref name="result"/>.
|
||||
/// Using outside values to evaluate failure may introduce event ordering discrepancies, use
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModFlashlight;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Restricted view area.";
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
|
||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||
public abstract BindableFloat SizeMultiplier { get; }
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModHalfTime;
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
public override LocalisableString Description => "Less zoom...";
|
||||
public override bool Ranked => SpeedChange.IsDefault;
|
||||
public override bool Ranked => true;
|
||||
|
||||
[SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(0.75)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Everything just got a bit harder...";
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) };
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
public override bool ValidForFreestyleAsRequiredMod => true;
|
||||
|
||||
protected const float ADJUST_RATIO = 1.4f;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override string Acronym => "HD";
|
||||
public override IconUsage? Icon => OsuIcon.ModHidden;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
|
||||
public virtual void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModNightcore;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Uguuuuuuuu...";
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
|
||||
[SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(1.5)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override LocalisableString Description => "You can't fail, no matter what.";
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) };
|
||||
public override bool Ranked => UsesDefaultConfiguration;
|
||||
public override bool Ranked => true;
|
||||
public override bool ValidForFreestyleAsRequiredMod => true;
|
||||
|
||||
private readonly Bindable<bool> showHealthBar = new Bindable<bool>();
|
||||
|
||||
47
osu.Game/Rulesets/Mods/ModRateAdjustConcrete.cs
Normal file
47
osu.Game/Rulesets/Mods/ModRateAdjustConcrete.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModRateAdjustConcrete : ModRateAdjust
|
||||
{
|
||||
public override string Name => "Rate Adjust";
|
||||
public override LocalisableString Description => "[DEBUG BUILDS ONLY] Set any speed";
|
||||
public override string Acronym => "_R";
|
||||
private readonly RateAdjustModHelper rateAdjustHelper;
|
||||
|
||||
[SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))]
|
||||
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(0.75)
|
||||
{
|
||||
MinValue = 0.1, // BASS breaks at lower rates
|
||||
MaxValue = 10,
|
||||
Precision = 0.01
|
||||
};
|
||||
|
||||
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
|
||||
public virtual BindableBool AdjustPitch { get; } = new BindableBool();
|
||||
|
||||
|
||||
protected ModRateAdjustConcrete()
|
||||
{
|
||||
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
|
||||
rateAdjustHelper.HandleAudioAdjustments(AdjustPitch);
|
||||
}
|
||||
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override void ApplyToTrack(IAdjustableAudioComponent track)
|
||||
{
|
||||
rateAdjustHelper.ApplyToTrack(track);
|
||||
}
|
||||
public override bool Ranked => false;
|
||||
public override ModType Type => ModType.Special;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
Conversion,
|
||||
Automation,
|
||||
Fun,
|
||||
System
|
||||
System,
|
||||
Special
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public readonly string OriginalAcronym;
|
||||
|
||||
public override string Name => $"Unknown mod ({OriginalAcronym})";
|
||||
public override string Acronym => $"{OriginalAcronym}??";
|
||||
public override string Acronym => $"{OriginalAcronym}!";
|
||||
public override LocalisableString Description => "This mod could not be resolved by the game.";
|
||||
public override double ScoreMultiplier => 0;
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -32,6 +34,8 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||
|
||||
private readonly RulesetKeyBindingContainer rulesetKeyBindingContainer;
|
||||
|
||||
[Resolved]
|
||||
private ScoreProcessor? scoreProcessor { get; set; }
|
||||
|
||||
@@ -64,9 +68,9 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
{
|
||||
InternalChild = KeyBindingContainer =
|
||||
CreateKeyBindingContainer(ruleset, variant, unique)
|
||||
.WithChild(content = new Container { RelativeSizeAxes = Axes.Both });
|
||||
rulesetKeyBindingContainer = createRulesetKeyBindingContainer(ruleset, variant, unique);
|
||||
|
||||
InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique).WithChild(content = new Container { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
@@ -174,11 +178,10 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public void Attach(InputCountController inputCountController)
|
||||
{
|
||||
var triggers = KeyBindingContainer.DefaultKeyBindings
|
||||
.Select(b => b.GetAction<T>())
|
||||
.Distinct()
|
||||
.Select(action => new KeyCounterActionTrigger<T>(action))
|
||||
.ToArray();
|
||||
var bindings = rulesetKeyBindingContainer.DefaultKeyBindings;
|
||||
var triggers = bindings.Select(b => new KeyCounterBindingTrigger<T>(b, b.GetAction<T>()))
|
||||
.DistinctBy(b => b.Action)
|
||||
.ToArray();
|
||||
|
||||
KeyBindingContainer.AddRange(triggers);
|
||||
inputCountController.AddRange(triggers);
|
||||
@@ -215,6 +218,9 @@ namespace osu.Game.Rulesets.UI
|
||||
protected virtual KeyBindingContainer<T> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
=> new RulesetKeyBindingContainer(ruleset, variant, unique);
|
||||
|
||||
private RulesetKeyBindingContainer createRulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
=> new RulesetKeyBindingContainer(ruleset, variant, unique);
|
||||
|
||||
public partial class RulesetKeyBindingContainer : DatabasedKeyBindingContainer<T>
|
||||
{
|
||||
protected override bool HandleRepeats => false;
|
||||
|
||||
@@ -486,7 +486,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private void loadSongSelect() => this.Push(forceSSV1.Value ? new PlaySongSelect() : new SoloSongSelect());
|
||||
|
||||
private void joinOrLeaveMatchmakingQueue() => this.Push(new OnlinePlay.Matchmaking.Intro.IntroScreen());
|
||||
private void joinOrLeaveMatchmakingQueue() => this.Push(new OnlinePlay.Matchmaking.Intro.ScreenIntro());
|
||||
|
||||
private partial class MobileDisclaimerDialog : PopupDialog
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro
|
||||
/// <summary>
|
||||
/// A brief intro animation that introduces matchmaking to the user.
|
||||
/// </summary>
|
||||
public partial class IntroScreen : OsuScreen
|
||||
public partial class ScreenIntro : OsuScreen
|
||||
{
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => false;
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new MatchmakingIntroBackgroundScreen(colourProvider);
|
||||
|
||||
public IntroScreen()
|
||||
public ScreenIntro()
|
||||
{
|
||||
ValidForResume = false;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapSetOverlay? beatmapSetOverlay { get; set; }
|
||||
|
||||
public BeatmapCardMatchmaking(APIBeatmap beatmap)
|
||||
: base(beatmap.BeatmapSet!, false)
|
||||
{
|
||||
@@ -319,7 +322,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>
|
||||
{
|
||||
new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, DefaultAction)
|
||||
new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, () => beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineID))
|
||||
};
|
||||
|
||||
foreach (var button in buttonContainer.Buttons)
|
||||
|
||||
@@ -111,7 +111,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect
|
||||
{
|
||||
Debug.Assert(card == null);
|
||||
|
||||
var beatmap = b.GetResultSafely()!;
|
||||
APIBeatmap beatmap = b.GetResultSafely() ?? new APIBeatmap
|
||||
{
|
||||
BeatmapSet = new APIBeatmapSet
|
||||
{
|
||||
Title = "unknown beatmap",
|
||||
TitleUnicode = "unknown beatmap",
|
||||
Artist = "unknown artist",
|
||||
ArtistUnicode = "unknown artist",
|
||||
}
|
||||
};
|
||||
|
||||
beatmap.StarRating = Item.StarRating;
|
||||
|
||||
mainContent.Add(card = new BeatmapCardMatchmaking(beatmap)
|
||||
|
||||
@@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
@@ -27,6 +28,7 @@ using osu.Game.Online.Matchmaking.Events;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
|
||||
@@ -107,6 +109,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
private BufferedContainer backgroundQuitTarget = null!;
|
||||
private BufferedContainer avatarQuitTarget = null!;
|
||||
|
||||
private Box downloadProgressBar = null!;
|
||||
|
||||
private PlayerPanelDisplayMode displayMode = PlayerPanelDisplayMode.Horizontal;
|
||||
private bool hasQuit;
|
||||
|
||||
@@ -158,80 +162,91 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = mainContent = new Container
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
mainContent = new Container
|
||||
{
|
||||
quitText = new OsuSpriteText
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "QUIT",
|
||||
Font = OsuFont.Default.With(weight: "Bold", size: 70),
|
||||
Rotation = -22.5f,
|
||||
Colour = OsuColour.Gray(0.3f),
|
||||
Blending = BlendingParameters.Additive
|
||||
},
|
||||
avatarPositionTarget = new Container
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Size = avatar_size,
|
||||
Child = avatarJumpTarget = new Container
|
||||
quitText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "QUIT",
|
||||
Font = OsuFont.Default.With(weight: "Bold", size: 70),
|
||||
Rotation = -22.5f,
|
||||
Colour = OsuColour.Gray(0.3f),
|
||||
Blending = BlendingParameters.Additive
|
||||
},
|
||||
avatarPositionTarget = new Container
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Size = avatar_size,
|
||||
Child = avatarJumpTarget = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = avatar = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// Needs to be re-buffered as the avatar is proxied outside of the parent buffered container.
|
||||
Child = avatarQuitTarget = new BufferedContainer
|
||||
{
|
||||
FrameBufferScale = new Vector2(1.5f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new MatchmakingAvatar(User, isOwnUser: User.Id == api.LocalUser.Value.Id)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
rankText = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Margin = new MarginPadding(4),
|
||||
Text = "-",
|
||||
Font = OsuFont.Style.Title.With(size: 55),
|
||||
},
|
||||
username = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = avatar = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// Needs to be re-buffered as the avatar is proxied outside of the parent buffered container.
|
||||
Child = avatarQuitTarget = new BufferedContainer
|
||||
{
|
||||
FrameBufferScale = new Vector2(1.5f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new MatchmakingAvatar(User, isOwnUser: User.Id == api.LocalUser.Value.Id)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = Vector2.One
|
||||
}
|
||||
}
|
||||
},
|
||||
Text = User.Username,
|
||||
Font = OsuFont.Style.Heading1,
|
||||
},
|
||||
scoreText = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(10),
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Style.Heading2,
|
||||
Text = "0 pts"
|
||||
}
|
||||
},
|
||||
rankText = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Margin = new MarginPadding(4),
|
||||
Text = "-",
|
||||
Font = OsuFont.Style.Title.With(size: 55),
|
||||
},
|
||||
username = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Text = User.Username,
|
||||
Font = OsuFont.Style.Heading1,
|
||||
},
|
||||
scoreText = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(10),
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Style.Heading2,
|
||||
Text = "0 pts"
|
||||
}
|
||||
},
|
||||
downloadProgressBar = new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Size = new Vector2(0, 4),
|
||||
Colour = colourProvider?.Content2 ?? colours.Gray3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,6 +265,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
|
||||
client.MatchRoomStateChanged += onRoomStateChanged;
|
||||
client.MatchEvent += onMatchEvent;
|
||||
client.BeatmapAvailabilityChanged += onBeatmapAvailabilityChanged;
|
||||
|
||||
onRoomStateChanged(client.Room!.MatchState);
|
||||
|
||||
@@ -414,8 +430,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
if (!matchmakingState.Users.UserDictionary.TryGetValue(User.Id, out MatchmakingUser? userScore))
|
||||
return;
|
||||
|
||||
rankText.Text = userScore.Placement.Ordinalize(CultureInfo.CurrentCulture);
|
||||
rankText.FadeColour(SubScreenResults.ColourForPlacement(userScore.Placement));
|
||||
if (userScore.Placement == null)
|
||||
return;
|
||||
|
||||
rankText.Text = userScore.Placement.Value.Ordinalize(CultureInfo.CurrentCulture);
|
||||
rankText.FadeColour(SubScreenResults.ColourForPlacement(userScore.Placement.Value));
|
||||
scoreText.Text = $"{userScore.Points} pts";
|
||||
});
|
||||
|
||||
@@ -469,6 +488,16 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
}
|
||||
}
|
||||
|
||||
private void onBeatmapAvailabilityChanged(MultiplayerRoomUser user, BeatmapAvailability availability) => Scheduler.Add(() =>
|
||||
{
|
||||
if (availability.State == DownloadState.Downloading)
|
||||
downloadProgressBar.FadeIn(200, Easing.OutPow10);
|
||||
else
|
||||
downloadProgressBar.FadeOut(200, Easing.OutPow10);
|
||||
|
||||
downloadProgressBar.ResizeWidthTo(availability.DownloadProgress ?? 0, 200, Easing.OutPow10);
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
@@ -477,6 +506,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
{
|
||||
client.MatchRoomStateChanged -= onRoomStateChanged;
|
||||
client.MatchEvent -= onMatchEvent;
|
||||
client.BeatmapAvailabilityChanged -= onBeatmapAvailabilityChanged;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
|
||||
if (client.Room?.MatchState is not MatchmakingRoomState matchmakingState)
|
||||
continue;
|
||||
|
||||
if (matchmakingState.Users.UserDictionary.TryGetValue(panels[i].User.Id, out MatchmakingUser? user))
|
||||
SetLayoutPosition(Children[i], user.Placement);
|
||||
if (matchmakingState.Users.UserDictionary.TryGetValue(panels[i].User.Id, out MatchmakingUser? user) && user.Placement != null)
|
||||
SetLayoutPosition(Children[i], user.Placement.Value);
|
||||
else
|
||||
SetLayoutPosition(Children[i], float.MaxValue);
|
||||
}
|
||||
|
||||
@@ -194,20 +194,25 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results
|
||||
{
|
||||
userStatistics.Clear();
|
||||
|
||||
if (state.Users[client.LocalUser!.UserID].Rounds.Count == 0)
|
||||
var localUserState = state.Users.GetOrAdd(client.LocalUser!.UserID);
|
||||
|
||||
if (localUserState.Rounds.Count == 0)
|
||||
{
|
||||
placementText.Text = "-";
|
||||
placementText.Colour = OsuColour.Gray(1f);
|
||||
return;
|
||||
}
|
||||
|
||||
int overallPlacement = state.Users[client.LocalUser!.UserID].Placement;
|
||||
int? overallPlacement = localUserState.Placement;
|
||||
|
||||
placementText.Text = overallPlacement.Ordinalize(CultureInfo.CurrentCulture);
|
||||
placementText.Colour = ColourForPlacement(overallPlacement);
|
||||
if (overallPlacement != null)
|
||||
{
|
||||
placementText.Text = overallPlacement.Value.Ordinalize(CultureInfo.CurrentCulture);
|
||||
placementText.Colour = ColourForPlacement(overallPlacement.Value);
|
||||
|
||||
int overallPoints = state.Users[client.LocalUser!.UserID].Points;
|
||||
addStatistic(overallPlacement, $"Overall position ({overallPoints} points)");
|
||||
int overallPoints = localUserState.Points;
|
||||
addStatistic(overallPlacement.Value, $"Overall position ({overallPoints} points)");
|
||||
}
|
||||
|
||||
var accuracyOrderedUsers = state.Users.Select(u => (user: u, avgAcc: u.Rounds.Select(r => r.Accuracy).DefaultIfEmpty(0).Average()))
|
||||
.OrderByDescending(t => t.avgAcc)
|
||||
@@ -216,15 +221,16 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results
|
||||
int accuracyPlacement = accuracyOrderedUsers.index + 1;
|
||||
addStatistic(accuracyPlacement, $"Overall accuracy ({accuracyOrderedUsers.info.avgAcc.FormatAccuracy()})");
|
||||
|
||||
var maxComboOrderedUsers = state.Users.Select(u => (user: u, maxCombo: u.Rounds.Max(r => r.MaxCombo)))
|
||||
var maxComboOrderedUsers = state.Users.Select(u => (user: u, maxCombo: u.Rounds.Select(r => r.MaxCombo).DefaultIfEmpty(0).Max()))
|
||||
.OrderByDescending(t => t.maxCombo)
|
||||
.Select((t, i) => (info: t, index: i))
|
||||
.Single(t => t.info.user.UserId == client.LocalUser!.UserID);
|
||||
int maxComboPlacement = maxComboOrderedUsers.index + 1;
|
||||
addStatistic(maxComboPlacement, $"Best max combo ({maxComboOrderedUsers.info.maxCombo}x)");
|
||||
|
||||
var bestPlacement = state.Users[client.LocalUser!.UserID].Rounds.MinBy(r => r.Placement);
|
||||
addStatistic(bestPlacement!.Placement, $"Best round placement (round {bestPlacement.Round})");
|
||||
var bestPlacement = localUserState.Rounds.MinBy(r => r.Placement);
|
||||
if (bestPlacement != null)
|
||||
addStatistic(bestPlacement.Placement, $"Best round placement (round {bestPlacement.Round})");
|
||||
|
||||
void addStatistic(int position, string text) => userStatistics.Add(new PanelUserStatistic(position, text));
|
||||
}
|
||||
@@ -255,27 +261,27 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results
|
||||
roomAwards.Clear();
|
||||
|
||||
long maxScore = long.MinValue;
|
||||
int maxScoreUserId = 0;
|
||||
int maxScoreUserId = -1;
|
||||
|
||||
double maxAccuracy = double.MinValue;
|
||||
int maxAccuracyUserId = 0;
|
||||
int maxAccuracyUserId = -1;
|
||||
|
||||
int maxCombo = int.MinValue;
|
||||
int maxComboUserId = 0;
|
||||
int maxComboUserId = -1;
|
||||
|
||||
long maxBonusScore = 0;
|
||||
int maxBonusScoreUserId = 0;
|
||||
int maxBonusScoreUserId = -1;
|
||||
|
||||
long largestScoreDifference = long.MinValue;
|
||||
int largestScoreDifferenceUserId = 0;
|
||||
int largestScoreDifferenceUserId = -1;
|
||||
|
||||
long smallestScoreDifference = long.MaxValue;
|
||||
int smallestScoreDifferenceUserId = 0;
|
||||
int smallestScoreDifferenceUserId = -1;
|
||||
|
||||
for (int round = 1; round <= state.CurrentRound; round++)
|
||||
{
|
||||
long roundHighestScore = long.MinValue;
|
||||
int roundHighestScoreUserId = 0;
|
||||
int roundHighestScoreUserId = -1;
|
||||
|
||||
long roundLowestScore = long.MaxValue;
|
||||
|
||||
@@ -344,11 +350,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results
|
||||
}
|
||||
}
|
||||
|
||||
addAward(maxScoreUserId, "Score champ", "Highest score in a single round");
|
||||
if (maxScoreUserId > 0)
|
||||
addAward(maxScoreUserId, "Score champ", "Highest score in a single round");
|
||||
|
||||
addAward(maxAccuracyUserId, "Most accurate", "Highest accuracy in a single round");
|
||||
if (maxAccuracyUserId > 0)
|
||||
addAward(maxAccuracyUserId, "Most accurate", "Highest accuracy in a single round");
|
||||
|
||||
addAward(maxComboUserId, "Top combo", "Highest combo in a single round");
|
||||
if (maxComboUserId > 0)
|
||||
addAward(maxComboUserId, "Top combo", "Highest combo in a single round");
|
||||
|
||||
if (maxBonusScoreUserId > 0)
|
||||
addAward(maxBonusScoreUserId, "Biggest bonus", "Biggest bonus score across all rounds");
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
@@ -32,11 +37,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
[Resolved]
|
||||
private INotificationOverlay? notifications { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner? performer { get; set; }
|
||||
|
||||
private ProgressNotification? backgroundNotification;
|
||||
private Notification? readyNotification;
|
||||
private BackgroundQueueNotification? backgroundNotification;
|
||||
private bool isBackgrounded;
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -118,27 +119,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
if (backgroundNotification != null)
|
||||
return;
|
||||
|
||||
notifications?.Post(backgroundNotification = new ProgressNotification
|
||||
{
|
||||
Text = "Searching for opponents...",
|
||||
CompletionTarget = n => notifications.Post(readyNotification = n),
|
||||
CompletionText = "Your match is ready! Click to join.",
|
||||
CompletionClickAction = () =>
|
||||
{
|
||||
client.MatchmakingAcceptInvitation().FireAndForget();
|
||||
performer?.PerformFromScreen(s => s.Push(new IntroScreen()));
|
||||
|
||||
closeNotifications();
|
||||
return true;
|
||||
},
|
||||
CancelRequested = () =>
|
||||
{
|
||||
client.MatchmakingLeaveQueue().FireAndForget();
|
||||
|
||||
closeNotifications();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
notifications?.Post(backgroundNotification = new BackgroundQueueNotification(this));
|
||||
}
|
||||
|
||||
private void closeNotifications()
|
||||
@@ -146,13 +127,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
if (backgroundNotification != null)
|
||||
{
|
||||
backgroundNotification.State = ProgressNotificationState.Cancelled;
|
||||
backgroundNotification.Close(false);
|
||||
backgroundNotification.CloseAll();
|
||||
backgroundNotification = null;
|
||||
}
|
||||
|
||||
readyNotification?.Close(false);
|
||||
|
||||
backgroundNotification = null;
|
||||
readyNotification = null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@@ -168,5 +145,87 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
||||
client.MatchmakingRoomReady -= onMatchmakingRoomReady;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class BackgroundQueueNotification : ProgressNotification
|
||||
{
|
||||
[Resolved]
|
||||
private IPerformFromScreenRunner? performer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private readonly QueueController controller;
|
||||
|
||||
private Notification? foundNotification;
|
||||
private Sample? matchFoundSample;
|
||||
|
||||
public BackgroundQueueNotification(QueueController controller)
|
||||
{
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
Text = "Searching for opponents...";
|
||||
|
||||
CompletionClickAction = () =>
|
||||
{
|
||||
client.MatchmakingAcceptInvitation().FireAndForget();
|
||||
controller.CurrentState.Value = ScreenQueue.MatchmakingScreenState.AcceptedWaitingForRoom;
|
||||
|
||||
performer?.PerformFromScreen(s => s.Push(new ScreenIntro()));
|
||||
|
||||
Close(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
CancelRequested = () =>
|
||||
{
|
||||
client.MatchmakingLeaveQueue().FireAndForget();
|
||||
return true;
|
||||
};
|
||||
|
||||
matchFoundSample = audio.Samples.Get(@"Multiplayer/Matchmaking/match-found");
|
||||
}
|
||||
|
||||
protected override Notification CreateCompletionNotification()
|
||||
{
|
||||
// Playing here means it will play even if notification overlay is hidden.
|
||||
//
|
||||
// If we add support for the completion notification to be processed during gameplay,
|
||||
// this can be moved inside the `MatchFoundNotification` implementation.
|
||||
matchFoundSample?.Play();
|
||||
|
||||
return foundNotification = new MatchFoundNotification
|
||||
{
|
||||
Activated = CompletionClickAction,
|
||||
Text = "Your match is ready! Click to join.",
|
||||
};
|
||||
}
|
||||
|
||||
public void CloseAll()
|
||||
{
|
||||
foundNotification?.Close(false);
|
||||
Close(false);
|
||||
}
|
||||
|
||||
public partial class MatchFoundNotification : ProgressCompletionNotification
|
||||
{
|
||||
protected override IconUsage CloseButtonIcon => FontAwesome.Solid.Times;
|
||||
|
||||
public MatchFoundNotification()
|
||||
{
|
||||
IsCritical = true;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Icon = FontAwesome.Solid.Bolt;
|
||||
IconContent.Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.YellowLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
AllowPause = false,
|
||||
AllowRestart = false,
|
||||
AllowSkipping = room.AutoSkip,
|
||||
AutomaticallySkipIntro = room.AutoSkip,
|
||||
ShowLeaderboard = true,
|
||||
})
|
||||
@@ -121,6 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
client.GameplayStarted += onGameplayStarted;
|
||||
client.ResultsReady += onResultsReady;
|
||||
client.VoteToSkipIntroPassed += onVoteToSkipIntroPassed;
|
||||
|
||||
ScoreProcessor.HasCompleted.BindValueChanged(_ =>
|
||||
{
|
||||
@@ -148,6 +148,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Debug.Assert(client.Room != null);
|
||||
}
|
||||
|
||||
protected override SkipOverlay CreateSkipOverlay(double startTime) => new MultiplayerSkipOverlay(startTime);
|
||||
|
||||
protected override void StartGameplay()
|
||||
{
|
||||
// We can enter this screen one of two ways:
|
||||
@@ -219,6 +221,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
await Task.WhenAny(resultsReady.Task, Task.Delay(TimeSpan.FromSeconds(60))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override void RequestIntroSkip()
|
||||
{
|
||||
// If the room is set up such that the intro is automatically skipped, there's no need to vote on it.
|
||||
if (Configuration.AutomaticallySkipIntro)
|
||||
{
|
||||
base.RequestIntroSkip();
|
||||
return;
|
||||
}
|
||||
|
||||
// No base call because we aren't skipping yet.
|
||||
client.VoteToSkipIntro().FireAndForget();
|
||||
}
|
||||
|
||||
private void onVoteToSkipIntroPassed()
|
||||
{
|
||||
Schedule(() => PerformIntroSkip(true));
|
||||
}
|
||||
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||
{
|
||||
Debug.Assert(Room.RoomID != null);
|
||||
@@ -242,6 +262,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
client.GameplayStarted -= onGameplayStarted;
|
||||
client.ResultsReady -= onResultsReady;
|
||||
client.VoteToSkipIntroPassed -= onVoteToSkipIntroPassed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public partial class MultiplayerSkipOverlay : SkipOverlay
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; } = null!;
|
||||
|
||||
private Drawable votedIcon = null!;
|
||||
private OsuSpriteText countText = null!;
|
||||
|
||||
public MultiplayerSkipOverlay(double startTime)
|
||||
: base(startTime)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
FadingContent.AddRange(
|
||||
[
|
||||
votedIcon = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Position = new Vector2(50, 0),
|
||||
Size = new Vector2(20),
|
||||
Alpha = 0,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Green
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(0.5f),
|
||||
Icon = FontAwesome.Solid.Check
|
||||
}
|
||||
}
|
||||
},
|
||||
countText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.Centre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Position = new Vector2(0.75f, 0),
|
||||
Font = OsuFont.Default.With(size: 36, weight: FontWeight.Bold)
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
client.UserLeft += onUserLeft;
|
||||
client.UserStateChanged += onUserStateChanged;
|
||||
client.UserVotedToSkipIntro += onUserVotedToSkipIntro;
|
||||
|
||||
updateText();
|
||||
}
|
||||
|
||||
private void onUserLeft(MultiplayerRoomUser user)
|
||||
{
|
||||
Schedule(updateText);
|
||||
}
|
||||
|
||||
private void onUserStateChanged(MultiplayerRoomUser user, MultiplayerUserState state)
|
||||
{
|
||||
Schedule(updateText);
|
||||
}
|
||||
|
||||
private void onUserVotedToSkipIntro(int userId) => Schedule(() =>
|
||||
{
|
||||
updateText();
|
||||
|
||||
countText.ScaleTo(1.5f).ScaleTo(1, 200, Easing.OutSine);
|
||||
|
||||
if (userId == client.LocalUser?.UserID)
|
||||
{
|
||||
votedIcon.ScaleTo(1.5f).ScaleTo(1, 200, Easing.OutSine);
|
||||
votedIcon.FadeInFromZero(100);
|
||||
}
|
||||
});
|
||||
|
||||
private void updateText()
|
||||
{
|
||||
if (client.Room == null || client.Room.Settings.AutoSkip)
|
||||
return;
|
||||
|
||||
int countTotal = client.Room.Users.Count(u => u.State == MultiplayerUserState.Playing);
|
||||
int countSkipped = client.Room.Users.Count(u => u.State == MultiplayerUserState.Playing && u.VotedToSkipIntro);
|
||||
int countRequired = countTotal / 2 + 1;
|
||||
|
||||
countText.Text = $"{Math.Min(countRequired, countSkipped)} / {countRequired}";
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (client.IsNotNull())
|
||||
{
|
||||
client.UserLeft -= onUserLeft;
|
||||
client.UserStateChanged -= onUserStateChanged;
|
||||
client.UserVotedToSkipIntro -= onUserVotedToSkipIntro;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,33 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Screens.Play.Break
|
||||
{
|
||||
public partial class RemainingTimeCounter : Counter
|
||||
{
|
||||
private readonly OsuSpriteText counter;
|
||||
private readonly ArgonCounterTextComponent counter;
|
||||
|
||||
public RemainingTimeCounter()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChild = counter = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Numeric.With(size: 33),
|
||||
};
|
||||
InternalChild = counter = new ArgonCounterTextComponent(Anchor.Centre);
|
||||
counter.Scale *= 1.25f; // this seems to be the only way to make the counter bigger, I hope I'm wrong
|
||||
counter.WireframeOpacity.BindTo(new BindableFloat(0.125f));
|
||||
}
|
||||
private string lookup(char c)
|
||||
{
|
||||
return c.ToString();
|
||||
}
|
||||
protected override void OnCountChanged(double count)
|
||||
{
|
||||
string displayText = ((int)Math.Ceiling(count / 1000)).ToString();
|
||||
counter.Text = displayText;
|
||||
counter.WireframeTemplate = new string('#', displayText.Length);
|
||||
}
|
||||
|
||||
protected override void OnCountChanged(double count) => counter.Text = ((int)Math.Ceiling(count / 1000)).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
97
osu.Game/Screens/Play/HUD/ArgonLongestComboCounter.cs
Normal file
97
osu.Game/Screens/Play/HUD/ArgonLongestComboCounter.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation.SkinComponents;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class ArgonLongestComboCounter : ComboCounter
|
||||
{
|
||||
protected ArgonCounterTextComponent Text = null!;
|
||||
|
||||
protected override double RollingDuration => 250;
|
||||
|
||||
protected virtual bool DisplayXSymbol => true;
|
||||
|
||||
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
|
||||
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
|
||||
{
|
||||
Precision = 0.01f,
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
};
|
||||
|
||||
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))]
|
||||
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
|
||||
|
||||
[SettingSource("Show animation on increase", "Shows a bouncing animation when the combo increases")]
|
||||
public Bindable<bool> ShowRolling { get; } = new BindableBool(true);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
Current.BindTo(scoreProcessor.HighestCombo);
|
||||
Current.BindValueChanged(combo =>
|
||||
{
|
||||
bool wasIncrease = combo.NewValue > combo.OldValue;
|
||||
bool wasMiss = combo.OldValue > 1 && combo.NewValue == 0;
|
||||
|
||||
float newScale = Math.Clamp(Text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f);
|
||||
|
||||
float duration = ShowRolling.Value ? 500 : 0;
|
||||
|
||||
Text.NumberContainer
|
||||
.ScaleTo(new Vector2(newScale))
|
||||
.ScaleTo(Vector2.One, duration, Easing.OutQuint);
|
||||
});
|
||||
}
|
||||
|
||||
public override int DisplayedCount
|
||||
{
|
||||
get => base.DisplayedCount;
|
||||
set
|
||||
{
|
||||
base.DisplayedCount = value;
|
||||
updateWireframe();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWireframe()
|
||||
{
|
||||
int digitsRequiredForDisplayCount = getDigitsRequiredForDisplayCount();
|
||||
|
||||
if (digitsRequiredForDisplayCount != Text.WireframeTemplate.Length)
|
||||
Text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount);
|
||||
}
|
||||
|
||||
private int getDigitsRequiredForDisplayCount()
|
||||
{
|
||||
// one for the single presumed starting digit, one for the "x" at the end (unless disabled).
|
||||
int digitsRequired = DisplayXSymbol ? 2 : 1;
|
||||
long c = DisplayedCount;
|
||||
while ((c /= 10) > 0)
|
||||
digitsRequired++;
|
||||
return digitsRequired;
|
||||
}
|
||||
|
||||
protected override LocalisableString FormatCount(int count) => DisplayXSymbol ? $@"{count}x" : count.ToString();
|
||||
|
||||
protected override IHasText CreateText() => Text = new ArgonCounterTextComponent(Anchor.TopLeft, "MAX COMBO")
|
||||
{
|
||||
WireframeOpacity = { BindTarget = WireframeOpacity },
|
||||
ShowLabel = { BindTarget = ShowLabel },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation.SkinComponents;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||
{
|
||||
public partial class ArgonClicksPerSecondCounter : RollingCounter<int>, ISerialisableDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private ClicksPerSecondController controller { get; set; } = null!;
|
||||
|
||||
protected override double RollingDuration => 175;
|
||||
|
||||
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
|
||||
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
|
||||
{
|
||||
Precision = 0.01f,
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
};
|
||||
|
||||
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel))]
|
||||
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public ArgonClicksPerSecondCounter()
|
||||
{
|
||||
Current.Value = 0;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Current.Value = controller.Value;
|
||||
}
|
||||
|
||||
protected override IHasText CreateText() => new TextComponent()
|
||||
{
|
||||
WireframeOpacity = { BindTarget = WireframeOpacity },
|
||||
ShowLabel = { BindTarget = ShowLabel },
|
||||
};
|
||||
|
||||
private partial class TextComponent : CompositeDrawable, IHasText
|
||||
{
|
||||
private readonly ArgonCounterTextComponent cpsValue;
|
||||
|
||||
public IBindable<float> WireframeOpacity { get; } = new BindableFloat();
|
||||
|
||||
public Bindable<bool> ShowLabel { get; } = new BindableBool();
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
get => cpsValue.Text;
|
||||
set => cpsValue.Text = value;
|
||||
}
|
||||
|
||||
public TextComponent()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = cpsValue = new ArgonCounterTextComponent(Anchor.TopLeft, "KEYS/SEC") // welp, not good
|
||||
{
|
||||
WireframeOpacity = { BindTarget = WireframeOpacity },
|
||||
WireframeTemplate = @"##",
|
||||
ShowLabel = { BindTarget = ShowLabel },
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
osu.Game/Screens/Play/HUD/KeyCounterBindingTrigger.cs
Normal file
61
osu.Game/Screens/Play/HUD/KeyCounterBindingTrigger.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class KeyCounterBindingTrigger<T> : InputTrigger, IKeyBindingHandler<T>
|
||||
where T : struct
|
||||
{
|
||||
public IKeyBinding Key { get; }
|
||||
public T Action { get; }
|
||||
|
||||
[Resolved]
|
||||
private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ReadableKeyCombinationProvider keys)
|
||||
{
|
||||
keyCombinationProvider = keys;
|
||||
}
|
||||
|
||||
private string getName(IKeyBinding key, T action)
|
||||
{
|
||||
return keyCombinationProvider?.GetReadableString(key.KeyCombination) ?? $"B{(int)(object)action + 1}";
|
||||
}
|
||||
public KeyCounterBindingTrigger(IKeyBinding key, T action)
|
||||
: base("")
|
||||
{
|
||||
Key = key;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Name = getName(Key, Action);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
|
||||
return false;
|
||||
|
||||
Activate(Clock.Rate >= 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
|
||||
return;
|
||||
|
||||
Deactivate(Clock.Rate >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class SkinnableBeatmapSetOnlineStatusPill : CompositeDrawable, ISerialisableDrawable
|
||||
{
|
||||
private BeatmapSetOnlineStatusPill beatmapSetOnlineStatusPill { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
public SkinnableBeatmapSetOnlineStatusPill() // WARNING: this is awful
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
beatmapSetOnlineStatusPill = new BeatmapSetOnlineStatusPill()
|
||||
{
|
||||
ShowUnknownStatus = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
beatmap.BindValueChanged(b =>
|
||||
{
|
||||
beatmapSetOnlineStatusPill.Status = beatmap.Value.BeatmapInfo.Status;
|
||||
}, true);
|
||||
}
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
}
|
||||
}
|
||||
64
osu.Game/Screens/Play/HUD/SkinnableStarRatingDisplay.cs
Normal file
64
osu.Game/Screens/Play/HUD/SkinnableStarRatingDisplay.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation.SkinComponents;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class SkinnableStarRatingDisplay : CompositeDrawable, ISerialisableDrawable
|
||||
{
|
||||
private StarRatingDisplay starRatingDisplay { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||
|
||||
private CancellationTokenSource? difficultyCancellationSource;
|
||||
private IBindable<StarDifficulty>? difficultyBindable;
|
||||
|
||||
public SkinnableStarRatingDisplay() // WARNING: this is awful
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
starRatingDisplay = new StarRatingDisplay(new StarDifficulty(0.00, 0), animated: true)
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
beatmap.BindValueChanged(b =>
|
||||
{
|
||||
difficultyCancellationSource?.Cancel();
|
||||
difficultyCancellationSource = new CancellationTokenSource();
|
||||
|
||||
difficultyBindable?.UnbindAll();
|
||||
difficultyBindable = difficultyCache.GetBindableDifficulty(b.NewValue.BeatmapInfo, difficultyCancellationSource.Token);
|
||||
}, true);
|
||||
starRatingDisplay.Current.BindTo((Bindable<StarDifficulty>)difficultyBindable!);
|
||||
}
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -115,14 +115,15 @@ namespace osu.Game.Screens.Play
|
||||
/// <summary>
|
||||
/// Skip forward to the next valid skip point.
|
||||
/// </summary>
|
||||
public void Skip()
|
||||
/// <param name="fullLength"><c>true</c> to skip as close to gameplay as possible, or <c>false</c> to skip only to the next valid skip point.</param>
|
||||
public void Skip(bool fullLength = false)
|
||||
{
|
||||
if (GameplayClock.CurrentTime > GameplayStartTime - MINIMUM_SKIP_TIME)
|
||||
return;
|
||||
|
||||
double skipTarget = GameplayStartTime - MINIMUM_SKIP_TIME;
|
||||
|
||||
if (StartTime < -10000 && GameplayClock.CurrentTime < 0 && skipTarget > 6000)
|
||||
if (!fullLength && StartTime < -10000 && GameplayClock.CurrentTime < 0 && skipTarget > 6000)
|
||||
// double skip exception for storyboards with very long intros
|
||||
skipTarget = 0;
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private BreakTracker breakTracker;
|
||||
|
||||
private SkipOverlay skipIntroOverlay;
|
||||
protected SkipOverlay SkipIntroOverlay { get; private set; }
|
||||
private SkipOverlay skipOutroOverlay;
|
||||
|
||||
protected ScoreProcessor ScoreProcessor { get; private set; }
|
||||
@@ -200,6 +200,8 @@ namespace osu.Game.Screens.Play
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
private OsuConfigManager config;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@@ -226,12 +228,13 @@ namespace osu.Game.Screens.Play
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuConfigManager config, OsuGameBase game, CancellationToken cancellationToken)
|
||||
{
|
||||
this.config = config;
|
||||
var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray();
|
||||
|
||||
if (gameplayMods.Any(m => m is UnknownMod))
|
||||
{
|
||||
Logger.Log("Gameplay was started with an unknown mod applied.", level: LogLevel.Important);
|
||||
return;
|
||||
// return;
|
||||
}
|
||||
|
||||
if (Beatmap.Value is DummyWorkingBeatmap)
|
||||
@@ -500,10 +503,10 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
// display the cursor above some HUD elements.
|
||||
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
|
||||
skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
|
||||
SkipIntroOverlay = CreateSkipOverlay(DrawableRuleset.GameplayStartTime).With(o =>
|
||||
{
|
||||
RequestSkip = performUserRequestedSkip
|
||||
},
|
||||
o.RequestSkip = RequestIntroSkip;
|
||||
}),
|
||||
skipOutroOverlay = new SkipOverlay(GameplayState.Storyboard.LatestEventTime ?? 0)
|
||||
{
|
||||
RequestSkip = () => progressToResults(false),
|
||||
@@ -523,13 +526,15 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
if (!Configuration.AllowSkipping || !DrawableRuleset.AllowGameplayOverlays)
|
||||
{
|
||||
skipIntroOverlay.Expire();
|
||||
SkipIntroOverlay.Expire();
|
||||
skipOutroOverlay.Expire();
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
protected virtual SkipOverlay CreateSkipOverlay(double startTime) => new SkipOverlay(startTime);
|
||||
|
||||
private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime)
|
||||
{
|
||||
updateGameplayState();
|
||||
@@ -716,13 +721,22 @@ namespace osu.Game.Screens.Play
|
||||
PerformExit();
|
||||
}
|
||||
|
||||
private void performUserRequestedSkip()
|
||||
protected virtual void RequestIntroSkip()
|
||||
{
|
||||
PerformIntroSkip();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip forward to the next valid skip point.
|
||||
/// </summary>
|
||||
/// <param name="fullLength"><c>true</c> to skip as close to gameplay as possible, or <c>false</c> to skip only to the next valid skip point.</param>
|
||||
protected void PerformIntroSkip(bool fullLength = false)
|
||||
{
|
||||
// user requested skip
|
||||
// disable sample playback to stop currently playing samples and perform skip
|
||||
samplePlaybackDisabled.Value = true;
|
||||
|
||||
(GameplayClockContainer as MasterGameplayClockContainer)?.Skip();
|
||||
(GameplayClockContainer as MasterGameplayClockContainer)?.Skip(fullLength);
|
||||
|
||||
// return samplePlaybackDisabled.Value to what is defined by the beatmap's current state
|
||||
updateSampleDisabledState();
|
||||
@@ -983,6 +997,15 @@ namespace osu.Game.Screens.Play
|
||||
if (PauseOverlay.State.Value == Visibility.Visible)
|
||||
PauseOverlay.Hide();
|
||||
|
||||
bool exitOnFail = GameplayState.Mods.OfType<IApplicableFailExit>().Any(m => m.ExitOnFail)
|
||||
&& Score.ScoreInfo.User.Username == config.Get<string>(OsuSetting.Username); // TODO: do more concrete checks
|
||||
if (exitOnFail)
|
||||
{
|
||||
// game.AttemptExit();
|
||||
game.Exit();
|
||||
}
|
||||
|
||||
|
||||
bool restartOnFail = GameplayState.Mods.OfType<IApplicableFailOverride>().Any(m => m.RestartOnFail);
|
||||
if (!restartOnFail)
|
||||
failAnimationContainer.Start();
|
||||
@@ -1168,7 +1191,7 @@ namespace osu.Game.Screens.Play
|
||||
GameplayClockContainer.Reset(startClock: true);
|
||||
|
||||
if (Configuration.AutomaticallySkipIntro)
|
||||
skipIntroOverlay.SkipWhenReady();
|
||||
SkipIntroOverlay.SkipWhenReady();
|
||||
}
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
|
||||
@@ -38,20 +38,21 @@ namespace osu.Game.Screens.Play
|
||||
private readonly double startTime;
|
||||
|
||||
public Action RequestSkip;
|
||||
|
||||
protected FadeContainer FadingContent { get; private set; }
|
||||
|
||||
private Button button;
|
||||
private ButtonContainer buttonContainer;
|
||||
private Circle remainingTimeBox;
|
||||
|
||||
private FadeContainer fadeContainer;
|
||||
private double displayTime;
|
||||
|
||||
private bool isClickable;
|
||||
private bool skipQueued;
|
||||
|
||||
[Resolved]
|
||||
private IGameplayClock gameplayClock { get; set; }
|
||||
|
||||
internal bool IsButtonVisible => fadeContainer.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible;
|
||||
internal bool IsButtonVisible => FadingContent.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +78,7 @@ namespace osu.Game.Screens.Play
|
||||
InternalChild = buttonContainer = new ButtonContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = fadeContainer = new FadeContainer
|
||||
Child = FadingContent = new FadeContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
@@ -107,13 +108,13 @@ namespace osu.Game.Screens.Play
|
||||
public override void Hide()
|
||||
{
|
||||
base.Hide();
|
||||
fadeContainer.Hide();
|
||||
FadingContent.Hide();
|
||||
}
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
base.Show();
|
||||
fadeContainer.TriggerShow();
|
||||
FadingContent.TriggerShow();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -136,7 +137,7 @@ namespace osu.Game.Screens.Play
|
||||
RequestSkip?.Invoke();
|
||||
};
|
||||
|
||||
fadeContainer.TriggerShow();
|
||||
FadingContent.TriggerShow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -183,7 +184,7 @@ namespace osu.Game.Screens.Play
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
if (isClickable && !e.HasAnyButtonPressed)
|
||||
fadeContainer.TriggerShow();
|
||||
FadingContent.TriggerShow();
|
||||
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
@@ -215,6 +216,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
|
||||
this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
|
||||
|
||||
accuracyCircle.Colour = DrawableRank.GetRankLetterColour(ScoreRank.F); // default for failed scores
|
||||
|
||||
if (!withFlair && score.Rank != ScoreRank.F)
|
||||
accuracyCircle.Colour = OsuColour.ForRank(score.Rank).Lighten(0.125f);
|
||||
|
||||
if (withFlair)
|
||||
{
|
||||
const double swoosh_pre_delay = 443f;
|
||||
@@ -307,6 +313,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
{
|
||||
var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound;
|
||||
|
||||
accuracyCircle.FadeColour(OsuColour.ForRank(badge.Rank), 100, Easing.InOutSine); // TODO: nicer animations
|
||||
dink!.FrequencyTo(1 + badgeNum++ * 0.05);
|
||||
dink!.Play();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -43,45 +44,63 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new[]
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularContainer
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 12,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
CornerRadius = 8f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#222")
|
||||
Colour = Color4Extensions.FromHex("#2222229f"),
|
||||
},
|
||||
HeaderText = new OsuSpriteText
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = header.ToUpper(),
|
||||
Anchor = Anchor.TopLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
HeaderText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = header.ToUpper(),
|
||||
},
|
||||
content = CreateContent().With(d =>
|
||||
{
|
||||
d.Origin = Anchor.TopCentre;
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.Alpha = 0;
|
||||
d.AlwaysPresent = true;
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
content = CreateContent().With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.Origin = Anchor.TopCentre;
|
||||
d.Alpha = 0;
|
||||
d.AlwaysPresent = true;
|
||||
}),
|
||||
}
|
||||
}
|
||||
// new Container
|
||||
// {
|
||||
// Anchor = Anchor.TopCentre,
|
||||
// Origin = Anchor.TopCentre,
|
||||
// AutoSizeAxes = Axes.Both,
|
||||
// Children = new[]
|
||||
// {
|
||||
// content = CreateContent().With(d =>
|
||||
// {
|
||||
// d.Anchor = Anchor.TopCentre;
|
||||
// d.Origin = Anchor.TopCentre;
|
||||
// d.Alpha = 0;
|
||||
// d.AlwaysPresent = true;
|
||||
// }),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ImageExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@@ -14,12 +15,15 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Contracted;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using ZstdSharp.Unsafe;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
@@ -96,6 +100,7 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
private OsuColour colour { get; set; } = null!;
|
||||
|
||||
private AudioContainer audioContent = null!;
|
||||
|
||||
@@ -124,12 +129,14 @@ namespace osu.Game.Screens.Ranking
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
private void load(AudioManager audio, OsuColour colour)
|
||||
{
|
||||
// ScorePanel doesn't include the top extruding area in its own size.
|
||||
// Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale.
|
||||
const float vertical_fudge = 20;
|
||||
|
||||
this.colour = colour;
|
||||
|
||||
InternalChild = audioContent = new AudioContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@@ -239,6 +246,20 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
ColourInfo getColour(ColourInfo info)
|
||||
{
|
||||
var ci = info.AverageColour;
|
||||
(_, _, float v) = Color4Extensions.ToHSV(ci);
|
||||
|
||||
var rank = (ColourInfo)OsuColour.ForRank(Score.Rank);
|
||||
(float rh, float rs, _) = Color4Extensions.ToHSV(rank);
|
||||
|
||||
if (Score.Rank != ScoreRank.F)
|
||||
return Color4Extensions.FromHSV(rh, rs * 0.3f, v * 1.1f);
|
||||
else
|
||||
return Color4Extensions.FromHSV(rh, rs, v * 0.45f);
|
||||
}
|
||||
|
||||
topLayerContent?.FadeOut(content_fade_duration).Expire();
|
||||
middleLayerContent?.FadeOut(content_fade_duration).Expire();
|
||||
|
||||
@@ -247,8 +268,8 @@ namespace osu.Game.Screens.Ranking
|
||||
case PanelState.Expanded:
|
||||
Size = new Vector2(EXPANDED_WIDTH, expanded_height);
|
||||
|
||||
topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||
topLayerBackground.FadeColour(getColour(expanded_top_layer_colour), RESIZE_DURATION, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(getColour(expanded_middle_layer_colour), RESIZE_DURATION, Easing.OutQuint);
|
||||
|
||||
bool firstLoad = topLayerContent == null;
|
||||
topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User, firstLoad) { Alpha = 0 });
|
||||
@@ -261,8 +282,8 @@ namespace osu.Game.Screens.Ranking
|
||||
case PanelState.Contracted:
|
||||
Size = new Vector2(CONTRACTED_WIDTH, contracted_height);
|
||||
|
||||
topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint);
|
||||
topLayerBackground.FadeColour(getColour(contracted_top_layer_colour), RESIZE_DURATION, Easing.OutQuint);
|
||||
middleLayerBackground.FadeColour(getColour(contracted_middle_layer_colour), RESIZE_DURATION, Easing.OutQuint);
|
||||
|
||||
topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent
|
||||
{
|
||||
|
||||
@@ -163,6 +163,7 @@ namespace osu.Game.Screens.Select
|
||||
private FillFlowContainer infoLabelContainer;
|
||||
private Container bpmLabelContainer;
|
||||
private Container lengthLabelContainer;
|
||||
private Container performanceLabelContainer;
|
||||
|
||||
private readonly WorkingBeatmap working;
|
||||
private readonly RulesetInfo ruleset;
|
||||
@@ -269,6 +270,7 @@ namespace osu.Game.Screens.Select
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
Status = beatmapInfo.Status,
|
||||
ShowUnknownStatus = true,
|
||||
Alpha = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? 0 : 1
|
||||
}
|
||||
}
|
||||
@@ -343,9 +345,11 @@ namespace osu.Game.Screens.Select
|
||||
settingChangeTracker?.Dispose();
|
||||
|
||||
refreshBPMAndLengthLabel();
|
||||
refreshPerformanceLabel();
|
||||
|
||||
settingChangeTracker = new ModSettingChangeTracker(m.NewValue);
|
||||
settingChangeTracker.SettingChanged += _ => refreshBPMAndLengthLabel();
|
||||
settingChangeTracker.SettingChanged += _ => refreshPerformanceLabel();
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -384,7 +388,11 @@ namespace osu.Game.Screens.Select
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(20, 0),
|
||||
Children = playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray()
|
||||
}
|
||||
},
|
||||
performanceLabelContainer = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -428,6 +436,27 @@ namespace osu.Game.Screens.Select
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshPerformanceLabel()
|
||||
{
|
||||
var beatmap = working.Beatmap;
|
||||
|
||||
if (beatmap == null || performanceLabelContainer == null)
|
||||
return;
|
||||
|
||||
var diff = difficultyCache.GetBindableDifficulty(beatmap.BeatmapInfo);
|
||||
diff.BindValueChanged(d =>
|
||||
{
|
||||
float perf = (float?)d.NewValue.PerformanceAttributes?.Total ?? 0.0f;
|
||||
string disp = $"{Math.Round(perf, 1)} pp";
|
||||
performanceLabelContainer.Child = new InfoLabel(new BeatmapStatistic
|
||||
{
|
||||
Name = "Max PP",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Accuracy),
|
||||
Content = disp
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
private Drawable getMapper(BeatmapMetadata metadata)
|
||||
{
|
||||
if (string.IsNullOrEmpty(metadata.Author.Username))
|
||||
|
||||
@@ -81,6 +81,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
Anchor = Anchor.CentreLeft,
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
ShowUnknownStatus = true,
|
||||
Status = beatmapSet.Status
|
||||
},
|
||||
iconFlow = new FillFlowContainer<DifficultyIcon>
|
||||
|
||||
@@ -497,25 +497,35 @@ namespace osu.Game.Screens.SelectV2
|
||||
// The filter might have changed the set of available groups, which means that the current selection may point to a stale group.
|
||||
// Check whether that is the case.
|
||||
bool groupingRemainsOff = currentGroupedBeatmap?.Group == null && grouping.GroupItems.Count == 0;
|
||||
bool groupStillExists = currentGroupedBeatmap?.Group != null && grouping.GroupItems.ContainsKey(currentGroupedBeatmap.Group);
|
||||
|
||||
if (groupingRemainsOff || groupStillExists)
|
||||
bool groupStillValid = false;
|
||||
|
||||
if (currentGroupedBeatmap?.Group != null)
|
||||
{
|
||||
groupStillValid = grouping.GroupItems.TryGetValue(currentGroupedBeatmap.Group, out var items)
|
||||
&& items.Any(i => CheckModelEquality(i.Model, currentGroupedBeatmap));
|
||||
}
|
||||
|
||||
if (groupingRemainsOff || groupStillValid)
|
||||
{
|
||||
// Only update the visual state of the selected item.
|
||||
HandleItemSelected(currentGroupedBeatmap);
|
||||
}
|
||||
else if (currentGroupedBeatmap != null)
|
||||
{
|
||||
// If the group no longer exists, grab an arbitrary other instance of the beatmap under the first group encountered.
|
||||
// If the group no longer exists (or the item no longer exists in the previous group), grab an arbitrary other instance of the beatmap under the first group encountered.
|
||||
var newSelection = GetCarouselItems()?.Select(i => i.Model).OfType<GroupedBeatmap>().FirstOrDefault(gb => gb.Beatmap.Equals(currentGroupedBeatmap.Beatmap));
|
||||
|
||||
// Only change the selection if we actually got a positive hit.
|
||||
// This is necessary so that selection isn't lost if the panel reappears later due to e.g. unapplying some filter criteria that made it disappear in the first place.
|
||||
if (newSelection != null)
|
||||
{
|
||||
CurrentSelection = newSelection;
|
||||
groupForReselection = newSelection.Group;
|
||||
}
|
||||
}
|
||||
|
||||
// If a group was selected that is not the one containing the selection, attempt to reselect it.
|
||||
// If the original group was not found, ExpandedGroup will already have been updated to a valid value in `HandleItemSelected` above.
|
||||
if (groupForReselection != null && grouping.GroupItems.TryGetValue(groupForReselection, out _))
|
||||
setExpandedGroup(groupForReselection);
|
||||
}
|
||||
@@ -689,7 +699,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
var groupItem = GetCarouselItems()?.FirstOrDefault(i => CheckModelEquality(i.Model, CurrentGroupedBeatmap.Group));
|
||||
if (groupItem != null)
|
||||
HandleItemActivated(groupItem);
|
||||
Activate(groupItem);
|
||||
}
|
||||
|
||||
protected override double? GetScrollTarget()
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Configuration;
|
||||
@@ -233,6 +234,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
countStatisticsDisplay.ForceTiny = true;
|
||||
cancellationSource?.Cancel();
|
||||
cancellationSource = new CancellationTokenSource();
|
||||
|
||||
@@ -293,8 +295,22 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
Ruleset rulesetInstance = ruleset.Value.CreateInstance();
|
||||
|
||||
var workingBeatmap = beatmap.Value;
|
||||
var diff = difficultyCache.GetBindableDifficulty(workingBeatmap.BeatmapInfo);
|
||||
|
||||
float perf = 0.0f;
|
||||
var displayAttributes = rulesetInstance.GetBeatmapAttributesForDisplay(beatmap.Value.BeatmapInfo, mods.Value).ToList();
|
||||
difficultyStatisticsDisplay.Statistics = displayAttributes.Select(a => new StatisticDifficulty.Data(a)).ToList();
|
||||
difficultyStatisticsDisplay.Statistics = displayAttributes.Select(a => new StatisticDifficulty.Data(a))
|
||||
.Prepend(new StatisticDifficulty.Data("Max PP", perf, perf, perf));
|
||||
|
||||
// at first, performance points won't be available, so we'd have to update them later
|
||||
diff.BindValueChanged(d =>
|
||||
{
|
||||
perf = (float)Math.Round((float?)d.NewValue.PerformanceAttributes?.Total ?? 0f, 1); // yikes
|
||||
var arr = difficultyStatisticsDisplay.Statistics.ToArray();
|
||||
arr[0] = new StatisticDifficulty.Data("Max PP", perf, perf, perf);
|
||||
difficultyStatisticsDisplay.Statistics = arr.AsEnumerable();
|
||||
});
|
||||
});
|
||||
|
||||
protected override void Update()
|
||||
|
||||
@@ -27,9 +27,11 @@ namespace osu.Game.Screens.SelectV2
|
||||
private readonly FillFlowContainer<StatisticDifficulty> statisticsFlow;
|
||||
private readonly GridContainer tinyStatisticsGrid;
|
||||
|
||||
private IReadOnlyList<StatisticDifficulty.Data> statistics = Array.Empty<StatisticDifficulty.Data>();
|
||||
public bool ForceTiny { get; set; } = false;
|
||||
|
||||
public IReadOnlyList<StatisticDifficulty.Data> Statistics
|
||||
private IEnumerable<StatisticDifficulty.Data> statistics = Array.Empty<StatisticDifficulty.Data>();
|
||||
|
||||
public IEnumerable<StatisticDifficulty.Data> Statistics
|
||||
{
|
||||
get => statistics;
|
||||
set
|
||||
@@ -137,7 +139,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
return;
|
||||
|
||||
float flowWidth = statisticsFlow[0].Width * statisticsFlow.Count + statisticsFlow.Spacing.X * (statisticsFlow.Count - 1);
|
||||
bool tiny = !autoSize && DrawWidth < flowWidth - 20;
|
||||
bool tiny = !autoSize && DrawWidth < flowWidth - 20 || ForceTiny;
|
||||
|
||||
if (displayedTinyStatistics != tiny)
|
||||
{
|
||||
@@ -180,8 +182,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (statisticsFlow.Select(s => s.Value.Label)
|
||||
.SequenceEqual(statistics.Select(s => s.Label)))
|
||||
{
|
||||
for (int i = 0; i < statistics.Count; i++)
|
||||
statisticsFlow[i].Value = statistics[i];
|
||||
for (int i = 0; i < statistics.Count(); i++)
|
||||
statisticsFlow[i].Value = statistics.ToArray()[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -143,6 +143,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
TextSize = OsuFont.Style.Caption2.Size,
|
||||
Margin = new MarginPadding { Right = 5f },
|
||||
Animated = false,
|
||||
ShowUnknownStatus = true
|
||||
},
|
||||
updateButton = new PanelUpdateBeatmapButton
|
||||
{
|
||||
|
||||
@@ -149,6 +149,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
Anchor = Anchor.BottomLeft,
|
||||
TextSize = OsuFont.Style.Caption2.Size,
|
||||
Margin = new MarginPadding { Right = 4f },
|
||||
ShowUnknownStatus = true
|
||||
},
|
||||
updateButton = new PanelUpdateBeatmapButton
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user