28 Commits

Author SHA1 Message Date
6cb99c13c2 added cookie color customization (which shouldn't have been done) 2025-08-24 19:46:36 +03:00
96008e06ab added settings toggle for song select v1 2025-08-24 05:42:59 +03:00
590b0a8028 welcome back select v1 2025-08-24 04:55:25 +03:00
70f7f09a83 synchronize with github (ppy/osu) 2025-08-23 18:11:07 +03:00
Dean Herbert
16343fd7d6 Merge pull request #34766 from bdach/remove-double-lookup
Pull up online beatmap set lookup to song select level to avoid two components doing the same fetch independently
2025-08-23 20:50:17 +09:00
Dean Herbert
acafc06bcc Merge pull request #34757 from bdach/new-mod-icons
Update mod icons
2025-08-23 19:55:10 +09:00
Bartłomiej Dach
5292d4a04e Fix song select favourite button potentially showing stale data from (un)favourite request callback 2025-08-22 14:25:25 +02:00
Bartłomiej Dach
d3ae20dd88 Pull up online beatmap set lookup to song select level to avoid two components doing the same fetch independently 2025-08-22 14:25:21 +02:00
Dean Herbert
c852e5854c Merge pull request #34723 from bdach/status-updates-are-great-arent-they
Refresh realm before performing song select refetches following an online metadata lookup
2025-08-22 20:25:36 +09:00
Dan Balasescu
0756c45d70 No longer download iOS simulator
https://github.com/actions/runner-images/issues/12862#issuecomment-3209787203
2025-08-22 13:29:46 +09:00
Bartłomiej Dach
4627c8a859 Update resources 2025-08-21 14:44:43 +02:00
Dean Herbert
30f7da8f71 Merge pull request #34759 from bdach/BACKGROUND-STUCK-PLEASE-I-BEG-YOU
Fix song select background being stuck in revealed state
2025-08-21 21:01:59 +09:00
Dean Herbert
4b8ff481fd Merge pull request #34752 from bdach/avoid-endless-futile-backpopulation
Fix submission & rank date backpopulation failing every launch for some users
2025-08-21 20:37:03 +09:00
Bartłomiej Dach
a7f1795f98 Fix song select background being stuck in revealed state
Closes https://github.com/ppy/osu/issues/34731.

The failure scenario here is as follows:

- User holds down left mouse button for >200ms to reveal the background.
- User presses down another mouse button and releases it in <200ms.
- User releases left mouse button. Song select does not return.

The timing here is key because what is happening here is that the second
mouse button press is overwriting the `revealingBackground` scheduled
delegate. Releasing that same mouse button within 200ms leads to that
scheduled delegate being cancelled and cleared, and thus the release of
left mouse wrongly decides there is nothing left to do.

One thing I'm not entirely sure about is the release behaviour even with
this change; as things stand, the first release of any mouse button will
bring song select back, even if it was not the button that was initially
held down to reveal the background. That's probably easily fixed if
deemed required, but I'm most interested in fixing the bad breakage.
2025-08-21 11:31:37 +02:00
Bartłomiej Dach
c053cfbf9b Adjust icon sizings in mod display to match new assets 2025-08-21 09:00:48 +02:00
Bartłomiej Dach
e47a60f303 Add test steps to mod icon test scene for exercising all rulesets 2025-08-21 09:00:46 +02:00
Bartłomiej Dach
92016a7d9b Add and use new mod icon assets 2025-08-21 09:00:44 +02:00
Dean Herbert
41885c0fc0 Merge pull request #34643 from frenzibyte/leaderboard-resize
Fix leaderboard not resizing correctly
2025-08-21 13:33:24 +09:00
Dean Herbert
e75a6b4010 Log bass issues for more than one frame 2025-08-21 13:27:14 +09:00
Dean Herbert
ddce11fbc8 Adjust bass invalid data threshold 2025-08-21 13:27:13 +09:00
Bartłomiej Dach
c894969d17 Fix submission & rank date failing every launch for some users
Addresses https://github.com/ppy/osu/discussions/34705, I suppose.

The cagey tone of that statement is because this change merely papers
over the issue. The issue in question for the user that reported this is
that they have a bunch of very old beatmaps, whose md5 hashes do not
match the online hashes, that need updating. The submission/rank date
population was running every single time for these, and failing every
time, because there is really not much useful that the lookup *can* do.

Because mappers have made `OnlineID` essentially useless for determining
the provenance of a beatmap due to reusing them to "fix" beatmap
submission failures, online IDs have been explicitly disallowed from use
in any sort of beatmap lookup flow. The only things that are allowed to
be used are: md5 of the beatmap, and filename as a fallback for very old
beatmaps / beatmap packs.

If the user has local beatmaps with md5 not matching online, chances are
that any metadata lookups are likely to fail or return bogus data. At
that point my personal feeling is that backpopulation flows should leave
such beatmaps well alone and the user should just go update the beatmap
themselves.

I am aware that updating 124 individual beatmap sets would - in the
current state of things - would probably be a ridiculously onerous thing
to do, and that people have been asking multiple times for a facility to
update all local beatmaps at once, but that discussion is out of scope
at this stage.
2025-08-20 09:21:35 +02:00
Bartłomiej Dach
ad6c0c272d Fix leaderboard score text never showing if leaderboard starts collapsed
Only seems to reproduce in gameplay for whatever reason. Can't justify
spending time to chase down why really because the previous code looked
obviously wrong on closer inspection anyway (`rightLayer` has transforms
applied to it on collapse/expand).
2025-08-19 13:32:01 +02:00
Salman Alshamrani
bb5933ef80 Add test for scores with long score/combo numbers 2025-08-18 13:46:51 +03:00
Salman Alshamrani
62548244bc Hide right-side numbers when not enough space is available 2025-08-18 13:46:08 +03:00
Bartłomiej Dach
a393b3c6b1 Refresh realm before performing song select refetches following an online metadata lookup
Probably closes https://github.com/ppy/osu/issues/34716

Can't see any other cause, can reproduce the issue on master using
manual db modifications via realm studio and it is not a consistent
reproduction, so seems like an open-and-shut lack of refresh.
2025-08-18 09:48:32 +02:00
Salman Alshamrani
a3443f76be Limit leaderboard size to sane minimum values 2025-08-13 12:52:08 +03:00
Salman Alshamrani
d998847271 Fix leaderboard not resizing correctly 2025-08-13 12:51:51 +03:00
Salman Alshamrani
62803af1de Add ability to resize leaderboard in tests 2025-08-13 12:51:09 +03:00
84 changed files with 1038 additions and 391 deletions

View File

@@ -148,9 +148,7 @@ jobs:
# https://github.com/dotnet/macios/issues/19157
# https://github.com/actions/runner-images/issues/12758
- name: Use Xcode 16.4
run: |
sudo xcode-select -switch /Applications/Xcode_16.4.app
xcodebuild -downloadPlatform iOS
run: sudo xcode-select -switch /Applications/Xcode_16.4.app
- name: Build
run: dotnet build -c Debug osu.iOS.slnf

View File

@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override string Acronym => "FF";
public override LocalisableString Description => "The fruits are... floating?";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Cloud;
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{

View File

@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override LocalisableString Description => "Dashing by default, slow down!";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Running;
public override IconUsage? Icon => OsuIcon.ModMovingFast;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
private DrawableCatchRuleset drawableRuleset = null!;

View File

@@ -4,6 +4,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => FontAwesome.Solid.Equals;
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
public override ModType Type => ModType.Conversion;

View File

@@ -4,8 +4,10 @@
using System;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
@@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override string Name => "Cover";
public override string Acronym => "CO";
public override IconUsage? Icon => OsuIcon.ModCover;
public override LocalisableString Description => @"Decrease the playfield's viewing area.";

View File

@@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods;
@@ -13,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages";
public override string Acronym => "DS";
public override LocalisableString Description => @"Double the stages, double the fun!";
public override IconUsage? Icon => OsuIcon.ModDualStages;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;

View File

@@ -3,7 +3,9 @@
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
@@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override string Name => "Fade In";
public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModFadeIn;
public override LocalisableString Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
public override bool ValidForFreestyleAsRequiredMod => false;

View File

@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
public override IconUsage? Icon => OsuIcon.ModHoldOff;
public override ModType Type => ModType.Conversion;

View File

@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "Hold the keys. To the beat.";
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
public override IconUsage? Icon => OsuIcon.ModInvert;
public override ModType Type => ModType.Conversion;

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 1;
public override string Name => "One Key";
public override string Acronym => "1K";
public override IconUsage? Icon => OsuIcon.ModOneKey;
public override LocalisableString Description => @"Play with one key.";
public override bool Ranked => false;
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 10;
public override string Name => "Ten Keys";
public override string Acronym => "10K";
public override IconUsage? Icon => OsuIcon.ModTenKeys;
public override LocalisableString Description => @"Play with ten keys.";
public override bool Ranked => false;
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 2;
public override string Name => "Two Keys";
public override string Acronym => "2K";
public override IconUsage? Icon => OsuIcon.ModTwoKeys;
public override LocalisableString Description => @"Play with two keys.";
public override bool Ranked => false;
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 3;
public override string Name => "Three Keys";
public override string Acronym => "3K";
public override IconUsage? Icon => OsuIcon.ModThreeKeys;
public override LocalisableString Description => @"Play with three keys.";
public override bool Ranked => false;
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 4;
public override string Name => "Four Keys";
public override string Acronym => "4K";
public override IconUsage? Icon => OsuIcon.ModFourKeys;
public override LocalisableString Description => @"Play with four keys.";
}
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 5;
public override string Name => "Five Keys";
public override string Acronym => "5K";
public override IconUsage? Icon => OsuIcon.ModFiveKeys;
public override LocalisableString Description => @"Play with five keys.";
}
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 6;
public override string Name => "Six Keys";
public override string Acronym => "6K";
public override IconUsage? Icon => OsuIcon.ModSixKeys;
public override LocalisableString Description => @"Play with six keys.";
}
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 7;
public override string Name => "Seven Keys";
public override string Acronym => "7K";
public override IconUsage? Icon => OsuIcon.ModSevenKeys;
public override LocalisableString Description => @"Play with seven keys.";
}
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 8;
public override string Name => "Eight Keys";
public override string Acronym => "8K";
public override IconUsage? Icon => OsuIcon.ModEightKeys;
public override LocalisableString Description => @"Play with eight keys.";
}
}

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 9;
public override string Name => "Nine Keys";
public override string Acronym => "9K";
public override IconUsage? Icon => OsuIcon.ModNineKeys;
public override LocalisableString Description => @"Play with nine keys.";
}
}

