1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-19 13:07:20 +08:00

Merge remote-tracking branch 'upstream/master' into customized-mods

This commit is contained in:
Dean Herbert 2019-12-06 15:31:20 +09:00
commit 170954bc6e
95 changed files with 1465 additions and 561 deletions

View File

@ -1,5 +1,6 @@
M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.

View File

@ -2,6 +2,7 @@
<Project>
<PropertyGroup Label="C#">
<LangVersion>8.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
@ -15,7 +16,7 @@
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.7" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
</ItemGroup>
<PropertyGroup Label="Documentation">
@ -28,6 +29,7 @@
<NoWarn>$(NoWarn);NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Nuget">
<IsPackable>false</IsPackable>
<Authors>ppy Pty Ltd</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>

4
build.ps1 → InspectCode.ps1 Executable file → Normal file
View File

@ -22,6 +22,6 @@ if ($Experimental) { $cakeArguments += "-experimental" }
$cakeArguments += $ScriptArgs
dotnet tool restore
dotnet cake ./build/build.cake --bootstrap
dotnet cake ./build/build.cake $cakeArguments
dotnet cake ./build/InspectCode.cake --bootstrap
dotnet cake ./build/InspectCode.cake $cakeArguments
exit $LASTEXITCODE

View File

@ -9,11 +9,11 @@
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
## Status
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
@ -23,7 +23,8 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore30&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
## Running osu!
@ -58,44 +59,50 @@ git pull
### Building
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations.
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
- Visual Studio Code users must run the `Restore` task before any build attempt.
You can also build and run osu! from the command-line with a single command:
You can also build and run *osu!* from the command-line with a single command:
```shell
dotnet run --project osu.Desktop
```
If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
If you are not interested in debugging *osu!*, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
If the build fails, try to restore NuGet packages with `dotnet restore`.
_Due to a historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._
### Testing with resource/framework modifications
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages.
### Code analysis
Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under Windows due to [ReSharper CLI shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`, which is [only supported on Windows](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
## Contributing
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible.
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible.
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
## Licence
The osu! client code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.

View File

@ -1,6 +1,27 @@
clone_depth: 1
version: '{branch}-{build}'
image: Visual Studio 2019
test: off
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
dotnet_csproj:
patch: true
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
version: '0.0.{build}'
cache:
- '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
before_build:
- ps: dotnet --info # Useful when version mismatch between CI and local
- ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
build:
project: osu.sln
parallel: true
verbosity: minimal
publish_nuget: true
after_build:
- ps: dotnet tool restore
- ps: dotnet format --dry-run --check
- ps: .\InspectCode.ps1
test:
assemblies:
except:
- '**\*Android*'
- '**\*iOS*'
- 'build\**\*'

View File

@ -1,10 +1,21 @@
clone_depth: 1
version: '{build}'
image: Visual Studio 2019
dotnet_csproj:
patch: true
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
version: $(APPVEYOR_REPO_TAG_NAME)
before_build:
- ps: dotnet --info # Useful when version mismatch between CI and local
- ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
test: off
skip_non_tags: true
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
configuration: Release
build:
project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
parallel: true
verbosity: minimal
publish_nuget: true
deploy:
- provider: Environment
name: nuget

View File

@ -1,17 +0,0 @@
echo "Installing Cake.Tool..."
dotnet tool restore
# Parse arguments.
CAKE_ARGUMENTS=()
for i in "$@"; do
case $1 in
-s|--script) SCRIPT="$2"; shift ;;
--) shift; CAKE_ARGUMENTS+=("$@"); break ;;
*) CAKE_ARGUMENTS+=("$1") ;;
esac
shift
done
echo "Running build script..."
dotnet cake ./build/build.cake --bootstrap
dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}"

View File

@ -7,45 +7,29 @@ var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
// ARGUMENTS
///////////////////////////////////////////////////////////////////////////////
var target = Argument("target", "Build");
var target = Argument("target", "CodeAnalysis");
var configuration = Argument("configuration", "Release");
var rootDirectory = new DirectoryPath("..");
var sln = rootDirectory.CombineWithFilePath("osu.sln");
var desktopBuilds = rootDirectory.CombineWithFilePath("build/Desktop.proj");
var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
///////////////////////////////////////////////////////////////////////////////
// TASKS
///////////////////////////////////////////////////////////////////////////////
Task("Compile")
.Does(() => {
DotNetCoreBuild(desktopBuilds.FullPath, new DotNetCoreBuildSettings {
Configuration = configuration,
});
});
Task("Test")
.IsDependentOn("Compile")
.Does(() => {
var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll");
DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
Parallel = true,
ToolTimeout = TimeSpan.FromMinutes(10),
});
});
// windows only because both inspectcore and nvika depend on net45
// windows only because both inspectcode and nvika depend on net45
Task("InspectCode")
.WithCriteria(IsRunningOnWindows())
.IsDependentOn("Compile")
.Does(() => {
InspectCode(desktopSlnf, new InspectCodeSettings {
CachesHome = "inspectcode",
OutputFile = "inspectcodereport.xml",
ArgumentCustomization = arg => {
if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output
arg.Append("--verbosity:WARN");
return arg;
},
});
int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
@ -61,13 +45,8 @@ Task("CodeFileSanity")
});
});
Task("DotnetFormat")
.Does(() => DotNetCoreTool(sln.FullPath, "format", "--dry-run --check"));
Task("Build")
Task("CodeAnalysis")
.IsDependentOn("CodeFileSanity")
.IsDependentOn("DotnetFormat")
.IsDependentOn("InspectCode")
.IsDependentOn("Test");
.IsDependentOn("InspectCode");
RunTarget(target);

View File

@ -54,6 +54,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1126.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1205.0" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch.Tests.Android" android:installLocation="auto">
<!-- using a different name because package name cannot contain 'catch' -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch_Tests.Android" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
</manifest>

View File

@ -34,12 +34,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
mods = Score.Mods;
var legacyScore = Score as LegacyScoreInfo;
fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect];
ticksHit = legacyScore?.Count100 ?? 0;
tinyTicksHit = legacyScore?.Count50 ?? 0;
tinyTicksMissed = legacyScore?.CountKatu ?? 0;
fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
ticksHit = Score?.GetCount100() ?? 0;
tinyTicksHit = Score?.GetCount50() ?? 0;
tinyTicksMissed = Score?.GetCountKatu() ?? 0;
misses = Score.Statistics[HitResult.Miss];
// Don't count scores made with supposedly unranked mods

View File

@ -1,12 +1,48 @@
// 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.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModRelax : ModRelax
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>
{
public override string Description => @"Use the mouse to control the catcher.";
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
}
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
{
private readonly CatcherArea.Catcher catcher;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public MouseInputHelper(CatchPlayfield playfield)
{
catcher = playfield.CatcherArea.MovableCatcher;
RelativeSizeAxes = Axes.Both;
}
//disable keyboard controls
public bool OnPressed(CatchAction action) => true;
public bool OnReleased(CatchAction action) => true;
protected override bool OnMouseMove(MouseMoveEvent e)
{
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X);
return base.OnMouseMove(e);
}
}
}
}

View File

@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI
internal readonly CatcherArea CatcherArea;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos);
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
{
Container explodingFruitContainer;

View File

@ -377,8 +377,7 @@ namespace osu.Game.Rulesets.Catch.UI
double dashModifier = Dashing ? 1 : 0.5;
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
// Correct overshooting.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
@ -452,6 +451,17 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.LifetimeStart = Time.Current;
fruit.Expire();
}
public void UpdatePosition(float position)
{
position = Math.Clamp(position, 0, 1);
if (position == X)
return;
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
X = position;
}
}
}
}

View File

@ -37,12 +37,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
countPerfect = Score.Statistics[HitResult.Perfect];
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countOk = Score.Statistics[HitResult.Ok];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
if (mods.Any(m => !m.Ranked))
return 0;

View File

@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))

View File

@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.UI
{
@ -30,6 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
{
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this);
protected override Playfield CreatePlayfield() => new OsuPlayfield();

View File

@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))

View File

@ -24,6 +24,7 @@
<Compile Include="..\osu.Game.Tests\**\Beatmaps\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\osu.Game.Tests\Beatmaps\Formats\OsuJsonDecoderTest.cs" />
<Compile Include="..\osu.Game.Tests\**\Chat\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
@ -68,10 +69,5 @@
<Name>osu.Game</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DeepEqual">
<Version>2.0.0</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

View File

@ -0,0 +1,73 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Scoring;
namespace osu.Game.Tests.Scores.IO
{
[TestFixture]
public class TestScoreEquality
{
[Test]
public void TestNonMatchingByReference()
{
ScoreInfo score1 = new ScoreInfo();
ScoreInfo score2 = new ScoreInfo();
Assert.That(score1, Is.Not.EqualTo(score2));
}
[Test]
public void TestMatchingByReference()
{
ScoreInfo score = new ScoreInfo();
Assert.That(score, Is.EqualTo(score));
}
[Test]
public void TestNonMatchingByPrimaryKey()
{
ScoreInfo score1 = new ScoreInfo { ID = 1 };
ScoreInfo score2 = new ScoreInfo { ID = 2 };
Assert.That(score1, Is.Not.EqualTo(score2));
}
[Test]
public void TestMatchingByPrimaryKey()
{
ScoreInfo score1 = new ScoreInfo { ID = 1 };
ScoreInfo score2 = new ScoreInfo { ID = 1 };
Assert.That(score1, Is.EqualTo(score2));
}
[Test]
public void TestNonMatchingByHash()
{
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
ScoreInfo score2 = new ScoreInfo { Hash = "b" };
Assert.That(score1, Is.Not.EqualTo(score2));
}
[Test]
public void TestMatchingByHash()
{
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
ScoreInfo score2 = new ScoreInfo { Hash = "a" };
Assert.That(score1, Is.EqualTo(score2));
}
[Test]
public void TestNonMatchingByNull()
{
ScoreInfo score = new ScoreInfo();
Assert.That(score, Is.Not.EqualTo(null));
}
}
}

View File

@ -209,9 +209,10 @@ namespace osu.Game.Tests.Visual.Background
public void TransitionTest()
{
performFullSetup();
var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } });
AddStep("Transition to Results", () => player.Push(results));
AddUntilStep("Wait for results is current", results.IsCurrentScreen);
FadeAccessibleResults results = null;
AddStep("Transition to Results", () => player.Push(results =
new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
waitForDim();
AddAssert("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());

View File

@ -115,8 +115,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestExitTooSoon()
{
pauseAndConfirm();
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
pauseAndConfirm();
resume();
AddStep("exit too soon", () => Player.Exit());
@ -176,7 +177,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestExitFromGameplay()
{
AddStep("exit", () => Player.Exit());
confirmPaused();
AddStep("exit", () => Player.Exit());
confirmExited();
}
@ -214,6 +217,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestRestartAfterResume()
{
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
pauseAndConfirm();
resumeAndConfirm();
restart();

View File

@ -5,11 +5,12 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Users;
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets;
using osu.Game.Screens.Ranking.Pages;
namespace osu.Game.Tests.Visual.Gameplay
@ -17,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneReplayDownloadButton : OsuTestScene
{
[Resolved]
private RulesetStore rulesets { get; set; }
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ReplayDownloadButton)
@ -49,16 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
return new APILegacyScoreInfo
{
ID = 1,
OnlineScoreID = 2553163309,
Ruleset = new OsuRuleset().RulesetInfo,
OnlineRulesetID = 0,
Replay = replayAvailable,
User = new User
{
Id = 39828,
Username = @"WubWoofWolf",
}
};
}.CreateScoreInfo(rulesets);
}
private class TestReplayDownloadButton : ReplayDownloadButton

View File

@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard
{
protected override APIRequest FetchScores(Action<IEnumerable<APIRoomScoreInfo>> scoresCallback)
protected override APIRequest FetchScores(Action<IEnumerable<APIUserScoreAggregate>> scoresCallback)
{
var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray();
@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
return null;
}
private APIRoomScoreInfo createRoomScore(int id) => new APIRoomScoreInfo
private APIUserScoreAggregate createRoomScore(int id) => new APIUserScoreAggregate
{
User = new User { Id = id, Username = $"User {id}" },
Accuracy = 0.98,

View File

@ -0,0 +1,129 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Rankings.Tables;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Graphics.UserInterface;
using System.Threading;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Catch;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneRankingsTables : OsuTestScene
{
protected override bool UseOnlineAPI => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(PerformanceTable),
typeof(ScoresTable),
typeof(CountriesTable),
typeof(TableRowBackground),
typeof(UserBasedTable),
typeof(RankingsTable<>)
};
[Resolved]
private IAPIProvider api { get; set; }
private readonly BasicScrollContainer scrollFlow;
private readonly DimmedLoadingLayer loading;
private CancellationTokenSource cancellationToken;
private APIRequest request;
public TestSceneRankingsTables()
{
Children = new Drawable[]
{
scrollFlow = new BasicScrollContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Width = 0.8f,
},
loading = new DimmedLoadingLayer(),
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null));
AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo));
AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo));
AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10));
}
private void createCountryTable(RulesetInfo ruleset, int page = 1)
{
onLoadStarted();
request = new GetCountryRankingsRequest(ruleset, page);
((GetCountryRankingsRequest)request).Success += rankings => Schedule(() =>
{
var table = new CountriesTable(page, rankings.Countries);
loadTable(table);
});
api.Queue(request);
}
private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1)
{
onLoadStarted();
request = new GetUserRankingsRequest(ruleset, country: country, page: page);
((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
{
var table = new PerformanceTable(page, rankings.Users);
loadTable(table);
});
api.Queue(request);
}
private void createScoreTable(RulesetInfo ruleset, int page = 1)
{
onLoadStarted();
request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page);
((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
{
var table = new ScoresTable(page, rankings.Users);
loadTable(table);
});
api.Queue(request);
}
private void onLoadStarted()
{
loading.Show();
request?.Cancel();
cancellationToken?.Cancel();
cancellationToken = new CancellationTokenSource();
}
private void loadTable(Drawable table)
{
LoadComponentAsync(table, t =>
{
scrollFlow.Clear();
scrollFlow.Add(t);
loading.Hide();
}, cancellationToken.Token);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@ -9,9 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet.Scores;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Users;
using osuTK.Graphics;
@ -66,12 +64,12 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"ES",
},
},
Mods = new Mod[]
Mods = new[]
{
new OsuModDoubleTime(),
new OsuModHidden(),
new OsuModFlashlight(),
new OsuModHardRock(),
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new OsuModFlashlight().Acronym,
new OsuModHardRock().Acronym,
},
Rank = ScoreRank.XH,
PP = 200,
@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"BR",
},
},
Mods = new Mod[]
Mods = new[]
{
new OsuModDoubleTime(),
new OsuModHidden(),
new OsuModFlashlight(),
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new OsuModFlashlight().Acronym,
},
Rank = ScoreRank.S,
PP = 190,
@ -115,10 +113,10 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"JP",
},
},
Mods = new Mod[]
Mods = new[]
{
new OsuModDoubleTime(),
new OsuModHidden(),
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
},
Rank = ScoreRank.B,
PP = 180,
@ -138,9 +136,9 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"CA",
},
},
Mods = new Mod[]
Mods = new[]
{
new OsuModDoubleTime(),
new OsuModDoubleTime().Acronym,
},
Rank = ScoreRank.C,
PP = 170,
@ -208,12 +206,12 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"ES",
},
},
Mods = new Mod[]
Mods = new[]
{
new OsuModDoubleTime(),
new OsuModHidden(),
new OsuModFlashlight(),
new OsuModHardRock(),
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new OsuModFlashlight().Acronym,
new OsuModHardRock().Acronym,
},
Rank = ScoreRank.XH,
PP = 200,
@ -226,10 +224,13 @@ namespace osu.Game.Tests.Visual.Online
foreach (var s in allScores.Scores)
{
s.Statistics.Add(HitResult.Great, RNG.Next(2000));
s.Statistics.Add(HitResult.Good, RNG.Next(2000));
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
s.Statistics.Add(HitResult.Miss, RNG.Next(2000));
s.Statistics = new Dictionary<string, int>
{
{ "count_300", RNG.Next(2000) },
{ "count_100", RNG.Next(2000) },
{ "count_50", RNG.Next(2000) },
{ "count_miss", RNG.Next(2000) }
};
}
AddStep("Load all scores", () =>

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select.Leaderboards;
@ -62,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
User = new User
{
Id = 6602580,

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
@ -52,7 +51,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
User = new User
{
Id = 6602580,

View File

@ -54,8 +54,8 @@ namespace osu.Game.Tournament
{
Resources.AddStore(new DllResourceStore(@"osu.Game.Tournament.dll"));
Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Regular"));
Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Light"));
AddFont(Resources, @"Resources/Fonts/Aquatico-Regular");
AddFont(Resources, @"Resources/Fonts/Aquatico-Light");
Textures.AddStore(new TextureLoaderStore(new ResourceStore<byte[]>(new StorageBackedResourceStore(storage))));
@ -129,6 +129,9 @@ namespace osu.Game.Tournament
ladder = new LadderInfo();
}
if (ladder.Ruleset.Value == null)
ladder.Ruleset.Value = RulesetStore.AvailableRulesets.First();
Ruleset.BindTo(ladder.Ruleset);
dependencies.Cache(ladder);
@ -225,7 +228,7 @@ namespace osu.Game.Tournament
if (b.BeatmapInfo == null && b.ID > 0)
{
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
req.Perform(API);
API.Perform(req);
b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
addedInfo = true;

View File

@ -398,7 +398,7 @@ namespace osu.Game.Beatmaps
try
{
// intentionally blocking to limit web request concurrency
req.Perform(api);
api.Perform(req);
var res = req.Result;

View File

@ -25,6 +25,6 @@ namespace osu.Game.Beatmaps.ControlPoints
/// <returns>Whether equivalent.</returns>
public abstract bool EquivalentTo(ControlPoint other);
public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other);
public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other);
}
}

View File

@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps.Drawables
difficultyName.Text = beatmap.Version;
starRating.Text = $"{beatmap.StarDifficulty:0.##}";
difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating);
difficultyFlow.Colour = colours.ForDifficultyRating(beatmap.DifficultyRating, true);
return true;
}

View File

@ -99,17 +99,7 @@ namespace osu.Game.Database
currentDownloads.Add(request);
PostNotification?.Invoke(notification);
Task.Factory.StartNew(() =>
{
try
{
request.Perform(api);
}
catch (Exception error)
{
triggerFailure(error);
}
}, TaskCreationOptions.LongRunning);
api.PerformAsync(request);
DownloadBegan?.Invoke(request);
return true;

View File

@ -16,6 +16,8 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary>
public class Background : CompositeDrawable
{
private const float blur_scale = 0.5f;
public readonly Sprite Sprite;
private readonly string textureName;
@ -43,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds
Sprite.Texture = textures.Get(textureName);
}
public Vector2 BlurSigma => bufferedContainer?.BlurSigma ?? Vector2.Zero;
public Vector2 BlurSigma => bufferedContainer?.BlurSigma / blur_scale ?? Vector2.Zero;
/// <summary>
/// Smoothly adjusts <see cref="IBufferedContainer.BlurSigma"/> over time.
@ -64,7 +66,10 @@ namespace osu.Game.Graphics.Backgrounds
});
}
bufferedContainer?.BlurTo(newBlurSigma, duration, easing);
if (bufferedContainer != null)
bufferedContainer.FrameBufferScale = newBlurSigma == Vector2.Zero ? Vector2.One : new Vector2(blur_scale);
bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing);
}
}
}

View File

@ -19,14 +19,8 @@ namespace osu.Game.Graphics.Containers
{
}
private OsuGame game;
[BackgroundDependencyLoader(true)]
private void load(OsuGame game)
{
// will be null in tests
this.game = game;
}
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
public void AddLinks(string text, List<Link> links)
{
@ -82,7 +76,7 @@ namespace osu.Game.Graphics.Containers
if (action != null)
action();
else
game.HandleLink(link);
game?.HandleLink(link);
},
});
}

View File

@ -67,33 +67,21 @@ namespace osu.Game.Graphics.Containers
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BlockScreenWideMouse || base.ReceivePositionalInputAt(screenSpacePos);
protected override bool OnClick(ClickEvent e)
{
if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
Hide();
private bool closeOnMouseUp;
return base.OnClick(e);
protected override bool OnMouseDown(MouseDownEvent e)
{
closeOnMouseUp = !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition);
return base.OnMouseDown(e);
}
private bool closeOnDragEnd;
protected override bool OnDragStart(DragStartEvent e)
protected override bool OnMouseUp(MouseUpEvent e)
{
if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
closeOnDragEnd = true;
return base.OnDragStart(e);
}
protected override bool OnDragEnd(DragEndEvent e)
{
if (closeOnDragEnd)
{
if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
Hide();
closeOnDragEnd = false;
}
return base.OnDragEnd(e);
return base.OnMouseUp(e);
}
public virtual bool OnPressed(GlobalAction action)

View File

@ -38,7 +38,7 @@ namespace osu.Game.Graphics
}
}
public Color4 ForDifficultyRating(DifficultyRating difficulty)
public Color4 ForDifficultyRating(DifficultyRating difficulty, bool useLighterColour = false)
{
switch (difficulty)
{
@ -56,10 +56,10 @@ namespace osu.Game.Graphics
return Pink;
case DifficultyRating.Expert:
return Purple;
return useLighterColour ? PurpleLight : Purple;
case DifficultyRating.ExpertPlus:
return Gray0;
return useLighterColour ? Gray9 : Gray0;
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;

View File

@ -9,6 +9,7 @@ using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
namespace osu.Game.Graphics.UserInterface
{
@ -20,6 +21,11 @@ namespace osu.Game.Graphics.UserInterface
{
private SampleChannel sampleHover;
/// <summary>
/// Length of debounce for hover sound playback, in milliseconds. Default is 50ms.
/// </summary>
public double HoverDebounceTime { get; set; } = 50;
protected readonly HoverSampleSet SampleSet;
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
@ -28,9 +34,17 @@ namespace osu.Game.Graphics.UserInterface
RelativeSizeAxes = Axes.Both;
}
private ScheduledDelegate playDelegate;
protected override bool OnHover(HoverEvent e)
{
sampleHover?.Play();
playDelegate?.Cancel();
if (HoverDebounceTime <= 0)
sampleHover?.Play();
else
playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), HoverDebounceTime);
return base.OnHover(e);
}

View File

@ -151,18 +151,18 @@ namespace osu.Game.Graphics.UserInterface
private void updateTooltipText(T value)
{
if (CurrentNumber.IsInteger)
TooltipText = ((int)Convert.ChangeType(value, typeof(int))).ToString("N0");
TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0");
else
{
double floatValue = (double)Convert.ChangeType(value, typeof(double));
double floatMinValue = (double)Convert.ChangeType(CurrentNumber.MinValue, typeof(double));
double floatMaxValue = (double)Convert.ChangeType(CurrentNumber.MaxValue, typeof(double));
double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo);
double floatMinValue = CurrentNumber.MinValue.ToDouble(NumberFormatInfo.InvariantInfo);
double floatMaxValue = CurrentNumber.MaxValue.ToDouble(NumberFormatInfo.InvariantInfo);
if (floatMaxValue == 1 && floatMinValue >= -1)
TooltipText = floatValue.ToString("P0");
else
{
var decimalPrecision = normalise((decimal)Convert.ChangeType(CurrentNumber.Precision, typeof(decimal)), max_decimal_digits);
var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);
// Find the number of significant digits (we could have less than 5 after normalize())
var significantDigits = findPrecision(decimalPrecision);

View File

@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterface
public override bool OnPressed(PlatformAction action)
{
// Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox
// as we do not allow arrow key navigation in the first place (ie. the care should always be at the end of text)
// as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text)
// Avoid handling it here to allow other components to potentially consume the shortcut.
if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete)
return false;

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -198,6 +199,22 @@ namespace osu.Game.Online.API
}
}
public void Perform(APIRequest request)
{
try
{
request.Perform(this);
}
catch (Exception e)
{
// todo: fix exception handling
request.Fail(e);
}
}
public Task PerformAsync(APIRequest request) =>
Task.Factory.StartNew(() => Perform(request), TaskCreationOptions.LongRunning);
public void Login(string username, string password)
{
Debug.Assert(State == APIState.Offline);

View File

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Users;
@ -56,6 +57,10 @@ namespace osu.Game.Online.API
{
}
public void Perform(APIRequest request) { }
public Task PerformAsync(APIRequest request) => Task.CompletedTask;
public void Register(IOnlineComponent component)
{
Scheduler.Add(delegate { components.Add(component); });

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Threading.Tasks;
using osu.Framework.Bindables;
using osu.Game.Users;
@ -42,6 +43,24 @@ namespace osu.Game.Online.API
/// <param name="request">The request to perform.</param>
void Queue(APIRequest request);
/// <summary>
/// Perform a request immediately, bypassing any API state checks.
/// </summary>
/// <remarks>
/// Can be used to run requests as a guest user.
/// </remarks>
/// <param name="request">The request to perform.</param>
void Perform(APIRequest request);
/// <summary>
/// Perform a request immediately, bypassing any API state checks.
/// </summary>
/// <remarks>
/// Can be used to run requests as a guest user.
/// </remarks>
/// <param name="request">The request to perform.</param>
Task PerformAsync(APIRequest request);
/// <summary>
/// Register a component to receive state changes.
/// </summary>

View File

@ -0,0 +1,15 @@
// 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;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class GetCountriesResponse : ResponseWithCursor
{
[JsonProperty("ranking")]
public List<CountryStatistics> Countries;
}
}

View File

@ -0,0 +1,17 @@
// 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.Rulesets;
namespace osu.Game.Online.API.Requests
{
public class GetCountryRankingsRequest : GetRankingsRequest<GetCountriesResponse>
{
public GetCountryRankingsRequest(RulesetInfo ruleset, int page = 1)
: base(ruleset, page)
{
}
protected override string TargetPostfix() => "country";
}
}

View File

@ -0,0 +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.
using osu.Framework.IO.Network;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public abstract class GetRankingsRequest<TModel> : APIRequest<TModel>
{
private readonly RulesetInfo ruleset;
private readonly int page;
protected GetRankingsRequest(RulesetInfo ruleset, int page = 1)
{
this.ruleset = ruleset;
this.page = page;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.AddParameter("page", page.ToString());
return req;
}
protected override string Target => $"rankings/{ruleset.ShortName}/{TargetPostfix()}";
protected abstract string TargetPostfix();
}
}

View File

@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetRoomScoresRequest : APIRequest<List<APIRoomScoreInfo>>
public class GetRoomScoresRequest : APIRequest<List<APIUserScoreAggregate>>
{
private readonly int roomId;

View File

@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Mods;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
namespace osu.Game.Online.API.Requests
{
@ -37,10 +38,12 @@ namespace osu.Game.Online.API.Requests
private void onSuccess(APILegacyScores r)
{
Debug.Assert(ruleset.ID != null, "ruleset.ID != null");
foreach (APILegacyScoreInfo score in r.Scores)
{
score.Beatmap = beatmap;
score.Ruleset = ruleset;
score.OnlineRulesetID = ruleset.ID.Value;
}
var userScore = r.UserScore;
@ -48,7 +51,7 @@ namespace osu.Game.Online.API.Requests
if (userScore != null)
{
userScore.Score.Beatmap = beatmap;
userScore.Score.Ruleset = ruleset;
userScore.Score.OnlineRulesetID = ruleset.ID.Value;
}
}

View File

@ -0,0 +1,39 @@
// 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.IO.Network;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public class GetUserRankingsRequest : GetRankingsRequest<GetUsersResponse>
{
private readonly string country;
private readonly UserRankingsType type;
public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null)
: base(ruleset, page)
{
this.type = type;
this.country = country;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
if (country != null)
req.AddParameter("country", country);
return req;
}
protected override string TargetPostfix() => type.ToString().ToLowerInvariant();
}
public enum UserRankingsType
{
Performance,
Score
}
}

View File

@ -42,5 +42,6 @@ namespace osu.Game.Online.API.Requests
Ranked,
Approved,
Qualified,
Loved
}
}

View File

@ -3,13 +3,13 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class GetUsersResponse : ResponseWithCursor
{
[JsonProperty("ranking")]
public List<APIUser> Users;
public List<UserStatistics> Users;
}
}

View File

@ -5,56 +5,106 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses
{
public class APILegacyScoreInfo : LegacyScoreInfo
public class APILegacyScoreInfo
{
[JsonProperty(@"score")]
private int totalScore
public ScoreInfo CreateScoreInfo(RulesetStore rulesets)
{
set => TotalScore = value;
var ruleset = rulesets.GetRuleset(OnlineRulesetID);
var mods = Mods != null ? ruleset.CreateInstance().GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty<Mod>();
var scoreInfo = new ScoreInfo
{
TotalScore = TotalScore,
MaxCombo = MaxCombo,
User = User,
Accuracy = Accuracy,
OnlineScoreID = OnlineScoreID,
Date = Date,
PP = PP,
Beatmap = Beatmap,
RulesetID = OnlineRulesetID,
Hash = "online", // todo: temporary?
Rank = Rank,
Ruleset = ruleset,
Mods = mods,
};
if (Statistics != null)
{
foreach (var kvp in Statistics)
{
switch (kvp.Key)
{
case @"count_geki":
scoreInfo.SetCountGeki(kvp.Value);
break;
case @"count_300":
scoreInfo.SetCount300(kvp.Value);
break;
case @"count_katu":
scoreInfo.SetCountKatu(kvp.Value);
break;
case @"count_100":
scoreInfo.SetCount100(kvp.Value);
break;
case @"count_50":
scoreInfo.SetCount50(kvp.Value);
break;
case @"count_miss":
scoreInfo.SetCountMiss(kvp.Value);
break;
}
}
}
return scoreInfo;
}
[JsonProperty(@"score")]
public int TotalScore { get; set; }
[JsonProperty(@"max_combo")]
private int maxCombo
{
set => MaxCombo = value;
}
public int MaxCombo { get; set; }
[JsonProperty(@"user")]
private User user
{
set => User = value;
}
public User User { get; set; }
[JsonProperty(@"id")]
private long onlineScoreID
{
set => OnlineScoreID = value;
}
public long OnlineScoreID { get; set; }
[JsonProperty(@"replay")]
public bool Replay { get; set; }
[JsonProperty(@"created_at")]
private DateTimeOffset date
{
set => Date = value;
}
public DateTimeOffset Date { get; set; }
[JsonProperty(@"beatmap")]
private BeatmapInfo beatmap
{
set => Beatmap = value;
}
public BeatmapInfo Beatmap { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
[JsonProperty(@"pp")]
public double? PP { get; set; }
[JsonProperty(@"beatmapset")]
private BeatmapMetadata metadata
public BeatmapMetadata Metadata
{
set
{
@ -67,68 +117,16 @@ namespace osu.Game.Online.API.Requests.Responses
}
[JsonProperty(@"statistics")]
private Dictionary<string, int> jsonStats
{
set
{
foreach (var kvp in value)
{
switch (kvp.Key)
{
case @"count_geki":
CountGeki = kvp.Value;
break;
case @"count_300":
Count300 = kvp.Value;
break;
case @"count_katu":
CountKatu = kvp.Value;
break;
case @"count_100":
Count100 = kvp.Value;
break;
case @"count_50":
Count50 = kvp.Value;
break;
case @"count_miss":
CountMiss = kvp.Value;
break;
default:
continue;
}
}
}
}
public Dictionary<string, int> Statistics { get; set; }
[JsonProperty(@"mode_int")]
public int OnlineRulesetID
{
get => RulesetID;
set => RulesetID = value;
}
public int OnlineRulesetID { get; set; }
[JsonProperty(@"mods")]
private string[] modStrings { get; set; }
public string[] Mods { get; set; }
public override RulesetInfo Ruleset
{
get => base.Ruleset;
set
{
base.Ruleset = value;
if (modStrings != null)
{
// Evaluate the mod string
Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.Acronym)).ToArray();
}
}
}
[JsonProperty("rank")]
[JsonConverter(typeof(StringEnumConverter))]
public ScoreRank Rank { get; set; }
}
}

View File

@ -1,17 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIRoomScoreInfo : ScoreInfo
{
[JsonProperty("attempts")]
public int TotalAttempts { get; set; }
[JsonProperty("completed")]
public int CompletedBeatmaps { get; set; }
}
}

View File

@ -0,0 +1,45 @@
// 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 Newtonsoft.Json;
using osu.Game.Scoring;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIUserScoreAggregate
{
[JsonProperty("attempts")]
public int TotalAttempts { get; set; }
[JsonProperty("completed")]
public int CompletedBeatmaps { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
[JsonProperty(@"pp")]
public double? PP { get; set; }
[JsonProperty(@"room_id")]
public int RoomID { get; set; }
[JsonProperty("total_score")]
public long TotalScore { get; set; }
[JsonProperty(@"user_id")]
public long UserID { get; set; }
[JsonProperty("user")]
public User User { get; set; }
public ScoreInfo CreateScoreInfo() =>
new ScoreInfo
{
Accuracy = Accuracy,
PP = PP,
TotalScore = TotalScore,
User = User,
};
}
}

View File

@ -406,11 +406,11 @@ namespace osu.Game
nextBeatmap?.LoadBeatmapAsync();
}
private void currentTrackCompleted()
private void currentTrackCompleted() => Schedule(() =>
{
if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled)
musicController.NextTrack();
}
});
#endregion

View File

@ -131,29 +131,29 @@ namespace osu.Game
dependencies.CacheAs(this);
dependencies.Cache(LocalConfig);
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic"));
AddFont(Resources, @"Fonts/osuFont");
AddFont(Resources, @"Fonts/Exo2.0-Medium");
AddFont(Resources, @"Fonts/Exo2.0-MediumItalic");
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility"));
AddFont(Resources, @"Fonts/Noto-Basic");
AddFont(Resources, @"Fonts/Noto-Hangul");
AddFont(Resources, @"Fonts/Noto-CJK-Basic");
AddFont(Resources, @"Fonts/Noto-CJK-Compatibility");
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBold"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBoldItalic"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Bold"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BoldItalic"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Light"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-LightItalic"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Black"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic"));
AddFont(Resources, @"Fonts/Exo2.0-Regular");
AddFont(Resources, @"Fonts/Exo2.0-RegularItalic");
AddFont(Resources, @"Fonts/Exo2.0-SemiBold");
AddFont(Resources, @"Fonts/Exo2.0-SemiBoldItalic");
AddFont(Resources, @"Fonts/Exo2.0-Bold");
AddFont(Resources, @"Fonts/Exo2.0-BoldItalic");
AddFont(Resources, @"Fonts/Exo2.0-Light");
AddFont(Resources, @"Fonts/Exo2.0-LightItalic");
AddFont(Resources, @"Fonts/Exo2.0-Black");
AddFont(Resources, @"Fonts/Exo2.0-BlackItalic");
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Medium"));
AddFont(Resources, @"Fonts/Venera");
AddFont(Resources, @"Fonts/Venera-Light");
AddFont(Resources, @"Fonts/Venera-Medium");
runMigrations();

View File

@ -41,6 +41,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
private GetScoresRequest getScoresRequest;
protected APILegacyScores Scores
@ -56,16 +59,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
return;
}
scoreTable.Scores = value.Scores;
var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList();
scoreTable.Scores = scoreInfos;
scoreTable.Show();
var topScore = value.Scores.First();
var topScore = scoreInfos.First();
var userScore = value.UserScore;
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets);
topScoresContainer.Add(new DrawableTopScore(topScore));
if (userScore != null && userScore.Score.OnlineScoreID != topScore.OnlineScoreID)
topScoresContainer.Add(new DrawableTopScore(userScore.Score, userScore.Position));
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID)
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
});
}

View File

@ -4,7 +4,6 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -43,18 +42,7 @@ namespace osu.Game.Overlays.Changelog
};
req.Failure += _ => complete = true;
// This is done on a separate thread to support cancellation below
Task.Run(() =>
{
try
{
req.Perform(api);
}
catch
{
complete = true;
}
});
api.PerformAsync(req);
while (!complete)
{

View File

@ -191,15 +191,7 @@ namespace osu.Game.Overlays
tcs.SetResult(false);
};
try
{
req.Perform(API);
}
catch
{
initialFetchTask = null;
tcs.SetResult(false);
}
await API.PerformAsync(req);
await tcs.Task;
});

View File

@ -16,6 +16,7 @@ using osu.Game.Users;
namespace osu.Game.Overlays.MedalSplash
{
[LongRunningLoad]
public class DrawableMedal : Container, IStateful<DisplayState>
{
private const float scale_when_unlocked = 0.76f;

View File

@ -12,6 +12,7 @@ using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
[LongRunningLoad]
public class DrawableBadge : CompositeDrawable, IHasTooltip
{
public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40);

View File

@ -19,8 +19,8 @@ namespace osu.Game.Overlays.Profile.Sections
private const int fade_duration = 200;
private Box underscoreLine;
private readonly Box coloredBackground;
private readonly Container background;
private Box coloredBackground;
private Container background;
/// <summary>
/// A visual element displayed to the left of <see cref="LeftFlowContainer"/> content.
@ -36,6 +36,19 @@ namespace osu.Game.Overlays.Profile.Sections
{
RelativeSizeAxes = Axes.X;
Height = 60;
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 0.97f,
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colour)
{
InternalChildren = new Drawable[]
{
background = new Container
@ -53,21 +66,7 @@ namespace osu.Game.Overlays.Profile.Sections
},
Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both }
},
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 0.97f,
},
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colour)
{
AddRange(new Drawable[]
{
Content,
underscoreLine = new Box
{
Anchor = Anchor.BottomCentre,
@ -101,7 +100,7 @@ namespace osu.Game.Overlays.Profile.Sections
Origin = Anchor.CentreRight,
Direction = FillDirection.Vertical,
},
});
};
coloredBackground.Colour = underscoreLine.Colour = colour.Gray4;
}

View File

@ -29,14 +29,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
ItemsContainer.Direction = FillDirection.Vertical;
}
protected override void UpdateItems(List<APILegacyScoreInfo> items)
{
foreach (var item in items)
item.Ruleset = Rulesets.GetRuleset(item.RulesetID);
base.UpdateItems(items);
}
protected override APIRequest<List<APILegacyScoreInfo>> CreateRequest() =>
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
@ -45,10 +37,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
switch (type)
{
default:
return new DrawablePerformanceScore(model, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null);
return new DrawablePerformanceScore(model.CreateScoreInfo(Rulesets), includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null);
case ScoreType.Recent:
return new DrawableTotalScore(model);
return new DrawableTotalScore(model.CreateScoreInfo(Rulesets));
}
}
}

View File

@ -66,11 +66,14 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
};
case RecentActivityType.Achievement:
return new MedalIcon(activity.Achievement.Slug)
return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
})
{
RelativeSizeAxes = Axes.Y,
Width = 60,
FillMode = FillMode.Fit,
};
default:

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Textures;
namespace osu.Game.Overlays.Profile.Sections.Recent
{
[LongRunningLoad]
public class MedalIcon : Container
{
private readonly string slug;

View File

@ -0,0 +1,67 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System;
using osu.Game.Users;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics;
using System.Collections.Generic;
namespace osu.Game.Overlays.Rankings.Tables
{
public class CountriesTable : RankingsTable<CountryStatistics>
{
public CountriesTable(int page, IReadOnlyList<CountryStatistics> rankings)
: base(page, rankings)
{
}
protected override TableColumn[] CreateAdditionalHeaders() => new[]
{
new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
};
protected override Country GetCountry(CountryStatistics item) => item.Country;
protected override Drawable CreateFlagContent(CountryStatistics item) => new OsuSpriteText
{
Font = OsuFont.GetFont(size: TEXT_SIZE),
Text = $@"{item.Country.FullName}",
};
protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[]
{
new ColoredRowText
{
Text = $@"{item.ActiveUsers:N0}",
},
new ColoredRowText
{
Text = $@"{item.PlayCount:N0}",
},
new ColoredRowText
{
Text = $@"{item.RankedScore:N0}",
},
new ColoredRowText
{
Text = $@"{item.RankedScore / Math.Max(item.ActiveUsers, 1):N0}",
},
new RowText
{
Text = $@"{item.Performance:N0}",
},
new ColoredRowText
{
Text = $@"{item.Performance / Math.Max(item.ActiveUsers, 1):N0}",
}
};
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables
{
public class PerformanceTable : UserBasedTable
{
public PerformanceTable(int page, IReadOnlyList<UserStatistics> rankings)
: base(page, rankings)
{
}
protected override TableColumn[] CreateUniqueHeaders() => new[]
{
new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
};
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{
new RowText { Text = $@"{item.PP:N0}", }
};
}
}

View File

@ -0,0 +1,140 @@
// 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.Containers;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Overlays.Rankings.Tables
{
public abstract class RankingsTable<TModel> : TableContainer
{
protected const int TEXT_SIZE = 14;
private const float horizontal_inset = 20;
private const float row_height = 25;
private const int items_per_page = 50;
private readonly int page;
private readonly IReadOnlyList<TModel> rankings;
protected RankingsTable(int page, IReadOnlyList<TModel> rankings)
{
this.page = page;
this.rankings = rankings;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Horizontal = horizontal_inset };
RowSize = new Dimension(GridSizeMode.Absolute, row_height);
}
[BackgroundDependencyLoader]
private void load()
{
FillFlowContainer backgroundFlow;
AddInternal(backgroundFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Depth = 1f,
Margin = new MarginPadding { Top = row_height }
});
rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground()));
Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray();
Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular();
}
private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray();
private static TableColumn[] mainHeaders => new[]
{
new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 50)), // place
new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username (country name)
};
protected abstract TableColumn[] CreateAdditionalHeaders();
protected abstract Drawable[] CreateAdditionalContent(TModel item);
protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty, HighlightedColumn());
protected abstract Country GetCountry(TModel item);
protected abstract Drawable CreateFlagContent(TModel item);
private OsuSpriteText createIndexDrawable(int index) => new OsuSpriteText
{
Text = $"#{index + 1}",
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold)
};
private FillFlowContainer createMainContent(TModel item) => new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(7, 0),
Children = new[]
{
new UpdateableFlag(GetCountry(item))
{
Size = new Vector2(20, 13),
ShowPlaceholderOnNull = false,
},
CreateFlagContent(item)
}
};
protected virtual string HighlightedColumn() => @"Performance";
private class HeaderText : OsuSpriteText
{
private readonly string highlighted;
public HeaderText(string text, string highlighted)
{
this.highlighted = highlighted;
Text = text;
Font = OsuFont.GetFont(size: 12);
Margin = new MarginPadding { Horizontal = 10 };
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
if (Text != highlighted)
Colour = colours.GreySeafoamLighter;
}
}
protected class RowText : OsuSpriteText
{
public RowText()
{
Font = OsuFont.GetFont(size: TEXT_SIZE);
Margin = new MarginPadding { Horizontal = 10 };
}
}
protected class ColoredRowText : RowText
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.GreySeafoamLighter;
}
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables
{
public class ScoresTable : UserBasedTable
{
public ScoresTable(int page, IReadOnlyList<UserStatistics> rankings)
: base(page, rankings)
{
}
protected override TableColumn[] CreateUniqueHeaders() => new[]
{
new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize))
};
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{
new ColoredRowText
{
Text = $@"{item.TotalScore:N0}",
},
new RowText
{
Text = $@"{item.RankedScore:N0}",
}
};
protected override string HighlightedColumn() => @"Ranked Score";
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Overlays.Rankings.Tables
{
public class TableRowBackground : CompositeDrawable
{
private const int fade_duration = 100;
private readonly Box background;
private Color4 idleColour;
private Color4 hoverColour;
public TableRowBackground()
{
RelativeSizeAxes = Axes.X;
Height = 25;
CornerRadius = 3;
Masking = true;
InternalChild = background = new Box
{
RelativeSizeAxes = Axes.Both,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = idleColour = colours.GreySeafoam;
hoverColour = colours.GreySeafoamLight;
}
protected override bool OnHover(HoverEvent e)
{
background.FadeColour(hoverColour, fade_duration, Easing.OutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
background.FadeColour(idleColour, fade_duration, Easing.OutQuint);
base.OnHoverLost(e);
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables
{
public abstract class UserBasedTable : RankingsTable<UserStatistics>
{
protected UserBasedTable(int page, IReadOnlyList<UserStatistics> rankings)
: base(page, rankings)
{
}
protected override TableColumn[] CreateAdditionalHeaders() => new[]
{
new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}.Concat(CreateUniqueHeaders()).Concat(new[]
{
new TableColumn("SS", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("S", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("A", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}).ToArray();
protected sealed override Country GetCountry(UserStatistics item) => item.User.Country;
protected sealed override Drawable CreateFlagContent(UserStatistics item)
{
var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: TEXT_SIZE)) { AutoSizeAxes = Axes.Both };
username.AddUserLink(item.User);
return username;
}
protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[]
{
new ColoredRowText { Text = $@"{item.Accuracy:F2}%", },
new ColoredRowText { Text = $@"{item.PlayCount:N0}", },
}.Concat(CreateUniqueContent(item)).Concat(new[]
{
new ColoredRowText { Text = $@"{item.GradesCount.SS + item.GradesCount.SSPlus:N0}", },
new ColoredRowText { Text = $@"{item.GradesCount.S + item.GradesCount.SPlus:N0}", },
new ColoredRowText { Text = $@"{item.GradesCount.A:N0}", }
}).ToArray();
protected abstract TableColumn[] CreateUniqueHeaders();
protected abstract Drawable[] CreateUniqueContent(UserStatistics item);
}
}

View File

@ -273,14 +273,6 @@ namespace osu.Game.Rulesets.Objects
return p0 + (p1 - p0) * (float)w;
}
public bool Equals(SliderPath other)
{
if (ControlPoints == null && other.ControlPoints != null)
return false;
if (other.ControlPoints == null && ControlPoints != null)
return false;
return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type;
}
public bool Equals(SliderPath other) => ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type;
}
}

View File

@ -34,6 +34,7 @@ using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.UI
{
@ -331,6 +332,9 @@ namespace osu.Game.Rulesets.UI
protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor
// only show the cursor when within the playfield, by default.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos);
CursorContainer IProvideCursor.Cursor => Playfield.Cursor;
public override GameplayCursorContainer Cursor => Playfield.Cursor;

View File

@ -100,10 +100,13 @@ namespace osu.Game.Rulesets.UI
public GameplayCursorContainer Cursor { get; private set; }
/// <summary>
/// Provide an optional cursor which is to be used for gameplay.
/// Provide a cursor which is to be used for gameplay.
/// </summary>
/// <returns>The cursor, or null if a cursor is not rqeuired.</returns>
protected virtual GameplayCursorContainer CreateCursor() => null;
/// <remarks>
/// The default provided cursor is invisible when inside the bounds of the <see cref="Playfield"/>.
/// </remarks>
/// <returns>The cursor, or null to show the menu cursor.</returns>
protected virtual GameplayCursorContainer CreateCursor() => new InvisibleCursorContainer();
/// <summary>
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.
@ -143,5 +146,14 @@ namespace osu.Game.Rulesets.UI
/// Creates the container that will be used to contain the <see cref="DrawableHitObject"/>s.
/// </summary>
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
public class InvisibleCursorContainer : GameplayCursorContainer
{
protected override Drawable CreateCursor() => new InvisibleCursor();
private class InvisibleCursor : Drawable
{
}
}
}
}

View File

@ -1,118 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring.Legacy
{
public class LegacyScoreInfo : ScoreInfo
{
private int countGeki;
public int CountGeki
{
get => countGeki;
set
{
countGeki = value;
switch (Ruleset?.ID ?? RulesetID)
{
case 3:
Statistics[HitResult.Perfect] = value;
break;
}
}
}
private int count300;
public int Count300
{
get => count300;
set
{
count300 = value;
switch (Ruleset?.ID ?? RulesetID)
{
case 0:
case 1:
case 3:
Statistics[HitResult.Great] = value;
break;
case 2:
Statistics[HitResult.Perfect] = value;
break;
}
}
}
private int countKatu;
public int CountKatu
{
get => countKatu;
set
{
countKatu = value;
switch (Ruleset?.ID ?? RulesetID)
{
case 3:
Statistics[HitResult.Good] = value;
break;
}
}
}
private int count100;
public int Count100
{
get => count100;
set
{
count100 = value;
switch (Ruleset?.ID ?? RulesetID)
{
case 0:
case 1:
Statistics[HitResult.Good] = value;
break;
case 3:
Statistics[HitResult.Ok] = value;
break;
}
}
}
private int count50;
public int Count50
{
get => count50;
set
{
count50 = value;
switch (Ruleset?.ID ?? RulesetID)
{
case 0:
case 3:
Statistics[HitResult.Meh] = value;
break;
}
}
}
public int CountMiss
{
get => Statistics[HitResult.Miss];
set => Statistics[HitResult.Miss] = value;
}
}
}

View File

@ -35,7 +35,7 @@ namespace osu.Game.Scoring.Legacy
using (SerializationReader sr = new SerializationReader(stream))
{
currentRuleset = GetRuleset(sr.ReadByte());
var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo };
var scoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo };
score.ScoreInfo = scoreInfo;
@ -53,12 +53,12 @@ namespace osu.Game.Scoring.Legacy
// MD5Hash
sr.ReadString();
scoreInfo.Count300 = sr.ReadUInt16();
scoreInfo.Count100 = sr.ReadUInt16();
scoreInfo.Count50 = sr.ReadUInt16();
scoreInfo.CountGeki = sr.ReadUInt16();
scoreInfo.CountKatu = sr.ReadUInt16();
scoreInfo.CountMiss = sr.ReadUInt16();
scoreInfo.SetCount300(sr.ReadUInt16());
scoreInfo.SetCount100(sr.ReadUInt16());
scoreInfo.SetCount50(sr.ReadUInt16());
scoreInfo.SetCountGeki(sr.ReadUInt16());
scoreInfo.SetCountKatu(sr.ReadUInt16());
scoreInfo.SetCountMiss(sr.ReadUInt16());
scoreInfo.TotalScore = sr.ReadInt32();
scoreInfo.MaxCombo = sr.ReadUInt16();

View File

@ -0,0 +1,143 @@
// 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.Rulesets.Scoring;
namespace osu.Game.Scoring.Legacy
{
public static class ScoreInfoExtensions
{
public static int? GetCountGeki(this ScoreInfo scoreInfo)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 3:
return scoreInfo.Statistics[HitResult.Perfect];
}
return null;
}
public static void SetCountGeki(this ScoreInfo scoreInfo, int value)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 3:
scoreInfo.Statistics[HitResult.Perfect] = value;
break;
}
}
public static int? GetCount300(this ScoreInfo scoreInfo)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 0:
case 1:
case 3:
return scoreInfo.Statistics[HitResult.Great];
case 2:
return scoreInfo.Statistics[HitResult.Perfect];
}
return null;
}
public static void SetCount300(this ScoreInfo scoreInfo, int value)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 0:
case 1:
case 3:
scoreInfo.Statistics[HitResult.Great] = value;
break;
case 2:
scoreInfo.Statistics[HitResult.Perfect] = value;
break;
}
}
public static int? GetCountKatu(this ScoreInfo scoreInfo)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 3:
return scoreInfo.Statistics[HitResult.Good];
}
return null;
}
public static void SetCountKatu(this ScoreInfo scoreInfo, int value)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 3:
scoreInfo.Statistics[HitResult.Good] = value;
break;
}
}
public static int? GetCount100(this ScoreInfo scoreInfo)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 0:
case 1:
return scoreInfo.Statistics[HitResult.Good];
case 3:
return scoreInfo.Statistics[HitResult.Ok];
}
return null;
}
public static void SetCount100(this ScoreInfo scoreInfo, int value)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 0:
case 1:
scoreInfo.Statistics[HitResult.Good] = value;
break;
case 3:
scoreInfo.Statistics[HitResult.Ok] = value;
break;
}
}
public static int? GetCount50(this ScoreInfo scoreInfo)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 0:
case 3:
return scoreInfo.Statistics[HitResult.Meh];
}
return null;
}
public static void SetCount50(this ScoreInfo scoreInfo, int value)
{
switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID)
{
case 0:
case 3:
scoreInfo.Statistics[HitResult.Meh] = value;
break;
}
}
public static int? GetCountMiss(this ScoreInfo scoreInfo) =>
scoreInfo.Statistics[HitResult.Miss];
public static void SetCountMiss(this ScoreInfo scoreInfo, int value) =>
scoreInfo.Statistics[HitResult.Miss] = value;
}
}

View File

@ -183,10 +183,21 @@ namespace osu.Game.Scoring
public override string ToString() => $"{User} playing {Beatmap}";
public bool Equals(ScoreInfo other) =>
other != null
&& other.OnlineScoreID == OnlineScoreID
&& other.BeatmapInfoID == BeatmapInfoID
&& other.Hash == Hash;
public bool Equals(ScoreInfo other)
{
if (other == null)
return false;
if (ID != 0 && other.ID != 0)
return ID == other.ID;
if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue)
return OnlineScoreID == other.OnlineScoreID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash;
return ReferenceEquals(this, other);
}
}
}

View File

@ -13,9 +13,9 @@ using osu.Game.Online.Multiplayer;
namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchLeaderboard : Leaderboard<MatchLeaderboardScope, APIRoomScoreInfo>
public class MatchLeaderboard : Leaderboard<MatchLeaderboardScope, APIUserScoreAggregate>
{
public Action<IEnumerable<APIRoomScoreInfo>> ScoresLoaded;
public Action<IEnumerable<APIUserScoreAggregate>> ScoresLoaded;
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
@ -35,7 +35,7 @@ namespace osu.Game.Screens.Multi.Match.Components
protected override bool IsOnlineScope => true;
protected override APIRequest FetchScores(Action<IEnumerable<APIRoomScoreInfo>> scoresCallback)
protected override APIRequest FetchScores(Action<IEnumerable<APIUserScoreAggregate>> scoresCallback)
{
if (roomId.Value == null)
return null;
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Multi.Match.Components
return req;
}
protected override LeaderboardScore CreateDrawableScore(APIRoomScoreInfo model, int index) => new MatchLeaderboardScore(model, index);
protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index);
}
public enum MatchLeaderboardScope

View File

@ -12,9 +12,12 @@ namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchLeaderboardScore : LeaderboardScore
{
public MatchLeaderboardScore(APIRoomScoreInfo score, int rank)
: base(score, rank)
private readonly APIUserScoreAggregate score;
public MatchLeaderboardScore(APIUserScoreAggregate score, int rank)
: base(score.CreateScoreInfo(), rank)
{
this.score = score;
}
[BackgroundDependencyLoader]
@ -26,8 +29,8 @@ namespace osu.Game.Screens.Multi.Match.Components
protected override IEnumerable<LeaderboardScoreStatistic> GetStatistics(ScoreInfo model) => new[]
{
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)),
new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", score.TotalAttempts.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", score.CompletedBeatmaps.ToString()),
};
}
}

View File

@ -74,7 +74,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages
leaderboard.ScoresLoaded = scoresLoaded;
}
private void scoresLoaded(IEnumerable<APIRoomScoreInfo> scores)
private void scoresLoaded(IEnumerable<APIUserScoreAggregate> scores)
{
void gray(SpriteText s) => s.Colour = colours.GrayC;
@ -87,7 +87,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages
rankText.AddText(name + "\n", white);
rankText.AddText("You are placed ", gray);
int index = scores.IndexOf(new APIRoomScoreInfo { User = Score.User }, new FuncEqualityComparer<APIRoomScoreInfo>((s1, s2) => s1.User.Id.Equals(s2.User.Id)));
int index = scores.IndexOf(new APIUserScoreAggregate { User = Score.User }, new FuncEqualityComparer<APIUserScoreAggregate>((s1, s2) => s1.User.Id.Equals(s2.User.Id)));
rankText.AddText($"#{index + 1} ", s =>
{
@ -104,7 +104,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages
{
protected override bool FadeTop => true;
protected override LeaderboardScore CreateDrawableScore(APIRoomScoreInfo model, int index)
protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index)
=> new ResultsMatchLeaderboardScore(model, index);
protected override FillFlowContainer<LeaderboardScore> CreateScoreFlow()
@ -120,7 +120,7 @@ namespace osu.Game.Screens.Multi.Ranking.Pages
private class ResultsMatchLeaderboardScore : MatchLeaderboardScore
{
public ResultsMatchLeaderboardScore(APIRoomScoreInfo score, int rank)
public ResultsMatchLeaderboardScore(APIUserScoreAggregate score, int rank)
: base(score, rank)
{
}

View File

@ -536,6 +536,12 @@ namespace osu.Game.Screens.Play
return true;
}
if (canPause)
{
Pause();
return true;
}
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
// as we are no longer the current screen, we cannot guarantee the track is still usable.
GameplayClockContainer.StopUsingBeatmapClock();

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring;
using osuTK;
@ -24,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Pages
if (State.Value == DownloadState.LocallyAvailable)
return ReplayAvailability.Local;
if (Model.Value is APILegacyScoreInfo apiScore && apiScore.Replay)
if (!string.IsNullOrEmpty(Model.Value.Hash))
return ReplayAvailability.Online;
return ReplayAvailability.NotAvailable;

View File

@ -70,7 +70,10 @@ namespace osu.Game.Screens.Ranking.Pages
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new UserHeader(Score.User)
new DelayedLoadWrapper(new UserHeader(Score.User)
{
RelativeSizeAxes = Axes.Both,
})
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@ -370,6 +373,7 @@ namespace osu.Game.Screens.Ranking.Pages
}
}
[LongRunningLoad]
private class UserHeader : Container
{
private readonly User user;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select.Filter;
@ -29,28 +30,29 @@ namespace osu.Game.Screens.Select.Carousel
Beatmap.RulesetID == criteria.Ruleset.ID ||
(Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
match &= criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty);
match &= criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate);
match &= criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate);
match &= criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize);
match &= criteria.Length.IsInRange(Beatmap.Length);
match &= criteria.BPM.IsInRange(Beatmap.BPM);
match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty);
match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate);
match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate);
match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize);
match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length);
match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM);
match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor);
match &= criteria.OnlineStatus.IsInRange(Beatmap.Status);
match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor);
match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(Beatmap.Status);
match &= criteria.Creator.Matches(Beatmap.Metadata.AuthorString);
match &= criteria.Artist.Matches(Beatmap.Metadata.Artist) ||
match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(Beatmap.Metadata.AuthorString);
match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(Beatmap.Metadata.Artist) ||
criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode);
if (match)
{
var terms = new List<string>();
terms.AddRange(Beatmap.Metadata.SearchableTerms);
terms.Add(Beatmap.Version);
foreach (var criteriaTerm in criteria.SearchTerms)
{
match &=
Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
}
match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0);
}
Filtered.Value = !match;

View File

@ -46,6 +46,8 @@ namespace osu.Game.Screens.Select
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
where T : struct, IComparable
{
public bool HasFilter => Max != null || Min != null;
public bool IsInRange(T value)
{
if (Min != null)
@ -79,17 +81,19 @@ namespace osu.Game.Screens.Select
public bool IsUpperInclusive;
public bool Equals(OptionalRange<T> other)
=> Min.Equals(other.Min)
&& Max.Equals(other.Max)
=> EqualityComparer<T?>.Default.Equals(Min, other.Min)
&& EqualityComparer<T?>.Default.Equals(Max, other.Max)
&& IsLowerInclusive.Equals(other.IsLowerInclusive)
&& IsUpperInclusive.Equals(other.IsUpperInclusive);
}
public struct OptionalTextFilter : IEquatable<OptionalTextFilter>
{
public bool HasFilter => !string.IsNullOrEmpty(SearchTerm);
public bool Matches(string value)
{
if (string.IsNullOrEmpty(SearchTerm))
if (!HasFilter)
return true;
// search term is guaranteed to be non-empty, so if the string we're comparing is empty, it's not matching

View File

@ -21,6 +21,9 @@ namespace osu.Game.Screens.Select.Leaderboards
{
public Action<ScoreInfo> ScoreSelected;
[Resolved]
private RulesetStore rulesets { get; set; }
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
@ -172,7 +175,7 @@ namespace osu.Game.Screens.Select.Leaderboards
req.Success += r =>
{
scoresCallback?.Invoke(r.Scores);
scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)));
TopScore = r.UserScore;
};

View File

@ -3,6 +3,7 @@
using System;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -10,6 +11,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osuTK;
@ -27,6 +29,9 @@ namespace osu.Game.Screens.Select.Leaderboards
protected override bool StartHidden => true;
[Resolved]
private RulesetStore rulesets { get; set; }
public UserTopScoreContainer()
{
RelativeSizeAxes = Axes.X;
@ -77,9 +82,11 @@ namespace osu.Game.Screens.Select.Leaderboards
if (newScore == null)
return;
LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false)
var scoreInfo = newScore.Score.CreateScoreInfo(rulesets);
LoadComponentAsync(new LeaderboardScore(scoreInfo, newScore.Position, false)
{
Action = () => ScoreSelected?.Invoke(newScore.Score)
Action = () => ScoreSelected?.Invoke(scoreInfo)
}, drawableScore =>
{
scoreContainer.Child = drawableScore;

View File

@ -240,6 +240,6 @@ namespace osu.Game.Tests.Beatmaps
set => Objects = value;
}
public virtual bool Equals(ConvertMapping<TConvertValue> other) => StartTime.Equals(other?.StartTime);
public virtual bool Equals(ConvertMapping<TConvertValue> other) => StartTime == other?.StartTime;
}
}

View File

@ -0,0 +1,28 @@
// 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 Newtonsoft.Json;
namespace osu.Game.Users
{
public class CountryStatistics
{
[JsonProperty]
public Country Country;
[JsonProperty(@"code")]
public string FlagName;
[JsonProperty(@"active_users")]
public long ActiveUsers;
[JsonProperty(@"play_count")]
public long PlayCount;
[JsonProperty(@"ranked_score")]
public long RankedScore;
[JsonProperty(@"performance")]
public long Performance;
}
}

View File

@ -23,6 +23,7 @@ namespace osu.Game.Users
protected override Drawable CreateDrawable(User user) => new Cover(user);
[LongRunningLoad]
private class Cover : CompositeDrawable
{
private readonly User user;

View File

@ -10,6 +10,9 @@ namespace osu.Game.Users
{
public class UserStatistics
{
[JsonProperty]
public User User;
[JsonProperty(@"level")]
public LevelInfo Level;

View File

@ -7,7 +7,9 @@
<PropertyGroup Label="Nuget">
<Title>osu!</Title>
<PackageId>ppy.osu.Game</PackageId>
<Version>0.0.0</Version>
<PackageIcon>icon.png</PackageIcon>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup Label="Icon">
<None Include="..\assets\lazer-nuget.png">
@ -21,7 +23,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1126.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1205.0" />
<PackageReference Include="Sentry" Version="1.2.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -74,7 +74,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1126.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1205.0" />
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
@ -82,7 +82,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1126.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1205.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />