Compare commits
136 Commits
4c10d941b3
...
2025.829.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 081355864e | |||
| 6435a835d1 | |||
| 628181a883 | |||
| 835329efd3 | |||
| 5399943118 | |||
| d07f82f6f4 | |||
|
|
2bea59e65f | ||
|
|
c0fd5637de | ||
|
|
5e7a99c97f | ||
|
|
8f628d16ae | ||
|
|
2ccb65aa65 | ||
|
|
4d851f2527 | ||
|
|
4bafbfb9e4 | ||
|
|
3f179e3903 | ||
|
|
196b28115e | ||
|
|
7660a9ba8e | ||
|
|
e908b80359 | ||
|
|
a2bf8e3988 | ||
| 5b186bb740 | |||
|
|
6e8246b539 | ||
| 6cb99c13c2 | |||
|
|
3cca458c21 | ||
|
|
bc59270f3e | ||
| 96008e06ab | |||
| 590b0a8028 | |||
| 70f7f09a83 | |||
|
|
16343fd7d6 | ||
|
|
acafc06bcc | ||
|
|
c0c3690908 | ||
| 490137405f | |||
| f3c6f53f70 | |||
| 8cb5c682b4 | |||
| c3d79295d3 | |||
|
|
5292d4a04e | ||
|
|
d3ae20dd88 | ||
|
|
c852e5854c | ||
|
|
0756c45d70 | ||
| f31d310135 | |||
| 26029de27d | |||
| c37f72f567 | |||
|
|
73624e4e25 | ||
|
|
f374af7ce7 | ||
|
|
7530ad1a7b | ||
|
|
a049f5065d | ||
|
|
4627c8a859 | ||
|
|
30f7da8f71 | ||
|
|
4b8ff481fd | ||
|
|
a7f1795f98 | ||
|
|
c053cfbf9b | ||
|
|
e47a60f303 | ||
|
|
92016a7d9b | ||
|
|
41885c0fc0 | ||
|
|
e75a6b4010 | ||
|
|
ddce11fbc8 | ||
|
|
c894969d17 | ||
|
|
47fecfb669 | ||
|
|
3b49673e83 | ||
|
|
b1296b0c83 | ||
|
|
33df7dc5e5 | ||
|
|
ad6c0c272d | ||
|
|
7c3249c24c | ||
|
|
35ab30e83f | ||
|
|
aba160fb62 | ||
|
|
ceb8a621ff | ||
|
|
cf38bdfb04 | ||
|
|
a337c8bb99 | ||
|
|
807ba111fd | ||
|
|
fde2887068 | ||
|
|
49bb157fb8 | ||
|
|
b2dbd4a9dc | ||
|
|
8dd349fd17 | ||
|
|
df210241fc | ||
|
|
d696ac99d4 | ||
|
|
777ab61143 | ||
|
|
bb5933ef80 | ||
|
|
62548244bc | ||
|
|
fe612d465b | ||
|
|
a393b3c6b1 | ||
|
|
d26f31b71d | ||
|
|
59ec6ed2eb | ||
|
|
5b1b22cb66 | ||
|
|
a1fb7acef3 | ||
|
|
e77fb987a9 | ||
|
|
c4163e33e5 | ||
|
|
a96d00a55f | ||
|
|
6a16200314 | ||
|
|
4d1ecab4e3 | ||
|
|
f2839c7b65 | ||
|
|
0bcf29304b | ||
|
|
7b455efe34 | ||
|
|
14530fe894 | ||
|
|
148bc4ac34 | ||
|
|
a3443f76be | ||
|
|
d998847271 | ||
|
|
62803af1de | ||
|
|
375da52a34 | ||
|
|
cd7a304640 | ||
|
|
bcc9bc4498 | ||
|
|
5fc5d0bd5f | ||
|
|
18803fbec0 | ||
|
|
9542e77d16 | ||
|
|
0fa0568f13 | ||
|
|
771081b9a7 | ||
|
|
20921577d7 | ||
|
|
84c0bd0052 | ||
|
|
398ac1b98d | ||
|
|
00fd22841d | ||
|
|
e487f20f1b | ||
|
|
33ab00ecd8 | ||
|
|
098ade2cfa | ||
|
|
0b5251fcf4 | ||
|
|
55bc043719 | ||
|
|
bf70552186 | ||
|
|
9e3c7e2ca9 | ||
|
|
d22435b55f | ||
|
|
4e0dca69ed | ||
|
|
5ffb92b638 | ||
|
|
1cd2331d28 | ||
|
|
814f39058e | ||
|
|
f61cb3caa7 | ||
|
|
07d81c0824 | ||
|
|
3195681805 | ||
|
|
77a2ac8f42 | ||
|
|
6eda09aff4 | ||
|
|
380c3d0444 | ||
|
|
ad1fdc631d | ||
|
|
c16ef5eac3 | ||
|
|
f9076183d0 | ||
|
|
6fa6de7c27 | ||
|
|
daff00300a | ||
|
|
72d97f4ad6 | ||
|
|
3e56633882 | ||
|
|
18d3e9154f | ||
|
|
eae4227c5a | ||
|
|
de65e90abf | ||
|
|
01815de675 |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@ bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Pp]ub/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
36
.vscode/launch.json
vendored
36
.vscode/launch.json
vendored
@@ -7,9 +7,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||
"${workspaceFolder}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -19,9 +19,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||
"${workspaceFolder}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -31,9 +31,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
|
||||
"${workspaceFolder}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tests (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -43,9 +43,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
|
||||
"${workspaceFolder}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tests (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -55,10 +55,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"${workspaceFolder}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -68,10 +68,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||
"${workspaceFolder}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -81,10 +81,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"${workspaceFolder}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tournament tests (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -94,10 +94,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"${workspaceFolder}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tournament tests (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -105,12 +105,12 @@
|
||||
"name": "Benchmark",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
|
||||
"program": "${workspaceFolder}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
|
||||
"args": [
|
||||
"--filter",
|
||||
"*"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build benchmarks",
|
||||
"console": "internalConsole"
|
||||
}
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "osu.Desktop.slnf"
|
||||
}
|
||||
19
MakeInstaller.ps1
Normal file
19
MakeInstaller.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env powershell
|
||||
param (
|
||||
[string]$Version,
|
||||
[string]$BuildConfig = "Release"
|
||||
)
|
||||
|
||||
if ($Version -eq "") {
|
||||
Write-Host "Usage: .\MakeInstaller.ps1 <VERSION_NUMBER> [-BuildConfig <BUILD_CONFIG>]"
|
||||
Write-Host "Example: .\MakeInstaller.ps1 2025.823.0 -BuildConfig Debug"
|
||||
exit
|
||||
}
|
||||
|
||||
$tmpPub = ".\pub"
|
||||
if (-not (Test-Path -Path $tmpPub)) {
|
||||
New-Item -ItemType Directory -path $tmpPub
|
||||
}
|
||||
|
||||
dotnet publish -c $BuildConfig osu.Desktop --self-contained -r win-x64 -o $tmpPub -verbosity:m /p:Version=$Version
|
||||
vpk pack --packId jvnkosu.Client --packTitle "jvnkosu!lazer" --packVersion $Version --packDir ./pub --mainExe="osu!.exe"
|
||||
@@ -115,10 +115,12 @@ namespace osu.Desktop
|
||||
if (IsFirstRun)
|
||||
LocalConfig.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
|
||||
|
||||
if (IsPackageManaged)
|
||||
return new NoActionUpdateManager();
|
||||
// if (IsPackageManaged)
|
||||
// return new NoActionUpdateManager();
|
||||
|
||||
return new VelopackUpdateManager();
|
||||
// return new VelopackUpdateManager();
|
||||
|
||||
return new NoActionUpdateManager(); // for now, APIs are useless for actually downloading the releases. TODO: adapt UpdateManager for gitea
|
||||
}
|
||||
|
||||
public override bool RestartAppWhenExited()
|
||||
|
||||
@@ -21,9 +21,9 @@ namespace osu.Desktop
|
||||
public static class Program
|
||||
{
|
||||
#if DEBUG
|
||||
private const string base_game_name = @"osu-development";
|
||||
private const string base_game_name = @"jvnkosu-development";
|
||||
#else
|
||||
private const string base_game_name = @"osu";
|
||||
private const string base_game_name = @"jvnkosu";
|
||||
#endif
|
||||
|
||||
private static LegacyTcpIpcProvider? legacyIpc;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||
|
||||
public override float DefaultFlashlightSize => 325;
|
||||
public override float DefaultFlashlightSize => 203.125f;
|
||||
|
||||
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -32,26 +32,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestPlayfieldBasedSize()
|
||||
{
|
||||
OsuModFlashlight flashlight;
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()],
|
||||
PassCondition = () =>
|
||||
{
|
||||
var flashlightOverlay = Player.DrawableRuleset.Overlays
|
||||
.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
|
||||
.First();
|
||||
|
||||
// the combo check is here because the flashlight radius decreases for the first time at 100 combo
|
||||
// and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()`
|
||||
return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderDimsOnlyAfterStartTime()
|
||||
{
|
||||
|
||||
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/sliderpoint10.png
Normal file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/sliderpoint10.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/sliderpoint30.png
Normal file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/sliderpoint30.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
162
osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs
Normal file
162
osu.Game.Rulesets.Osu.Tests/TestSceneAimErrorMeter.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Rulesets.Osu.HUD;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public partial class TestSceneAimErrorMeter : OsuManualInputManagerTestScene
|
||||
{
|
||||
private DependencyProvidingContainer dependencyContainer = null!;
|
||||
private ScoreProcessor scoreProcessor = null!;
|
||||
|
||||
private TestAimErrorMeter aimErrorMeter = null!;
|
||||
|
||||
private CircularContainer gameObject = null!;
|
||||
|
||||
private ScheduledDelegate? automaticAdditionDelegate;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddSliderStep("Hit marker size", 0f, 12f, 7f, t =>
|
||||
{
|
||||
if (aimErrorMeter.IsNotNull())
|
||||
aimErrorMeter.HitMarkerSize.Value = t;
|
||||
});
|
||||
AddSliderStep("Average position marker size", 1f, 25f, 7f, t =>
|
||||
{
|
||||
if (aimErrorMeter.IsNotNull())
|
||||
aimErrorMeter.AverageMarkerSize.Value = t;
|
||||
});
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetupSteps() => AddStep("Create components", () =>
|
||||
{
|
||||
automaticAdditionDelegate?.Cancel();
|
||||
automaticAdditionDelegate = null;
|
||||
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
scoreProcessor = new ScoreProcessor(ruleset);
|
||||
Child = dependencyContainer = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(ScoreProcessor), scoreProcessor)
|
||||
}
|
||||
};
|
||||
dependencyContainer.Children = new Drawable[]
|
||||
{
|
||||
aimErrorMeter = new TestAimErrorMeter
|
||||
{
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = 100
|
||||
},
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Scale = new Vector2(2),
|
||||
},
|
||||
|
||||
gameObject = new CircularContainer
|
||||
{
|
||||
Size = new Vector2(2 * OsuHitObject.OBJECT_RADIUS),
|
||||
Position = new Vector2(256, 192),
|
||||
Colour = Color4.Yellow,
|
||||
Masking = true,
|
||||
BorderThickness = 2,
|
||||
BorderColour = Color4.White,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(4),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
// the division by 2 is because CS=5 applies a 0.5x (plus fudge) multiplier to `OBJECT_RADIUS`
|
||||
aimErrorMeter.AddPoint((gameObject.ToLocalSpace(e.ScreenSpaceMouseDownPosition) - new Vector2(OsuHitObject.OBJECT_RADIUS)) / 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManyHitPointsAutomatic()
|
||||
{
|
||||
AddStep("add scheduled delegate", () =>
|
||||
{
|
||||
automaticAdditionDelegate = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
var randomPos = new Vector2(
|
||||
RNG.NextSingle(0, 2 * OsuHitObject.OBJECT_RADIUS),
|
||||
RNG.NextSingle(0, 2 * OsuHitObject.OBJECT_RADIUS));
|
||||
|
||||
aimErrorMeter.AddPoint(randomPos - new Vector2(OsuHitObject.OBJECT_RADIUS));
|
||||
InputManager.MoveMouseTo(gameObject.ToScreenSpace(randomPos));
|
||||
}, 1, true);
|
||||
});
|
||||
AddWaitStep("wait for some hit points", 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayStyles()
|
||||
{
|
||||
AddStep("Switch hit position marker style to +", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus);
|
||||
AddStep("Switch hit position marker style to x", () => aimErrorMeter.HitMarkerStyle.Value = AimErrorMeter.MarkerStyle.X);
|
||||
AddStep("Switch average position marker style to +", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.Plus);
|
||||
AddStep("Switch average position marker style to x", () => aimErrorMeter.AverageMarkerStyle.Value = AimErrorMeter.MarkerStyle.X);
|
||||
|
||||
AddStep("Switch position display to absolute", () => aimErrorMeter.PositionDisplayStyle.Value = AimErrorMeter.PositionDisplay.Absolute);
|
||||
AddStep("Switch position display to relative", () => aimErrorMeter.PositionDisplayStyle.Value = AimErrorMeter.PositionDisplay.Normalised);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualPlacement()
|
||||
{
|
||||
AddStep("return user input", () => InputManager.UseParentInput = true);
|
||||
}
|
||||
|
||||
private partial class TestAimErrorMeter : AimErrorMeter
|
||||
{
|
||||
public void AddPoint(Vector2 position)
|
||||
{
|
||||
OnNewJudgement(new OsuHitCircleJudgementResult(new HitCircle(), new OsuJudgement())
|
||||
{
|
||||
CursorPositionAtHit = position
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public partial class TestSceneDrawableJudgementSliderTicks : OsuSkinnableTestScene
|
||||
{
|
||||
private bool classic;
|
||||
private readonly JudgementPooler<DrawableOsuJudgement>[] judgementPools;
|
||||
|
||||
public TestSceneDrawableJudgementSliderTicks()
|
||||
{
|
||||
judgementPools = new JudgementPooler<DrawableOsuJudgement>[Rows * Cols];
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
int cellIndex = 0;
|
||||
|
||||
SetContents(_ =>
|
||||
{
|
||||
var container = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
judgementPools[cellIndex] = new JudgementPooler<DrawableOsuJudgement>(new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Miss,
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.SliderTailHit,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.IgnoreMiss,
|
||||
}),
|
||||
new GridContainer
|
||||
{
|
||||
Padding = new MarginPadding { Top = 26f },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
Content =
|
||||
new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
Empty(),
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "hit",
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "miss",
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
},
|
||||
}.Concat(new[]
|
||||
{
|
||||
"head",
|
||||
"tick",
|
||||
"repeat",
|
||||
"tail",
|
||||
"slider",
|
||||
}.Select(label => new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = label,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
},
|
||||
new Container<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||
new Container<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||
})).ToArray(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
cellIndex++;
|
||||
|
||||
return container;
|
||||
});
|
||||
|
||||
AddToggleStep("Toggle classic behaviour", c => classic = c);
|
||||
|
||||
AddStep("Show judgements", createAllJudgements);
|
||||
}
|
||||
|
||||
private void createAllJudgements()
|
||||
{
|
||||
for (int cellIndex = 0; cellIndex < Rows * Cols; cellIndex++)
|
||||
{
|
||||
var slider = new Slider { StartTime = Time.Current, ClassicSliderBehaviour = classic };
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
var drawableHitObjects = new DrawableOsuHitObject[]
|
||||
{
|
||||
new DrawableSliderHead(new SliderHeadCircle { StartTime = Time.Current, ClassicSliderBehaviour = classic }),
|
||||
new DrawableSliderTick(new SliderTick { StartTime = Time.Current }),
|
||||
new DrawableSliderRepeat(new SliderRepeat(slider) { StartTime = Time.Current }),
|
||||
new DrawableSliderTail(new SliderTailCircle(slider) { StartTime = Time.Current, ClassicSliderBehaviour = classic }),
|
||||
new DrawableSlider(slider),
|
||||
};
|
||||
|
||||
var containers = Cell(cellIndex).ChildrenOfType<Container<DrawableOsuJudgement>>().ToArray();
|
||||
|
||||
for (int i = 0; i < drawableHitObjects.Length; i++)
|
||||
{
|
||||
createJudgement(judgementPools[cellIndex], containers[i * 2], drawableHitObjects[i], true);
|
||||
createJudgement(judgementPools[cellIndex], containers[i * 2 + 1], drawableHitObjects[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createJudgement(JudgementPooler<DrawableOsuJudgement> pool, Container<DrawableOsuJudgement> container, DrawableOsuHitObject drawableHitObject, bool hit)
|
||||
{
|
||||
container.Clear(false);
|
||||
|
||||
if (!drawableHitObject.DisplayResult)
|
||||
return;
|
||||
|
||||
var hitObject = drawableHitObject.HitObject;
|
||||
var result = new OsuJudgementResult(hitObject, hitObject.Judgement)
|
||||
{
|
||||
Type = hit ? hitObject.Judgement.MaxResult : hitObject.Judgement.MinResult,
|
||||
};
|
||||
|
||||
var judgement = pool.Get(result.Type, d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
d.Scale = new Vector2(0.7f);
|
||||
d.Apply(result, null);
|
||||
});
|
||||
|
||||
if (judgement != null)
|
||||
container.Add(judgement);
|
||||
}
|
||||
}
|
||||
}
|
||||
475
osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs
Normal file
475
osu.Game.Rulesets.Osu/HUD/AimErrorMeter.cs
Normal file
@@ -0,0 +1,475 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Localisation.HUD;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Statistics;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.HUD
|
||||
{
|
||||
[Cached]
|
||||
public partial class AimErrorMeter : HitErrorMeter
|
||||
{
|
||||
[SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitMarkerSize), nameof(AimErrorMeterStrings.HitMarkerSizeDescription))]
|
||||
public BindableNumber<float> HitMarkerSize { get; } = new BindableNumber<float>(7f)
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = 12f,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
[SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.HitMarkerStyle), nameof(AimErrorMeterStrings.HitMarkerStyleDescription))]
|
||||
public Bindable<MarkerStyle> HitMarkerStyle { get; } = new Bindable<MarkerStyle>();
|
||||
|
||||
[SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageMarkerSize), nameof(AimErrorMeterStrings.AverageMarkerSizeDescription))]
|
||||
public BindableNumber<float> AverageMarkerSize { get; } = new BindableNumber<float>(12f)
|
||||
{
|
||||
MinValue = 7f,
|
||||
MaxValue = 25f,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
[SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.AverageMarkerStyle), nameof(AimErrorMeterStrings.AverageMarkerStyleDescription))]
|
||||
public Bindable<MarkerStyle> AverageMarkerStyle { get; } = new Bindable<MarkerStyle>(MarkerStyle.Plus);
|
||||
|
||||
[SettingSource(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.PositionDisplayStyle), nameof(AimErrorMeterStrings.PositionDisplayStyleDescription))]
|
||||
public Bindable<PositionDisplay> PositionDisplayStyle { get; } = new Bindable<PositionDisplay>();
|
||||
|
||||
// used for calculate relative position.
|
||||
private Vector2? lastObjectPosition;
|
||||
|
||||
private Container averagePositionMarker = null!;
|
||||
private Container averagePositionMarkerRotationContainer = null!;
|
||||
private Vector2? averagePosition;
|
||||
|
||||
private readonly DrawablePool<HitPositionMarker> hitPositionPool = new DrawablePool<HitPositionMarker>(30);
|
||||
private Container hitPositionMarkerContainer = null!;
|
||||
|
||||
private Container arrowBackgroundContainer = null!;
|
||||
private UprightAspectMaintainingContainer rotateFixedContainer = null!;
|
||||
private Container mainContainer = null!;
|
||||
|
||||
private float objectRadius;
|
||||
|
||||
private const int max_concurrent_judgements = 30;
|
||||
|
||||
private const float line_thickness = 2;
|
||||
private const float inner_portion = 0.85f;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public AimErrorMeter()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
AlwaysPresent = true;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, ScoreProcessor processor)
|
||||
{
|
||||
InternalChild = new Container
|
||||
{
|
||||
Height = 100,
|
||||
Width = 100,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hitPositionPool,
|
||||
rotateFixedContainer = new UprightAspectMaintainingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
mainContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
BorderColour = Colour4.White,
|
||||
Masking = true,
|
||||
BorderThickness = 2,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(inner_portion),
|
||||
Child = new Box
|
||||
{
|
||||
Colour = Colour4.Gray,
|
||||
Alpha = 0.3f,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
},
|
||||
arrowBackgroundContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Name = "Arrow Background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = 45,
|
||||
Alpha = 0f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Height = inner_portion + 0.2f,
|
||||
Width = line_thickness / 2,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Height = 5f,
|
||||
Width = line_thickness / 2,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Margin = new MarginPadding(-line_thickness / 4),
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Y = -(inner_portion + 0.2f) / 2,
|
||||
Rotation = -45
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Height = 5f,
|
||||
Width = line_thickness / 2,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Margin = new MarginPadding(-line_thickness / 4),
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Y = -(inner_portion + 0.2f) / 2,
|
||||
Rotation = 45
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = "Cross Background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0.5f,
|
||||
Width = line_thickness,
|
||||
Height = inner_portion * 0.9f
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0.5f,
|
||||
Width = line_thickness,
|
||||
Height = inner_portion * 0.9f,
|
||||
Rotation = 90
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0.2f,
|
||||
Width = line_thickness / 2,
|
||||
Height = inner_portion * 0.9f,
|
||||
Rotation = 45
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0.2f,
|
||||
Width = line_thickness / 2,
|
||||
Height = inner_portion * 0.9f,
|
||||
Rotation = 135
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hitPositionMarkerContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
averagePositionMarker = new UprightAspectMaintainingContainer
|
||||
{
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = averagePositionMarkerRotationContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.25f,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.25f,
|
||||
Rotation = 90
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// handle IApplicableToDifficulty for CS change.
|
||||
BeatmapDifficulty newDifficulty = new BeatmapDifficulty();
|
||||
beatmap.Value.Beatmap.Difficulty.CopyTo(newDifficulty);
|
||||
|
||||
var mods = processor.Mods.Value;
|
||||
|
||||
foreach (var mod in mods.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(newDifficulty);
|
||||
|
||||
objectRadius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(newDifficulty.CircleSize, true);
|
||||
|
||||
AverageMarkerSize.BindValueChanged(size => averagePositionMarker.Size = new Vector2(size.NewValue), true);
|
||||
AverageMarkerStyle.BindValueChanged(style => averagePositionMarkerRotationContainer.Rotation = style.NewValue == MarkerStyle.Plus ? 0 : 45, true);
|
||||
|
||||
PositionDisplayStyle.BindValueChanged(s =>
|
||||
{
|
||||
Clear();
|
||||
|
||||
if (s.NewValue == PositionDisplay.Normalised)
|
||||
{
|
||||
arrowBackgroundContainer.FadeIn(100);
|
||||
rotateFixedContainer.Remove(mainContainer, false);
|
||||
AddInternal(mainContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
arrowBackgroundContainer.FadeOut(100);
|
||||
// when in absolute mode, rotation of the aim error meter as a whole should not affect how the component is displayed
|
||||
RemoveInternal(mainContainer, false);
|
||||
rotateFixedContainer.Add(mainContainer);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void OnNewJudgement(JudgementResult judgement)
|
||||
{
|
||||
if (judgement is not OsuHitCircleJudgementResult circleJudgement) return;
|
||||
|
||||
if (circleJudgement.CursorPositionAtHit == null) return;
|
||||
|
||||
if (hitPositionMarkerContainer.Count > max_concurrent_judgements)
|
||||
{
|
||||
const double quick_fade_time = 300;
|
||||
|
||||
// check with a bit of lenience to avoid precision error in comparison.
|
||||
var old = hitPositionMarkerContainer.FirstOrDefault(j => j.LifetimeEnd > Clock.CurrentTime + quick_fade_time * 1.1);
|
||||
|
||||
if (old != null)
|
||||
{
|
||||
old.ClearTransforms();
|
||||
old.FadeOut(quick_fade_time).Expire();
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 hitPosition;
|
||||
|
||||
if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null)
|
||||
{
|
||||
hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition,
|
||||
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * (inner_portion / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// get relative position between mouse position and current object.
|
||||
hitPosition = (circleJudgement.CursorPositionAtHit.Value - ((OsuHitObject)circleJudgement.HitObject).StackedPosition) / objectRadius / 2 * inner_portion;
|
||||
}
|
||||
|
||||
hitPosition = Vector2.Clamp(hitPosition, new Vector2(-0.5f), new Vector2(0.5f));
|
||||
|
||||
hitPositionPool.Get(drawableHit =>
|
||||
{
|
||||
drawableHit.X = hitPosition.X;
|
||||
drawableHit.Y = hitPosition.Y;
|
||||
drawableHit.Colour = getColourForPosition(hitPosition);
|
||||
|
||||
hitPositionMarkerContainer.Add(drawableHit);
|
||||
});
|
||||
|
||||
var newAveragePosition = 0.1f * hitPosition + 0.9f * (averagePosition ?? hitPosition);
|
||||
averagePositionMarker.MoveTo(newAveragePosition, 800, Easing.OutQuint);
|
||||
averagePosition = newAveragePosition;
|
||||
lastObjectPosition = ((OsuHitObject)circleJudgement.HitObject).StackedPosition;
|
||||
}
|
||||
|
||||
private Color4 getColourForPosition(Vector2 position)
|
||||
{
|
||||
float distance = Vector2.Distance(position, Vector2.Zero);
|
||||
|
||||
if (distance >= 0.5f * inner_portion)
|
||||
return colours.Red;
|
||||
|
||||
if (distance >= 0.35f * inner_portion)
|
||||
return colours.Yellow;
|
||||
|
||||
if (distance >= 0.2f * inner_portion)
|
||||
return colours.Green;
|
||||
|
||||
return colours.Blue;
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
averagePosition = null;
|
||||
averagePositionMarker.MoveTo(Vector2.Zero, 800, Easing.OutQuint);
|
||||
lastObjectPosition = null;
|
||||
|
||||
foreach (var h in hitPositionMarkerContainer)
|
||||
{
|
||||
h.ClearTransforms();
|
||||
h.Expire();
|
||||
}
|
||||
}
|
||||
|
||||
private partial class HitPositionMarker : PoolableDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private AimErrorMeter aimErrorMeter { get; set; } = null!;
|
||||
|
||||
public readonly BindableNumber<float> MarkerSize = new BindableFloat();
|
||||
public readonly Bindable<MarkerStyle> Style = new Bindable<MarkerStyle>();
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
public HitPositionMarker()
|
||||
{
|
||||
RelativePositionAxes = Axes.Both;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChild = new UprightAspectMaintainingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.25f,
|
||||
Rotation = -45
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.25f,
|
||||
Rotation = 45
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
MarkerSize.BindTo(aimErrorMeter.HitMarkerSize);
|
||||
MarkerSize.BindValueChanged(size => Size = new Vector2(size.NewValue), true);
|
||||
Style.BindTo(aimErrorMeter.HitMarkerStyle);
|
||||
Style.BindValueChanged(style => content.Rotation = style.NewValue == MarkerStyle.X ? 0 : 45, true);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
const int judgement_fade_in_duration = 100;
|
||||
const int judgement_fade_out_duration = 5000;
|
||||
|
||||
this
|
||||
.ResizeTo(new Vector2(0))
|
||||
.FadeInFromZero(judgement_fade_in_duration, Easing.OutQuint)
|
||||
.ResizeTo(new Vector2(MarkerSize.Value), judgement_fade_in_duration, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeOut(judgement_fade_out_duration)
|
||||
.Expire();
|
||||
}
|
||||
}
|
||||
|
||||
public enum MarkerStyle
|
||||
{
|
||||
[Description("x")]
|
||||
X,
|
||||
|
||||
[Description("+")]
|
||||
Plus,
|
||||
}
|
||||
|
||||
public enum PositionDisplay
|
||||
{
|
||||
[LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Absolute))]
|
||||
Absolute,
|
||||
|
||||
[LocalisableDescription(typeof(AimErrorMeterStrings), nameof(AimErrorMeterStrings.Normalised))]
|
||||
Normalised,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) };
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||
|
||||
public override float DefaultFlashlightSize => 200;
|
||||
public override float DefaultFlashlightSize => 125;
|
||||
|
||||
private OsuFlashlight flashlight = null!;
|
||||
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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!";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private Vector2 screenSpacePosition;
|
||||
private Vector2? screenSpacePosition;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -65,7 +65,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Lighting.ResetAnimation();
|
||||
Lighting.SetColourFrom(this, Result);
|
||||
Position = Parent!.ToLocalSpace(screenSpacePosition);
|
||||
|
||||
if (screenSpacePosition != null)
|
||||
Position = Parent!.ToLocalSpace(screenSpacePosition.Value);
|
||||
}
|
||||
|
||||
protected override void ApplyHitAnimations()
|
||||
@@ -87,7 +89,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.ApplyHitAnimations();
|
||||
}
|
||||
|
||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new OsuJudgementPiece(result);
|
||||
protected override Drawable CreateDefaultJudgement(HitResult result) =>
|
||||
// Tick hits don't show a judgement by default
|
||||
result.IsHit() && result.IsTick() ? Empty() : new OsuJudgementPiece(result);
|
||||
|
||||
private partial class OsuJudgementPiece : DefaultJudgementPiece
|
||||
{
|
||||
|
||||
@@ -16,17 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
|
||||
public override bool DisplayResult
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HitObject?.ClassicSliderBehaviour == true)
|
||||
return false;
|
||||
|
||||
return base.DisplayResult;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
||||
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (targetJudgement == null || targetResult == null)
|
||||
Colour = Color4.White;
|
||||
else
|
||||
Colour = targetResult.IsHit ? targetJudgement.AccentColour : Color4.Transparent;
|
||||
Colour = targetResult.IsHit && !targetResult.Type.IsTick() ? targetJudgement.AccentColour : Color4.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.LargeTickHit:
|
||||
case HitResult.SliderTailHit:
|
||||
return null;
|
||||
|
||||
case HitResult.IgnoreMiss:
|
||||
case HitResult.LargeTickMiss:
|
||||
return new ArgonJudgementPieceSliderTickMiss(result);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
public partial class LegacyJudgementPieceSliderTickHit : Sprite, IAnimatableJudgement
|
||||
{
|
||||
public void PlayAnimation()
|
||||
{
|
||||
// https://github.com/peppy/osu-stable-reference/blob/0e91e49bc83fe8b21c3ba5f1eb2d5d06456eae84/osu!/GameModes/Play/Rulesets/Ruleset.cs#L804-L806
|
||||
this.MoveToOffset(new Vector2(0, -10), 300, Easing.Out)
|
||||
.Then()
|
||||
.FadeOut(60);
|
||||
}
|
||||
|
||||
public Drawable GetAboveHitObjectsProxiedContent() => CreateProxy();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@@ -115,6 +117,39 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
return null;
|
||||
|
||||
case SkinComponentLookup<HitResult> resultComponent:
|
||||
switch (resultComponent.Component)
|
||||
{
|
||||
case HitResult.LargeTickHit:
|
||||
case HitResult.SliderTailHit:
|
||||
if (getSliderPointTexture(resultComponent.Component) is Texture texture)
|
||||
return new LegacyJudgementPieceSliderTickHit { Texture = texture };
|
||||
|
||||
break;
|
||||
|
||||
// If the corresponding hit result displays a judgement and the miss texture isn't provided by this skin, don't look up the miss texture from any further skins.
|
||||
case HitResult.LargeTickMiss:
|
||||
case HitResult.IgnoreMiss:
|
||||
if (getSliderPointTexture(resultComponent.Component == HitResult.LargeTickMiss
|
||||
? HitResult.LargeTickHit
|
||||
: HitResult.SliderTailHit) != null)
|
||||
return base.GetDrawableComponent(lookup) ?? Drawable.Empty();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
Texture? getSliderPointTexture(HitResult result)
|
||||
{
|
||||
// https://github.com/peppy/osu-stable-reference/blob/0e91e49bc83fe8b21c3ba5f1eb2d5d06456eae84/osu!/GameModes/Play/Rulesets/Ruleset.cs#L799
|
||||
if (GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value < 2m)
|
||||
// Note that osu!stable used sliderpoint30 for heads and repeats, and sliderpoint10 for ticks, but the mapping is intentionally changed here so that each texture represents one type of HitResult.
|
||||
return GetTexture(result == HitResult.LargeTickHit ? "sliderpoint30" : "sliderpoint10");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
case OsuSkinComponentLookup osuComponent:
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
|
||||
@@ -232,10 +232,47 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
if (pointGrid.Content.Count == 0)
|
||||
return;
|
||||
|
||||
double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point.
|
||||
double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point.
|
||||
Vector2 relativePosition = FindRelativeHitPosition(start, end, hitPoint, radius, rotation);
|
||||
|
||||
var localCentre = new Vector2(points_per_dimension - 1) / 2;
|
||||
float localRadius = localCentre.X * inner_portion;
|
||||
var localPoint = localCentre + localRadius * relativePosition;
|
||||
|
||||
// Find the most relevant hit point.
|
||||
int r = (int)Math.Round(localPoint.Y);
|
||||
int c = (int)Math.Round(localPoint.X);
|
||||
|
||||
if (r < 0 || r >= points_per_dimension || c < 0 || c >= points_per_dimension)
|
||||
return;
|
||||
|
||||
PeakValue = Math.Max(PeakValue, ((GridPoint)pointGrid.Content[r][c]).Increment());
|
||||
|
||||
bufferedGrid.ForceRedraw();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalises the position of a hit on a circle such that it is relative to the movement that was performed to arrive at said circle.
|
||||
/// </summary>
|
||||
/// <param name="previousObjectPosition">The position of the object prior to the one getting hit.</param>
|
||||
/// <param name="nextObjectPosition">The position of the object which is getting hit.</param>
|
||||
/// <param name="hitPoint">The point at which the user hit.</param>
|
||||
/// <param name="objectRadius">The radius of <paramref name="previousObjectPosition"/> and <paramref name="nextObjectPosition"/>.</param>
|
||||
/// <param name="rotation">
|
||||
/// The rotation of the axis which is to be considered in the same direction as the vector
|
||||
/// leading from <paramref name="previousObjectPosition"/> to <paramref name="nextObjectPosition"/>.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A 2D vector representing the <paramref name="hitPoint"/> as relative to the movement between <paramref name="previousObjectPosition"/> and <paramref name="nextObjectPosition"/>
|
||||
/// and relative to the <paramref name="objectRadius"/>.
|
||||
/// If the object was hit perfectly in the middle, the return value will be <see cref="Vector2.Zero"/>.
|
||||
/// If the object was hit perfectly at its edge, the returned vector will have a magnitude of 1.
|
||||
/// </returns>
|
||||
public static Vector2 FindRelativeHitPosition(Vector2 previousObjectPosition, Vector2 nextObjectPosition, Vector2 hitPoint, float objectRadius, float rotation)
|
||||
{
|
||||
double angle1 = Math.Atan2(nextObjectPosition.Y - hitPoint.Y, hitPoint.X - nextObjectPosition.X); // Angle between the end point and the hit point.
|
||||
double angle2 = Math.Atan2(nextObjectPosition.Y - previousObjectPosition.Y, previousObjectPosition.X - nextObjectPosition.X); // Angle between the end point and the start point.
|
||||
double finalAngle = angle2 - angle1; // Angle between start, end, and hit points.
|
||||
float normalisedDistance = Vector2.Distance(hitPoint, end) / radius;
|
||||
float normalisedDistance = Vector2.Distance(hitPoint, nextObjectPosition) / objectRadius; // Distance between the hit point and the end point.
|
||||
|
||||
// Consider two objects placed horizontally, with the start on the left and the end on the right.
|
||||
// The above calculated the angle between {end, start}, and the angle between {end, hitPoint}, in the form:
|
||||
@@ -254,22 +291,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
//
|
||||
// We also need to apply the anti-clockwise rotation.
|
||||
double rotatedAngle = finalAngle - float.DegreesToRadians(rotation);
|
||||
var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle));
|
||||
|
||||
Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2;
|
||||
float localRadius = localCentre.X * inner_portion * normalisedDistance;
|
||||
Vector2 localPoint = localCentre + localRadius * rotatedCoordinate;
|
||||
|
||||
// Find the most relevant hit point.
|
||||
int r = (int)Math.Round(localPoint.Y);
|
||||
int c = (int)Math.Round(localPoint.X);
|
||||
|
||||
if (r < 0 || r >= points_per_dimension || c < 0 || c >= points_per_dimension)
|
||||
return;
|
||||
|
||||
PeakValue = Math.Max(PeakValue, ((GridPoint)pointGrid.Content[r][c]).Increment());
|
||||
|
||||
bufferedGrid.ForceRedraw();
|
||||
return -normalisedDistance * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle));
|
||||
}
|
||||
|
||||
private abstract partial class GridPoint : CompositeDrawable
|
||||
|
||||
@@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
HitResult.Ok,
|
||||
HitResult.Meh,
|
||||
HitResult.Miss,
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.SliderTailHit,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.IgnoreMiss,
|
||||
}, onJudgementLoaded));
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK;
|
||||
@@ -12,6 +15,34 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneTaikoModFlashlight : TaikoModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestAspectRatios([Values] bool withClassicMod)
|
||||
{
|
||||
if (withClassicMod)
|
||||
CreateModTest(new ModTestData { Mods = new Mod[] { new TaikoModFlashlight(), new TaikoModClassic() }, PassCondition = () => true });
|
||||
else
|
||||
CreateModTest(new ModTestData { Mod = new TaikoModFlashlight(), PassCondition = () => true });
|
||||
|
||||
AddStep("clear dim", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0.0));
|
||||
|
||||
AddStep("reset", () => Stack.FillMode = FillMode.Stretch);
|
||||
AddStep("set to 16:9", () =>
|
||||
{
|
||||
Stack.FillAspectRatio = 16 / 9f;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
AddStep("set to 4:3", () =>
|
||||
{
|
||||
Stack.FillAspectRatio = 4 / 3f;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
AddSliderStep("aspect ratio", 0.01f, 5f, 1f, v =>
|
||||
{
|
||||
Stack.FillAspectRatio = v;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.25f)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -47,28 +47,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
this.taikoPlayfield = taikoPlayfield;
|
||||
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSize = new Vector2(0, GetSize());
|
||||
FlashlightSmoothness = 1.4f;
|
||||
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the aspect ratio-adjusted size of the flashlight.
|
||||
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
|
||||
/// </summary>
|
||||
/// <param name="size">
|
||||
/// The size of the flashlight.
|
||||
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
|
||||
/// </param>
|
||||
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
|
||||
{
|
||||
return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
|
||||
}
|
||||
|
||||
protected override void UpdateFlashlightSize(float size)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
@@ -82,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
ClearTransforms(targetMember: nameof(FlashlightSize));
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSize = new Vector2(0, GetSize());
|
||||
|
||||
flashlightProperties.Validate();
|
||||
}
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -117,6 +117,52 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapAudioTracksExemptedFromCheck()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
|
||||
{
|
||||
var beatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Files =
|
||||
{
|
||||
CheckTestHelpers.CreateMockFile("wav"),
|
||||
CheckTestHelpers.CreateMockFile("mp3")
|
||||
}
|
||||
};
|
||||
|
||||
var firstPlayable = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BeatmapSet = beatmapSet,
|
||||
Metadata = new BeatmapMetadata { AudioFile = beatmapSet.Files[0].Filename }
|
||||
}
|
||||
};
|
||||
var firstWorking = new Mock<TestWorkingBeatmap>(firstPlayable, null, null);
|
||||
firstWorking.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
var secondPlayable = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BeatmapSet = beatmapSet,
|
||||
Metadata = new BeatmapMetadata { AudioFile = beatmapSet.Files[1].Filename }
|
||||
}
|
||||
};
|
||||
var secondWorking = new Mock<TestWorkingBeatmap>(secondPlayable, null, null);
|
||||
secondWorking.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
var context = new BeatmapVerifierContext(
|
||||
new BeatmapVerifierContext.VerifiedBeatmap(firstWorking.Object, firstPlayable),
|
||||
[new BeatmapVerifierContext.VerifiedBeatmap(secondWorking.Object, secondPlayable)],
|
||||
DifficultyRating.ExpertPlus);
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
Assert.That(issues, Is.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
|
||||
151
osu.Game.Tests/Editing/Checks/CheckInconsistentAudioTest.cs
Normal file
151
osu.Game.Tests/Editing/Checks/CheckInconsistentAudioTest.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckInconsistentAudioTest
|
||||
{
|
||||
private CheckInconsistentAudio check = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckInconsistentAudio();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConsistentAudio()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("audio.mp3", "audio.mp3");
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInconsistentAudio()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("audio1.mp3", "audio2.mp3");
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckInconsistentAudio.IssueTemplateInconsistentAudio);
|
||||
Assert.That(issues.Single().ToString(), Contains.Substring("audio1.mp3"));
|
||||
Assert.That(issues.Single().ToString(), Contains.Substring("audio2.mp3"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInconsistentAudioWithNull()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("audio.mp3", null);
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckInconsistentAudio.IssueTemplateInconsistentAudio);
|
||||
Assert.That(issues.Single().ToString(), Contains.Substring("audio.mp3"));
|
||||
Assert.That(issues.Single().ToString(), Contains.Substring("not set"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInconsistentAudioWithEmptyString()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("audio.mp3", "");
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckInconsistentAudio.IssueTemplateInconsistentAudio);
|
||||
Assert.That(issues.Single().ToString(), Contains.Substring("audio.mp3"));
|
||||
Assert.That(issues.Single().ToString(), Contains.Substring("not set"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBothAudioNotSet()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("", "");
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleInconsistencies()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("audio1.mp3", "audio2.mp3", "audio3.mp3");
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckInconsistentAudio.IssueTemplateInconsistentAudio));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleDifficulty()
|
||||
{
|
||||
var beatmaps = createBeatmapSetWithAudio("audio.mp3");
|
||||
var context = createContextWithMultipleDifficulties(beatmaps.First(), beatmaps);
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
private IBeatmap createBeatmapWithAudio(string audioFile, RealmNamedFileUsage? file)
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata { AudioFile = audioFile },
|
||||
BeatmapSet = new BeatmapSetInfo()
|
||||
}
|
||||
};
|
||||
|
||||
if (file != null)
|
||||
beatmap.BeatmapInfo.BeatmapSet!.Files.Add(file);
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private IBeatmap[] createBeatmapSetWithAudio(params string?[] audioFiles)
|
||||
{
|
||||
var beatmapSet = new BeatmapSetInfo();
|
||||
var beatmaps = new IBeatmap[audioFiles.Length];
|
||||
|
||||
for (int i = 0; i < audioFiles.Length; i++)
|
||||
{
|
||||
string? audioFile = audioFiles[i];
|
||||
var file = !string.IsNullOrEmpty(audioFile) ? CheckTestHelpers.CreateMockFile("mp3") : null;
|
||||
|
||||
beatmaps[i] = createBeatmapWithAudio(audioFile ?? "", file);
|
||||
beatmaps[i].BeatmapInfo.BeatmapSet = beatmapSet;
|
||||
beatmaps[i].BeatmapInfo.DifficultyName = $"Difficulty {i + 1}";
|
||||
beatmapSet.Beatmaps.Add(beatmaps[i].BeatmapInfo);
|
||||
}
|
||||
|
||||
return beatmaps;
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext createContextWithMultipleDifficulties(IBeatmap currentBeatmap, IBeatmap[] allDifficulties)
|
||||
{
|
||||
var verifiedCurrentBeatmap = new BeatmapVerifierContext.VerifiedBeatmap(new TestWorkingBeatmap(currentBeatmap), currentBeatmap);
|
||||
var verifiedOtherBeatmaps = allDifficulties.Select(b => new BeatmapVerifierContext.VerifiedBeatmap(new TestWorkingBeatmap(b), b)).ToList();
|
||||
|
||||
return new BeatmapVerifierContext(verifiedCurrentBeatmap, verifiedOtherBeatmaps, DifficultyRating.ExpertPlus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -24,6 +24,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
protected TestReplayPlayer Player = null!;
|
||||
|
||||
[Test]
|
||||
public void TestFailedBeatmapLoad()
|
||||
{
|
||||
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo, withHitObjects: false));
|
||||
|
||||
AddUntilStep("wait for exit", () => Player.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseViaSpace()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -92,7 +96,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
||||
SetDefault(OsuSetting.PreferNoVideo, false);
|
||||
|
||||
SetDefault(OsuSetting.BackgroundCategory, "Default");
|
||||
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
|
||||
|
||||
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
|
||||
@@ -193,7 +197,8 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles);
|
||||
|
||||
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
|
||||
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
|
||||
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never);
|
||||
SetDefault(OsuSetting.UseSeasonalBackgroundsV2, true);
|
||||
|
||||
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
|
||||
|
||||
@@ -405,6 +410,7 @@ namespace osu.Game.Configuration
|
||||
ChatDisplayHeight,
|
||||
BeatmapListingCardSize,
|
||||
ToolbarClockDisplayMode,
|
||||
ForceLegacySongSelect,
|
||||
SongSelectBackgroundBlur,
|
||||
Version,
|
||||
ShowFirstRunSetup,
|
||||
@@ -412,6 +418,7 @@ namespace osu.Game.Configuration
|
||||
Skin,
|
||||
ScreenshotFormat,
|
||||
ScreenshotCaptureMenuCursor,
|
||||
MenuCookieColor,
|
||||
BeatmapSkins,
|
||||
BeatmapColours,
|
||||
BeatmapHitsounds,
|
||||
@@ -436,6 +443,8 @@ namespace osu.Game.Configuration
|
||||
MenuBackgroundSource,
|
||||
GameplayDisableWinKey,
|
||||
SeasonalBackgroundMode,
|
||||
UseSeasonalBackgroundsV2, // TODO: add migrations
|
||||
BackgroundCategory,
|
||||
EditorWaveformOpacity,
|
||||
EditorShowHitMarkers,
|
||||
EditorAutoSeekOnPlacement,
|
||||
|
||||
@@ -14,12 +14,6 @@ namespace osu.Game.Configuration
|
||||
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.AlwaysSeasonalBackground))]
|
||||
Always,
|
||||
|
||||
/// <summary>
|
||||
/// Seasonal backgrounds are shown only during their corresponding season.
|
||||
/// </summary>
|
||||
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SometimesSeasonalBackground))]
|
||||
Sometimes,
|
||||
|
||||
/// <summary>
|
||||
/// Seasonal backgrounds are never shown.
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -725,7 +727,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(@$"Failed to update ranked/submitted dates for beatmap set {id}: {e}");
|
||||
Logger.Log(@$"Failed to update user tags for beatmap {id}: {e}");
|
||||
++failedCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,10 +204,11 @@ namespace osu.Game.Database
|
||||
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
||||
Filename += realm_extension;
|
||||
|
||||
#if DEBUG
|
||||
// TODO: fix
|
||||
// #if DEBUG
|
||||
if (!DebugUtils.IsNUnitRunning)
|
||||
applyFilenameSchemaSuffix(ref Filename);
|
||||
#endif
|
||||
// #endif
|
||||
|
||||
// `prepareFirstRealmAccess()` triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date.
|
||||
using (var realm = prepareFirstRealmAccess())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -19,79 +20,121 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
public partial class SeasonalBackgroundLoader : Component
|
||||
{
|
||||
public event Action<Exception> OnLoadFailure;
|
||||
public event Action BackgroundChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when background should be changed due to receiving backgrounds from API
|
||||
/// or when the user setting is changed (as it might require unloading the seasonal background).
|
||||
/// Fired when categories have been successfully refreshed from the server.
|
||||
/// </summary>
|
||||
public event Action SeasonalBackgroundChanged;
|
||||
public event Action OnCategoriesRefreshed;
|
||||
|
||||
public readonly Bindable<IEnumerable<string>> AvailableCategories = new Bindable<IEnumerable<string>>(new List<string>());
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private Bindable<SeasonalBackgroundMode> seasonalBackgroundMode;
|
||||
private Bindable<APISeasonalBackgrounds> seasonalBackgrounds;
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
private Bindable<string> selectedCategory;
|
||||
private Bindable<APISeasonalBackgrounds> currentBackgrounds;
|
||||
|
||||
private int current;
|
||||
private int currentBackgroundIndex;
|
||||
|
||||
private bool shouldShowCustomBackgrounds => useSeasonalBackgrounds.Value;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, SessionStatics sessionStatics)
|
||||
{
|
||||
seasonalBackgroundMode = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
|
||||
seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke());
|
||||
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
|
||||
useSeasonalBackgrounds.BindValueChanged(_ => BackgroundChanged?.Invoke());
|
||||
|
||||
seasonalBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
|
||||
seasonalBackgrounds.BindValueChanged(_ =>
|
||||
{
|
||||
if (shouldShowSeasonal)
|
||||
SeasonalBackgroundChanged?.Invoke();
|
||||
});
|
||||
selectedCategory = config.GetBindable<string>(OsuSetting.BackgroundCategory);
|
||||
selectedCategory.BindValueChanged(_ => fetchBackgroundsForSelectedCategory());
|
||||
|
||||
fetchSeasonalBackgrounds();
|
||||
currentBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
|
||||
|
||||
if (shouldShowCustomBackgrounds)
|
||||
fetchCategories(true);
|
||||
}
|
||||
|
||||
private void fetchSeasonalBackgrounds()
|
||||
/// <summary>
|
||||
/// Public method to trigger a refresh of categories from the UI.
|
||||
/// </summary>
|
||||
public void RefreshCategories(bool ignoreSuccess = false)
|
||||
{
|
||||
if (seasonalBackgrounds.Value != null)
|
||||
return;
|
||||
fetchCategories(ignoreSuccess);
|
||||
}
|
||||
|
||||
private void fetchCategories(bool ignoreSuccess = false)
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds) return;
|
||||
|
||||
var request = new GetBackgroundCategoriesRequest();
|
||||
|
||||
var request = new GetSeasonalBackgroundsRequest();
|
||||
request.Success += response =>
|
||||
{
|
||||
seasonalBackgrounds.Value = response;
|
||||
current = RNG.Next(0, response.Backgrounds?.Count ?? 0);
|
||||
var serverCategories = response.Categories ?? Enumerable.Empty<string>();
|
||||
|
||||
AvailableCategories.Value = serverCategories.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
if (!AvailableCategories.Value.Any())
|
||||
{
|
||||
selectedCategory.Value = "";
|
||||
return; // we don't have any categories!!!
|
||||
}
|
||||
|
||||
if (!AvailableCategories.Value.Contains(selectedCategory.Value))
|
||||
selectedCategory.Value = AvailableCategories.Value.Contains("Default")
|
||||
? "Default"
|
||||
: AvailableCategories.Value.ElementAt(0);
|
||||
|
||||
fetchBackgroundsForSelectedCategory();
|
||||
|
||||
if (!ignoreSuccess)
|
||||
OnCategoriesRefreshed?.Invoke();
|
||||
};
|
||||
|
||||
request.Failure += exception =>
|
||||
{
|
||||
AvailableCategories.Value = Array.Empty<string>();
|
||||
OnLoadFailure?.Invoke(exception);
|
||||
};
|
||||
|
||||
api.PerformAsync(request);
|
||||
}
|
||||
|
||||
public SeasonalBackground LoadNextBackground()
|
||||
private void fetchBackgroundsForSelectedCategory()
|
||||
{
|
||||
if (!shouldShowSeasonal)
|
||||
if (!shouldShowCustomBackgrounds) return;
|
||||
|
||||
string categoryToFetch = selectedCategory.Value == "Default" ? null : selectedCategory.Value;
|
||||
var request = new GetSeasonalBackgroundsRequest(categoryToFetch);
|
||||
|
||||
request.Success += response =>
|
||||
{
|
||||
currentBackgrounds.Value = response;
|
||||
currentBackgroundIndex = RNG.Next(0, response.Backgrounds?.Count ?? 0);
|
||||
BackgroundChanged?.Invoke();
|
||||
};
|
||||
|
||||
request.Failure += exception =>
|
||||
{
|
||||
OnLoadFailure?.Invoke(exception);
|
||||
};
|
||||
|
||||
api.PerformAsync(request);
|
||||
}
|
||||
|
||||
public Background LoadNextBackground()
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds || currentBackgrounds.Value?.Backgrounds?.Any() != true)
|
||||
return null;
|
||||
|
||||
var backgrounds = seasonalBackgrounds.Value.Backgrounds;
|
||||
|
||||
current = (current + 1) % backgrounds.Count;
|
||||
string url = backgrounds[current].Url;
|
||||
var backgrounds = currentBackgrounds.Value.Backgrounds;
|
||||
currentBackgroundIndex = (currentBackgroundIndex + 1) % backgrounds.Count;
|
||||
string url = backgrounds[currentBackgroundIndex].Url;
|
||||
|
||||
return new SeasonalBackground(url);
|
||||
}
|
||||
|
||||
private bool shouldShowSeasonal
|
||||
{
|
||||
get
|
||||
{
|
||||
if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never)
|
||||
return false;
|
||||
|
||||
if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason)
|
||||
return false;
|
||||
|
||||
return seasonalBackgrounds.Value?.Backgrounds?.Any() == true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate;
|
||||
}
|
||||
|
||||
[LongRunningLoad]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,6 +78,20 @@ Your experience will not be perfect, and may even feel subpar compared to games
|
||||
|
||||
Please bear with us as we continue to improve the game for you!");
|
||||
|
||||
/// <summary>
|
||||
/// "Welcome to jvnkosu!lazer!"
|
||||
/// </summary>
|
||||
public static LocalisableString GreetingNotification => new TranslatableString(getKey(@"greeting_notification"), @"Welcome to jvnkosu!lazer!");
|
||||
|
||||
/// <summary>
|
||||
/// "Failed to load backgrounds!\nCheck your internet connection"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsFail => new TranslatableString(getKey(@"seasonal_backgrounds_fail"), @"Failed to load backgrounds!\nCheck your internet connection"); // TODO: implement l10n in osu-resources
|
||||
|
||||
/// <summary>
|
||||
/// "Successfully refreshed background categories!"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsRefreshed => new TranslatableString(getKey(@"seasonal_backgrounds_refreshed"), @"Successfully refreshed background categories!");
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
74
osu.Game/Localisation/HUD/AimErrorMeterStrings.cs
Normal file
74
osu.Game/Localisation/HUD/AimErrorMeterStrings.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// 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.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.HUD
|
||||
{
|
||||
public static class AimErrorMeterStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.HUD.AimErrorMeterStrings";
|
||||
|
||||
/// <summary>
|
||||
/// "Hit marker size"
|
||||
/// </summary>
|
||||
public static LocalisableString HitMarkerSize => new TranslatableString(getKey(@"hit_marker_size"), @"Hit marker size");
|
||||
|
||||
/// <summary>
|
||||
/// "Controls the size of the markers displayed after every hit."
|
||||
/// </summary>
|
||||
public static LocalisableString HitMarkerSizeDescription => new TranslatableString(getKey(@"hit_marker_size_description"), @"Controls the size of the markers displayed after every hit.");
|
||||
|
||||
/// <summary>
|
||||
/// "Hit marker style"
|
||||
/// </summary>
|
||||
public static LocalisableString HitMarkerStyle => new TranslatableString(getKey(@"hit_marker_style"), @"Hit marker style");
|
||||
|
||||
/// <summary>
|
||||
/// "The visual style of the hit markers."
|
||||
/// </summary>
|
||||
public static LocalisableString HitMarkerStyleDescription => new TranslatableString(getKey(@"hit_marker_style_description"), @"The visual style of the hit markers.");
|
||||
|
||||
/// <summary>
|
||||
/// "Average position marker size"
|
||||
/// </summary>
|
||||
public static LocalisableString AverageMarkerSize => new TranslatableString(getKey(@"average_marker_size"), @"Average position marker size");
|
||||
|
||||
/// <summary>
|
||||
/// "Controls the size of the marker showing average hit position."
|
||||
/// </summary>
|
||||
public static LocalisableString AverageMarkerSizeDescription => new TranslatableString(getKey(@"average_marker_size_description"), @"Controls the size of the marker showing average hit position.");
|
||||
|
||||
/// <summary>
|
||||
/// "Average position marker style"
|
||||
/// </summary>
|
||||
public static LocalisableString AverageMarkerStyle => new TranslatableString(getKey(@"average_marker_style"), @"Average position marker style");
|
||||
|
||||
/// <summary>
|
||||
/// "The visual style of the average position marker."
|
||||
/// </summary>
|
||||
public static LocalisableString AverageMarkerStyleDescription => new TranslatableString(getKey(@"average_marker_style_description"), @"The visual style of the average position marker.");
|
||||
|
||||
/// <summary>
|
||||
/// "Position display style"
|
||||
/// </summary>
|
||||
public static LocalisableString PositionDisplayStyle => new TranslatableString(getKey(@"position_style"), @"Position display style");
|
||||
|
||||
/// <summary>
|
||||
/// "Controls whether positions displayed on the meter are absolute (as seen on screen) or normalised (relative to the direction of movement from previous object)."
|
||||
/// </summary>
|
||||
public static LocalisableString PositionDisplayStyleDescription => new TranslatableString(getKey(@"position_style_description"), @"Controls whether positions displayed on the meter are absolute (as seen on screen) or normalised (relative to the direction of movement from previous object).");
|
||||
|
||||
/// <summary>
|
||||
/// "Absolute"
|
||||
/// </summary>
|
||||
public static LocalisableString Absolute => new TranslatableString(getKey(@"absolute"), @"Absolute");
|
||||
|
||||
/// <summary>
|
||||
/// "Normalised"
|
||||
/// </summary>
|
||||
public static LocalisableString Normalised => new TranslatableString(getKey(@"normalised"), @"Normalised");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,26 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
|
||||
|
||||
/// <summary>
|
||||
/// "Use custom backgrounds from server"
|
||||
/// </summary>
|
||||
public static LocalisableString UseSeasonalBackgrounds => new TranslatableString(getKey(@"use_seasonal_backgrounds"), @"Use custom backgrounds from server");
|
||||
|
||||
/// <summary>
|
||||
/// "Seasonal backgrounds"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
|
||||
|
||||
/// <summary>
|
||||
/// "Background categories"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsCategories => new TranslatableString(getKey(@"seasonal_backgrounds_categories"), @"Background categories");
|
||||
|
||||
/// <summary>
|
||||
/// "Refresh categories"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsRefresh => new TranslatableString(getKey(@"seasonal_backgrounds_refresh"), @"Refresh categories");
|
||||
|
||||
/// <summary>
|
||||
/// "Changes to this setting will only apply with an active osu!supporter tag."
|
||||
/// </summary>
|
||||
@@ -169,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}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetBackgroundCategoriesRequest : APIRequest<APIBackgroundCategories>
|
||||
{
|
||||
protected override string Target => @"seasonal-backgrounds-categories";
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests
|
||||
public class GetMenuContentRequest : OsuJsonWebRequest<APIMenuContent>
|
||||
{
|
||||
public GetMenuContentRequest()
|
||||
: base(@"https://assets.ppy.sh/menu-content.json")
|
||||
: base(@"https://osu.jvnko.boats/uploads/menu-content.json") // TODO: backend
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
// 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.
|
||||
#nullable enable
|
||||
|
||||
using System.Net;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetSeasonalBackgroundsRequest : APIRequest<APISeasonalBackgrounds>
|
||||
{
|
||||
protected override string Target => @"seasonal-backgrounds";
|
||||
private readonly string? category;
|
||||
|
||||
public GetSeasonalBackgroundsRequest()
|
||||
{
|
||||
}
|
||||
|
||||
public GetSeasonalBackgroundsRequest(string? category)
|
||||
{
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
protected override string Target => getPath();
|
||||
|
||||
private string getPath()
|
||||
{
|
||||
if (string.IsNullOrEmpty(category))
|
||||
return @"seasonal-backgrounds";
|
||||
|
||||
return $@"seasonal-backgrounds?category={WebUtility.UrlEncode(category)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APIBackgroundCategories
|
||||
{
|
||||
[JsonProperty("categories")]
|
||||
public List<string>? Categories { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ namespace osu.Game.Online
|
||||
{
|
||||
public DevelopmentEndpointConfiguration()
|
||||
{
|
||||
WebsiteUrl = APIUrl = @"https://dev.ppy.sh";
|
||||
APIClientSecret = @"3LP2mhUrV89xxzD1YKNndXHEhWWCRLPNKioZ9ymT";
|
||||
APIClientID = "5";
|
||||
SpectatorUrl = $@"{APIUrl}/signalr/spectator";
|
||||
MultiplayerUrl = $@"{APIUrl}/signalr/multiplayer";
|
||||
MetadataUrl = $@"{APIUrl}/signalr/metadata";
|
||||
BeatmapSubmissionServiceUrl = $@"{APIUrl}/beatmap-submission";
|
||||
WebsiteUrl = APIUrl = @"https://osu.jvnko.boats";
|
||||
APIClientSecret = @"ijBg9O6aULCYGnvEELYD3IdW7fqrYiFaoMdkzQNA";
|
||||
APIClientID = "1";
|
||||
SpectatorUrl = $@"https://osu-spec.jvnko.boats/spectator";
|
||||
MultiplayerUrl = $@"https://osu-spec.jvnko.boats/multiplayer";
|
||||
MetadataUrl = $@"https://osu-spec.jvnko.boats/metadata";
|
||||
BeatmapSubmissionServiceUrl = $@"https://osu-bss.jvnko.boats";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace osu.Game.Online
|
||||
{
|
||||
public ProductionEndpointConfiguration()
|
||||
{
|
||||
WebsiteUrl = APIUrl = @"https://osu.ppy.sh";
|
||||
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
APIClientID = "5";
|
||||
SpectatorUrl = "https://spectator.ppy.sh/spectator";
|
||||
MultiplayerUrl = "https://spectator.ppy.sh/multiplayer";
|
||||
MetadataUrl = "https://spectator.ppy.sh/metadata";
|
||||
BeatmapSubmissionServiceUrl = "https://bss.ppy.sh";
|
||||
WebsiteUrl = APIUrl = @"https://osu.jvnko.boats";
|
||||
APIClientSecret = @"ijBg9O6aULCYGnvEELYD3IdW7fqrYiFaoMdkzQNA";
|
||||
APIClientID = "1";
|
||||
SpectatorUrl = $@"https://osu-spec.jvnko.boats/spectator";
|
||||
MultiplayerUrl = $@"https://osu-spec.jvnko.boats/multiplayer";
|
||||
MetadataUrl = $@"https://osu-spec.jvnko.boats/metadata";
|
||||
BeatmapSubmissionServiceUrl = $@"https://osu-bss.jvnko.boats";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace osu.Game.Online
|
||||
{
|
||||
protected override string GetLookupUrl(string url)
|
||||
{
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase))
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".jvnko.boats", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important);
|
||||
return string.Empty;
|
||||
|
||||
@@ -38,6 +38,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input;
|
||||
@@ -92,9 +93,9 @@ namespace osu.Game
|
||||
{
|
||||
#if DEBUG
|
||||
// Different port allows running release and debug builds alongside each other.
|
||||
public const string IPC_PIPE_NAME = "osu-lazer-debug";
|
||||
public const string IPC_PIPE_NAME = "jvnkosu-debug";
|
||||
#else
|
||||
public const string IPC_PIPE_NAME = "osu-lazer";
|
||||
public const string IPC_PIPE_NAME = "jvnkosu";
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
@@ -169,6 +170,9 @@ namespace osu.Game
|
||||
[Cached]
|
||||
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
||||
|
||||
[Cached]
|
||||
private SeasonalBackgroundLoader seasonalBackgroundLoader;
|
||||
|
||||
protected SentryLogger SentryLogger;
|
||||
|
||||
public virtual StableStorage GetStorageForStableInstall() => null;
|
||||
@@ -263,6 +267,8 @@ namespace osu.Game
|
||||
tabletLogNotifyOnError = true;
|
||||
}, true);
|
||||
});
|
||||
|
||||
initializeSeasonalBackgrounds();
|
||||
}
|
||||
|
||||
#region IOverlayManager
|
||||
@@ -927,7 +933,7 @@ namespace osu.Game
|
||||
|
||||
protected virtual Loader CreateLoader() => new Loader();
|
||||
|
||||
protected virtual UpdateManager CreateUpdateManager() => new UpdateManager();
|
||||
protected virtual UpdateManager CreateUpdateManager() => new NoActionUpdateManager();
|
||||
|
||||
/// <summary>
|
||||
/// Adjust the globally applied <see cref="DrawSizePreservingFillContainer.TargetDrawSize"/> in every <see cref="ScalingContainer"/>.
|
||||
@@ -1186,8 +1192,8 @@ namespace osu.Game
|
||||
Margin = new MarginPadding(5),
|
||||
}, topMostOverlayContent.Add);
|
||||
|
||||
if (!IsDeployedBuild)
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
// if (!IsDeployedBuild) // we're going to have the "developer build" banner for a while
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
|
||||
loadComponentSingleFile(osuLogo, _ =>
|
||||
{
|
||||
@@ -1232,6 +1238,8 @@ namespace osu.Game
|
||||
|
||||
loadComponentSingleFile(screenshotManager, Add);
|
||||
|
||||
loadComponentSingleFile(seasonalBackgroundLoader, Add);
|
||||
|
||||
// dependency on notification overlay, dependent by settings overlay
|
||||
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
||||
|
||||
@@ -1469,6 +1477,40 @@ namespace osu.Game
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeSeasonalBackgrounds()
|
||||
{
|
||||
seasonalBackgroundLoader = new SeasonalBackgroundLoader();
|
||||
seasonalBackgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
|
||||
seasonalBackgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
|
||||
}
|
||||
|
||||
private void handleCategoriesRefreshed()
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsRefreshed,
|
||||
Icon = FontAwesome.Solid.CheckCircle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleBackgroundLoadFailure(Exception exception)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsFail,
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private Task asyncLoadStream;
|
||||
|
||||
/// <summary>
|
||||
@@ -1701,6 +1743,12 @@ namespace osu.Game
|
||||
{
|
||||
case IntroScreen intro:
|
||||
introScreen = intro;
|
||||
SimpleNotification notification = new SimpleNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.GreetingNotification,
|
||||
Transient = true,
|
||||
};
|
||||
Notifications?.Post(notification);
|
||||
devBuildBanner?.Show();
|
||||
break;
|
||||
|
||||
|
||||
@@ -76,12 +76,12 @@ namespace osu.Game
|
||||
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider
|
||||
{
|
||||
#if DEBUG
|
||||
public const string GAME_NAME = "osu! (development)";
|
||||
public const string GAME_NAME = "jvnkosu! (development)";
|
||||
#else
|
||||
public const string GAME_NAME = "osu!";
|
||||
public const string GAME_NAME = "jvnkosu!";
|
||||
#endif
|
||||
|
||||
public const string OSU_PROTOCOL = "osu://";
|
||||
public const string OSU_PROTOCOL = "jnvkosu://";
|
||||
|
||||
/// <summary>
|
||||
/// The filename of the main client database.
|
||||
|
||||
@@ -30,9 +30,18 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12),
|
||||
Colour = colours.YellowDark,
|
||||
Text = @"DEVELOPER BUILD",
|
||||
Font = OsuFont.Torus.With(size: 12),
|
||||
Colour = colours.GrayF,
|
||||
Text = $@"jvnkosu! " + game.Version,
|
||||
Y = -12,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 15),
|
||||
Colour = colours.Yellow,
|
||||
Text = "Experimental version",
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@@ -18,21 +19,55 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
protected override LocalisableString Header => UserInterfaceStrings.MainMenuHeader;
|
||||
|
||||
[Resolved]
|
||||
private SeasonalBackgroundLoader backgroundLoader { get; set; }
|
||||
|
||||
private IBindable<APIUser> user;
|
||||
|
||||
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
|
||||
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, IAPIProvider api)
|
||||
{
|
||||
user = api.LocalUser.GetBoundCopy();
|
||||
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
|
||||
|
||||
var backgroundToggle = new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
|
||||
Current = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2),
|
||||
ClassicDefault = true
|
||||
};
|
||||
|
||||
var categoryDropdown = new SettingsDropdown<string>
|
||||
{
|
||||
LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories,
|
||||
Current = config.GetBindable<string>(OsuSetting.BackgroundCategory)
|
||||
};
|
||||
|
||||
var refreshButton = new SettingsButton
|
||||
{
|
||||
Text = UserInterfaceStrings.SeasonalBackgroundsRefresh,
|
||||
Action = () => backgroundLoader.RefreshCategories()
|
||||
};
|
||||
|
||||
// TODO: the category dropdown disappear if no backgrounds (e.g. when first enabling the setting)
|
||||
refreshButton.CanBeShown.BindTo(useSeasonalBackgrounds);
|
||||
categoryDropdown.CanBeShown.BindTo(useSeasonalBackgrounds);
|
||||
useSeasonalBackgrounds.BindValueChanged(
|
||||
_ => backgroundLoader.RefreshCategories(true)
|
||||
);
|
||||
|
||||
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ShowMenuTips,
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuTips)
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuTips),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@@ -56,11 +91,15 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
LabelText = UserInterfaceStrings.BackgroundSource,
|
||||
Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
|
||||
},
|
||||
new SettingsEnumDropdown<SeasonalBackgroundMode>
|
||||
backgroundToggle,
|
||||
categoryDropdown,
|
||||
refreshButton,
|
||||
new SettingsColour
|
||||
{
|
||||
LabelText = UserInterfaceStrings.SeasonalBackgrounds,
|
||||
Current = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode),
|
||||
}
|
||||
LabelText = @"osu! logo colour",
|
||||
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
|
||||
ClassicDefault = Colour4.FromHex(@"ff66ba"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
new CheckDelayedHitsounds(),
|
||||
new CheckSongFormat(),
|
||||
new CheckHitsoundsFormat(),
|
||||
new CheckInconsistentAudio(),
|
||||
|
||||
// Files
|
||||
new CheckZeroByteFiles(),
|
||||
|
||||
@@ -7,6 +7,7 @@ using ManagedBass;
|
||||
using osu.Framework.Audio.Callbacks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
@@ -24,13 +25,22 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
var beatmapSet = context.CurrentDifficulty.Playable.BeatmapInfo.BeatmapSet;
|
||||
var audioFile = beatmapSet?.GetFile(context.CurrentDifficulty.Playable.Metadata.AudioFile);
|
||||
|
||||
if (beatmapSet == null) yield break;
|
||||
|
||||
// Collect all audio files from all difficulties to exclude them from the check, as they aren't hitsounds.
|
||||
var audioFiles = new HashSet<RealmNamedFileUsage>(ReferenceEqualityComparer.Instance);
|
||||
|
||||
foreach (var difficulty in context.AllDifficulties)
|
||||
{
|
||||
var audioFile = beatmapSet.GetFile(difficulty.Playable.Metadata.AudioFile);
|
||||
if (audioFile != null)
|
||||
audioFiles.Add(audioFile);
|
||||
}
|
||||
|
||||
foreach (var file in beatmapSet.Files)
|
||||
{
|
||||
if (audioFile != null && ReferenceEquals(file.File, audioFile.File)) continue;
|
||||
if (audioFiles.Contains(file)) continue;
|
||||
|
||||
using (Stream data = context.CurrentDifficulty.Working.GetStream(file.File.GetStoragePath()))
|
||||
{
|
||||
|
||||
53
osu.Game/Rulesets/Edit/Checks/CheckInconsistentAudio.cs
Normal file
53
osu.Game/Rulesets/Edit/Checks/CheckInconsistentAudio.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckInconsistentAudio : ICheck
|
||||
{
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Inconsistent audio files", CheckScope.BeatmapSet);
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateInconsistentAudio(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
if (context.AllDifficulties.Count() <= 1)
|
||||
yield break;
|
||||
|
||||
var referenceBeatmap = context.CurrentDifficulty.Playable;
|
||||
string referenceAudioFile = referenceBeatmap.Metadata.AudioFile;
|
||||
|
||||
foreach (var beatmap in context.OtherDifficulties)
|
||||
{
|
||||
string currentAudioFile = beatmap.Playable.Metadata.AudioFile;
|
||||
|
||||
if (referenceAudioFile != currentAudioFile)
|
||||
{
|
||||
yield return new IssueTemplateInconsistentAudio(this).Create(
|
||||
string.IsNullOrEmpty(referenceAudioFile) ? "not set" : referenceAudioFile,
|
||||
beatmap.Playable.BeatmapInfo.DifficultyName,
|
||||
string.IsNullOrEmpty(currentAudioFile) ? "not set" : currentAudioFile
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateInconsistentAudio : IssueTemplate
|
||||
{
|
||||
public IssueTemplateInconsistentAudio(ICheck check)
|
||||
: base(check, IssueType.Problem, "Inconsistent audio file between this difficulty ({0}) and \"{1}\" ({2}).")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string referenceAudio, string otherDifficulty, string otherAudio)
|
||||
=> new Issue(this, referenceAudio, otherDifficulty, otherAudio);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user