View File

@@ -4,8 +4,10 @@
using System;
using System.Linq;
using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 0.9;
public override IconUsage? Icon => OsuIcon.ModNoRelease;
public override ModType Type => ModType.DifficultyReduction;
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };

View File

@@ -5,6 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => @"Alternate";
public override string Acronym => @"AL";
public override LocalisableString Description => @"Don't use the same key twice in a row!";
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
public override IconUsage? Icon => OsuIcon.ModAlternate;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;

View File

@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "AD";
public override LocalisableString Description => "Never trust the approach circles...";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };

View File

@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Play with blinds on your screen.";
public override string Acronym => "BL";
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
public override IconUsage? Icon => OsuIcon.ModBlinds;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;

View File

@@ -3,9 +3,11 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
@@ -21,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Bloom";
public override string Acronym => "BM";
public override IconUsage? Icon => OsuIcon.ModBloom;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "The cursor blooms into.. a larger cursor!";
public override double ScoreMultiplier => 1;

View File

@@ -11,7 +11,9 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -34,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModBubbles;
public override ModType Type => ModType.Fun;
// Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect

View File

@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "DF";
public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
public override IconUsage? Icon => OsuIcon.ModDeflate;
public override LocalisableString Description => "Hit them at the right size!";

View File

@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Depth";
public override string Acronym => "DP";
public override IconUsage? Icon => FontAwesome.Solid.Cube;
public override IconUsage? Icon => OsuIcon.ModDepth;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "3D. Almost.";
public override double ScoreMultiplier => 1;

View File

@@ -4,8 +4,10 @@
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -19,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "FR";
public override IconUsage? Icon => OsuIcon.ModFreezeFrame;
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "Burn the notes into your memory.";

View File

@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "GR";
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
public override IconUsage? Icon => OsuIcon.ModGrow;
public override LocalisableString Description => "Hit them at the right size!";

View File

@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Magnetised";
public override string Acronym => "MG";
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
public override IconUsage? Icon => OsuIcon.ModMagnetised;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "No need to chase the circles your cursor is a magnet!";
public override double ScoreMultiplier => 0.5;

View File

@@ -4,10 +4,12 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -23,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Repel";
public override string Acronym => "RP";
public override IconUsage? Icon => OsuIcon.ModRepel;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Hit objects run away!";
public override double ScoreMultiplier => 1;

View File

@@ -3,7 +3,9 @@
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -11,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => @"Single Tap";
public override string Acronym => @"SG";
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"You must only use one key!";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();

View File

@@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spin In";
public override string Acronym => "SI";
public override IconUsage? Icon => FontAwesome.Solid.Undo;
public override IconUsage? Icon => OsuIcon.ModSpinIn;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;

View File

@@ -4,8 +4,10 @@
using System;
using System.Linq;
using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -24,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => @"Strict Tracking";
public override string Acronym => @"ST";
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
public override double ScoreMultiplier => 1.0;

View File

@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Target Practice";
public override string Acronym => "TP";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.ModTarget;
public override IconUsage? Icon => OsuIcon.ModTargetPractice;
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 0.1;

View File

@@ -4,7 +4,9 @@
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -18,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Traceable";
public override string Acronym => "TC";
public override IconUsage? Icon => OsuIcon.ModTraceable;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;

View File

@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Transform";
public override string Acronym => "TR";
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
public override IconUsage? Icon => OsuIcon.ModTransform;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;

View File

@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
@@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Wiggle";
public override string Acronym => "WG";
public override IconUsage? Icon => FontAwesome.Solid.Certificate;
public override IconUsage? Icon => OsuIcon.ModWiggle;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;

View File

@@ -4,6 +4,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.Mods;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Acronym => "CS";
public override double ScoreMultiplier => 0.9;
public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => FontAwesome.Solid.Equals;
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
public override ModType Type => ModType.Conversion;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)

View File

@@ -5,10 +5,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
@@ -21,6 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Acronym => "SR";
public override double ScoreMultiplier => 0.6;
public override LocalisableString Description => "Simplify tricky rhythms!";
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
public override ModType Type => ModType.DifficultyReduction;
[SettingSource("1/3 to 1/2 conversion", "Converts 1/3 patterns to 1/2 rhythm.")]

View File

@@ -6,9 +6,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Objects;
@@ -24,6 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override string Name => @"Single Tap";
public override string Acronym => @"SG";
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"One key for dons, one key for kats.";
public override double ScoreMultiplier => 1.0;

View File

@@ -3,8 +3,10 @@
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
@@ -16,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Name => "Swap";
public override string Acronym => "SW";
public override LocalisableString Description => @"Dons become kats, kats become dons";
public override IconUsage? Icon => OsuIcon.ModSwap;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();

View File

