mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 00:23:59 +09:00
Merge branch 'master' into transformers-per-skin
This commit is contained in:
@ -2,12 +2,6 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"cake.tool": {
|
|
||||||
"version": "0.35.0",
|
|
||||||
"commands": [
|
|
||||||
"dotnet-cake"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dotnet-format": {
|
"dotnet-format": {
|
||||||
"version": "3.1.37601",
|
"version": "3.1.37601",
|
||||||
"commands": [
|
"commands": [
|
||||||
@ -20,20 +14,20 @@
|
|||||||
"jb"
|
"jb"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nvika": {
|
"smoogipoo.nvika": {
|
||||||
"version": "2.0.0",
|
"version": "1.0.1",
|
||||||
"commands": [
|
"commands": [
|
||||||
"nvika"
|
"nvika"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"codefilesanity": {
|
"codefilesanity": {
|
||||||
"version": "15.0.0",
|
"version": "0.0.36",
|
||||||
"commands": [
|
"commands": [
|
||||||
"CodeFileSanity"
|
"CodeFileSanity"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2021.524.0",
|
"version": "2021.608.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
93
.github/workflows/ci.yml
vendored
Normal file
93
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
on: [push, pull_request]
|
||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ${{matrix.os.fullname}}
|
||||||
|
env:
|
||||||
|
OSU_EXECUTION_MODE: ${{matrix.threadingMode}}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- { prettyname: Windows, fullname: windows-latest }
|
||||||
|
- { prettyname: macOS, fullname: macos-latest }
|
||||||
|
- { prettyname: Linux, fullname: ubuntu-latest }
|
||||||
|
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install .NET 5.0.x
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: "5.0.x"
|
||||||
|
|
||||||
|
# FIXME: libavformat is not included in Ubuntu. Let's fix that.
|
||||||
|
# https://github.com/ppy/osu-framework/issues/4349
|
||||||
|
# Remove this once https://github.com/actions/virtual-environments/issues/3306 has been resolved.
|
||||||
|
- name: Install libavformat-dev
|
||||||
|
if: ${{matrix.os.fullname == 'ubuntu-latest'}}
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && \
|
||||||
|
sudo apt-get -y install libavformat-dev
|
||||||
|
|
||||||
|
- name: Compile
|
||||||
|
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
# Attempt to upload results even if test fails.
|
||||||
|
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||||
|
- name: Upload Test Results
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
if: ${{ always() }}
|
||||||
|
with:
|
||||||
|
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
|
path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx
|
||||||
|
|
||||||
|
inspect-code:
|
||||||
|
name: Code Quality
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side.
|
||||||
|
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
||||||
|
- name: Install .NET 3.1.x LTS
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
|
- name: Install .NET 5.0.x
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: "5.0.x"
|
||||||
|
|
||||||
|
- name: Restore Tools
|
||||||
|
run: dotnet tool restore
|
||||||
|
|
||||||
|
- name: Restore Packages
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: CodeFileSanity
|
||||||
|
run: |
|
||||||
|
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
|
||||||
|
# FIXME: Suppress warnings from templates project
|
||||||
|
dotnet codefilesanity | while read -r line; do
|
||||||
|
echo "::warning::$line"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded.
|
||||||
|
# - name: .NET Format (Dry Run)
|
||||||
|
# run: dotnet format --dry-run --check
|
||||||
|
|
||||||
|
- name: InspectCode
|
||||||
|
run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --output=$(pwd)/inspectcodereport.xml --cachesDir=$(pwd)/inspectcode --verbosity=WARN
|
||||||
|
|
||||||
|
- name: NVika
|
||||||
|
run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors
|
31
.github/workflows/report-nunit.yml
vendored
Normal file
31
.github/workflows/report-nunit.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# This is a workaround to allow PRs to report their coverage. This will run inside the base repository.
|
||||||
|
# See:
|
||||||
|
# * https://github.com/dorny/test-reporter#recommended-setup-for-public-repositories
|
||||||
|
# * https://docs.github.com/en/actions/reference/authentication-in-a-workflow#permissions-for-the-github_token
|
||||||
|
name: Annotate CI run with test results
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Continuous Integration"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
jobs:
|
||||||
|
annotate:
|
||||||
|
name: Annotate CI run with test results
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- { prettyname: Windows }
|
||||||
|
- { prettyname: macOS }
|
||||||
|
- { prettyname: Linux }
|
||||||
|
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||||
|
steps:
|
||||||
|
- name: Annotate CI run with test results
|
||||||
|
uses: dorny/test-reporter@v1.4.2
|
||||||
|
with:
|
||||||
|
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
|
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||||
|
path: "*.trx"
|
||||||
|
reporter: dotnet-trx
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -113,20 +113,6 @@
|
|||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build benchmarks",
|
"preLaunchTask": "Build benchmarks",
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cake: Debug Script",
|
|
||||||
"type": "coreclr",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll",
|
|
||||||
"args": [
|
|
||||||
"${workspaceRoot}/build/build.cake",
|
|
||||||
"--debug",
|
|
||||||
"--verbosity=diagnostic"
|
|
||||||
],
|
|
||||||
"cwd": "${workspaceRoot}/build",
|
|
||||||
"stopAtEntry": true,
|
|
||||||
"externalConsole": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,11 @@
|
|||||||
[CmdletBinding()]
|
|
||||||
Param(
|
|
||||||
[string]$Target,
|
|
||||||
[string]$Configuration,
|
|
||||||
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
|
|
||||||
[string]$Verbosity,
|
|
||||||
[switch]$ShowDescription,
|
|
||||||
[Alias("WhatIf", "Noop")]
|
|
||||||
[switch]$DryRun,
|
|
||||||
[Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)]
|
|
||||||
[string[]]$ScriptArgs
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build Cake arguments
|
|
||||||
$cakeArguments = "";
|
|
||||||
if ($Target) { $cakeArguments += "-target=$Target" }
|
|
||||||
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
|
|
||||||
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
|
|
||||||
if ($ShowDescription) { $cakeArguments += "-showdescription" }
|
|
||||||
if ($DryRun) { $cakeArguments += "-dryrun" }
|
|
||||||
if ($Experimental) { $cakeArguments += "-experimental" }
|
|
||||||
$cakeArguments += $ScriptArgs
|
|
||||||
|
|
||||||
dotnet tool restore
|
dotnet tool restore
|
||||||
dotnet cake ./build/InspectCode.cake --bootstrap
|
|
||||||
dotnet cake ./build/InspectCode.cake $cakeArguments
|
# Temporarily disabled until the tool is upgraded to 5.0.
|
||||||
|
# The version specified in .config/dotnet-tools.json (3.1.37601) won't run on .NET hosts >=5.0.7.
|
||||||
|
# - cmd: dotnet format --dry-run --check
|
||||||
|
|
||||||
|
dotnet CodeFileSanity
|
||||||
|
dotnet jb inspectcode "osu.Desktop.slnf" --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
||||||
|
dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors
|
||||||
|
|
||||||
exit $LASTEXITCODE
|
exit $LASTEXITCODE
|
6
InspectCode.sh
Executable file
6
InspectCode.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
dotnet tool restore
|
||||||
|
dotnet CodeFileSanity
|
||||||
|
dotnet jb inspectcode "osu.Desktop.slnf" --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
|
||||||
|
dotnet nvika parsereport "inspectcodereport.xml" --treatwarningsaserrors
|
15
appveyor.yml
15
appveyor.yml
@ -1,24 +1,27 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{branch}-{build}'
|
version: '{branch}-{build}'
|
||||||
image: Visual Studio 2019
|
image: Visual Studio 2019
|
||||||
|
cache:
|
||||||
|
- '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
|
||||||
|
|
||||||
dotnet_csproj:
|
dotnet_csproj:
|
||||||
patch: true
|
patch: true
|
||||||
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
||||||
version: '0.0.{build}'
|
version: '0.0.{build}'
|
||||||
cache:
|
|
||||||
- '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
|
|
||||||
before_build:
|
before_build:
|
||||||
- ps: dotnet --info # Useful when version mismatch between CI and local
|
- cmd: 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
|
- cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
|
||||||
|
|
||||||
build:
|
build:
|
||||||
project: osu.sln
|
project: osu.sln
|
||||||
parallel: true
|
parallel: true
|
||||||
verbosity: minimal
|
verbosity: minimal
|
||||||
publish_nuget: true
|
publish_nuget: true
|
||||||
|
|
||||||
after_build:
|
after_build:
|
||||||
- ps: dotnet tool restore
|
|
||||||
- ps: dotnet format --dry-run --check
|
|
||||||
- ps: .\InspectCode.ps1
|
- ps: .\InspectCode.ps1
|
||||||
|
|
||||||
test:
|
test:
|
||||||
assemblies:
|
assemblies:
|
||||||
except:
|
except:
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.Build.Traversal">
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\osu.Desktop\osu.Desktop.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch.Tests\osu.Game.Rulesets.Catch.Tests.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania.Tests\osu.Game.Rulesets.Mania.Tests.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Osu.Tests\osu.Game.Rulesets.Osu.Tests.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko.Tests\osu.Game.Rulesets.Taiko.Tests.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Tournament.Tests\osu.Game.Tournament.Tests.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game.Tournament\osu.Game.Tournament.csproj" />
|
|
||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
@ -1,41 +0,0 @@
|
|||||||
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// ARGUMENTS
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
var target = Argument("target", "CodeAnalysis");
|
|
||||||
var configuration = Argument("configuration", "Release");
|
|
||||||
|
|
||||||
var rootDirectory = new DirectoryPath("..");
|
|
||||||
var sln = rootDirectory.CombineWithFilePath("osu.sln");
|
|
||||||
var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
// TASKS
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
Task("InspectCode")
|
|
||||||
.Does(() => {
|
|
||||||
var inspectcodereport = "inspectcodereport.xml";
|
|
||||||
var cacheDir = "inspectcode";
|
|
||||||
var verbosity = AppVeyor.IsRunningOnAppVeyor ? "WARN" : "INFO"; // Don't flood CI output
|
|
||||||
|
|
||||||
DotNetCoreTool(rootDirectory.FullPath,
|
|
||||||
"jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity={verbosity}");
|
|
||||||
DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
|
|
||||||
});
|
|
||||||
|
|
||||||
Task("CodeFileSanity")
|
|
||||||
.Does(() => {
|
|
||||||
ValidateCodeSanity(new ValidateCodeSanitySettings {
|
|
||||||
RootDirectory = rootDirectory.FullPath,
|
|
||||||
IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Task("CodeAnalysis")
|
|
||||||
.IsDependentOn("CodeFileSanity")
|
|
||||||
.IsDependentOn("InspectCode");
|
|
||||||
|
|
||||||
RunTarget(target);
|
|
@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
[Nuget]
|
|
||||||
Source=https://api.nuget.org/v3/index.json
|
|
||||||
UseInProcessClient=true
|
|
||||||
LoadDependencies=true
|
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.614.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.609.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.614.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
[General]
|
||||||
|
// no version specified means v1
|
@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
public class CatchDifficultyAttributes : DifficultyAttributes
|
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double ApproachRate;
|
public double ApproachRate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
|
|
||||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||||
{
|
{
|
||||||
private readonly Catcher catcher;
|
private readonly CatcherArea catcherArea;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
public MouseInputHelper(CatchPlayfield playfield)
|
public MouseInputHelper(CatchPlayfield playfield)
|
||||||
{
|
{
|
||||||
catcher = playfield.CatcherArea.MovableCatcher;
|
catcherArea = playfield.CatcherArea;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
catcherArea.SetCatcherPosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -26,7 +25,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
public class Catcher : SkinReloadableDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
||||||
@ -54,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const double BASE_SPEED = 1.0;
|
public const double BASE_SPEED = 1.0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current speed of the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught".
|
/// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -96,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public bool Dashing
|
public bool Dashing
|
||||||
{
|
{
|
||||||
get => dashing;
|
get => dashing;
|
||||||
protected set
|
set
|
||||||
{
|
{
|
||||||
if (value == dashing) return;
|
if (value == dashing) return;
|
||||||
|
|
||||||
@ -106,6 +110,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Direction VisualDirection
|
||||||
|
{
|
||||||
|
get => Scale.X > 0 ? Direction.Right : Direction.Left;
|
||||||
|
set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -116,8 +126,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
private int currentDirection;
|
|
||||||
|
|
||||||
private double hyperDashModifier = 1;
|
private double hyperDashModifier = 1;
|
||||||
private int hyperDashDirection;
|
private int hyperDashDirection;
|
||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
@ -315,55 +323,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePosition(float position)
|
|
||||||
{
|
|
||||||
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
|
||||||
|
|
||||||
if (position == X)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
|
||||||
X = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(CatchAction action)
|
|
||||||
{
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case CatchAction.MoveLeft:
|
|
||||||
currentDirection--;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case CatchAction.MoveRight:
|
|
||||||
currentDirection++;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case CatchAction.Dash:
|
|
||||||
Dashing = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnReleased(CatchAction action)
|
|
||||||
{
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case CatchAction.MoveLeft:
|
|
||||||
currentDirection++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CatchAction.MoveRight:
|
|
||||||
currentDirection--;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CatchAction.Dash:
|
|
||||||
Dashing = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop any fruit off the plate.
|
/// Drop any fruit off the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -405,15 +364,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (currentDirection == 0) return;
|
|
||||||
|
|
||||||
var direction = Math.Sign(currentDirection);
|
|
||||||
|
|
||||||
var dashModifier = Dashing ? 1 : 0.5;
|
|
||||||
var speed = BASE_SPEED * dashModifier * hyperDashModifier;
|
|
||||||
|
|
||||||
UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
|
|
||||||
|
|
||||||
// Correct overshooting.
|
// Correct overshooting.
|
||||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||||
(hyperDashDirection < 0 && hyperDashTargetPosition > X))
|
(hyperDashDirection < 0 && hyperDashTargetPosition > X))
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
@ -14,13 +16,20 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class CatcherArea : Container
|
public class CatcherArea : Container, IKeyBindingHandler<CatchAction>
|
||||||
{
|
{
|
||||||
public const float CATCHER_SIZE = 106.75f;
|
public const float CATCHER_SIZE = 106.75f;
|
||||||
|
|
||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>-1</c> when only left button is pressed.
|
||||||
|
/// <c>1</c> when only right button is pressed.
|
||||||
|
/// <c>0</c> when none or both left and right buttons are pressed.
|
||||||
|
/// </summary>
|
||||||
|
private int currentDirection;
|
||||||
|
|
||||||
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
@ -63,16 +72,73 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
MovableCatcher.OnRevertResult(hitObject, result);
|
MovableCatcher.OnRevertResult(hitObject, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||||
|
|
||||||
|
SetCatcherPosition(
|
||||||
|
replayState?.CatcherX ??
|
||||||
|
(float)(MovableCatcher.X + MovableCatcher.Speed * currentDirection * Clock.ElapsedFrameTime));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
|
||||||
|
|
||||||
if (state?.CatcherX != null)
|
|
||||||
MovableCatcher.X = state.CatcherX.Value;
|
|
||||||
|
|
||||||
comboDisplay.X = MovableCatcher.X;
|
comboDisplay.X = MovableCatcher.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCatcherPosition(float X)
|
||||||
|
{
|
||||||
|
float lastPosition = MovableCatcher.X;
|
||||||
|
float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
|
MovableCatcher.X = newPosition;
|
||||||
|
|
||||||
|
if (lastPosition < newPosition)
|
||||||
|
MovableCatcher.VisualDirection = Direction.Right;
|
||||||
|
else if (lastPosition > newPosition)
|
||||||
|
MovableCatcher.VisualDirection = Direction.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(CatchAction action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case CatchAction.MoveLeft:
|
||||||
|
currentDirection--;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case CatchAction.MoveRight:
|
||||||
|
currentDirection++;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case CatchAction.Dash:
|
||||||
|
MovableCatcher.Dashing = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(CatchAction action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case CatchAction.MoveLeft:
|
||||||
|
currentDirection++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CatchAction.MoveRight:
|
||||||
|
currentDirection--;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CatchAction.Dash:
|
||||||
|
MovableCatcher.Dashing = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
osu.Game.Rulesets.Catch/UI/Direction.cs
Normal file
11
osu.Game.Rulesets.Catch/UI/Direction.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public enum Direction
|
||||||
|
{
|
||||||
|
Right = 1,
|
||||||
|
Left = -1
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double GreatHitWindow;
|
public double GreatHitWindow { get; set; }
|
||||||
public double ScoreMultiplier;
|
public double ScoreMultiplier { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[General]
|
[General]
|
||||||
Version: 1.0
|
// no version specified means v1
|
||||||
|
|
||||||
[Fonts]
|
[Fonts]
|
||||||
HitCircleOverlap: 3
|
HitCircleOverlap: 3
|
||||||
|
@ -7,11 +7,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyAttributes : DifficultyAttributes
|
public class OsuDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double AimStrain;
|
public double AimStrain { get; set; }
|
||||||
public double SpeedStrain;
|
public double SpeedStrain { get; set; }
|
||||||
public double ApproachRate;
|
public double ApproachRate { get; set; }
|
||||||
public double OverallDifficulty;
|
public double OverallDifficulty { get; set; }
|
||||||
public int HitCircleCount;
|
public int HitCircleCount { get; set; }
|
||||||
public int SpinnerCount;
|
public int SpinnerCount { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = source.GetTexture("spinner-background"),
|
Texture = source.GetTexture("spinner-background"),
|
||||||
|
Colour = source.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SpinnerBackground)?.Value ?? new Color4(100, 100, 100, 255),
|
||||||
Scale = new Vector2(SPRITE_SCALE),
|
Scale = new Vector2(SPRITE_SCALE),
|
||||||
Y = SPINNER_Y_CENTRE,
|
Y = SPINNER_Y_CENTRE,
|
||||||
},
|
},
|
||||||
|
@ -7,6 +7,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
SliderTrackOverride,
|
SliderTrackOverride,
|
||||||
SliderBorder,
|
SliderBorder,
|
||||||
SliderBall
|
SliderBall,
|
||||||
|
SpinnerBackground,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
public class TaikoDifficultyAttributes : DifficultyAttributes
|
public class TaikoDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double StaminaStrain;
|
public double StaminaStrain { get; set; }
|
||||||
public double RhythmStrain;
|
public double RhythmStrain { get; set; }
|
||||||
public double ColourStrain;
|
public double ColourStrain { get; set; }
|
||||||
public double ApproachRate;
|
public double ApproachRate { get; set; }
|
||||||
public double GreatHitWindow;
|
public double GreatHitWindow { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,19 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
public class Hit : TaikoStrongableHitObject
|
public class Hit : TaikoStrongableHitObject, IHasDisplayColour
|
||||||
{
|
{
|
||||||
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
|
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
|
||||||
|
|
||||||
|
public Bindable<Color4> DisplayColour { get; } = new Bindable<Color4>(COLOUR_CENTRE);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
|
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -20,9 +25,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
set => TypeBindable.Value = value;
|
set => TypeBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly Color4 COLOUR_CENTRE = Color4Extensions.FromHex(@"bb1177");
|
||||||
|
public static readonly Color4 COLOUR_RIM = Color4Extensions.FromHex(@"2299bb");
|
||||||
|
|
||||||
public Hit()
|
public Hit()
|
||||||
{
|
{
|
||||||
TypeBindable.BindValueChanged(_ => updateSamplesFromType());
|
TypeBindable.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateSamplesFromType();
|
||||||
|
DisplayColour.Value = Type == HitType.Centre ? COLOUR_CENTRE : COLOUR_RIM;
|
||||||
|
});
|
||||||
|
|
||||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
AccentColour = colours.PinkDarker;
|
AccentColour = Hit.COLOUR_CENTRE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
AccentColour = colours.BlueDarker;
|
AccentColour = Hit.COLOUR_RIM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -113,7 +113,6 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
await importCollectionsFromStream(osu, ms);
|
await importCollectionsFromStream(osu, ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(host.UpdateThread.Running, Is.True);
|
|
||||||
Assert.That(exceptionThrown, Is.False);
|
Assert.That(exceptionThrown, Is.False);
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0));
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,11 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
* outside of the range.
|
* outside of the range.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[Test]
|
[TestCase("star")]
|
||||||
public void TestApplyStarQueries()
|
[TestCase("stars")]
|
||||||
|
public void TestApplyStarQueries(string variant)
|
||||||
{
|
{
|
||||||
const string query = "stars<4 easy";
|
string query = $"{variant}<4 easy";
|
||||||
var filterCriteria = new FilterCriteria();
|
var filterCriteria = new FilterCriteria();
|
||||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
|
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
[General]
|
[General]
|
||||||
Version: 1.0
|
// no version specified means v1
|
||||||
|
BIN
osu.Game.Tests/Resources/special-skin/scorebar-bg.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 B |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-marker.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 B |
@ -161,15 +161,18 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
|
|
||||||
private void loadNextBackground()
|
private void loadNextBackground()
|
||||||
{
|
{
|
||||||
|
SeasonalBackground previousBackground = null;
|
||||||
SeasonalBackground background = null;
|
SeasonalBackground background = null;
|
||||||
|
|
||||||
AddStep("create next background", () =>
|
AddStep("create next background", () =>
|
||||||
{
|
{
|
||||||
|
previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault();
|
||||||
background = backgroundLoader.LoadNextBackground();
|
background = backgroundLoader.LoadNextBackground();
|
||||||
LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
|
LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("background loaded", () => background.IsLoaded);
|
AddUntilStep("background loaded", () => background.IsLoaded);
|
||||||
|
AddAssert("background is different", () => !background.Equals(previousBackground));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertAnyBackground()
|
private void assertAnyBackground()
|
||||||
|
144
osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs
Normal file
144
osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneMetadataSection : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap());
|
||||||
|
|
||||||
|
private TestMetadataSection metadataSection;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMinimalMetadata()
|
||||||
|
{
|
||||||
|
AddStep("set metadata", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Metadata.Artist = "Example Artist";
|
||||||
|
editorBeatmap.Metadata.ArtistUnicode = null;
|
||||||
|
|
||||||
|
editorBeatmap.Metadata.Title = "Example Title";
|
||||||
|
editorBeatmap.Metadata.TitleUnicode = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
createSection();
|
||||||
|
|
||||||
|
assertArtist("Example Artist");
|
||||||
|
assertRomanisedArtist("Example Artist", false);
|
||||||
|
|
||||||
|
assertTitle("Example Title");
|
||||||
|
assertRomanisedTitle("Example Title", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInitialisationFromNonRomanisedVariant()
|
||||||
|
{
|
||||||
|
AddStep("set metadata", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
|
||||||
|
editorBeatmap.Metadata.Artist = null;
|
||||||
|
|
||||||
|
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
|
||||||
|
editorBeatmap.Metadata.Title = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
createSection();
|
||||||
|
|
||||||
|
assertArtist("*なみりん");
|
||||||
|
assertRomanisedArtist(string.Empty, true);
|
||||||
|
|
||||||
|
assertTitle("コイシテイク・プラネット");
|
||||||
|
assertRomanisedTitle(string.Empty, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInitialisationPreservesOriginalValues()
|
||||||
|
{
|
||||||
|
AddStep("set metadata", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
|
||||||
|
editorBeatmap.Metadata.Artist = "*namirin";
|
||||||
|
|
||||||
|
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
|
||||||
|
editorBeatmap.Metadata.Title = "Koishiteiku Planet";
|
||||||
|
});
|
||||||
|
|
||||||
|
createSection();
|
||||||
|
|
||||||
|
assertArtist("*なみりん");
|
||||||
|
assertRomanisedArtist("*namirin", true);
|
||||||
|
|
||||||
|
assertTitle("コイシテイク・プラネット");
|
||||||
|
assertRomanisedTitle("Koishiteiku Planet", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestValueTransfer()
|
||||||
|
{
|
||||||
|
AddStep("set metadata", () =>
|
||||||
|
{
|
||||||
|
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
|
||||||
|
editorBeatmap.Metadata.Artist = null;
|
||||||
|
|
||||||
|
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
|
||||||
|
editorBeatmap.Metadata.Title = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
createSection();
|
||||||
|
|
||||||
|
AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin");
|
||||||
|
assertArtist("*namirin");
|
||||||
|
assertRomanisedArtist("*namirin", false);
|
||||||
|
|
||||||
|
AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん");
|
||||||
|
assertArtist("*なみりん");
|
||||||
|
assertRomanisedArtist("*namirin", true);
|
||||||
|
|
||||||
|
AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori");
|
||||||
|
assertTitle("Hitokoto no kyori");
|
||||||
|
assertRomanisedTitle("Hitokoto no kyori", false);
|
||||||
|
|
||||||
|
AddStep("set native title", () => metadataSection.TitleTextBox.Current.Value = "ヒトコトの距離");
|
||||||
|
assertTitle("ヒトコトの距離");
|
||||||
|
assertRomanisedTitle("Hitokoto no kyori", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSection()
|
||||||
|
=> AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection());
|
||||||
|
|
||||||
|
private void assertArtist(string expected)
|
||||||
|
=> AddAssert($"artist is {expected}", () => metadataSection.ArtistTextBox.Current.Value == expected);
|
||||||
|
|
||||||
|
private void assertRomanisedArtist(string expected, bool editable)
|
||||||
|
{
|
||||||
|
AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value == expected);
|
||||||
|
AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTitle(string expected)
|
||||||
|
=> AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value == expected);
|
||||||
|
|
||||||
|
private void assertRomanisedTitle(string expected, bool editable)
|
||||||
|
{
|
||||||
|
AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value == expected);
|
||||||
|
AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMetadataSection : MetadataSection
|
||||||
|
{
|
||||||
|
public new LabelledTextBox ArtistTextBox => base.ArtistTextBox;
|
||||||
|
public new LabelledTextBox RomanisedArtistTextBox => base.RomanisedArtistTextBox;
|
||||||
|
|
||||||
|
public new LabelledTextBox TitleTextBox => base.TitleTextBox;
|
||||||
|
public new LabelledTextBox RomanisedTitleTextBox => base.RomanisedTitleTextBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
240
osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
Normal file
240
osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneMessageNotifier : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private User friend;
|
||||||
|
private Channel publicChannel;
|
||||||
|
private Channel privateMessageChannel;
|
||||||
|
private TestContainer testContainer;
|
||||||
|
|
||||||
|
private int messageIdCounter;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
if (API is DummyAPIAccess daa)
|
||||||
|
{
|
||||||
|
daa.HandleRequest = dummyAPIHandleRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend = new User { Id = 0, Username = "Friend" };
|
||||||
|
publicChannel = new Channel { Id = 1, Name = "osu" };
|
||||||
|
privateMessageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM };
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel })
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
|
||||||
|
testContainer.ChatOverlay.Show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool dummyAPIHandleRequest(APIRequest request)
|
||||||
|
{
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
case GetMessagesRequest messagesRequest:
|
||||||
|
messagesRequest.TriggerSuccess(new List<Message>(0));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case CreateChannelRequest createChannelRequest:
|
||||||
|
var apiChatChannel = new APIChatChannel
|
||||||
|
{
|
||||||
|
RecentMessages = new List<Message>(0),
|
||||||
|
ChannelID = (int)createChannelRequest.Channel.Id
|
||||||
|
};
|
||||||
|
createChannelRequest.TriggerSuccess(apiChatChannel);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ListChannelsRequest listChannelsRequest:
|
||||||
|
listChannelsRequest.TriggerSuccess(new List<Channel>(1) { publicChannel });
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GetUpdatesRequest updatesRequest:
|
||||||
|
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
||||||
|
{
|
||||||
|
Messages = new List<Message>(0),
|
||||||
|
Presence = new List<Channel>(0)
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case JoinChannelRequest joinChannelRequest:
|
||||||
|
joinChannelRequest.TriggerSuccess();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPublicChannelMention()
|
||||||
|
{
|
||||||
|
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||||
|
|
||||||
|
AddStep("receive public message", () => receiveMessage(friend, publicChannel, "Hello everyone"));
|
||||||
|
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||||
|
|
||||||
|
AddStep("receive message containing mention", () => receiveMessage(friend, publicChannel, $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!"));
|
||||||
|
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||||
|
|
||||||
|
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||||
|
AddStep("click notification", clickNotification<MessageNotifier.MentionNotification>);
|
||||||
|
|
||||||
|
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddAssert("public channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPrivateMessageNotification()
|
||||||
|
{
|
||||||
|
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||||
|
|
||||||
|
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, $"Hello {API.LocalUser.Value.Username}"));
|
||||||
|
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||||
|
|
||||||
|
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||||
|
AddStep("click notification", clickNotification<MessageNotifier.PrivateMessageNotification>);
|
||||||
|
|
||||||
|
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddAssert("PM channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoNotificationWhenPMChannelOpen()
|
||||||
|
{
|
||||||
|
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||||
|
|
||||||
|
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, "you're reading this, right?"));
|
||||||
|
|
||||||
|
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoNotificationWhenMentionedInOpenPublicChannel()
|
||||||
|
{
|
||||||
|
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||||
|
|
||||||
|
AddStep("receive mention", () => receiveMessage(friend, publicChannel, $"{API.LocalUser.Value.Username.ToUpperInvariant()} has been reading this"));
|
||||||
|
|
||||||
|
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoNotificationOnSelfMention()
|
||||||
|
{
|
||||||
|
AddStep("switch to PM channel", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||||
|
|
||||||
|
AddStep("receive self-mention", () => receiveMessage(API.LocalUser.Value, publicChannel, $"my name is {API.LocalUser.Value.Username}"));
|
||||||
|
|
||||||
|
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoNotificationOnPMFromSelf()
|
||||||
|
{
|
||||||
|
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||||
|
|
||||||
|
AddStep("receive PM from self", () => receiveMessage(API.LocalUser.Value, privateMessageChannel, "hey hey"));
|
||||||
|
|
||||||
|
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNotificationsNotFiredTwice()
|
||||||
|
{
|
||||||
|
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||||
|
|
||||||
|
AddStep("receive same PM twice", () =>
|
||||||
|
{
|
||||||
|
var message = createMessage(friend, privateMessageChannel, "hey hey");
|
||||||
|
privateMessageChannel.AddNewMessages(message, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||||
|
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receiveMessage(User sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
|
||||||
|
|
||||||
|
private Message createMessage(User sender, Channel channel, string content) => new Message(messageIdCounter++)
|
||||||
|
{
|
||||||
|
Content = content,
|
||||||
|
Sender = sender,
|
||||||
|
ChannelId = channel.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
private void clickNotification<T>() where T : Notification
|
||||||
|
{
|
||||||
|
var notification = testContainer.NotificationOverlay.ChildrenOfType<T>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(notification);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
public ChannelManager ChannelManager { get; } = new ChannelManager();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
};
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
public ChatOverlay ChatOverlay { get; } = new ChatOverlay();
|
||||||
|
|
||||||
|
private readonly MessageNotifier messageNotifier = new MessageNotifier();
|
||||||
|
|
||||||
|
private readonly Channel[] channels;
|
||||||
|
|
||||||
|
public TestContainer(Channel[] channels)
|
||||||
|
{
|
||||||
|
this.channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
ChannelManager,
|
||||||
|
ChatOverlay,
|
||||||
|
NotificationOverlay,
|
||||||
|
messageNotifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
|
||||||
|
|
||||||
|
foreach (var channel in channels)
|
||||||
|
ChannelManager.JoinChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new AccuracyCircle(score)
|
new AccuracyCircle(score, true)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -2,15 +2,22 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Select.Leaderboards;
|
using osu.Game.Screens.Select.Leaderboards;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -23,32 +30,98 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly DialogOverlay dialogOverlay;
|
private readonly DialogOverlay dialogOverlay;
|
||||||
|
|
||||||
|
private ScoreManager scoreManager;
|
||||||
|
|
||||||
|
private RulesetStore rulesetStore;
|
||||||
|
private BeatmapManager beatmapManager;
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
|
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||||
|
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||||
|
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
public TestSceneBeatmapLeaderboard()
|
public TestSceneBeatmapLeaderboard()
|
||||||
{
|
{
|
||||||
Add(dialogOverlay = new DialogOverlay
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
Depth = -1
|
dialogOverlay = new DialogOverlay
|
||||||
|
{
|
||||||
|
Depth = -1
|
||||||
|
},
|
||||||
|
leaderboard = new FailableLeaderboard
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Size = new Vector2(550f, 450f),
|
||||||
|
Scope = BeatmapLeaderboardScope.Global,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalScoresDisplay()
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmapInfo = null;
|
||||||
|
|
||||||
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||||
|
|
||||||
|
AddStep(@"Set beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
|
|
||||||
|
leaderboard.Beatmap = beatmapInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(leaderboard = new FailableLeaderboard
|
clearScores();
|
||||||
{
|
checkCount(0);
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Size = new Vector2(550f, 450f),
|
|
||||||
Scope = BeatmapLeaderboardScope.Global,
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep(@"New Scores", newScores);
|
loadMoreScores(() => beatmapInfo);
|
||||||
|
checkCount(10);
|
||||||
|
|
||||||
|
loadMoreScores(() => beatmapInfo);
|
||||||
|
checkCount(20);
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGlobalScoresDisplay()
|
||||||
|
{
|
||||||
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global);
|
||||||
|
AddStep(@"New Scores", () => leaderboard.Scores = generateSampleScores(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPersonalBest()
|
||||||
|
{
|
||||||
AddStep(@"Show personal best", showPersonalBest);
|
AddStep(@"Show personal best", showPersonalBest);
|
||||||
|
AddStep("null personal best position", showPersonalBestWithNullPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceholderStates()
|
||||||
|
{
|
||||||
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
|
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
|
||||||
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
|
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
|
||||||
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
|
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
|
||||||
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
|
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
|
||||||
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
|
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
|
||||||
AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
|
AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapStates()
|
||||||
|
{
|
||||||
foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus)))
|
foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus)))
|
||||||
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
|
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
|
||||||
AddStep("null personal best position", showPersonalBestWithNullPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPersonalBestWithNullPosition()
|
private void showPersonalBestWithNullPosition()
|
||||||
@ -96,9 +169,26 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void newScores()
|
private void loadMoreScores(Func<BeatmapInfo> beatmapInfo)
|
||||||
{
|
{
|
||||||
var scores = new[]
|
AddStep(@"Load new scores via manager", () =>
|
||||||
|
{
|
||||||
|
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||||
|
scoreManager.Import(score).Wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearScores()
|
||||||
|
{
|
||||||
|
AddStep("Clear all scores", () => scoreManager.Delete(scoreManager.GetAllUsableScores()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCount(int expected) =>
|
||||||
|
AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count() == expected);
|
||||||
|
|
||||||
|
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
{
|
{
|
||||||
new ScoreInfo
|
new ScoreInfo
|
||||||
{
|
{
|
||||||
@ -107,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 6602580,
|
Id = 6602580,
|
||||||
@ -125,6 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 4608074,
|
Id = 4608074,
|
||||||
@ -143,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 1014222,
|
Id = 1014222,
|
||||||
@ -161,6 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 1541390,
|
Id = 1541390,
|
||||||
@ -179,6 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 2243452,
|
Id = 2243452,
|
||||||
@ -197,6 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 2705430,
|
Id = 2705430,
|
||||||
@ -215,6 +311,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 7151382,
|
Id = 7151382,
|
||||||
@ -233,6 +330,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 2051389,
|
Id = 2051389,
|
||||||
@ -251,6 +349,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 6169483,
|
Id = 6169483,
|
||||||
@ -269,6 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
MaxCombo = 244,
|
MaxCombo = 244,
|
||||||
TotalScore = 1707827,
|
TotalScore = 1707827,
|
||||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
Beatmap = beatmap,
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
Id = 6702666,
|
Id = 6702666,
|
||||||
@ -281,8 +381,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
leaderboard.Scores = scores;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showBeatmapWithStatus(BeatmapSetOnlineStatus status)
|
private void showBeatmapWithStatus(BeatmapSetOnlineStatus status)
|
||||||
|
@ -101,10 +101,20 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Rulesets ordered descending by their respective recommended difficulties.
|
/// Rulesets ordered descending by their respective recommended difficulties.
|
||||||
/// The currently selected ruleset will always be first.
|
/// The currently selected ruleset will always be first.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
private IEnumerable<RulesetInfo> orderedRulesets =>
|
private IEnumerable<RulesetInfo> orderedRulesets
|
||||||
recommendedDifficultyMapping
|
{
|
||||||
.OrderByDescending(pair => pair.Value).Select(pair => pair.Key).Where(r => !r.Equals(ruleset.Value))
|
get
|
||||||
.Prepend(ruleset.Value);
|
{
|
||||||
|
if (LoadState < LoadState.Ready || ruleset.Value == null)
|
||||||
|
return Enumerable.Empty<RulesetInfo>();
|
||||||
|
|
||||||
|
return recommendedDifficultyMapping
|
||||||
|
.OrderByDescending(pair => pair.Value)
|
||||||
|
.Select(pair => pair.Key)
|
||||||
|
.Where(r => !r.Equals(ruleset.Value))
|
||||||
|
.Prepend(ruleset.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
47
osu.Game/Beatmaps/MetadataUtils.cs
Normal file
47
osu.Game/Beatmaps/MetadataUtils.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Groups utility methods used to handle beatmap metadata.
|
||||||
|
/// </summary>
|
||||||
|
public static class MetadataUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <see langword="true"/> if the character <paramref name="c"/> can be used in <see cref="BeatmapMetadata.Artist"/> and <see cref="BeatmapMetadata.Title"/> fields.
|
||||||
|
/// Characters not matched by this method can be placed in <see cref="BeatmapMetadata.ArtistUnicode"/> and <see cref="BeatmapMetadata.TitleUnicode"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsRomanised(char c) => c <= 0xFF;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <see langword="true"/> if the string <paramref name="str"/> can be used in <see cref="BeatmapMetadata.Artist"/> and <see cref="BeatmapMetadata.Title"/> fields.
|
||||||
|
/// Strings not matched by this method can be placed in <see cref="BeatmapMetadata.ArtistUnicode"/> and <see cref="BeatmapMetadata.TitleUnicode"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsRomanised(string? str) => string.IsNullOrEmpty(str) || str.All(IsRomanised);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a copy of <paramref name="str"/> with all characters that do not match <see cref="IsRomanised(char)"/> removed.
|
||||||
|
/// </summary>
|
||||||
|
public static string StripNonRomanisedCharacters(string? str)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var stringBuilder = new StringBuilder(str.Length);
|
||||||
|
|
||||||
|
foreach (var c in str)
|
||||||
|
{
|
||||||
|
if (IsRomanised(c))
|
||||||
|
stringBuilder.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.ToString().Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -61,6 +61,9 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
|
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
|
||||||
|
|
||||||
|
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
|
||||||
|
SetDefault(OsuSetting.NotifyOnPrivateMessage, true);
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
|
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
|
||||||
|
|
||||||
@ -259,6 +262,8 @@ namespace osu.Game.Configuration
|
|||||||
ScalingSizeY,
|
ScalingSizeY,
|
||||||
UIScale,
|
UIScale,
|
||||||
IntroSequence,
|
IntroSequence,
|
||||||
|
NotifyOnUsernameMentioned,
|
||||||
|
NotifyOnPrivateMessage,
|
||||||
UIHoldActivationDelay,
|
UIHoldActivationDelay,
|
||||||
HitLighting,
|
HitLighting,
|
||||||
MenuBackgroundSource,
|
MenuBackgroundSource,
|
||||||
|
@ -1,20 +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 System.ComponentModel;
|
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
|
||||||
{
|
|
||||||
public enum RankingType
|
|
||||||
{
|
|
||||||
Local,
|
|
||||||
|
|
||||||
[Description("Global")]
|
|
||||||
Top,
|
|
||||||
|
|
||||||
[Description("Selected Mods")]
|
|
||||||
SelectedMod,
|
|
||||||
Friends,
|
|
||||||
Country
|
|
||||||
}
|
|
||||||
}
|
|
@ -99,5 +99,14 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
// ensure we're not loading in without a transition.
|
// ensure we're not loading in without a transition.
|
||||||
this.FadeInFromZero(200, Easing.InOutSine);
|
this.FadeInFromZero(200, Easing.InOutSine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Equals(Background other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other)) return false;
|
||||||
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
|
||||||
|
return other.GetType() == GetType()
|
||||||
|
&& ((SeasonalBackground)other).url == url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet);
|
protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet);
|
||||||
|
|
||||||
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
||||||
{
|
{
|
||||||
this.sampleSet = sampleSet;
|
this.sampleSet = sampleSet;
|
||||||
}
|
}
|
||||||
|
@ -107,10 +107,10 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool playedPopInSound;
|
|
||||||
|
|
||||||
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
||||||
{
|
{
|
||||||
|
bool didChange = state.NewValue != state.OldValue;
|
||||||
|
|
||||||
switch (state.NewValue)
|
switch (state.NewValue)
|
||||||
{
|
{
|
||||||
case Visibility.Visible:
|
case Visibility.Visible:
|
||||||
@ -121,18 +121,15 @@ namespace osu.Game.Graphics.Containers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
samplePopIn?.Play();
|
if (didChange)
|
||||||
playedPopInSound = true;
|
samplePopIn?.Play();
|
||||||
|
|
||||||
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Visibility.Hidden:
|
case Visibility.Hidden:
|
||||||
if (playedPopInSound)
|
if (didChange)
|
||||||
{
|
|
||||||
samplePopOut?.Play();
|
samplePopOut?.Play();
|
||||||
playedPopInSound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
||||||
break;
|
break;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -23,9 +22,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private const int text_size = 17;
|
private const int text_size = 17;
|
||||||
private const int transition_length = 80;
|
private const int transition_length = 80;
|
||||||
|
|
||||||
private Sample sampleClick;
|
|
||||||
private Sample sampleHover;
|
|
||||||
|
|
||||||
private TextContainer text;
|
private TextContainer text;
|
||||||
|
|
||||||
public DrawableOsuMenuItem(MenuItem item)
|
public DrawableOsuMenuItem(MenuItem item)
|
||||||
@ -36,12 +32,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleHover = audio.Samples.Get(@"UI/generic-hover");
|
|
||||||
sampleClick = audio.Samples.Get(@"UI/generic-select");
|
|
||||||
|
|
||||||
BackgroundColour = Color4.Transparent;
|
BackgroundColour = Color4.Transparent;
|
||||||
BackgroundColourHover = Color4Extensions.FromHex(@"172023");
|
BackgroundColourHover = Color4Extensions.FromHex(@"172023");
|
||||||
|
|
||||||
|
AddInternal(new HoverClickSounds());
|
||||||
|
|
||||||
updateTextColour();
|
updateTextColour();
|
||||||
|
|
||||||
Item.Action.BindDisabledChanged(_ => updateState(), true);
|
Item.Action.BindDisabledChanged(_ => updateState(), true);
|
||||||
@ -84,7 +79,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
if (IsHovered && !Item.Action.Disabled)
|
if (IsHovered && !Item.Action.Disabled)
|
||||||
{
|
{
|
||||||
sampleHover.Play();
|
|
||||||
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
||||||
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
@ -95,12 +89,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
sampleClick.Play();
|
|
||||||
return base.OnClick(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed override Drawable CreateContent() => text = CreateTextContainer();
|
protected sealed override Drawable CreateContent() => text = CreateTextContainer();
|
||||||
protected virtual TextContainer CreateTextContainer() => new TextContainer();
|
protected virtual TextContainer CreateTextContainer() => new TextContainer();
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// Array of button codes which should trigger the click sound.
|
/// Array of button codes which should trigger the click sound.
|
||||||
/// If this optional parameter is omitted or set to <code>null</code>, the click sound will only be played on left click.
|
/// If this optional parameter is omitted or set to <code>null</code>, the click sound will only be played on left click.
|
||||||
/// </param>
|
/// </param>
|
||||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal, MouseButton[] buttons = null)
|
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[] buttons = null)
|
||||||
: base(sampleSet)
|
: base(sampleSet)
|
||||||
{
|
{
|
||||||
this.buttons = buttons ?? new[] { MouseButton.Left };
|
this.buttons = buttons ?? new[] { MouseButton.Left };
|
||||||
@ -45,7 +45,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}");
|
sampleClick = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-select")
|
||||||
|
?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
osu.Game/Graphics/UserInterface/HoverSampleSet.cs
Normal file
25
osu.Game/Graphics/UserInterface/HoverSampleSet.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
public enum HoverSampleSet
|
||||||
|
{
|
||||||
|
[Description("default")]
|
||||||
|
Default,
|
||||||
|
|
||||||
|
[Description("button")]
|
||||||
|
Button,
|
||||||
|
|
||||||
|
[Description("softer")]
|
||||||
|
Soft,
|
||||||
|
|
||||||
|
[Description("toolbar")]
|
||||||
|
Toolbar,
|
||||||
|
|
||||||
|
[Description("songselect")]
|
||||||
|
SongSelect
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
@ -22,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected readonly HoverSampleSet SampleSet;
|
protected readonly HoverSampleSet SampleSet;
|
||||||
|
|
||||||
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Default)
|
||||||
{
|
{
|
||||||
SampleSet = sampleSet;
|
SampleSet = sampleSet;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -31,7 +30,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, SessionStatics statics)
|
private void load(AudioManager audio, SessionStatics statics)
|
||||||
{
|
{
|
||||||
sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
sampleHover = audio.Samples.Get($@"UI/{SampleSet.GetDescription()}-hover")
|
||||||
|
?? audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-hover");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PlayHoverSample()
|
public override void PlayHoverSample()
|
||||||
@ -40,22 +40,4 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
sampleHover.Play();
|
sampleHover.Play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum HoverSampleSet
|
|
||||||
{
|
|
||||||
[Description("")]
|
|
||||||
Loud,
|
|
||||||
|
|
||||||
[Description("-soft")]
|
|
||||||
Normal,
|
|
||||||
|
|
||||||
[Description("-softer")]
|
|
||||||
Soft,
|
|
||||||
|
|
||||||
[Description("-toolbar")]
|
|
||||||
Toolbar,
|
|
||||||
|
|
||||||
[Description("-songselect")]
|
|
||||||
SongSelect
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly Box hover;
|
private readonly Box hover;
|
||||||
|
|
||||||
public OsuAnimatedButton()
|
public OsuAnimatedButton()
|
||||||
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
base.Content.Add(content = new Container
|
base.Content.Add(content = new Container
|
||||||
{
|
{
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected Box Background;
|
protected Box Background;
|
||||||
protected SpriteText SpriteText;
|
protected SpriteText SpriteText;
|
||||||
|
|
||||||
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Loud)
|
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
Height = 40;
|
Height = 40;
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
public bool ReadOnly
|
public bool ReadOnly
|
||||||
{
|
{
|
||||||
|
get => Component.ReadOnly;
|
||||||
set => Component.ReadOnly = value;
|
set => Component.ReadOnly = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,14 +46,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
Component.BorderColour = colours.Blue;
|
Component.BorderColour = colours.Blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox
|
protected virtual OsuTextBox CreateTextBox() => new OsuTextBox();
|
||||||
{
|
|
||||||
CommitOnFocusLost = true,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
CornerRadius = CORNER_RADIUS,
|
|
||||||
};
|
|
||||||
|
|
||||||
public override bool AcceptsFocus => true;
|
public override bool AcceptsFocus => true;
|
||||||
|
|
||||||
@ -64,6 +58,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
|
|
||||||
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
|
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
|
||||||
{
|
{
|
||||||
|
t.CommitOnFocusLost = true;
|
||||||
|
t.Anchor = Anchor.Centre;
|
||||||
|
t.Origin = Anchor.Centre;
|
||||||
|
t.RelativeSizeAxes = Axes.X;
|
||||||
|
t.CornerRadius = CORNER_RADIUS;
|
||||||
|
|
||||||
t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText);
|
t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,17 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
public static class ChatStrings
|
public static class ChatStrings
|
||||||
{
|
{
|
||||||
private const string prefix = "osu.Game.Localisation.Chat";
|
private const string prefix = @"osu.Game.Localisation.Chat";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "chat"
|
/// "chat"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "chat");
|
public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"chat");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "join the real-time discussion"
|
/// "join the real-time discussion"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "join the real-time discussion");
|
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
public static class CommonStrings
|
public static class CommonStrings
|
||||||
{
|
{
|
||||||
private const string prefix = "osu.Game.Localisation.Common";
|
private const string prefix = @"osu.Game.Localisation.Common";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Cancel"
|
/// "Cancel"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Cancel => new TranslatableString(getKey("cancel"), "Cancel");
|
public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
public enum Language
|
public enum Language
|
||||||
{
|
{
|
||||||
[Description("English")]
|
[Description(@"English")]
|
||||||
en,
|
en,
|
||||||
|
|
||||||
[Description("日本語")]
|
[Description(@"日本語")]
|
||||||
ja
|
ja
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,17 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
public static class NotificationsStrings
|
public static class NotificationsStrings
|
||||||
{
|
{
|
||||||
private const string prefix = "osu.Game.Localisation.Notifications";
|
private const string prefix = @"osu.Game.Localisation.Notifications";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "notifications"
|
/// "notifications"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "notifications");
|
public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"notifications");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "waiting for 'ya"
|
/// "waiting for 'ya"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "waiting for 'ya");
|
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"waiting for 'ya");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,17 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
public static class NowPlayingStrings
|
public static class NowPlayingStrings
|
||||||
{
|
{
|
||||||
private const string prefix = "osu.Game.Localisation.NowPlaying";
|
private const string prefix = @"osu.Game.Localisation.NowPlaying";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "now playing"
|
/// "now playing"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "now playing");
|
public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"now playing");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "manage the currently playing track"
|
/// "manage the currently playing track"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "manage the currently playing track");
|
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"manage the currently playing track");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Resources;
|
using System.Resources;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -34,7 +35,29 @@ namespace osu.Game.Localisation
|
|||||||
lock (resourceManagers)
|
lock (resourceManagers)
|
||||||
{
|
{
|
||||||
if (!resourceManagers.TryGetValue(ns, out var manager))
|
if (!resourceManagers.TryGetValue(ns, out var manager))
|
||||||
resourceManagers[ns] = manager = new ResourceManager(ns, GetType().Assembly);
|
{
|
||||||
|
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||||
|
|
||||||
|
// Traverse backwards through periods in the namespace to find a matching assembly.
|
||||||
|
string assemblyName = ns;
|
||||||
|
|
||||||
|
while (!string.IsNullOrEmpty(assemblyName))
|
||||||
|
{
|
||||||
|
var matchingAssembly = loadedAssemblies.FirstOrDefault(asm => asm.GetName().Name == assemblyName);
|
||||||
|
|
||||||
|
if (matchingAssembly != null)
|
||||||
|
{
|
||||||
|
resourceManagers[ns] = manager = new ResourceManager(ns, matchingAssembly);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastIndex = Math.Max(0, assemblyName.LastIndexOf('.'));
|
||||||
|
assemblyName = assemblyName.Substring(0, lastIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manager == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -7,17 +7,17 @@ namespace osu.Game.Localisation
|
|||||||
{
|
{
|
||||||
public static class SettingsStrings
|
public static class SettingsStrings
|
||||||
{
|
{
|
||||||
private const string prefix = "osu.Game.Localisation.Settings";
|
private const string prefix = @"osu.Game.Localisation.Settings";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "settings"
|
/// "settings"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderTitle => new TranslatableString(getKey("header_title"), "settings");
|
public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"settings");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "change the way osu! behaves"
|
/// "change the way osu! behaves"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString HeaderDescription => new TranslatableString(getKey("header_description"), "change the way osu! behaves");
|
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way osu! behaves");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,11 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
public class CreateChannelRequest : APIRequest<APIChatChannel>
|
public class CreateChannelRequest : APIRequest<APIChatChannel>
|
||||||
{
|
{
|
||||||
private readonly Channel channel;
|
public readonly Channel Channel;
|
||||||
|
|
||||||
public CreateChannelRequest(Channel channel)
|
public CreateChannelRequest(Channel channel)
|
||||||
{
|
{
|
||||||
this.channel = channel;
|
Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override WebRequest CreateWebRequest()
|
||||||
@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests
|
|||||||
req.Method = HttpMethod.Post;
|
req.Method = HttpMethod.Post;
|
||||||
|
|
||||||
req.AddParameter("type", $"{ChannelType.PM}");
|
req.AddParameter("type", $"{ChannelType.PM}");
|
||||||
req.AddParameter("target_id", $"{channel.Users.First().Id}");
|
req.AddParameter("target_id", $"{Channel.Users.First().Id}");
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
@ -63,5 +63,7 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||||
public override int GetHashCode() => Id.GetHashCode();
|
public override int GetHashCode() => Id.GetHashCode();
|
||||||
|
|
||||||
|
public override string ToString() => $"[{ChannelId}] ({Id}) {Sender}: {Content}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
181
osu.Game/Online/Chat/MessageNotifier.cs
Normal file
181
osu.Game/Online/Chat/MessageNotifier.cs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Chat
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Component that handles creating and posting notifications for incoming messages.
|
||||||
|
/// </summary>
|
||||||
|
public class MessageNotifier : Component
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private NotificationOverlay notifications { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ChatOverlay chatOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ChannelManager channelManager { get; set; }
|
||||||
|
|
||||||
|
private Bindable<bool> notifyOnUsername;
|
||||||
|
private Bindable<bool> notifyOnPrivateMessage;
|
||||||
|
|
||||||
|
private readonly IBindable<User> localUser = new Bindable<User>();
|
||||||
|
private readonly IBindableList<Channel> joinedChannels = new BindableList<Channel>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config, IAPIProvider api)
|
||||||
|
{
|
||||||
|
notifyOnUsername = config.GetBindable<bool>(OsuSetting.NotifyOnUsernameMentioned);
|
||||||
|
notifyOnPrivateMessage = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage);
|
||||||
|
|
||||||
|
localUser.BindTo(api.LocalUser);
|
||||||
|
joinedChannels.BindTo(channelManager.JoinedChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
joinedChannels.BindCollectionChanged(channelsChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
foreach (var channel in e.NewItems.Cast<Channel>())
|
||||||
|
channel.NewMessagesArrived += checkNewMessages;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
foreach (var channel in e.OldItems.Cast<Channel>())
|
||||||
|
channel.NewMessagesArrived -= checkNewMessages;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNewMessages(IEnumerable<Message> messages)
|
||||||
|
{
|
||||||
|
if (!messages.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId);
|
||||||
|
|
||||||
|
if (channel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Only send notifications, if ChatOverlay and the target channel aren't visible.
|
||||||
|
if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var message in messages.OrderByDescending(m => m.Id))
|
||||||
|
{
|
||||||
|
// ignore messages that already have been read
|
||||||
|
if (message.Id <= channel.LastReadId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (message.Sender.Id == localUser.Value.Id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// check for private messages first to avoid both posting two notifications about the same message
|
||||||
|
if (checkForPMs(channel, message))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
checkForMentions(channel, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the user enabled private message notifications and whether specified <paramref name="message"/> is a direct message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">The channel associated to the <paramref name="message"/></param>
|
||||||
|
/// <param name="message">The message to be checked</param>
|
||||||
|
/// <returns>Whether a notification was fired.</returns>
|
||||||
|
private bool checkForPMs(Channel channel, Message message)
|
||||||
|
{
|
||||||
|
if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForMentions(Channel channel, Message message)
|
||||||
|
{
|
||||||
|
if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return;
|
||||||
|
|
||||||
|
notifications.Post(new MentionNotification(message.Sender.Username, channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if <paramref name="message"/> contains <paramref name="username"/>.
|
||||||
|
/// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces).
|
||||||
|
/// </summary>
|
||||||
|
private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public class PrivateMessageNotification : OpenChannelNotification
|
||||||
|
{
|
||||||
|
public PrivateMessageNotification(string username, Channel channel)
|
||||||
|
: base(channel)
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.Envelope;
|
||||||
|
Text = $"You received a private message from '{username}'. Click to read it!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MentionNotification : OpenChannelNotification
|
||||||
|
{
|
||||||
|
public MentionNotification(string username, Channel channel)
|
||||||
|
: base(channel)
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.At;
|
||||||
|
Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class OpenChannelNotification : SimpleNotification
|
||||||
|
{
|
||||||
|
protected OpenChannelNotification(Channel channel)
|
||||||
|
{
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Channel channel;
|
||||||
|
|
||||||
|
public override bool IsImportant => false;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager)
|
||||||
|
{
|
||||||
|
IconBackgound.Colour = colours.PurpleDark;
|
||||||
|
|
||||||
|
Activated = delegate
|
||||||
|
{
|
||||||
|
notificationOverlay.Hide();
|
||||||
|
chatOverlay.Show();
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,9 +44,9 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
private IEnumerable<TScoreInfo> scores;
|
private ICollection<TScoreInfo> scores;
|
||||||
|
|
||||||
public IEnumerable<TScoreInfo> Scores
|
public ICollection<TScoreInfo> Scores
|
||||||
{
|
{
|
||||||
get => scores;
|
get => scores;
|
||||||
set
|
set
|
||||||
@ -126,7 +126,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
scope = value;
|
scope = value;
|
||||||
UpdateScores();
|
RefreshScores();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
case PlaceholderState.NetworkFailure:
|
case PlaceholderState.NetworkFailure:
|
||||||
replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync)
|
replacePlaceholder(new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync)
|
||||||
{
|
{
|
||||||
Action = UpdateScores,
|
Action = RefreshScores
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -254,8 +254,6 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
apiState.BindValueChanged(onlineStateChanged, true);
|
apiState.BindValueChanged(onlineStateChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshScores() => UpdateScores();
|
|
||||||
|
|
||||||
private APIRequest getScoresRequest;
|
private APIRequest getScoresRequest;
|
||||||
|
|
||||||
protected abstract bool IsOnlineScope { get; }
|
protected abstract bool IsOnlineScope { get; }
|
||||||
@ -267,12 +265,14 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
case APIState.Online:
|
case APIState.Online:
|
||||||
case APIState.Offline:
|
case APIState.Offline:
|
||||||
if (IsOnlineScope)
|
if (IsOnlineScope)
|
||||||
UpdateScores();
|
RefreshScores();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
public void RefreshScores() => Scheduler.AddOnce(UpdateScores);
|
||||||
|
|
||||||
protected void UpdateScores()
|
protected void UpdateScores()
|
||||||
{
|
{
|
||||||
// don't display any scores or placeholder until the first Scores_Set has been called.
|
// don't display any scores or placeholder until the first Scores_Set has been called.
|
||||||
@ -290,7 +290,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
getScoresRequest = FetchScores(scores => Schedule(() =>
|
getScoresRequest = FetchScores(scores => Schedule(() =>
|
||||||
{
|
{
|
||||||
Scores = scores;
|
Scores = scores.ToArray();
|
||||||
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
|
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -426,9 +426,12 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
// The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database
|
// The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database
|
||||||
// to ensure all the required data for presenting a replay are present.
|
// to ensure all the required data for presenting a replay are present.
|
||||||
var databasedScoreInfo = score.OnlineScoreID != null
|
ScoreInfo databasedScoreInfo = null;
|
||||||
? ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID)
|
|
||||||
: ScoreManager.Query(s => s.Hash == score.Hash);
|
if (score.OnlineScoreID != null)
|
||||||
|
databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID);
|
||||||
|
|
||||||
|
databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash);
|
||||||
|
|
||||||
if (databasedScoreInfo == null)
|
if (databasedScoreInfo == null)
|
||||||
{
|
{
|
||||||
@ -712,7 +715,6 @@ namespace osu.Game
|
|||||||
PostNotification = n => notifications.Post(n),
|
PostNotification = n => notifications.Post(n),
|
||||||
}, Add, true);
|
}, Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(difficultyRecommender, Add);
|
|
||||||
loadComponentSingleFile(stableImportManager, Add);
|
loadComponentSingleFile(stableImportManager, Add);
|
||||||
|
|
||||||
loadComponentSingleFile(screenshotManager, Add);
|
loadComponentSingleFile(screenshotManager, Add);
|
||||||
@ -728,6 +730,7 @@ namespace osu.Game
|
|||||||
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||||
|
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
||||||
loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true);
|
loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true);
|
||||||
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||||
@ -754,6 +757,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible;
|
chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible;
|
||||||
|
|
||||||
|
Add(difficultyRecommender);
|
||||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||||
Add(new MusicKeyBindingHandler());
|
Add(new MusicKeyBindingHandler());
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -126,15 +127,15 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
Padding = new MarginPadding { Horizontal = 10 },
|
Padding = new MarginPadding { Horizontal = 10 },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
generalFilter = new BeatmapSearchMultipleSelectionFilterRow<SearchGeneral>(@"General"),
|
generalFilter = new BeatmapSearchMultipleSelectionFilterRow<SearchGeneral>(BeatmapsStrings.ListingSearchFiltersGeneral),
|
||||||
modeFilter = new BeatmapSearchRulesetFilterRow(),
|
modeFilter = new BeatmapSearchRulesetFilterRow(),
|
||||||
categoryFilter = new BeatmapSearchFilterRow<SearchCategory>(@"Categories"),
|
categoryFilter = new BeatmapSearchFilterRow<SearchCategory>(BeatmapsStrings.ListingSearchFiltersStatus),
|
||||||
genreFilter = new BeatmapSearchFilterRow<SearchGenre>(@"Genre"),
|
genreFilter = new BeatmapSearchFilterRow<SearchGenre>(BeatmapsStrings.ListingSearchFiltersGenre),
|
||||||
languageFilter = new BeatmapSearchFilterRow<SearchLanguage>(@"Language"),
|
languageFilter = new BeatmapSearchFilterRow<SearchLanguage>(BeatmapsStrings.ListingSearchFiltersLanguage),
|
||||||
extraFilter = new BeatmapSearchMultipleSelectionFilterRow<SearchExtra>(@"Extra"),
|
extraFilter = new BeatmapSearchMultipleSelectionFilterRow<SearchExtra>(BeatmapsStrings.ListingSearchFiltersExtra),
|
||||||
ranksFilter = new BeatmapSearchScoreFilterRow(),
|
ranksFilter = new BeatmapSearchScoreFilterRow(),
|
||||||
playedFilter = new BeatmapSearchFilterRow<SearchPlayed>(@"Played"),
|
playedFilter = new BeatmapSearchFilterRow<SearchPlayed>(BeatmapsStrings.ListingSearchFiltersPlayed),
|
||||||
explicitContentFilter = new BeatmapSearchFilterRow<SearchExplicit>(@"Explicit Content"),
|
explicitContentFilter = new BeatmapSearchFilterRow<SearchExplicit>(BeatmapsStrings.ListingSearchFiltersNsfw),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,7 +173,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
|
|
||||||
public BeatmapSearchTextBox()
|
public BeatmapSearchTextBox()
|
||||||
{
|
{
|
||||||
PlaceholderText = @"type in keywords...";
|
PlaceholderText = BeatmapsStrings.ListingSearchPrompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
@ -11,8 +11,8 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using Humanizer;
|
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapListing
|
namespace osu.Game.Overlays.BeatmapListing
|
||||||
{
|
{
|
||||||
@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
set => current.Current = value;
|
set => current.Current = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeatmapSearchFilterRow(string headerName)
|
public BeatmapSearchFilterRow(LocalisableString header)
|
||||||
{
|
{
|
||||||
Drawable filter;
|
Drawable filter;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Font = OsuFont.GetFont(size: 13),
|
Font = OsuFont.GetFont(size: 13),
|
||||||
Text = headerName.Titleize()
|
Text = header
|
||||||
},
|
},
|
||||||
filter = CreateFilter()
|
filter = CreateFilter()
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapListing
|
namespace osu.Game.Overlays.BeatmapListing
|
||||||
@ -19,8 +20,8 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
|
|
||||||
private MultipleSelectionFilter filter;
|
private MultipleSelectionFilter filter;
|
||||||
|
|
||||||
public BeatmapSearchMultipleSelectionFilterRow(string headerName)
|
public BeatmapSearchMultipleSelectionFilterRow(LocalisableString header)
|
||||||
: base(headerName)
|
: base(header)
|
||||||
{
|
{
|
||||||
Current.BindTo(filter.Current);
|
Current.BindTo(filter.Current);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapListing
|
namespace osu.Game.Overlays.BeatmapListing
|
||||||
@ -10,7 +11,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
public class BeatmapSearchRulesetFilterRow : BeatmapSearchFilterRow<RulesetInfo>
|
public class BeatmapSearchRulesetFilterRow : BeatmapSearchFilterRow<RulesetInfo>
|
||||||
{
|
{
|
||||||
public BeatmapSearchRulesetFilterRow()
|
public BeatmapSearchRulesetFilterRow()
|
||||||
: base(@"Mode")
|
: base(BeatmapsStrings.ListingSearchFiltersMode)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapListing
|
namespace osu.Game.Overlays.BeatmapListing
|
||||||
@ -11,7 +13,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow<ScoreRank>
|
public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow<ScoreRank>
|
||||||
{
|
{
|
||||||
public BeatmapSearchScoreFilterRow()
|
public BeatmapSearchScoreFilterRow()
|
||||||
: base(@"Rank Achieved")
|
: base(BeatmapsStrings.ListingSearchFiltersRank)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,18 +33,36 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string LabelFor(ScoreRank value)
|
protected override LocalisableString LabelFor(ScoreRank value)
|
||||||
{
|
{
|
||||||
switch (value)
|
switch (value)
|
||||||
{
|
{
|
||||||
case ScoreRank.XH:
|
case ScoreRank.XH:
|
||||||
return @"Silver SS";
|
return BeatmapsStrings.RankXH;
|
||||||
|
|
||||||
|
case ScoreRank.X:
|
||||||
|
return BeatmapsStrings.RankX;
|
||||||
|
|
||||||
case ScoreRank.SH:
|
case ScoreRank.SH:
|
||||||
return @"Silver S";
|
return BeatmapsStrings.RankSH;
|
||||||
|
|
||||||
|
case ScoreRank.S:
|
||||||
|
return BeatmapsStrings.RankS;
|
||||||
|
|
||||||
|
case ScoreRank.A:
|
||||||
|
return BeatmapsStrings.RankA;
|
||||||
|
|
||||||
|
case ScoreRank.B:
|
||||||
|
return BeatmapsStrings.RankB;
|
||||||
|
|
||||||
|
case ScoreRank.C:
|
||||||
|
return BeatmapsStrings.RankC;
|
||||||
|
|
||||||
|
case ScoreRank.D:
|
||||||
|
return BeatmapsStrings.RankD;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return value.GetDescription();
|
throw new ArgumentException("Unsupported value.", nameof(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -66,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the label text to be used for the supplied <paramref name="value"/>.
|
/// Returns the label text to be used for the supplied <paramref name="value"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual string LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString();
|
protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString();
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -232,7 +233,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = @"... nope, nothing found.",
|
Text = BeatmapsStrings.ListingSearchNotFoundQuote,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -25,8 +24,6 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
|
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
|
||||||
|
|
||||||
private Sample sampleBack;
|
|
||||||
|
|
||||||
private List<APIChangelogBuild> builds;
|
private List<APIChangelogBuild> builds;
|
||||||
|
|
||||||
protected List<APIUpdateStream> Streams;
|
protected List<APIUpdateStream> Streams;
|
||||||
@ -41,8 +38,6 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
Header.Build.BindTarget = Current;
|
Header.Build.BindTarget = Current;
|
||||||
|
|
||||||
sampleBack = audio.Samples.Get(@"UI/generic-select-soft");
|
|
||||||
|
|
||||||
Current.BindValueChanged(e =>
|
Current.BindValueChanged(e =>
|
||||||
{
|
{
|
||||||
if (e.NewValue != null)
|
if (e.NewValue != null)
|
||||||
@ -108,7 +103,6 @@ namespace osu.Game.Overlays
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Current.Value = null;
|
Current.Value = null;
|
||||||
sampleBack?.Play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -6,18 +6,18 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osuTK.Graphics;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Cursor;
|
|
||||||
using osu.Game.Online.Chat;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
// 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.Game.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Online
|
||||||
|
{
|
||||||
|
public class AlertsAndPrivacySettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override string Header => "Alerts and Privacy";
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "Show a notification when someone mentions your name",
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.NotifyOnUsernameMentioned)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "Show a notification when you receive a private message",
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new WebSettings(),
|
new WebSettings(),
|
||||||
|
new AlertsAndPrivacySettings(),
|
||||||
new IntegrationSettings()
|
new IntegrationSettings()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,20 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class SettingsNumberBox : SettingsItem<string>
|
public class SettingsNumberBox : SettingsItem<string>
|
||||||
{
|
{
|
||||||
protected override Drawable CreateControl() => new OsuNumberBox
|
protected override Drawable CreateControl() => new NumberBox
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 5 },
|
Margin = new MarginPadding { Top = 5 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public class NumberBox : SettingsTextBox.TextBox
|
||||||
|
{
|
||||||
|
protected override bool CanAddCharacter(char character) => char.IsNumber(character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,60 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class SettingsTextBox : SettingsItem<string>
|
public class SettingsTextBox : SettingsItem<string>
|
||||||
{
|
{
|
||||||
protected override Drawable CreateControl() => new OsuTextBox
|
protected override Drawable CreateControl() => new TextBox
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 5 },
|
Margin = new MarginPadding { Top = 5 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
CommitOnFocusLost = true,
|
CommitOnFocusLost = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public class TextBox : OsuTextBox
|
||||||
|
{
|
||||||
|
private const float border_thickness = 3;
|
||||||
|
|
||||||
|
private Color4 borderColourFocused;
|
||||||
|
private Color4 borderColourUnfocused;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour)
|
||||||
|
{
|
||||||
|
borderColourUnfocused = colour.Gray4.Opacity(0.5f);
|
||||||
|
borderColourFocused = BorderColour;
|
||||||
|
|
||||||
|
updateBorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocus(e);
|
||||||
|
|
||||||
|
updateBorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnFocusLost(e);
|
||||||
|
|
||||||
|
updateBorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBorder()
|
||||||
|
{
|
||||||
|
BorderThickness = border_thickness;
|
||||||
|
BorderColour = HasFocus ? borderColourFocused : borderColourUnfocused;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
{
|
{
|
||||||
public class DifficultyAttributes
|
public class DifficultyAttributes
|
||||||
{
|
{
|
||||||
public Mod[] Mods;
|
public Mod[] Mods { get; set; }
|
||||||
public Skill[] Skills;
|
public Skill[] Skills { get; set; }
|
||||||
|
|
||||||
public double StarRating;
|
public double StarRating { get; set; }
|
||||||
public int MaxCombo;
|
public int MaxCombo { get; set; }
|
||||||
|
|
||||||
public DifficultyAttributes()
|
public DifficultyAttributes()
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly OsuNumberBox seedNumberBox;
|
private readonly SettingsNumberBox.NumberBox seedNumberBox;
|
||||||
|
|
||||||
public SeedControl()
|
public SeedControl()
|
||||||
{
|
{
|
||||||
@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
seedNumberBox = new OsuNumberBox
|
seedNumberBox = new SettingsNumberBox.NumberBox
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
CommitOnFocusLost = true
|
CommitOnFocusLost = true
|
||||||
|
@ -716,7 +716,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
if (HitObject != null)
|
if (HitObject != null)
|
||||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
|
||||||
CurrentSkin.SourceChanged -= skinSourceChanged;
|
if (CurrentSkin != null)
|
||||||
|
CurrentSkin.SourceChanged -= skinSourceChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs
Normal file
19
osu.Game/Rulesets/Objects/Types/IHasDisplayColour.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Objects.Types
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A HitObject which has a preferred display colour. Will be used for editor timeline display.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasDisplayColour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current display colour of this hit object.
|
||||||
|
/// </summary>
|
||||||
|
Bindable<Color4> DisplayColour { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,20 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
private readonly IBindable<double> timeRange = new BindableDouble();
|
private readonly IBindable<double> timeRange = new BindableDouble();
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the scrolling direction is horizontal or vertical.
|
||||||
|
/// </summary>
|
||||||
|
private Direction scrollingAxis => direction.Value == ScrollingDirection.Left || direction.Value == ScrollingDirection.Right ? Direction.Horizontal : Direction.Vertical;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scrolling axis is inverted if objects temporally farther in the future have a smaller position value across the scrolling axis.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>
|
||||||
|
/// <see cref="ScrollingDirection.Down"/> is inverted, because given two objects, one of which is at the current time and one of which is 1000ms in the future,
|
||||||
|
/// in the current time instant the future object is spatially above the current object, and therefore has a smaller value of the Y coordinate of its position.
|
||||||
|
/// </example>
|
||||||
|
private bool axisInverted => direction.Value == ScrollingDirection.Down || direction.Value == ScrollingDirection.Right;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A set of top-level <see cref="DrawableHitObject"/>s which have an up-to-date layout.
|
/// A set of top-level <see cref="DrawableHitObject"/>s which have an up-to-date layout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,99 +62,64 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a position in screen space, return the time within this column.
|
/// Given a position at <paramref name="currentTime"/>, return the time of the object corresponding to the position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
|
/// <remarks>
|
||||||
|
/// If there are multiple valid time values, one arbitrary time is returned.
|
||||||
|
/// </remarks>
|
||||||
|
public double TimeAtPosition(float localPosition, double currentTime)
|
||||||
{
|
{
|
||||||
// convert to local space of column so we can snap and fetch correct location.
|
float scrollPosition = axisInverted ? scrollLength - localPosition : localPosition;
|
||||||
Vector2 localPosition = ToLocalSpace(screenSpacePosition);
|
return scrollingInfo.Algorithm.TimeAt(scrollPosition, currentTime, timeRange.Value, scrollLength);
|
||||||
|
|
||||||
float position = 0;
|
|
||||||
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
position = localPosition.Y;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Right:
|
|
||||||
case ScrollingDirection.Left:
|
|
||||||
position = localPosition.X;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
flipPositionIfRequired(ref position);
|
|
||||||
|
|
||||||
return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, scrollLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a time, return the screen space position within this column.
|
/// Given a position at the current time in screen space, return the time of the object corresponding the position.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If there are multiple valid time values, one arbitrary time is returned.
|
||||||
|
/// </remarks>
|
||||||
|
public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
Vector2 localPosition = ToLocalSpace(screenSpacePosition);
|
||||||
|
return TimeAtPosition(scrollingAxis == Direction.Horizontal ? localPosition.X : localPosition.Y, Time.Current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a time, return the position along the scrolling axis within this <see cref="HitObjectContainer"/> at time <paramref name="currentTime"/>.
|
||||||
|
/// </summary>
|
||||||
|
public float PositionAtTime(double time, double currentTime)
|
||||||
|
{
|
||||||
|
float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength);
|
||||||
|
return axisInverted ? scrollLength - scrollPosition : scrollPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a time, return the position along the scrolling axis within this <see cref="HitObjectContainer"/> at the current time.
|
||||||
|
/// </summary>
|
||||||
|
public float PositionAtTime(double time) => PositionAtTime(time, Time.Current);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a time, return the screen space position within this <see cref="HitObjectContainer"/>.
|
||||||
|
/// In the non-scrolling axis, the center of this <see cref="HitObjectContainer"/> is returned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 ScreenSpacePositionAtTime(double time)
|
public Vector2 ScreenSpacePositionAtTime(double time)
|
||||||
{
|
{
|
||||||
var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, scrollLength);
|
float localPosition = PositionAtTime(time, Time.Current);
|
||||||
|
return scrollingAxis == Direction.Horizontal
|
||||||
flipPositionIfRequired(ref pos);
|
? ToScreenSpace(new Vector2(localPosition, DrawHeight / 2))
|
||||||
|
: ToScreenSpace(new Vector2(DrawWidth / 2, localPosition));
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
return ToScreenSpace(new Vector2(getBreadth() / 2, pos));
|
|
||||||
|
|
||||||
default:
|
|
||||||
return ToScreenSpace(new Vector2(pos, getBreadth() / 2));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float scrollLength
|
/// <summary>
|
||||||
|
/// Given a start time and end time of a scrolling object, return the length of the object along the scrolling axis.
|
||||||
|
/// </summary>
|
||||||
|
public float LengthAtTime(double startTime, double endTime)
|
||||||
{
|
{
|
||||||
get
|
return scrollingInfo.Algorithm.GetLength(startTime, endTime, timeRange.Value, scrollLength);
|
||||||
{
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Left:
|
|
||||||
case ScrollingDirection.Right:
|
|
||||||
return DrawWidth;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return DrawHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getBreadth()
|
private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight;
|
||||||
{
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
return DrawWidth;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return DrawHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void flipPositionIfRequired(ref float position)
|
|
||||||
{
|
|
||||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
|
||||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
|
||||||
// so when scrolling downwards the coordinates need to be flipped.
|
|
||||||
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
position = DrawHeight - position;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Right:
|
|
||||||
position = DrawWidth - position;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
@ -237,18 +216,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
{
|
{
|
||||||
if (hitObject.HitObject is IHasDuration e)
|
if (hitObject.HitObject is IHasDuration e)
|
||||||
{
|
{
|
||||||
switch (direction.Value)
|
float length = LengthAtTime(hitObject.HitObject.StartTime, e.EndTime);
|
||||||
{
|
if (scrollingAxis == Direction.Horizontal)
|
||||||
case ScrollingDirection.Up:
|
hitObject.Width = length;
|
||||||
case ScrollingDirection.Down:
|
else
|
||||||
hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength);
|
hitObject.Height = length;
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Left:
|
|
||||||
case ScrollingDirection.Right:
|
|
||||||
hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var obj in hitObject.NestedHitObjects)
|
foreach (var obj in hitObject.NestedHitObjects)
|
||||||
@ -262,24 +234,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
|
|
||||||
private void updatePosition(DrawableHitObject hitObject, double currentTime)
|
private void updatePosition(DrawableHitObject hitObject, double currentTime)
|
||||||
{
|
{
|
||||||
switch (direction.Value)
|
float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime);
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Down:
|
// The position returned from `PositionAtTime` is assuming the `TopLeft` anchor.
|
||||||
hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
// A correction is needed because the hit objects are using a different anchor for each direction (e.g. `BottomCentre` for `Bottom` direction).
|
||||||
break;
|
float anchorCorrection = axisInverted ? scrollLength : 0;
|
||||||
|
|
||||||
case ScrollingDirection.Left:
|
if (scrollingAxis == Direction.Horizontal)
|
||||||
hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
hitObject.X = position - anchorCorrection;
|
||||||
break;
|
else
|
||||||
|
hitObject.Y = position - anchorCorrection;
|
||||||
case ScrollingDirection.Right:
|
|
||||||
hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private Bindable<int> indexInCurrentComboBindable;
|
private Bindable<int> indexInCurrentComboBindable;
|
||||||
private Bindable<int> comboIndexBindable;
|
private Bindable<int> comboIndexBindable;
|
||||||
|
private Bindable<Color4> displayColourBindable;
|
||||||
|
|
||||||
private readonly ExtendableCircle circle;
|
private readonly ExtendableCircle circle;
|
||||||
private readonly Border border;
|
private readonly Border border;
|
||||||
@ -108,44 +109,64 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (Item is IHasComboInformation comboInfo)
|
switch (Item)
|
||||||
{
|
{
|
||||||
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
case IHasDisplayColour displayColour:
|
||||||
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
displayColourBindable = displayColour.DisplayColour.GetBoundCopy();
|
||||||
|
displayColourBindable.BindValueChanged(_ => updateColour(), true);
|
||||||
|
break;
|
||||||
|
|
||||||
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
case IHasComboInformation comboInfo:
|
||||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
indexInCurrentComboBindable = comboInfo.IndexInCurrentComboBindable.GetBoundCopy();
|
||||||
|
indexInCurrentComboBindable.BindValueChanged(_ => updateComboIndex(), true);
|
||||||
|
|
||||||
skin.SourceChanged += updateComboColour;
|
comboIndexBindable = comboInfo.ComboIndexBindable.GetBoundCopy();
|
||||||
|
comboIndexBindable.BindValueChanged(_ => updateColour(), true);
|
||||||
|
|
||||||
|
skin.SourceChanged += updateColour;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSelected()
|
protected override void OnSelected()
|
||||||
{
|
{
|
||||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||||
updateComboColour();
|
updateColour();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDeselected()
|
protected override void OnDeselected()
|
||||||
{
|
{
|
||||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||||
updateComboColour();
|
updateColour();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||||
|
|
||||||
private void updateComboColour()
|
private void updateColour()
|
||||||
{
|
{
|
||||||
if (!(Item is IHasComboInformation combo))
|
Color4 colour;
|
||||||
return;
|
|
||||||
|
|
||||||
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
switch (Item)
|
||||||
var comboColour = combo.GetComboColour(comboColours);
|
{
|
||||||
|
case IHasDisplayColour displayColour:
|
||||||
|
colour = displayColour.DisplayColour.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IHasComboInformation combo:
|
||||||
|
{
|
||||||
|
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||||
|
colour = combo.GetComboColour(comboColours);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsSelected)
|
if (IsSelected)
|
||||||
{
|
{
|
||||||
border.Show();
|
border.Show();
|
||||||
comboColour = comboColour.Lighten(0.3f);
|
colour = colour.Lighten(0.3f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -153,9 +174,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Item is IHasDuration duration && duration.Duration > 0)
|
if (Item is IHasDuration duration && duration.Duration > 0)
|
||||||
circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f));
|
circle.Colour = ColourInfo.GradientHorizontal(colour, colour.Lighten(0.4f));
|
||||||
else
|
else
|
||||||
circle.Colour = comboColour;
|
circle.Colour = colour;
|
||||||
|
|
||||||
var col = circle.Colour.TopLeft.Linear;
|
var col = circle.Colour.TopLeft.Linear;
|
||||||
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col);
|
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col);
|
||||||
|
20
osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs
Normal file
20
osu.Game/Screens/Edit/Setup/LabelledRomanisedTextBox.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Setup
|
||||||
|
{
|
||||||
|
internal class LabelledRomanisedTextBox : LabelledTextBox
|
||||||
|
{
|
||||||
|
protected override OsuTextBox CreateTextBox() => new RomanisedTextBox();
|
||||||
|
|
||||||
|
private class RomanisedTextBox : OsuTextBox
|
||||||
|
{
|
||||||
|
protected override bool CanAddCharacter(char character)
|
||||||
|
=> MetadataUtils.IsRomanised(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,79 +3,117 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Setup
|
namespace osu.Game.Screens.Edit.Setup
|
||||||
{
|
{
|
||||||
internal class MetadataSection : SetupSection
|
internal class MetadataSection : SetupSection
|
||||||
{
|
{
|
||||||
private LabelledTextBox artistTextBox;
|
protected LabelledTextBox ArtistTextBox;
|
||||||
private LabelledTextBox titleTextBox;
|
protected LabelledTextBox RomanisedArtistTextBox;
|
||||||
|
|
||||||
|
protected LabelledTextBox TitleTextBox;
|
||||||
|
protected LabelledTextBox RomanisedTitleTextBox;
|
||||||
|
|
||||||
private LabelledTextBox creatorTextBox;
|
private LabelledTextBox creatorTextBox;
|
||||||
private LabelledTextBox difficultyTextBox;
|
private LabelledTextBox difficultyTextBox;
|
||||||
|
private LabelledTextBox sourceTextBox;
|
||||||
|
private LabelledTextBox tagsTextBox;
|
||||||
|
|
||||||
public override LocalisableString Title => "Metadata";
|
public override LocalisableString Title => "Metadata";
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
var metadata = Beatmap.Metadata;
|
||||||
|
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
artistTextBox = new LabelledTextBox
|
ArtistTextBox = createTextBox<LabelledTextBox>("Artist",
|
||||||
{
|
!string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist),
|
||||||
Label = "Artist",
|
RomanisedArtistTextBox = createTextBox<LabelledRomanisedTextBox>("Romanised Artist",
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
!string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)),
|
||||||
Current = { Value = Beatmap.Metadata.Artist },
|
|
||||||
TabbableContentContainer = this
|
Empty(),
|
||||||
},
|
|
||||||
titleTextBox = new LabelledTextBox
|
TitleTextBox = createTextBox<LabelledTextBox>("Title",
|
||||||
{
|
!string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title),
|
||||||
Label = "Title",
|
RomanisedTitleTextBox = createTextBox<LabelledRomanisedTextBox>("Romanised Title",
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
!string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)),
|
||||||
Current = { Value = Beatmap.Metadata.Title },
|
|
||||||
TabbableContentContainer = this
|
Empty(),
|
||||||
},
|
|
||||||
creatorTextBox = new LabelledTextBox
|
creatorTextBox = createTextBox<LabelledTextBox>("Creator", metadata.AuthorString),
|
||||||
{
|
difficultyTextBox = createTextBox<LabelledTextBox>("Difficulty Name", Beatmap.BeatmapInfo.Version),
|
||||||
Label = "Creator",
|
sourceTextBox = createTextBox<LabelledTextBox>("Source", metadata.Source),
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
tagsTextBox = createTextBox<LabelledTextBox>("Tags", metadata.Tags)
|
||||||
Current = { Value = Beatmap.Metadata.AuthorString },
|
|
||||||
TabbableContentContainer = this
|
|
||||||
},
|
|
||||||
difficultyTextBox = new LabelledTextBox
|
|
||||||
{
|
|
||||||
Label = "Difficulty Name",
|
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
|
||||||
Current = { Value = Beatmap.BeatmapInfo.Version },
|
|
||||||
TabbableContentContainer = this
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var item in Children.OfType<LabelledTextBox>())
|
foreach (var item in Children.OfType<LabelledTextBox>())
|
||||||
item.OnCommit += onCommit;
|
item.OnCommit += onCommit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TTextBox createTextBox<TTextBox>(string label, string initialValue)
|
||||||
|
where TTextBox : LabelledTextBox, new()
|
||||||
|
=> new TTextBox
|
||||||
|
{
|
||||||
|
Label = label,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Current = { Value = initialValue },
|
||||||
|
TabbableContentContainer = this
|
||||||
|
};
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(artistTextBox.Current.Value))
|
if (string.IsNullOrEmpty(ArtistTextBox.Current.Value))
|
||||||
GetContainingInputManager().ChangeFocus(artistTextBox);
|
GetContainingInputManager().ChangeFocus(ArtistTextBox);
|
||||||
|
|
||||||
|
ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox));
|
||||||
|
TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox));
|
||||||
|
updateReadOnlyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transferIfRomanised(string value, LabelledTextBox target)
|
||||||
|
{
|
||||||
|
if (MetadataUtils.IsRomanised(value))
|
||||||
|
target.Current.Value = value;
|
||||||
|
|
||||||
|
updateReadOnlyState();
|
||||||
|
updateMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateReadOnlyState()
|
||||||
|
{
|
||||||
|
RomanisedArtistTextBox.ReadOnly = MetadataUtils.IsRomanised(ArtistTextBox.Current.Value);
|
||||||
|
RomanisedTitleTextBox.ReadOnly = MetadataUtils.IsRomanised(TitleTextBox.Current.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCommit(TextBox sender, bool newText)
|
private void onCommit(TextBox sender, bool newText)
|
||||||
{
|
{
|
||||||
if (!newText) return;
|
if (!newText) return;
|
||||||
|
|
||||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
// for now, update on commit rather than making BeatmapMetadata bindables.
|
||||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
Beatmap.Metadata.Artist = artistTextBox.Current.Value;
|
updateMetadata();
|
||||||
Beatmap.Metadata.Title = titleTextBox.Current.Value;
|
}
|
||||||
|
|
||||||
|
private void updateMetadata()
|
||||||
|
{
|
||||||
|
Beatmap.Metadata.ArtistUnicode = ArtistTextBox.Current.Value;
|
||||||
|
Beatmap.Metadata.Artist = RomanisedArtistTextBox.Current.Value;
|
||||||
|
|
||||||
|
Beatmap.Metadata.TitleUnicode = TitleTextBox.Current.Value;
|
||||||
|
Beatmap.Metadata.Title = RomanisedTitleTextBox.Current.Value;
|
||||||
|
|
||||||
Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value;
|
Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value;
|
||||||
Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
||||||
|
Beatmap.Metadata.Source = sourceTextBox.Current.Value;
|
||||||
|
Beatmap.Metadata.Tags = tagsTextBox.Current.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Spacing = new Vector2(20),
|
Spacing = new Vector2(10),
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock clock { get; set; }
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
public const float TIMING_COLUMN_WIDTH = 220;
|
public const float TIMING_COLUMN_WIDTH = 230;
|
||||||
|
|
||||||
public IEnumerable<ControlPointGroup> ControlGroups
|
public IEnumerable<ControlPointGroup> ControlGroups
|
||||||
{
|
{
|
||||||
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
Text = group.Time.ToEditorFormattedString(),
|
Text = group.Time.ToEditorFormattedString(),
|
||||||
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
||||||
Width = 60,
|
Width = 70,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
|
@ -5,14 +5,18 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||||
@ -79,13 +83,28 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
private Container<RankBadge> badges;
|
private Container<RankBadge> badges;
|
||||||
private RankText rankText;
|
private RankText rankText;
|
||||||
|
|
||||||
public AccuracyCircle(ScoreInfo score)
|
private PoolableSkinnableSample scoreTickSound;
|
||||||
|
private PoolableSkinnableSample badgeTickSound;
|
||||||
|
private PoolableSkinnableSample badgeMaxSound;
|
||||||
|
private PoolableSkinnableSample swooshUpSound;
|
||||||
|
private PoolableSkinnableSample rankImpactSound;
|
||||||
|
private PoolableSkinnableSample rankApplauseSound;
|
||||||
|
|
||||||
|
private readonly Bindable<double> tickPlaybackRate = new Bindable<double>();
|
||||||
|
|
||||||
|
private double lastTickPlaybackTime;
|
||||||
|
private bool isTicking;
|
||||||
|
|
||||||
|
private readonly bool withFlair;
|
||||||
|
|
||||||
|
public AccuracyCircle(ScoreInfo score, bool withFlair = false)
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
|
this.withFlair = withFlair;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(GameHost host)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -204,14 +223,19 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
},
|
},
|
||||||
rankText = new RankText(score.Rank)
|
rankText = new RankText(score.Rank)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
private ScoreRank getRank(ScoreRank rank)
|
if (withFlair)
|
||||||
{
|
{
|
||||||
foreach (var mod in score.Mods.OfType<IApplicableToScoreProcessor>())
|
AddRangeInternal(new Drawable[]
|
||||||
rank = mod.AdjustRank(rank, score.Accuracy);
|
{
|
||||||
|
rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)),
|
||||||
return rank;
|
rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", applauseSampleName)),
|
||||||
|
scoreTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/score-tick")),
|
||||||
|
badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")),
|
||||||
|
badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")),
|
||||||
|
swooshUpSound = new PoolableSkinnableSample(new SampleInfo(@"Results/swoosh-up")),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -220,33 +244,170 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
|
|
||||||
this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
|
this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true))
|
if (withFlair)
|
||||||
|
{
|
||||||
|
const double swoosh_pre_delay = 443f;
|
||||||
|
const double swoosh_volume = 0.4f;
|
||||||
|
|
||||||
|
this.Delay(swoosh_pre_delay).Schedule(() =>
|
||||||
|
{
|
||||||
|
swooshUpSound.VolumeTo(swoosh_volume);
|
||||||
|
swooshUpSound.Play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY))
|
||||||
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
||||||
|
|
||||||
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY, true))
|
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
|
||||||
{
|
{
|
||||||
double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
|
double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
|
||||||
|
|
||||||
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
||||||
|
|
||||||
|
if (withFlair)
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
const double score_tick_debounce_rate_start = 18f;
|
||||||
|
const double score_tick_debounce_rate_end = 300f;
|
||||||
|
const double score_tick_volume_start = 0.6f;
|
||||||
|
const double score_tick_volume_end = 1.0f;
|
||||||
|
|
||||||
|
this.TransformBindableTo(tickPlaybackRate, score_tick_debounce_rate_start);
|
||||||
|
this.TransformBindableTo(tickPlaybackRate, score_tick_debounce_rate_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine);
|
||||||
|
|
||||||
|
scoreTickSound.FrequencyTo(1 + targetAccuracy, ACCURACY_TRANSFORM_DURATION, Easing.OutSine);
|
||||||
|
scoreTickSound.VolumeTo(score_tick_volume_start).Then().VolumeTo(score_tick_volume_end, ACCURACY_TRANSFORM_DURATION, Easing.OutSine);
|
||||||
|
|
||||||
|
isTicking = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int badgeNum = 0;
|
||||||
|
|
||||||
foreach (var badge in badges)
|
foreach (var badge in badges)
|
||||||
{
|
{
|
||||||
if (badge.Accuracy > score.Accuracy)
|
if (badge.Accuracy > score.Accuracy)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true))
|
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||||
{
|
{
|
||||||
badge.Appear();
|
badge.Appear();
|
||||||
|
|
||||||
|
if (withFlair)
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound;
|
||||||
|
|
||||||
|
dink.FrequencyTo(1 + badgeNum++ * 0.05);
|
||||||
|
dink.Play();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
|
using (BeginDelayedSequence(TEXT_APPEAR_DELAY))
|
||||||
{
|
{
|
||||||
rankText.Appear();
|
rankText.Appear();
|
||||||
|
|
||||||
|
if (!withFlair) return;
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
isTicking = false;
|
||||||
|
rankImpactSound.Play();
|
||||||
|
});
|
||||||
|
|
||||||
|
const double applause_pre_delay = 545f;
|
||||||
|
const double applause_volume = 0.8f;
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(applause_pre_delay))
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
rankApplauseSound.VolumeTo(applause_volume);
|
||||||
|
rankApplauseSound.Play();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (isTicking && Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value)
|
||||||
|
{
|
||||||
|
scoreTickSound?.Play();
|
||||||
|
lastTickPlaybackTime = Clock.CurrentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string applauseSampleName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (score.Rank)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case ScoreRank.D:
|
||||||
|
return @"Results/applause-d";
|
||||||
|
|
||||||
|
case ScoreRank.C:
|
||||||
|
return @"Results/applause-c";
|
||||||
|
|
||||||
|
case ScoreRank.B:
|
||||||
|
return @"Results/applause-b";
|
||||||
|
|
||||||
|
case ScoreRank.A:
|
||||||
|
return @"Results/applause-a";
|
||||||
|
|
||||||
|
case ScoreRank.S:
|
||||||
|
case ScoreRank.SH:
|
||||||
|
case ScoreRank.X:
|
||||||
|
case ScoreRank.XH:
|
||||||
|
return @"Results/applause-s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string impactSampleName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (score.Rank)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case ScoreRank.D:
|
||||||
|
return @"Results/rank-impact-fail-d";
|
||||||
|
|
||||||
|
case ScoreRank.C:
|
||||||
|
case ScoreRank.B:
|
||||||
|
return @"Results/rank-impact-fail";
|
||||||
|
|
||||||
|
case ScoreRank.A:
|
||||||
|
case ScoreRank.S:
|
||||||
|
case ScoreRank.SH:
|
||||||
|
return @"Results/rank-impact-pass";
|
||||||
|
|
||||||
|
case ScoreRank.X:
|
||||||
|
case ScoreRank.XH:
|
||||||
|
return @"Results/rank-impact-pass-ss";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoreRank getRank(ScoreRank rank)
|
||||||
|
{
|
||||||
|
foreach (var mod in score.Mods.OfType<IApplicableToScoreProcessor>())
|
||||||
|
rank = mod.AdjustRank(rank, score.Accuracy);
|
||||||
|
|
||||||
|
return rank;
|
||||||
|
}
|
||||||
|
|
||||||
private double inverseEasing(Easing easing, double targetValue)
|
private double inverseEasing(Easing easing, double targetValue)
|
||||||
{
|
{
|
||||||
double test = 0;
|
double test = 0;
|
||||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
Margin = new MarginPadding { Top = 40 },
|
Margin = new MarginPadding { Top = 40 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 230,
|
Height = 230,
|
||||||
Child = new AccuracyCircle(score)
|
Child = new AccuracyCircle(score, withFlair)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -20,20 +19,13 @@ using osu.Game.Input.Bindings;
|
|||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking
|
namespace osu.Game.Screens.Ranking
|
||||||
{
|
{
|
||||||
public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>
|
public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Delay before the default applause sound should be played, in order to match the grade display timing in <see cref="AccuracyCircle"/>.
|
|
||||||
/// </summary>
|
|
||||||
public const double APPLAUSE_DELAY = AccuracyCircle.ACCURACY_TRANSFORM_DELAY + AccuracyCircle.TEXT_APPEAR_DELAY + ScorePanel.RESIZE_DURATION + ScorePanel.TOP_LAYER_EXPAND_DELAY - 1440;
|
|
||||||
|
|
||||||
protected const float BACKGROUND_BLUR = 20;
|
protected const float BACKGROUND_BLUR = 20;
|
||||||
private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y;
|
private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y;
|
||||||
|
|
||||||
@ -64,8 +56,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
private readonly bool allowRetry;
|
private readonly bool allowRetry;
|
||||||
private readonly bool allowWatchingReplay;
|
private readonly bool allowWatchingReplay;
|
||||||
|
|
||||||
private SkinnableSound applauseSound;
|
|
||||||
|
|
||||||
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true)
|
||||||
{
|
{
|
||||||
Score = score;
|
Score = score;
|
||||||
@ -156,13 +146,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable);
|
bool shouldFlair = player != null && Score.Mods.All(m => m.UserPlayable);
|
||||||
|
|
||||||
ScorePanelList.AddScore(Score, shouldFlair);
|
ScorePanelList.AddScore(Score, shouldFlair);
|
||||||
|
|
||||||
if (shouldFlair)
|
|
||||||
{
|
|
||||||
AddInternal(applauseSound = Score.Rank >= ScoreRank.A
|
|
||||||
? new SkinnableSound(new SampleInfo("Results/rankpass", "applause"))
|
|
||||||
: new SkinnableSound(new SampleInfo("Results/rankfail")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowWatchingReplay)
|
if (allowWatchingReplay)
|
||||||
@ -200,9 +183,6 @@ namespace osu.Game.Screens.Ranking
|
|||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
|
|
||||||
statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true);
|
statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true);
|
||||||
|
|
||||||
using (BeginDelayedSequence(APPLAUSE_DELAY))
|
|
||||||
Schedule(() => applauseSound?.Play());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
|
case "star":
|
||||||
case "stars":
|
case "stars":
|
||||||
return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2);
|
return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2);
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
|
|
||||||
private IBindable<WeakReference<ScoreInfo>> itemRemoved;
|
private IBindable<WeakReference<ScoreInfo>> itemRemoved;
|
||||||
|
|
||||||
|
private IBindable<WeakReference<ScoreInfo>> itemAdded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
|
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -85,6 +87,9 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
|
|
||||||
itemRemoved = scoreManager.ItemRemoved.GetBoundCopy();
|
itemRemoved = scoreManager.ItemRemoved.GetBoundCopy();
|
||||||
itemRemoved.BindValueChanged(onScoreRemoved);
|
itemRemoved.BindValueChanged(onScoreRemoved);
|
||||||
|
|
||||||
|
itemAdded = scoreManager.ItemUpdated.GetBoundCopy();
|
||||||
|
itemAdded.BindValueChanged(onScoreAdded);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Reset()
|
protected override void Reset()
|
||||||
@ -93,7 +98,25 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
TopScore = null;
|
TopScore = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onScoreRemoved(ValueChangedEvent<WeakReference<ScoreInfo>> score) => Schedule(RefreshScores);
|
private void onScoreRemoved(ValueChangedEvent<WeakReference<ScoreInfo>> score) =>
|
||||||
|
scoreStoreChanged(score);
|
||||||
|
|
||||||
|
private void onScoreAdded(ValueChangedEvent<WeakReference<ScoreInfo>> score) =>
|
||||||
|
scoreStoreChanged(score);
|
||||||
|
|
||||||
|
private void scoreStoreChanged(ValueChangedEvent<WeakReference<ScoreInfo>> score)
|
||||||
|
{
|
||||||
|
if (Scope != BeatmapLeaderboardScope.Local)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (score.NewValue.TryGetTarget(out var scoreInfo))
|
||||||
|
{
|
||||||
|
if (Beatmap?.ID != scoreInfo.BeatmapInfoID)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshScores();
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
|
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
|
||||||
|
|
||||||
|
@ -148,9 +148,9 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LegacyOldStyleFill : LegacyHealthPiece
|
internal abstract class LegacyFill : LegacyHealthPiece
|
||||||
{
|
{
|
||||||
public LegacyOldStyleFill(ISkin skin)
|
protected LegacyFill(ISkin skin)
|
||||||
{
|
{
|
||||||
// required for sizing correctly..
|
// required for sizing correctly..
|
||||||
var firstFrame = getTexture(skin, "colour-0");
|
var firstFrame = getTexture(skin, "colour-0");
|
||||||
@ -162,27 +162,29 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Drawable.Empty();
|
InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Empty();
|
||||||
Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight);
|
Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
Position = new Vector2(3, 10) * 1.6f;
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LegacyNewStyleFill : LegacyHealthPiece
|
internal class LegacyOldStyleFill : LegacyFill
|
||||||
|
{
|
||||||
|
public LegacyOldStyleFill(ISkin skin)
|
||||||
|
: base(skin)
|
||||||
|
{
|
||||||
|
Position = new Vector2(3, 10) * 1.6f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LegacyNewStyleFill : LegacyFill
|
||||||
{
|
{
|
||||||
public LegacyNewStyleFill(ISkin skin)
|
public LegacyNewStyleFill(ISkin skin)
|
||||||
|
: base(skin)
|
||||||
{
|
{
|
||||||
InternalChild = new Sprite
|
|
||||||
{
|
|
||||||
Texture = getTexture(skin, "colour"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Size = InternalChild.Size;
|
|
||||||
Position = new Vector2(7.5f, 7.8f) * 1.6f;
|
Position = new Vector2(7.5f, 7.8f) * 1.6f;
|
||||||
Masking = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Framework.Testing.Input;
|
using osu.Framework.Testing.Input;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -49,7 +48,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
InputManager = new ManualInputManager
|
InputManager = new ManualInputManager
|
||||||
{
|
{
|
||||||
UseParentInput = true,
|
UseParentInput = true,
|
||||||
Child = new PlatformActionContainer().WithChild(mainContent)
|
Child = mainContent
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,9 @@ namespace osu.Game.Users.Drawables
|
|||||||
private void load(LargeTextureStore textures)
|
private void load(LargeTextureStore textures)
|
||||||
{
|
{
|
||||||
if (user != null && user.Id > 1)
|
if (user != null && user.Id > 1)
|
||||||
Texture = textures.Get(user.AvatarUrl);
|
// TODO: The fallback here should not need to exist. Users should be looked up and populated via UserLookupCache or otherwise
|
||||||
|
// in remaining cases where this is required (chat tabs, local leaderboard), at which point this should be removed.
|
||||||
|
Texture = textures.Get(user.AvatarUrl ?? $@"https://a.ppy.sh/{user.Id}");
|
||||||
|
|
||||||
Texture ??= textures.Get(@"Online/avatar-guest");
|
Texture ??= textures.Get(@"Online/avatar-guest");
|
||||||
}
|
}
|
||||||
|
@ -30,12 +30,12 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2021.525.0">
|
<PackageReference Include="ppy.LocalisationAnalyser" Version="2021.608.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.609.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.614.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.614.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.4.0" />
|
<PackageReference Include="Sentry" Version="3.4.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
|
@ -70,8 +70,8 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.609.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.614.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.525.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.614.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.609.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.614.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
<PackageReference Include="SharpCompress" Version="0.28.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Reference in New Issue
Block a user