@@ -66,6 +66,18 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("toggle black background", () => blackBackground?.FadeTo(1 - blackBackground.Alpha, 300, Easing.OutQuint));
AddSliderStep("leaderboard width", 0, 800, 300, v =>
{
if (leaderboard.IsNotNull())
leaderboard.Width = v;
});
AddSliderStep("leaderboard height", 0, 1000, 300, v =>
{
if (leaderboard.IsNotNull())
leaderboard.Height = v;
});
AddSliderStep("set player score", 50, 1_000_000, 700_000, v => gameplayState.ScoreProcessor.TotalScore.Value = v);
}
@@ -108,6 +120,45 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1));
}
[Test]
public void TestLongScores()
{
AddStep("set scores", () =>
{
var friend = new APIUser { Username = "Friend", Id = 1337 };
var api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.Add(new APIRelation
{
Mutual = true,
RelationType = RelationType.Friend,
TargetID = friend.OnlineID,
TargetUser = friend
});
// this is dodgy but anything less dodgy is a lot of work
((Bindable<LeaderboardScores?>)leaderboardManager.Scores).Value = LeaderboardScores.Success(new[]
{
new ScoreInfo { User = new APIUser { Username = "Top", Id = 2 }, TotalScore = 900_000_000, Accuracy = 0.99, MaxCombo = 999999 },
new ScoreInfo { User = new APIUser { Username = "Second", Id = 14 }, TotalScore = 800_000_000, Accuracy = 0.9, MaxCombo = 888888 },
new ScoreInfo { User = friend, TotalScore = 700_000_000, Accuracy = 0.88, MaxCombo = 777777 },
}, 3, null);
});
createLeaderboard();
AddStep("set score to 650k", () => gameplayState.ScoreProcessor.TotalScore.Value = 650_000_000);
AddUntilStep("wait for 4th spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(4));
AddStep("set score to 750k", () => gameplayState.ScoreProcessor.TotalScore.Value = 750_000_000);
AddUntilStep("wait for 3rd spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(3));
AddStep("set score to 850k", () => gameplayState.ScoreProcessor.TotalScore.Value = 850_000_000);
AddUntilStep("wait for 2nd spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(2));
AddStep("set score to 950k", () => gameplayState.ScoreProcessor.TotalScore.Value = 950_000_000);
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1));
}
[Test]
public void TestLayoutWithManyScores()
{

View File

@@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
@@ -42,7 +43,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private DialogOverlay dialogOverlay = null!;
private LeaderboardManager leaderboardManager = null!;
private RealmPopulatingOnlineLookupSource lookupSource = null!;
private readonly IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -52,7 +54,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
dependencies.Cache(leaderboardManager = new LeaderboardManager());
dependencies.Cache(lookupSource = new RealmPopulatingOnlineLookupSource());
dependencies.CacheAs(onlineLookupResult);
Dependencies.Cache(Realm);
@@ -68,7 +70,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
LoadComponent(leaderboardManager);
LoadComponent(lookupSource);
Child = contentContainer = new OsuContextMenuContainer
{

View File

@@ -4,13 +4,12 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.SelectV2;
@@ -18,64 +17,25 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
public partial class TestSceneBeatmapMetadataWedge : SongSelectComponentsTestScene
{
private APIBeatmapSet? currentOnlineSet;
private BeatmapMetadataWedge wedge = null!;
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))]
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
protected override void LoadComplete()
{
base.LoadComplete();
var lookupSource = new RealmPopulatingOnlineLookupSource();
Child = new DependencyProvidingContainer
Child = wedge = new BeatmapMetadataWedge
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = [(typeof(RealmPopulatingOnlineLookupSource), lookupSource)],
Children =
[
lookupSource,
wedge = new BeatmapMetadataWedge
{
State = { Value = Visibility.Visible },
}
]
State = { Value = Visibility.Visible },
};
}
[SetUpSteps]
public override void SetUpSteps()
{
AddStep("register request handling", () =>
{
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
default:
return false;
}
};
});
}
[Test]
public void TestShowHide()
{
AddStep("all metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
AddStep("hide wedge", () => wedge.Hide());
AddStep("show wedge", () => wedge.Show());
@@ -84,67 +44,63 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestVariousMetrics()
{
AddStep("all metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
AddStep("null beatmap", () => Beatmap.SetDefault());
AddStep("no source", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
working.Metadata.Source = string.Empty;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no success rate", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().PlayCount = 0;
onlineSet.Beatmaps.Single().PassCount = 0;
online.Result!.Beatmaps.Single().PlayCount = 0;
online.Result!.Beatmaps.Single().PassCount = 0;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no user ratings", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Ratings = Array.Empty<int>();
online.Result!.Ratings = Array.Empty<int>();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no fail times", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().FailTimes = null;
online.Result!.Beatmaps.Single().FailTimes = null;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("no metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Ratings = Array.Empty<int>();
onlineSet.Beatmaps.Single().FailTimes = null;
online.Result!.Ratings = Array.Empty<int>();
online.Result!.Beatmaps.Single().FailTimes = null;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
AddStep("local beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, _) = createTestBeatmap();
working.BeatmapInfo.OnlineID = 0;
currentOnlineSet = onlineSet;
onlineLookupResult.Value = null;
Beatmap.Value = working;
});
}
@@ -154,16 +110,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
AddStep("long text", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
working.BeatmapInfo.Metadata.Author = new RealmUser { Username = "Verrrrryyyy llooonngggggg author" };
working.BeatmapInfo.Metadata.Source = "Verrrrryyyy llooonngggggg source";
working.BeatmapInfo.Metadata.Tags = string.Join(' ', Enumerable.Repeat(working.BeatmapInfo.Metadata.Tags, 3));
onlineSet.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
onlineSet.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
onlineSet.Beatmaps.Single().TopTags = Enumerable.Repeat(onlineSet.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
online.Result!.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
online.Result!.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
online.Result!.Beatmaps.Single().TopTags = Enumerable.Repeat(online.Result!.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
}
@@ -171,22 +127,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestOnlineAvailability()
{
AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("rating wedge visible", () => wedge.RatingsVisible);
AddUntilStep("fail time wedge visible", () => wedge.FailRetryVisible);
AddStep("online beatmapset with local diff", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, lookupResult) = createTestBeatmap();
working.BeatmapInfo.ResetOnlineInfo();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = lookupResult;
Beatmap.Value = working;
});
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
@@ -195,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
var (working, _) = createTestBeatmap();
currentOnlineSet = null;
onlineLookupResult.Value = null;
Beatmap.Value = working;
});
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
@@ -205,21 +156,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestUserTags()
{
AddStep("user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
AddStep("user tags", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("no user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().TopTags = null;
onlineSet.RelatedTags = null;
online.Result!.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = online;
Beatmap.Value = working;
});
}
@@ -227,72 +174,60 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestLoading()
{
AddStep("override request handling", () =>
{
currentOnlineSet = null;
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
Scheduler.AddDelayed(() => set.TriggerSuccess(currentOnlineSet!), 500);
return true;
default:
return false;
}
};
});
AddStep("set beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
AddStep("set beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.RelatedTags![0].Name = "other/tag";
onlineSet.RelatedTags[1].Name = "another/tag";
onlineSet.RelatedTags[2].Name = "some/tag";
online.Result!.RelatedTags![0].Name = "other/tag";
online.Result!.RelatedTags[1].Name = "another/tag";
online.Result!.RelatedTags[2].Name = "some/tag";
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
AddStep("no user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().TopTags = null;
onlineSet.RelatedTags = null;
online.Result!.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
AddStep("no user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, online) = createTestBeatmap();
onlineSet.Beatmaps.Single().TopTags = null;
onlineSet.RelatedTags = null;
online.Result!.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
currentOnlineSet = onlineSet;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
}
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap()
{
var working = CreateWorkingBeatmap(Ruleset.Value);
var onlineSet = new APIBeatmapSet
@@ -346,7 +281,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
return (working, onlineSet);
working.Metadata.UserTags.AddRange(onlineSet.RelatedTags.Select(t => t.Name));
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
}
}
}

View File

@@ -11,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
@@ -41,10 +42,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private BeatmapTitleWedge titleWedge = null!;
private BeatmapTitleWedge.DifficultyDisplay difficultyDisplay => titleWedge.ChildrenOfType<BeatmapTitleWedge.DifficultyDisplay>().Single();
private APIBeatmapSet? currentOnlineSet;
[Cached]
private RealmPopulatingOnlineLookupSource lookupSource = new RealmPopulatingOnlineLookupSource();
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))]
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
@@ -58,7 +57,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddRange(new Drawable[]
{
lookupSource,
new Container
{
RelativeSizeAxes = Axes.Both,
@@ -142,44 +140,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test]
public void TestOnlineAvailability()
{
AddStep("set up request handler", () =>
{
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
return false;
default:
return false;
}
};
});
AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("play count is 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("10,000"));
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
AddStep("online beatmapset with local diff", () =>
{
var (working, onlineSet) = createTestBeatmap();
var (working, lookupResult) = createTestBeatmap();
working.BeatmapInfo.ResetOnlineInfo();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
onlineLookupResult.Value = lookupResult;
});
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-"));
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
@@ -187,8 +159,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
var (working, _) = createTestBeatmap();
currentOnlineSet = null;
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
});
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-"));
AddUntilStep("favourites count is -", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("-"));
@@ -205,15 +177,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
case PostBeatmapFavouriteRequest favourite:
Task.Run(() =>
{
@@ -228,13 +191,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
};
});
AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("play count is 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("10,000"));
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
@@ -251,13 +209,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
AddStep("change to another beatmap", () =>
{
var (working, onlineSet) = createTestBeatmap();
onlineSet.FavouriteCount = 9999;
onlineSet.HasFavourited = true;
working.BeatmapSetInfo.OnlineID = onlineSet.OnlineID = 99999;
var (working, online) = createTestBeatmap();
online.Result!.FavouriteCount = 9999;
online.Result!.HasFavourited = true;
working.BeatmapSetInfo.OnlineID = online.Result!.OnlineID = 99999;
currentOnlineSet = onlineSet;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("allow request to complete", () => resetEvent.Set());
AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999"));
@@ -268,15 +226,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
case PostBeatmapFavouriteRequest favourite:
Task.Run(() =>
{
@@ -350,7 +299,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
}
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap()
{
var working = CreateWorkingBeatmap(Ruleset.Value);
var onlineSet = new APIBeatmapSet
@@ -371,7 +320,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
return (working, onlineSet);
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
}
private class TestHitObject : ConvertHitObject;

View File

@@ -4,11 +4,13 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.UserInterface
private FillFlowContainer spreadOutFlow = null!;
private ModDisplay modDisplay = null!;
[Resolved]
private RulesetStore rulesetStore { get; set; } = null!;
[SetUpSteps]
public void SetUpSteps()
{
@@ -70,9 +75,26 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestShowAllMods()
{
AddStep("create mod icons", () =>
createModIconsForRuleset(0);
createModIconsForRuleset(1);
createModIconsForRuleset(2);
createModIconsForRuleset(3);
AddStep("toggle selected", () =>
{
addRange(Ruleset.Value.CreateInstance().CreateAllMods().Select(m =>
foreach (var icon in this.ChildrenOfType<ModIcon>())
icon.Selected.Toggle();
});
}
private void createModIconsForRuleset(int rulesetId)
{
AddStep($"create mod icons for ruleset {rulesetId}", () =>
{
spreadOutFlow.Clear();
modDisplay.Current.Value = [];
addRange(rulesetStore.GetRuleset(rulesetId)!.CreateInstance().CreateAllMods().Select(m =>
{
if (m is OsuModFlashlight fl)
fl.FollowDelay.Value = 1245;
@@ -89,12 +111,6 @@ namespace osu.Game.Tests.Visual.UserInterface
return m;
}));
});
AddStep("toggle selected", () =>
{
foreach (var icon in this.ChildrenOfType<ModIcon>())
icon.Selected.Toggle();
});
}
[Test]

View File

@@ -8,6 +8,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Drawables.Cards;
@@ -41,6 +42,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.MenuCookieColor, Colour4.FromHex(@"ff66ba"));
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
@@ -65,6 +68,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
SetDefault(OsuSetting.ForceLegacySongSelect, false);
SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
// Online settings
@@ -405,6 +409,7 @@ namespace osu.Game.Configuration
ChatDisplayHeight,
BeatmapListingCardSize,
ToolbarClockDisplayMode,
ForceLegacySongSelect,
SongSelectBackgroundBlur,
Version,
ShowFirstRunSetup,
@@ -412,6 +417,7 @@ namespace osu.Game.Configuration
Skin,
ScreenshotFormat,
ScreenshotCaptureMenuCursor,
MenuCookieColor,
BeatmapSkins,
BeatmapColours,
BeatmapHitsounds,

View File

@@ -558,9 +558,15 @@ namespace osu.Game.Database
Logger.Log("Querying for beatmap sets that contain missing submission/rank date...");
// find all ranked beatmap sets with missing date ranked or date submitted that have at least one difficulty ranked as well.
// the reason for checking ranked status of the difficulties is that they can be locally modified or unknown too, and for those the lookup is likely to fail.
// this is because metadata lookups are primarily based on file hash, so they will fail to match if the beatmap does not match the online version
// (which is likely to be the case if the beatmap is locally modified or unknown).
// that said, one difficulty in ranked state is enough for the backpopulation to work.
HashSet<Guid> beatmapSetIds = realmAccess.Run(r => new HashSet<Guid>(
r.All<BeatmapSetInfo>()
.Where(b => b.StatusInt > 0 && (b.DateRanked == null || b.DateSubmitted == null))
.Filter($@"{nameof(BeatmapSetInfo.StatusInt)} > 0 && ({nameof(BeatmapSetInfo.DateRanked)} == null || {nameof(BeatmapSetInfo.DateSubmitted)} == null) "
+ $@"&& ANY {nameof(BeatmapSetInfo.Beatmaps)}.{nameof(BeatmapInfo.StatusInt)} > 0")
.AsEnumerable()
.Select(b => b.ID)));
@@ -591,11 +597,7 @@ namespace osu.Game.Database
{
BeatmapSetInfo beatmapSet = r.Find<BeatmapSetInfo>(id)!;
// we want any ranked representative of the set.
// the reason for checking ranked status of the difficulty is that it can be locally modified,
// at which point the lookup will fail - but there might still be another unmodified difficulty on which it will work.
if (beatmapSet.Beatmaps.FirstOrDefault(b => b.Status >= BeatmapOnlineStatus.Ranked) is not BeatmapInfo beatmap)
return false;
var beatmap = beatmapSet.Beatmaps.First(b => b.Status >= BeatmapOnlineStatus.Ranked);
bool lookupSucceeded = localMetadataSource.TryLookup(beatmap, out var result);

View File

@@ -81,27 +81,6 @@ namespace osu.Game.Graphics
public static IconUsage InsaneMania => get(0xe027);
public static IconUsage ExpertMania => get(0xe028);
// mod icons
public static IconUsage ModPerfect => get(0xe049);
public static IconUsage ModAutopilot => get(0xe03a);
public static IconUsage ModAuto => get(0xe03b);
public static IconUsage ModCinema => get(0xe03c);
public static IconUsage ModDoubleTime => get(0xe03d);
public static IconUsage ModEasy => get(0xe03e);
public static IconUsage ModFlashlight => get(0xe03f);
public static IconUsage ModHalftime => get(0xe040);
public static IconUsage ModHardRock => get(0xe041);
public static IconUsage ModHidden => get(0xe042);
public static IconUsage ModNightcore => get(0xe043);
public static IconUsage ModNoFail => get(0xe044);
public static IconUsage ModRelax => get(0xe045);
public static IconUsage ModSpunOut => get(0xe046);
public static IconUsage ModSuddenDeath => get(0xe047);
public static IconUsage ModTarget => get(0xe048);
// Use "Icons/BeatmapDetails/mod-icon" instead
// public static IconUsage ModBg => Get(0xe04a);
#endregion
#region New single-file-based icons
@@ -181,6 +160,88 @@ namespace osu.Game.Graphics
public static IconUsage Tortoise => get(OsuIconMapping.Tortoise);
public static IconUsage Hare => get(OsuIconMapping.Hare);
// mod icons
public static IconUsage ModNoMod => get(OsuIconMapping.ModNoMod);
/*
can be regenerated semi-automatically using osu-web's mod database via
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
sed 's/ //g' | \
awk '{print "public static IconUsage Mod" $0 " => get(OsuIconMapping.Mod" $0 ");"}' | pbcopy
*/
public static IconUsage ModAccuracyChallenge => get(OsuIconMapping.ModAccuracyChallenge);
public static IconUsage ModAdaptiveSpeed => get(OsuIconMapping.ModAdaptiveSpeed);
public static IconUsage ModAlternate => get(OsuIconMapping.ModAlternate);
public static IconUsage ModApproachDifferent => get(OsuIconMapping.ModApproachDifferent);
public static IconUsage ModAutopilot => get(OsuIconMapping.ModAutopilot);
public static IconUsage ModAutoplay => get(OsuIconMapping.ModAutoplay);
public static IconUsage ModBarrelRoll => get(OsuIconMapping.ModBarrelRoll);
public static IconUsage ModBlinds => get(OsuIconMapping.ModBlinds);
public static IconUsage ModBloom => get(OsuIconMapping.ModBloom);
public static IconUsage ModBubbles => get(OsuIconMapping.ModBubbles);
public static IconUsage ModCinema => get(OsuIconMapping.ModCinema);
public static IconUsage ModClassic => get(OsuIconMapping.ModClassic);
public static IconUsage ModConstantSpeed => get(OsuIconMapping.ModConstantSpeed);
public static IconUsage ModCover => get(OsuIconMapping.ModCover);
public static IconUsage ModDaycore => get(OsuIconMapping.ModDaycore);
public static IconUsage ModDeflate => get(OsuIconMapping.ModDeflate);
public static IconUsage ModDepth => get(OsuIconMapping.ModDepth);
public static IconUsage ModDifficultyAdjust => get(OsuIconMapping.ModDifficultyAdjust);
public static IconUsage ModDoubleTime => get(OsuIconMapping.ModDoubleTime);
public static IconUsage ModDualStages => get(OsuIconMapping.ModDualStages);
public static IconUsage ModEasy => get(OsuIconMapping.ModEasy);
public static IconUsage ModEightKeys => get(OsuIconMapping.ModEightKeys);
public static IconUsage ModFadeIn => get(OsuIconMapping.ModFadeIn);
public static IconUsage ModFiveKeys => get(OsuIconMapping.ModFiveKeys);
public static IconUsage ModFlashlight => get(OsuIconMapping.ModFlashlight);
public static IconUsage ModFloatingFruits => get(OsuIconMapping.ModFloatingFruits);
public static IconUsage ModFourKeys => get(OsuIconMapping.ModFourKeys);
public static IconUsage ModFreezeFrame => get(OsuIconMapping.ModFreezeFrame);
public static IconUsage ModGrow => get(OsuIconMapping.ModGrow);
public static IconUsage ModHalfTime => get(OsuIconMapping.ModHalfTime);
public static IconUsage ModHardRock => get(OsuIconMapping.ModHardRock);
public static IconUsage ModHidden => get(OsuIconMapping.ModHidden);
public static IconUsage ModHoldOff => get(OsuIconMapping.ModHoldOff);
public static IconUsage ModInvert => get(OsuIconMapping.ModInvert);
public static IconUsage ModMagnetised => get(OsuIconMapping.ModMagnetised);
public static IconUsage ModMirror => get(OsuIconMapping.ModMirror);
public static IconUsage ModMovingFast => get(OsuIconMapping.ModMovingFast);
public static IconUsage ModMuted => get(OsuIconMapping.ModMuted);
public static IconUsage ModNightcore => get(OsuIconMapping.ModNightcore);
public static IconUsage ModNineKeys => get(OsuIconMapping.ModNineKeys);
public static IconUsage ModNoFail => get(OsuIconMapping.ModNoFail);
public static IconUsage ModNoRelease => get(OsuIconMapping.ModNoRelease);
public static IconUsage ModNoScope => get(OsuIconMapping.ModNoScope);
public static IconUsage ModOneKey => get(OsuIconMapping.ModOneKey);
public static IconUsage ModPerfect => get(OsuIconMapping.ModPerfect);
public static IconUsage ModRandom => get(OsuIconMapping.ModRandom);
public static IconUsage ModRelax => get(OsuIconMapping.ModRelax);
public static IconUsage ModRepel => get(OsuIconMapping.ModRepel);
public static IconUsage ModScoreV2 => get(OsuIconMapping.ModScoreV2);
public static IconUsage ModSevenKeys => get(OsuIconMapping.ModSevenKeys);
public static IconUsage ModSimplifiedRhythm => get(OsuIconMapping.ModSimplifiedRhythm);
public static IconUsage ModSingleTap => get(OsuIconMapping.ModSingleTap);
public static IconUsage ModSixKeys => get(OsuIconMapping.ModSixKeys);
public static IconUsage ModSpinIn => get(OsuIconMapping.ModSpinIn);
public static IconUsage ModSpunOut => get(OsuIconMapping.ModSpunOut);
public static IconUsage ModStrictTracking => get(OsuIconMapping.ModStrictTracking);
public static IconUsage ModSuddenDeath => get(OsuIconMapping.ModSuddenDeath);
public static IconUsage ModSwap => get(OsuIconMapping.ModSwap);
public static IconUsage ModSynesthesia => get(OsuIconMapping.ModSynesthesia);
public static IconUsage ModTargetPractice => get(OsuIconMapping.ModTargetPractice);
public static IconUsage ModTenKeys => get(OsuIconMapping.ModTenKeys);
public static IconUsage ModThreeKeys => get(OsuIconMapping.ModThreeKeys);
public static IconUsage ModTouchDevice => get(OsuIconMapping.ModTouchDevice);
public static IconUsage ModTraceable => get(OsuIconMapping.ModTraceable);
public static IconUsage ModTransform => get(OsuIconMapping.ModTransform);
public static IconUsage ModTwoKeys => get(OsuIconMapping.ModTwoKeys);
public static IconUsage ModWiggle => get(OsuIconMapping.ModWiggle);
public static IconUsage ModWindDown => get(OsuIconMapping.ModWindDown);
public static IconUsage ModWindUp => get(OsuIconMapping.ModWindUp);
private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME);
private enum OsuIconMapping
@@ -400,6 +461,224 @@ namespace osu.Game.Graphics
[Description(@"hare")]
Hare,
// mod icons
[Description(@"Mods/mod-no-mod")]
ModNoMod,
/*
rest can be regenerated semi-automatically using osu-web's mod database via
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
awk '{kebab = $0; gsub(" ", "-", kebab); pascal = $0; gsub(" ", "", pascal); print "[Description(@\"Mods/mod-" tolower(kebab) "\")]\nMod" pascal ",\n" }' | pbcopy
*/
[Description(@"Mods/mod-accuracy-challenge")]
ModAccuracyChallenge,
[Description(@"Mods/mod-adaptive-speed")]
ModAdaptiveSpeed,
[Description(@"Mods/mod-alternate")]
ModAlternate,
[Description(@"Mods/mod-approach-different")]
ModApproachDifferent,
[Description(@"Mods/mod-autopilot")]
ModAutopilot,
[Description(@"Mods/mod-autoplay")]
ModAutoplay,
[Description(@"Mods/mod-barrel-roll")]
ModBarrelRoll,
[Description(@"Mods/mod-blinds")]
ModBlinds,
[Description(@"Mods/mod-bloom")]
ModBloom,
[Description(@"Mods/mod-bubbles")]
ModBubbles,
[Description(@"Mods/mod-cinema")]
ModCinema,
[Description(@"Mods/mod-classic")]
ModClassic,
[Description(@"Mods/mod-constant-speed")]
ModConstantSpeed,
[Description(@"Mods/mod-cover")]
ModCover,
[Description(@"Mods/mod-daycore")]
ModDaycore,
[Description(@"Mods/mod-deflate")]
ModDeflate,
[Description(@"Mods/mod-depth")]
ModDepth,
[Description(@"Mods/mod-difficulty-adjust")]
ModDifficultyAdjust,
[Description(@"Mods/mod-double-time")]
ModDoubleTime,
[Description(@"Mods/mod-dual-stages")]
ModDualStages,
[Description(@"Mods/mod-easy")]
ModEasy,
[Description(@"Mods/mod-eight-keys")]
ModEightKeys,
[Description(@"Mods/mod-fade-in")]
ModFadeIn,
[Description(@"Mods/mod-five-keys")]
ModFiveKeys,
[Description(@"Mods/mod-flashlight")]
ModFlashlight,
[Description(@"Mods/mod-floating-fruits")]
ModFloatingFruits,
[Description(@"Mods/mod-four-keys")]
ModFourKeys,
[Description(@"Mods/mod-freeze-frame")]
ModFreezeFrame,
[Description(@"Mods/mod-grow")]
ModGrow,
[Description(@"Mods/mod-half-time")]
ModHalfTime,
[Description(@"Mods/mod-hard-rock")]
ModHardRock,
[Description(@"Mods/mod-hidden")]
ModHidden,
[Description(@"Mods/mod-hold-off")]
ModHoldOff,
[Description(@"Mods/mod-invert")]
ModInvert,
[Description(@"Mods/mod-magnetised")]
ModMagnetised,
[Description(@"Mods/mod-mirror")]
ModMirror,
[Description(@"Mods/mod-moving-fast")]
ModMovingFast,
[Description(@"Mods/mod-muted")]
ModMuted,
[Description(@"Mods/mod-nightcore")]
ModNightcore,
[Description(@"Mods/mod-nine-keys")]
ModNineKeys,
[Description(@"Mods/mod-no-fail")]
ModNoFail,
[Description(@"Mods/mod-no-release")]
ModNoRelease,
[Description(@"Mods/mod-no-scope")]
ModNoScope,
[Description(@"Mods/mod-one-key")]
ModOneKey,
[Description(@"Mods/mod-perfect")]
ModPerfect,
[Description(@"Mods/mod-random")]
ModRandom,
[Description(@"Mods/mod-relax")]
ModRelax,
[Description(@"Mods/mod-repel")]
ModRepel,
[Description(@"Mods/mod-score-v2")]
ModScoreV2,
[Description(@"Mods/mod-seven-keys")]
ModSevenKeys,
[Description(@"Mods/mod-simplified-rhythm")]
ModSimplifiedRhythm,
[Description(@"Mods/mod-single-tap")]
ModSingleTap,
[Description(@"Mods/mod-six-keys")]
ModSixKeys,
[Description(@"Mods/mod-spin-in")]
ModSpinIn,
[Description(@"Mods/mod-spun-out")]
ModSpunOut,
[Description(@"Mods/mod-strict-tracking")]
ModStrictTracking,
[Description(@"Mods/mod-sudden-death")]
ModSuddenDeath,
[Description(@"Mods/mod-swap")]
ModSwap,
[Description(@"Mods/mod-synesthesia")]
ModSynesthesia,
[Description(@"Mods/mod-target-practice")]
ModTargetPractice,
[Description(@"Mods/mod-ten-keys")]
ModTenKeys,
[Description(@"Mods/mod-three-keys")]
ModThreeKeys,
[Description(@"Mods/mod-touch-device")]
ModTouchDevice,
[Description(@"Mods/mod-traceable")]
ModTraceable,
[Description(@"Mods/mod-transform")]
ModTransform,
[Description(@"Mods/mod-two-keys")]
ModTwoKeys,
[Description(@"Mods/mod-wiggle")]
ModWiggle,
[Description(@"Mods/mod-wind-down")]
ModWindDown,
[Description(@"Mods/mod-wind-up")]
ModWindUp,
}
public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore

View File

@@ -184,6 +184,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString SelectedMods => new TranslatableString(getKey(@"selected_mods"), @"Selected Mods");
/// <summary>
/// "Use legacy song select (SelectV1)"
/// </summary>
public static LocalisableString ForceLegacySongSelect => new TranslatableString(getKey(@"force_select_v1"), @"Use legacy song select (SelectV1)");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@@ -5,7 +5,9 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
@@ -104,6 +106,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
backgroundToggle,
categoryDropdown,
refreshButton,
new SettingsColour
{
LabelText = @"osu! logo colour",
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
ClassicDefault = Colour4.FromHex(@"ff66ba"),
},
};
}

View File

@@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ForceLegacySongSelect,
Current = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect),
ClassicDefault = false
},
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,

View File

@@ -6,8 +6,10 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Localisation.HUD;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
@@ -24,6 +26,8 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Fail if your accuracy drops too low!";
public override IconUsage? Icon => OsuIcon.ModAccuracyChallenge;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1.0;

View File

@@ -6,10 +6,12 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Let track speed adapt to you.";
public override IconUsage? Icon => OsuIcon.ModAdaptiveSpeed;
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 0.5;

View File

@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Autoplay";
public override string Acronym => "AT";
public override IconUsage? Icon => OsuIcon.ModAuto;
public override IconUsage? Icon => OsuIcon.ModAutoplay;
public override ModType Type => ModType.Automation;
public override LocalisableString Description => "Watch a perfect automated play through the song.";
public override double ScoreMultiplier => 1;

View File

@@ -6,8 +6,10 @@ using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osuTK;
@@ -36,6 +38,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Barrel Roll";
public override string Acronym => "BR";
public override IconUsage? Icon => OsuIcon.ModBarrelRoll;
public override LocalisableString Description => "The whole playfield is on a wheel!";
public override double ScoreMultiplier => 1;

View File

@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 0.96;
public override IconUsage? Icon => FontAwesome.Solid.History;
public override IconUsage? Icon => OsuIcon.ModClassic;
public override LocalisableString Description => "Feeling nostalgic?";

View File

@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
namespace osu.Game.Rulesets.Mods
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Daycore";
public override string Acronym => "DC";
public override IconUsage? Icon => null;
public override IconUsage? Icon => OsuIcon.ModDaycore;
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "Whoaaaaa...";
public override bool Ranked => UsesDefaultConfiguration;

View File

@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => FontAwesome.Solid.Hammer;
public override IconUsage? Icon => OsuIcon.ModDifficultyAdjust;
public override double ScoreMultiplier => 0.5;

View File

@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Half Time";
public override string Acronym => "HT";
public override IconUsage? Icon => OsuIcon.ModHalftime;
public override IconUsage? Icon => OsuIcon.ModHalfTime;
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "Less zoom...";
public override bool Ranked => SpeedChange.IsDefault;

View File

@@ -1,12 +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.Graphics.Sprites;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModMirror : Mod
{
public override string Name => "Mirror";
public override string Acronym => "MR";
public override IconUsage? Icon => OsuIcon.ModMirror;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
}

View File

@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Objects;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Muted";
public override string Acronym => "MU";
public override IconUsage? Icon => FontAwesome.Solid.VolumeMute;
public override IconUsage? Icon => OsuIcon.ModMuted;
public override LocalisableString Description => "Can you still feel the rhythm without music?";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;

View File

@@ -3,6 +3,7 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "NM";
public override LocalisableString Description => "No mods applied.";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => FontAwesome.Solid.Ban;
public override IconUsage? Icon => OsuIcon.ModNoMod;
public override ModType Type => ModType.System;
}
}

View File

@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
@@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "No Scope";
public override string Acronym => "NS";
public override ModType Type => ModType.Fun;
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
public override IconUsage? Icon => OsuIcon.ModNoScope;
public override double ScoreMultiplier => 1;
public override bool Ranked => true;

View File

@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Random";
public override string Acronym => "RD";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.Dice;
public override IconUsage? Icon => OsuIcon.ModRandom;
public override double ScoreMultiplier => 1;
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -13,6 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Score V2";
public override string Acronym => @"SV2";
public override IconUsage? Icon => OsuIcon.ModScoreV2;
public override ModType Type => ModType.System;
public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active.";
public override double ScoreMultiplier => 1;

View File

@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "SY";
public override LocalisableString Description => "Colours hit objects based on the rhythm.";
public override double ScoreMultiplier => 0.8;
public override IconUsage? Icon => OsuIcon.ModSynesthesia;
public override ModType Type => ModType.Fun;
}
}

View File

@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
{
public sealed override string Name => "Touch Device";
public sealed override string Acronym => "TD";
public sealed override IconUsage? Icon => OsuIcon.PlayStyleTouch;
public sealed override IconUsage? Icon => OsuIcon.ModTouchDevice;
public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen.";
public sealed override double ScoreMultiplier => 1;
public sealed override ModType Type => ModType.System;

View File

@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Wind Down";
public override string Acronym => "WD";
public override LocalisableString Description => "Sloooow doooown...";
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown;
public override IconUsage? Icon => OsuIcon.ModWindDown;
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
{

View File

@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
@@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Wind Up";
public override string Acronym => "WU";
public override LocalisableString Description => "Can you keep up?";
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp;
public override IconUsage? Icon => OsuIcon.ModWindUp;
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
{

View File

@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.UI
{
public ReplayInputHandler? ReplayInputHandler { get; set; }
private double? lastBackwardsSeekLogTime;
private int invalidBassTimeLogCount;
/// <summary>
/// The number of CPU milliseconds to spend at most during seek catch-up.
@@ -161,11 +161,11 @@ namespace osu.Game.Rulesets.UI
//
// In testing this triggers *very* rarely even when set to super low values (10 ms). The cases we're worried about involve multi-second jumps.
// A difference of more than 500 ms seems like a sane number we should never exceed.
if (!allowReferenceClockSeeks && Math.Abs(proposedTime - referenceClock.CurrentTime) > 500)
if (!allowReferenceClockSeeks && Math.Abs(proposedTime - referenceClock.CurrentTime) > 1500)
{
if (lastBackwardsSeekLogTime == null || Math.Abs(Clock.CurrentTime - lastBackwardsSeekLogTime.Value) > 1000)
if (invalidBassTimeLogCount < 10)
{
lastBackwardsSeekLogTime = Clock.CurrentTime;
invalidBassTimeLogCount++;
Logger.Log("Ignoring likely invalid time value provided by BASS during gameplay");
Logger.Log($"- provided: {referenceClock.CurrentTime:N2}");
Logger.Log($"- expected: {proposedTime:N2}");
@@ -175,6 +175,8 @@ namespace osu.Game.Rulesets.UI
return;
}
invalidBassTimeLogCount = 0;
// if the proposed time is the same as the current time, assume that the clock will continue progressing in the same direction as previously.
// this avoids spurious flips in direction from -1 to 1 during rewinds.
if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime)

View File

@@ -167,7 +167,13 @@ namespace osu.Game.Rulesets.UI
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(45),
RelativeSizeAxes = Axes.Both,
// the mod icon assets in `osu-resources` are sized such that they are flush with the hexagonal background with no shadow baked in.
// the `Icons/BeatmapDetails/mod-icon` asset (of size 135x100) has a shadow and some extra transparent pixels baked in.
// the hexagonal background on that asset, excluding its shadow and the transparent pixels, is 131px wide and 92px high.
// height is divided by 135 rather than by 100, because this entire component is square-sized.
Width = 131 / 135f,
Height = 92 / 135f,
Icon = FontAwesome.Solid.Question
},
adjustmentMarker = new Container

View File

@@ -61,7 +61,6 @@ namespace osu.Game.Rulesets.UI
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(0, 4),
Direction = FillDirection.Vertical,
Child = tinySwitch = new ModSwitchTiny(mod)
{
@@ -79,7 +78,9 @@ namespace osu.Game.Rulesets.UI
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(21),
Size = new Vector2(37, 26),
// arbitrary adjustment for better vertical alignment
Margin = new MarginPadding { Top = -1 },
Icon = mod.Icon.Value
});
tinySwitch.Scale = new Vector2(0.3f);

View File

@@ -39,6 +39,7 @@ using osu.Game.Screens.Edit;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Select;
using osu.Game.Screens.SelectV2;
using osu.Game.Seasonal;
using osuTK;
@@ -118,12 +119,15 @@ namespace osu.Game.Screens.Menu
[CanBeNull]
private IDisposable logoProxy;
private Bindable<bool> forceSSV1;
[BackgroundDependencyLoader(true)]
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio)
{
holdDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
showMobileDisclaimer = config.GetBindable<bool>(OsuSetting.ShowMobileDisclaimer);
forceSSV1 = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect);
if (host.CanExit)
{
@@ -479,7 +483,7 @@ namespace osu.Game.Screens.Menu
{
}
private void loadSongSelect() => this.Push(new SoloSongSelect());
private void loadSongSelect() => this.Push(forceSSV1.Value ? new PlaySongSelect() : new SoloSongSelect());
private partial class MobileDisclaimerDialog : PopupDialog
{

View File

@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -20,6 +21,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
@@ -63,7 +65,10 @@ namespace osu.Game.Screens.Menu
protected Sample SampleDownbeat;
private readonly Container colourAndTriangles;
private readonly TrianglesV2 triangles;
private Box colourBox;
private TrianglesV2 triangles;
private Bindable<Colour4> logoColour;
/// <summary>
/// Return value decides whether the logo should play its own sample for the click action.
@@ -186,10 +191,13 @@ namespace osu.Game.Screens.Menu
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
colourBox = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"cc5289")),
Colour = ColourInfo.GradientVertical(
Color4Extensions.FromHex(@"ff66ba"), // original osu! cookie pink
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 1.0f)
),
},
triangles = new TrianglesV2
{
@@ -198,7 +206,10 @@ namespace osu.Game.Screens.Menu
Thickness = 0.009f,
ScaleAdjust = 3,
SpawnRatio = 1.4f,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex(@"ff66ab"), Color4Extensions.FromHex(@"b6346f")),
Colour = ColourInfo.GradientVertical(
Color4Extensions.FromHex(@"ff66ba"),
Color4Extensions.Darken(Color4Extensions.FromHex(@"ff66ba"), 2.5f)
),
RelativeSizeAxes = Axes.Both,
},
}
@@ -249,6 +260,21 @@ namespace osu.Game.Screens.Menu
};
}
public void UpdateColour() {
if (triangles == null || colourBox == null)
return; // we're still loading
triangles.Colour = ColourInfo.GradientVertical(
logoColour.Value,
Color4Extensions.Darken(logoColour.Value, 1.0f)
);
colourBox.Colour = ColourInfo.GradientVertical(
logoColour.Value,
Color4Extensions.Darken(logoColour.Value, 2.5f)
);
}
public Container LogoElements { get; private set; }
/// <summary>
@@ -276,7 +302,7 @@ namespace osu.Game.Screens.Menu
}
[BackgroundDependencyLoader]
private void load(TextureStore textures, AudioManager audio)
private void load(TextureStore textures, AudioManager audio, OsuConfigManager config)
{
sampleClick = audio.Samples.Get(@"Menu/osu-logo-select");
@@ -285,6 +311,8 @@ namespace osu.Game.Screens.Menu
logo.Texture = textures.Get(@"Menu/logo");
ripple.Texture = textures.Get(@"Menu/logo");
logoColour = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor);
}
private int lastBeatIndex;
@@ -365,6 +393,7 @@ namespace osu.Game.Screens.Menu
protected override void Update()
{
base.Update();
UpdateColour();
const float scale_adjust_cutoff = 0.4f;

View File

@@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play.HUD
// Extra lenience is applied so the scores don't get cut off from the left due to elastic easing transforms.
float xOffset = DrawableGameplayLeaderboardScore.SHEAR_WIDTH + DrawableGameplayLeaderboardScore.ELASTIC_WIDTH_LENIENCE;
Width = DrawableGameplayLeaderboardScore.EXTENDED_WIDTH + xOffset;
Width = 260 + xOffset;
Height = 300;
InternalChildren = new Drawable[]
@@ -155,6 +155,10 @@ namespace osu.Game.Screens.Play.HUD
{
base.Update();
// limit leaderboard dimensions to a sane minimum.
Width = Math.Max(Width, Flow.X + DrawableGameplayLeaderboardScore.MIN_WIDTH);
Height = Math.Max(Height, DrawableGameplayLeaderboardScore.PANEL_HEIGHT);
requiresScroll = Flow.DrawHeight > Height;
if (requiresScroll && TrackedScore != null)

View File

@@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -26,13 +27,15 @@ namespace osu.Game.Screens.Play.HUD
{
public partial class DrawableGameplayLeaderboardScore : CompositeDrawable
{
public const float EXTENDED_WIDTH = extended_left_panel_width + right_panel_width;
public const float MIN_WIDTH = extended_left_panel_width + avatar_size / 2 + 5;
private const float left_panel_extension_width = 20;
private const float regular_left_panel_width = avatar_size + avatar_size / 2;
private const float extended_left_panel_width = regular_left_panel_width + left_panel_extension_width;
private const float right_panel_width = 180;
private const float accuracy_combo_width_cutoff = 150;
private const float username_score_width_cutoff = 50;
private const float avatar_size = PANEL_HEIGHT;
@@ -98,6 +101,8 @@ namespace osu.Game.Screens.Play.HUD
[Resolved]
private OsuColour colours { get; set; } = null!;
private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize);
/// <summary>
/// Creates a new <see cref="DrawableGameplayLeaderboardScore"/>.
/// </summary>
@@ -116,10 +121,12 @@ namespace osu.Game.Screens.Play.HUD
if (score.TeamColour != null)
BackgroundColour = score.TeamColour.Value;
AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.X;
Height = PANEL_HEIGHT;
Shear = OsuGame.SHEAR;
AddLayout(drawSizeLayout);
}
[BackgroundDependencyLoader]
@@ -198,7 +205,6 @@ namespace osu.Game.Screens.Play.HUD
},
rightLayer = new Container
{
Width = right_panel_width,
RelativeSizeAxes = Axes.Y,
// negative left margin to make the X position of the right layer directly at the avatar center (rendered behind it).
Margin = new MarginPadding { Left = -avatar_size / 2 },
@@ -210,8 +216,7 @@ namespace osu.Game.Screens.Play.HUD
},
scoreComponents = new Container
{
Width = right_panel_width,
RelativeSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = avatar_size / 2 + 4, Right = 20, Vertical = 5 },
Shear = -OsuGame.SHEAR,
Children = new Drawable[]
@@ -223,7 +228,6 @@ namespace osu.Game.Screens.Play.HUD
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(GridSizeMode.AutoSize),
},
RowDimensions = new[]
@@ -242,7 +246,6 @@ namespace osu.Game.Screens.Play.HUD
Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
},
Empty(),
accuracyText = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
@@ -252,27 +255,40 @@ namespace osu.Game.Screens.Play.HUD
}
},
},
new Container
new GridContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new[]
ColumnDimensions = new[]
{
scoreText = new OsuSpriteText
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Style.Body.With(weight: FontWeight.Regular),
},
comboText = new OsuSpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold),
},
}
scoreText = new TruncatingSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Style.Body.With(weight: FontWeight.Regular),
RelativeSizeAxes = Axes.X,
},
comboText = new OsuSpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold),
},
}
},
},
},
}
@@ -311,7 +327,7 @@ namespace osu.Game.Screens.Play.HUD
{
if (expanded.NewValue)
{
rightLayer.ResizeWidthTo(right_panel_width, panel_transition_duration, Easing.OutQuint);
rightLayer.ResizeWidthTo(computeRightLayerWidth(), panel_transition_duration, Easing.OutQuint);
scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint);
}
else
@@ -371,6 +387,34 @@ namespace osu.Game.Screens.Play.HUD
scorePanel.BorderColour = ColourInfo.GradientVertical(colours.Blue1.Opacity(0.2f), colours.Blue1);
}
protected override void Update()
{
base.Update();
if (!drawSizeLayout.IsValid)
{
if (Expanded.Value)
{
rightLayer.ClearTransforms(targetMember: nameof(Width));
rightLayer.Width = computeRightLayerWidth();
}
drawSizeLayout.Validate();
}
bool showAccuracyAndCombo = rightLayer.Width >= accuracy_combo_width_cutoff;
accuracyText.Alpha = showAccuracyAndCombo ? 1 : 0;
comboText.Alpha = showAccuracyAndCombo ? 1 : 0;
bool showUsernameAndScore = rightLayer.Width >= username_score_width_cutoff;
usernameText.Alpha = showUsernameAndScore ? 1 : 0;
scoreText.Alpha = showUsernameAndScore ? 1 : 0;
}
private float computeRightLayerWidth() => Math.Max(0, DrawWidth - extended_left_panel_width - avatar_size / 2);
private partial class ScoreAvatar : CompositeDrawable
{
private readonly IUser? user;

View File

@@ -0,0 +1,166 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Users;
using osu.Game.Utils;
using osuTK.Input;
namespace osu.Game.Screens.Select
{
public partial class PlaySongSelect : SongSelect
{
private OsuScreen? playerLoader;
[Resolved]
private INotificationOverlay? notifications { get; set; }
public override bool AllowExternalScreenChange => true;
public override MenuItem[] CreateForwardNavigationMenuItemsForBeatmap(Func<BeatmapInfo> getBeatmap) => new MenuItem[]
{
new OsuMenuItem(ButtonSystemStrings.Play.ToSentence(), MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())),
new OsuMenuItem(ButtonSystemStrings.Edit.ToSentence(), MenuItemType.Standard, () => Edit(getBeatmap()))
};
protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap();
private PlayBeatmapDetailArea playBeatmapDetailArea = null!;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BeatmapOptions.AddButton(ButtonSystemStrings.Edit.ToSentence(), @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => Edit());
AddInternal(new SongSelectTouchInputDetector());
}
protected void PresentScore(ScoreInfo score) =>
FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score)));
protected override BeatmapDetailArea CreateBeatmapDetailArea()
{
playBeatmapDetailArea = new PlayBeatmapDetailArea
{
Leaderboard =
{
ScoreSelected = PresentScore
}
};
return playBeatmapDetailArea;
}
protected override bool OnKeyDown(KeyDownEvent e)
{
switch (e.Key)
{
case Key.Enter:
case Key.KeypadEnter:
// this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is
// matching with exact modifier consideration (so Ctrl+Enter would be ignored).
FinaliseSelection();
return true;
}
return base.OnKeyDown(e);
}
private IReadOnlyList<Mod>? modsAtGameplayStart;
private ModAutoplay? getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
protected override bool OnStart()
{
if (playerLoader != null) return false;
modsAtGameplayStart = Mods.Value.Select(m => m.DeepClone()).ToArray();
// Ctrl+Enter should start map with autoplay enabled.
if (GetContainingInputManager()?.CurrentState?.Keyboard.ControlPressed == true)
{
var autoInstance = getAutoplayMod();
if (autoInstance == null)
{
notifications?.Post(new SimpleNotification
{
Text = NotificationsStrings.NoAutoplayMod
});
return false;
}
var mods = Mods.Value.Append(autoInstance).ToArray();
if (!ModUtils.CheckCompatibleSet(mods, out var invalid))
mods = mods.Except(invalid).Append(autoInstance).ToArray();
Mods.Value = mods;
}
SampleConfirm?.Play();
this.Push(playerLoader = new PlayerLoader(createPlayer));
return true;
Player createPlayer()
{
Player player;
var replayGeneratingMod = Mods.Value.OfType<ICreateReplayData>().FirstOrDefault();
if (replayGeneratingMod != null)
{
player = new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods));
}
else
{
player = new SoloPlayer();
}
return player;
}
}
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(e);
revertMods();
}
public override bool OnExiting(ScreenExitEvent e)
{
if (base.OnExiting(e))
return true;
revertMods();
return false;
}
private void revertMods()
{
if (playerLoader == null) return;
Mods.Value = modsAtGameplayStart;
playerLoader = null;
}
}
}

View File

@@ -3,16 +3,12 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Database;
@@ -20,7 +16,6 @@ using osu.Game.Graphics.Containers;
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;
using osu.Game.Resources.Localisation.Web;
using osuTK;
@@ -55,10 +50,10 @@ namespace osu.Game.Screens.SelectV2
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
private IBindable<SongSelect.BeatmapSetLookupResult> onlineLookupResult { get; set; } = null!;
[Resolved]
private RealmPopulatingOnlineLookupSource onlineLookupSource { get; set; } = null!;
private IAPIProvider api { get; set; } = null!;
[Resolved]
private RealmAccess realm { get; set; } = null!;
@@ -254,6 +249,7 @@ namespace osu.Game.Screens.SelectV2
{
base.LoadComplete();
beatmap.BindValueChanged(_ => updateDisplay());
onlineLookupResult.BindValueChanged(_ => updateDisplay());
apiState = api.State.GetBoundCopy();
apiState.BindValueChanged(_ => Scheduler.AddOnce(updateDisplay), true);
@@ -283,7 +279,7 @@ namespace osu.Game.Screens.SelectV2
// Needs some experimentation on what looks good.
var beatmapInfo = beatmap.Value.BeatmapInfo;
var currentOnlineBeatmap = currentOnlineBeatmapSet?.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmapInfo.OnlineID);
var currentOnlineBeatmap = onlineLookupResult.Value?.Result?.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmapInfo.OnlineID);
if (State.Value == Visibility.Visible && currentOnlineBeatmap != null)
{
@@ -365,41 +361,12 @@ namespace osu.Game.Screens.SelectV2
submitted.Date = beatmapSetInfo.DateSubmitted;
ranked.Date = beatmapSetInfo.DateRanked;
if (currentOnlineBeatmapSet == null || currentOnlineBeatmapSet.OnlineID != beatmapSetInfo.OnlineID)
refetchBeatmapSet();
updateOnlineDisplay();
}
private APIBeatmapSet? currentOnlineBeatmapSet;
private CancellationTokenSource? cancellationTokenSource;
private Task<APIBeatmapSet?>? currentFetchTask;
private void refetchBeatmapSet()
{
var beatmapSetInfo = beatmap.Value.BeatmapSetInfo;
cancellationTokenSource?.Cancel();
currentOnlineBeatmapSet = null;
if (beatmapSetInfo.OnlineID >= 1)
{
cancellationTokenSource = new CancellationTokenSource();
currentFetchTask = onlineLookupSource.GetBeatmapSetAsync(beatmapSetInfo.OnlineID);
currentFetchTask.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
currentOnlineBeatmapSet = t.GetResultSafely();
if (t.Exception != null)
Logger.Log($"Error when fetching online beatmap set: {t.Exception}", LoggingTarget.Network);
Scheduler.AddOnce(updateOnlineDisplay);
});
}
}
private void updateOnlineDisplay()
{
if (currentFetchTask?.IsCompleted == false)
if (onlineLookupResult.Value?.Status != SongSelect.BeatmapSetLookupStatus.Completed)
{
genre.Data = null;
language.Data = null;
@@ -407,7 +374,7 @@ namespace osu.Game.Screens.SelectV2
return;
}
if (currentOnlineBeatmapSet == null)
if (onlineLookupResult.Value.Result == null)
{
genre.Data = ("-", null);
language.Data = ("-", null);
@@ -416,7 +383,7 @@ namespace osu.Game.Screens.SelectV2
{
var beatmapInfo = beatmap.Value.BeatmapInfo;
var onlineBeatmapSet = currentOnlineBeatmapSet;
var onlineBeatmapSet = onlineLookupResult.Value.Result;
var onlineBeatmap = onlineBeatmapSet.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmapInfo.OnlineID);
genre.Data = (onlineBeatmapSet.Genre.Name, () => songSelect?.Search(onlineBeatmapSet.Genre.Name));
@@ -440,6 +407,7 @@ namespace osu.Game.Screens.SelectV2
string[] tags = realm.Run(r =>
{
// need to refetch because `beatmap.Value.BeatmapInfo` is not going to have the latest tags
r.Refresh();
var refetchedBeatmap = r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID);
return refetchedBeatmap?.Metadata.UserTags.ToArray() ?? [];
});

View File

@@ -8,11 +8,9 @@ using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Configuration;
@@ -21,7 +19,6 @@ using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -43,6 +40,9 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
[Resolved]
private IBindable<SongSelect.BeatmapSetLookupResult?> onlineLookupResult { get; set; } = null!;
protected override bool StartHidden => true;
private ModSettingChangeTracker? settingChangeTracker;
@@ -69,16 +69,9 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private LocalisationManager localisation { get; set; } = null!;
[Resolved]
private RealmPopulatingOnlineLookupSource onlineLookupSource { get; set; } = null!;
[Resolved]
private RealmAccess realm { get; set; } = null!;
private APIBeatmapSet? currentOnlineBeatmapSet;
private CancellationTokenSource? cancellationTokenSource;
private Task<APIBeatmapSet?>? currentFetchTask;
private FillFlowContainer statisticsFlow = null!;
public BeatmapTitleWedge()
@@ -190,6 +183,7 @@ namespace osu.Game.Screens.SelectV2
working.BindValueChanged(_ => updateDisplay());
ruleset.BindValueChanged(_ => updateDisplay());
onlineLookupResult.BindValueChanged(_ => updateDisplay());
mods.BindValueChanged(m =>
{
@@ -230,7 +224,6 @@ namespace osu.Game.Screens.SelectV2
{
var metadata = working.Value.Metadata;
var beatmapInfo = working.Value.BeatmapInfo;
var beatmapSetInfo = working.Value.BeatmapSetInfo;
statusPill.Status = beatmapInfo.Status;
@@ -243,10 +236,6 @@ namespace osu.Game.Screens.SelectV2
artistLink.Action = () => songSelect?.Search(artistText.GetPreferred(localisation.CurrentParameters.Value.PreferOriginalScript));
updateLengthAndBpmStatistics();
if (currentOnlineBeatmapSet == null || currentOnlineBeatmapSet.OnlineID != beatmapSetInfo.OnlineID)
refetchBeatmapSet();
updateOnlineDisplay();
}
@@ -289,40 +278,18 @@ namespace osu.Game.Screens.SelectV2
}, token);
}
private void refetchBeatmapSet()
{
var beatmapSetInfo = working.Value.BeatmapSetInfo;
cancellationTokenSource?.Cancel();
currentOnlineBeatmapSet = null;
if (beatmapSetInfo.OnlineID >= 1)
{
cancellationTokenSource = new CancellationTokenSource();
currentFetchTask = onlineLookupSource.GetBeatmapSetAsync(beatmapSetInfo.OnlineID);
currentFetchTask.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
currentOnlineBeatmapSet = t.GetResultSafely();
if (t.Exception != null)
Logger.Log($"Error when fetching online beatmap set: {t.Exception}", LoggingTarget.Network);
Scheduler.AddOnce(updateOnlineDisplay);
});
}
}
private void updateOnlineDisplay()
{
if (currentFetchTask?.IsCompleted == false)
if (onlineLookupResult.Value?.Status != SongSelect.BeatmapSetLookupStatus.Completed)
{
playCount.Value = null;
favouriteButton.SetLoading();
}
else
{
var onlineBeatmap = currentOnlineBeatmapSet?.Beatmaps.SingleOrDefault(b => b.OnlineID == working.Value.BeatmapInfo.OnlineID);
var onlineBeatmap = onlineLookupResult.Value.Result?.Beatmaps.SingleOrDefault(b => b.OnlineID == working.Value.BeatmapInfo.OnlineID);
playCount.Value = new StatisticPlayCount.Data(onlineBeatmap?.PlayCount ?? -1, onlineBeatmap?.UserPlayCount ?? -1);
favouriteButton.SetBeatmapSet(currentOnlineBeatmapSet);
favouriteButton.SetBeatmapSet(onlineLookupResult.Value.Result);
// the online fetch may have also updated the beatmap's status.
// this needs to be checked against the *local* beatmap model rather than the online one, because it's not known here whether the status change has occurred or not
@@ -332,6 +299,7 @@ namespace osu.Game.Screens.SelectV2
// which prevents working beatmap refetches caused by changes to the realm model of perceived low importance).
var status = realm.Run(r =>
{
r.Refresh();
var refetchedBeatmap = r.Find<BeatmapInfo>(working.Value.BeatmapInfo.ID);
return refetchedBeatmap?.Status;
});

View File

@@ -229,7 +229,10 @@ namespace osu.Game.Screens.SelectV2
bool hasFavourited = favouriteRequest.Action == BeatmapFavouriteAction.Favourite;
beatmapSet.HasFavourited = hasFavourited;
beatmapSet.FavouriteCount += hasFavourited ? 1 : -1;
setBeatmapSet(beatmapSet, withHeartAnimation: hasFavourited);
// if the beatmap set reference changed under the callback, abort visual updates to avoid showing stale data
if (onlineBeatmapSet == null || ReferenceEquals(beatmapSet, onlineBeatmapSet))
setBeatmapSet(beatmapSet, withHeartAnimation: hasFavourited);
};
favouriteRequest.Failure += e =>
{
@@ -238,7 +241,10 @@ namespace osu.Game.Screens.SelectV2
Text = e.Message,
Icon = FontAwesome.Solid.Times,
});
setBeatmapSet(beatmapSet, withHeartAnimation: false);
// if the beatmap set reference changed under the callback, abort visual updates to avoid showing stale data
if (onlineBeatmapSet == null || ReferenceEquals(beatmapSet, onlineBeatmapSet))
setBeatmapSet(beatmapSet, withHeartAnimation: false);
};
api.Queue(favouriteRequest);
setLoading();

View File

@@ -5,11 +5,14 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -34,6 +37,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Volume;
@@ -133,8 +137,7 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
[Cached]
private RealmPopulatingOnlineLookupSource onlineLookupSource = new RealmPopulatingOnlineLookupSource();
private readonly RealmPopulatingOnlineLookupSource onlineLookupSource = new RealmPopulatingOnlineLookupSource();
private Bindable<bool> configBackgroundBlur = null!;
@@ -349,6 +352,7 @@ namespace osu.Game.Screens.SelectV2
ensurePlayingSelected();
updateBackgroundDim();
updateWedgeVisibility();
fetchOnlineInfo();
});
}
@@ -850,7 +854,7 @@ namespace osu.Game.Screens.SelectV2
// For simplicity, disable this functionality on mobile.
bool isTouchInput = e.CurrentState.Mouse.LastSource is ISourcedFromTouch;
if (!carousel.AbsoluteScrolling && !isTouchInput && mouseDownPriority)
if (!carousel.AbsoluteScrolling && !isTouchInput && mouseDownPriority && revealingBackground == null)
{
revealingBackground = Scheduler.AddDelayed(() =>
{
@@ -954,6 +958,74 @@ namespace osu.Game.Screens.SelectV2
#endregion
#region Online lookups
public enum BeatmapSetLookupStatus
{
InProgress,
Completed,
}
public class BeatmapSetLookupResult
{
public BeatmapSetLookupStatus Status { get; }
public APIBeatmapSet? Result { get; }
private BeatmapSetLookupResult(BeatmapSetLookupStatus status, APIBeatmapSet? result)
{
Status = status;
Result = result;
}
public static BeatmapSetLookupResult InProgress() => new BeatmapSetLookupResult(BeatmapSetLookupStatus.InProgress, null);
public static BeatmapSetLookupResult Completed(APIBeatmapSet? beatmapSet) => new BeatmapSetLookupResult(BeatmapSetLookupStatus.Completed, beatmapSet);
}
/// <summary>
/// Result of the latest online beatmap set lookup.
/// Note that this being <see langword="null"/> or <see cref="BeatmapSetLookupResult.InProgress"/> is different from
/// being a <see cref="BeatmapSetLookupResult.Completed"/> with a <see cref="BeatmapSetLookupResult.Result"/> of null.
/// The former indicates a lookup never occurring or being in progress, while the latter indicates a completed lookup with no result.
/// </summary>
[Cached(typeof(IBindable<BeatmapSetLookupResult?>))]
private readonly Bindable<BeatmapSetLookupResult?> lastLookupResult = new Bindable<BeatmapSetLookupResult?>();
private CancellationTokenSource? onlineLookupCancellation;
private Task<APIBeatmapSet?>? currentOnlineLookup;
private void fetchOnlineInfo()
{
var beatmapSetInfo = Beatmap.Value.BeatmapSetInfo;
if (lastLookupResult.Value?.Result?.OnlineID == beatmapSetInfo.OnlineID)
return;
onlineLookupCancellation?.Cancel();
if (beatmapSetInfo.OnlineID < 0)
{
lastLookupResult.Value = BeatmapSetLookupResult.Completed(null);
return;
}
lastLookupResult.Value = BeatmapSetLookupResult.InProgress();
onlineLookupCancellation = new CancellationTokenSource();
currentOnlineLookup = onlineLookupSource.GetBeatmapSetAsync(beatmapSetInfo.OnlineID);
currentOnlineLookup.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
Schedule(() => lastLookupResult.Value = BeatmapSetLookupResult.Completed(t.GetResultSafely()));
if (t.Exception != null)
{
Logger.Log($"Error when fetching online beatmap set: {t.Exception}", LoggingTarget.Network);
Schedule(() => lastLookupResult.Value = BeatmapSetLookupResult.Completed(null));
}
});
}
#endregion
#region Implementation of ISongSelect
void ISongSelect.Search(string query) => filterControl.Search(query);

View File

@@ -36,7 +36,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="ppy.osu.Framework" Version="2025.808.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.819.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2025.821.0" />
<PackageReference Include="Sentry" Version="5.1.1" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.39.0" />