mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 23:53:51 +09:00
Merge branch 'master' into osu-target-mod
# Conflicts: # osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs # osu.Game/Rulesets/Mods/IHasSeed.cs
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
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -336,3 +336,6 @@ inspectcode
|
|||||||
/BenchmarkDotNet.Artifacts
|
/BenchmarkDotNet.Artifacts
|
||||||
|
|
||||||
*.GeneratedMSBuildEditorConfig.editorconfig
|
*.GeneratedMSBuildEditorConfig.editorconfig
|
||||||
|
|
||||||
|
# Fody (pulled in by Realm) - schema file
|
||||||
|
FodyWeavers.xsd
|
||||||
|
14
.idea/.idea.osu.Desktop/.idea/dataSources.xml
generated
14
.idea/.idea.osu.Desktop/.idea/dataSources.xml
generated
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
|
||||||
<data-source source="LOCAL" name="osu-client-sqlite" uuid="1aa4b9be-cd8d-47ae-8186-30a13cd724a5">
|
|
||||||
<driver-ref>sqlite.xerial</driver-ref>
|
|
||||||
<synchronize>true</synchronize>
|
|
||||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
|
||||||
<jdbc-url>jdbc:sqlite:$USER_HOME$/.local/share/osu/client.db</jdbc-url>
|
|
||||||
<driver-properties>
|
|
||||||
<property name="enable_load_extension" value="true" />
|
|
||||||
</driver-properties>
|
|
||||||
</data-source>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
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
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
3
FodyWeavers.xml
Normal file
3
FodyWeavers.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<Realm />
|
||||||
|
</Weavers>
|
@ -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
|
@ -43,7 +43,7 @@ If your platform is not listed above, there is still a chance you can manually b
|
|||||||
|
|
||||||
osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
|
osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
|
||||||
|
|
||||||
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852).
|
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
|
||||||
|
|
||||||
## Developing osu!
|
## Developing osu!
|
||||||
|
|
||||||
|
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,11 @@
|
|||||||
<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.618.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.609.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.622.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
<PackageReference Include="Realm" Version="10.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
14
osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs
Normal file
14
osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneEditor : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new CatchRuleset();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
[General]
|
||||||
|
// no version specified means v1
|
@ -174,8 +174,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private void addToPlayfield(DrawableCatchHitObject drawable)
|
private void addToPlayfield(DrawableCatchHitObject drawable)
|
||||||
{
|
{
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
mod.ApplyToDrawableHitObject(drawable);
|
||||||
|
|
||||||
drawableRuleset.Playfield.Add(drawable);
|
drawableRuleset.Playfield.Add(drawable);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Catch.MathUtils;
|
using osu.Game.Rulesets.Catch.MathUtils;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||||
@ -17,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
{
|
{
|
||||||
public const int RNG_SEED = 1337;
|
public const int RNG_SEED = 1337;
|
||||||
|
|
||||||
|
public bool HardRockOffsets { get; set; }
|
||||||
|
|
||||||
public CatchBeatmapProcessor(IBeatmap beatmap)
|
public CatchBeatmapProcessor(IBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
@ -43,11 +44,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods)
|
public void ApplyPositionOffsets(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var rng = new FastRandom(RNG_SEED);
|
var rng = new FastRandom(RNG_SEED);
|
||||||
|
|
||||||
bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock);
|
|
||||||
float? lastPosition = null;
|
float? lastPosition = null;
|
||||||
double lastStartTime = 0;
|
double lastStartTime = 0;
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case Fruit fruit:
|
case Fruit fruit:
|
||||||
if (shouldApplyHardRockOffset)
|
if (HardRockOffsets)
|
||||||
applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng);
|
applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -22,7 +22,9 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
@ -175,12 +177,14 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
|
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin);
|
||||||
|
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
|
||||||
|
|
||||||
public int LegacyID => 2;
|
public int LegacyID => 2;
|
||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
||||||
|
|
||||||
|
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
Fruit,
|
Fruit,
|
||||||
Banana,
|
Banana,
|
||||||
Droplet,
|
Droplet,
|
||||||
CatcherIdle,
|
Catcher,
|
||||||
CatcherFail,
|
|
||||||
CatcherKiai,
|
|
||||||
CatchComboCounter
|
CatchComboCounter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||||
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
|
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||||
|
|
||||||
// Don't count scores made with supposedly unranked mods
|
|
||||||
if (mods.Any(m => !m.Ranked))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// We are heavily relying on aim in catch the beat
|
// We are heavily relying on aim in catch the beat
|
||||||
double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
|
double value = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
|
||||||
|
|
||||||
|
24
osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
Normal file
24
osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class BananaShowerCompositionTool : HitObjectCompositionTool
|
||||||
|
{
|
||||||
|
public BananaShowerCompositionTool()
|
||||||
|
: base(nameof(BananaShower))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
|
public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class BananaShowerPlacementBlueprint : CatchPlacementBlueprint<BananaShower>
|
||||||
|
{
|
||||||
|
private readonly TimeSpanOutline outline;
|
||||||
|
|
||||||
|
public BananaShowerPlacementBlueprint()
|
||||||
|
{
|
||||||
|
InternalChild = outline = new TimeSpanOutline();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
outline.UpdateFrom(HitObjectContainer, HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
switch (PlacementActive)
|
||||||
|
{
|
||||||
|
case PlacementState.Waiting:
|
||||||
|
if (e.Button != MouseButton.Left) break;
|
||||||
|
|
||||||
|
BeginPlacement(true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case PlacementState.Active:
|
||||||
|
if (e.Button != MouseButton.Right) break;
|
||||||
|
|
||||||
|
// If the duration is negative, swap the start and the end time to make the duration positive.
|
||||||
|
if (HitObject.Duration < 0)
|
||||||
|
{
|
||||||
|
HitObject.StartTime = HitObject.EndTime;
|
||||||
|
HitObject.Duration = -HitObject.Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndPlacement(HitObject.Duration > 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
{
|
||||||
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
|
if (!(result.Time is double time)) return;
|
||||||
|
|
||||||
|
switch (PlacementActive)
|
||||||
|
{
|
||||||
|
case PlacementState.Waiting:
|
||||||
|
HitObject.StartTime = time;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlacementState.Active:
|
||||||
|
HitObject.EndTime = time;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class BananaShowerSelectionBlueprint : CatchSelectionBlueprint<BananaShower>
|
||||||
|
{
|
||||||
|
public BananaShowerSelectionBlueprint(BananaShower hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// 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.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class CatchPlacementBlueprint<THitObject> : PlacementBlueprint
|
||||||
|
where THitObject : CatchHitObject, new()
|
||||||
|
{
|
||||||
|
protected new THitObject HitObject => (THitObject)base.HitObject;
|
||||||
|
|
||||||
|
protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Playfield playfield { get; set; }
|
||||||
|
|
||||||
|
public CatchPlacementBlueprint()
|
||||||
|
: base(new THitObject())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public abstract class CatchSelectionBlueprint<THitObject> : HitObjectSelectionBlueprint<THitObject>
|
||||||
|
where THitObject : CatchHitObject
|
||||||
|
{
|
||||||
|
public override Vector2 ScreenSpaceSelectionPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
float x = HitObject.OriginalX;
|
||||||
|
float y = HitObjectContainer.PositionAtTime(HitObject.StartTime);
|
||||||
|
return HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SelectionQuad.Contains(screenSpacePos);
|
||||||
|
|
||||||
|
protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Playfield playfield { get; set; }
|
||||||
|
|
||||||
|
protected CatchSelectionBlueprint(THitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class FruitOutline : CompositeDrawable
|
||||||
|
{
|
||||||
|
public FruitOutline()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Size = new Vector2(2 * CatchHitObject.OBJECT_RADIUS);
|
||||||
|
InternalChild = new BorderPiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour osuColour)
|
||||||
|
{
|
||||||
|
Colour = osuColour.Yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
|
||||||
|
{
|
||||||
|
X = hitObject.EffectiveX;
|
||||||
|
Y = hitObjectContainer.PositionAtTime(hitObject.StartTime);
|
||||||
|
Scale = new Vector2(hitObject.Scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class TimeSpanOutline : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const float border_width = 4;
|
||||||
|
|
||||||
|
private const float opacity_when_empty = 0.5f;
|
||||||
|
|
||||||
|
private bool isEmpty = true;
|
||||||
|
|
||||||
|
public TimeSpanOutline()
|
||||||
|
{
|
||||||
|
Anchor = Origin = Anchor.BottomLeft;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
BorderThickness = border_width;
|
||||||
|
Alpha = opacity_when_empty;
|
||||||
|
|
||||||
|
// A box is needed to make the border visible.
|
||||||
|
InternalChild = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Transparent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour osuColour)
|
||||||
|
{
|
||||||
|
BorderColour = osuColour.Yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, BananaShower hitObject)
|
||||||
|
{
|
||||||
|
float startY = hitObjectContainer.PositionAtTime(hitObject.StartTime);
|
||||||
|
float endY = hitObjectContainer.PositionAtTime(hitObject.EndTime);
|
||||||
|
|
||||||
|
Y = Math.Max(startY, endY);
|
||||||
|
float height = Math.Abs(startY - endY);
|
||||||
|
|
||||||
|
bool wasEmpty = isEmpty;
|
||||||
|
isEmpty = height == 0;
|
||||||
|
if (wasEmpty != isEmpty)
|
||||||
|
this.FadeTo(isEmpty ? opacity_when_empty : 1f, 150);
|
||||||
|
|
||||||
|
Height = Math.Max(height, border_width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
// 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.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class FruitPlacementBlueprint : CatchPlacementBlueprint<Fruit>
|
||||||
|
{
|
||||||
|
private readonly FruitOutline outline;
|
||||||
|
|
||||||
|
public FruitPlacementBlueprint()
|
||||||
|
{
|
||||||
|
InternalChild = outline = new FruitOutline();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
BeginPlacement();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
outline.UpdateFrom(HitObjectContainer, HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button != MouseButton.Left) return base.OnMouseDown(e);
|
||||||
|
|
||||||
|
EndPlacement(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
{
|
||||||
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
|
HitObject.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class FruitSelectionBlueprint : CatchSelectionBlueprint<Fruit>
|
||||||
|
{
|
||||||
|
private readonly FruitOutline outline;
|
||||||
|
|
||||||
|
public FruitSelectionBlueprint(Fruit hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
InternalChild = outline = new FruitOutline();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (IsSelected)
|
||||||
|
outline.UpdateFrom(HitObjectContainer, HitObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class JuiceStreamSelectionBlueprint : CatchSelectionBlueprint<JuiceStream>
|
||||||
|
{
|
||||||
|
public override Quad SelectionQuad => HitObjectContainer.ToScreenSpace(getBoundingBox().Offset(new Vector2(0, HitObjectContainer.DrawHeight)));
|
||||||
|
|
||||||
|
private float minNestedX;
|
||||||
|
private float maxNestedX;
|
||||||
|
|
||||||
|
public JuiceStreamSelectionBlueprint(JuiceStream hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
HitObject.DefaultsApplied += onDefaultsApplied;
|
||||||
|
computeObjectBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDefaultsApplied(HitObject _) => computeObjectBounds();
|
||||||
|
|
||||||
|
private void computeObjectBounds()
|
||||||
|
{
|
||||||
|
minNestedX = HitObject.NestedHitObjects.OfType<CatchHitObject>().Min(nested => nested.OriginalX) - HitObject.OriginalX;
|
||||||
|
maxNestedX = HitObject.NestedHitObjects.OfType<CatchHitObject>().Max(nested => nested.OriginalX) - HitObject.OriginalX;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectangleF getBoundingBox()
|
||||||
|
{
|
||||||
|
float left = HitObject.OriginalX + minNestedX;
|
||||||
|
float right = HitObject.OriginalX + maxNestedX;
|
||||||
|
float top = HitObjectContainer.PositionAtTime(HitObject.EndTime);
|
||||||
|
float bottom = HitObjectContainer.PositionAtTime(HitObject.StartTime);
|
||||||
|
float objectRadius = CatchHitObject.OBJECT_RADIUS * HitObject.Scale;
|
||||||
|
return new RectangleF(left, top, right - left, bottom - top).Inflate(objectRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
Normal file
38
osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class CatchBlueprintContainer : ComposeBlueprintContainer
|
||||||
|
{
|
||||||
|
public CatchBlueprintContainer(CatchHitObjectComposer composer)
|
||||||
|
: base(composer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new CatchSelectionHandler();
|
||||||
|
|
||||||
|
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Fruit fruit:
|
||||||
|
return new FruitSelectionBlueprint(fruit);
|
||||||
|
|
||||||
|
case JuiceStream juiceStream:
|
||||||
|
return new JuiceStreamSelectionBlueprint(juiceStream);
|
||||||
|
|
||||||
|
case BananaShower bananaShower:
|
||||||
|
return new BananaShowerSelectionBlueprint(bananaShower);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateHitObjectBlueprintFor(hitObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
Normal file
27
osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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.Rulesets.Catch.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class CatchEditorPlayfield : CatchPlayfield
|
||||||
|
{
|
||||||
|
// TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen.
|
||||||
|
public CatchEditorPlayfield(BeatmapDifficulty difficulty)
|
||||||
|
: base(difficulty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// TODO: honor "hit animation" setting?
|
||||||
|
CatcherArea.MovableCatcher.CatchFruitOnPlate = false;
|
||||||
|
|
||||||
|
// TODO: disable hit lighting as well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
Normal file
42
osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class CatchHitObjectComposer : HitObjectComposer<CatchHitObject>
|
||||||
|
{
|
||||||
|
public CatchHitObjectComposer(CatchRuleset ruleset)
|
||||||
|
: base(ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
||||||
|
new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
|
||||||
|
|
||||||
|
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||||
|
{
|
||||||
|
new FruitCompositionTool(),
|
||||||
|
new BananaShowerCompositionTool()
|
||||||
|
};
|
||||||
|
|
||||||
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||||
|
// TODO: implement position snap
|
||||||
|
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
|
||||||
|
}
|
||||||
|
}
|
46
osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
Normal file
46
osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class CatchSelectionHandler : EditorSelectionHandler
|
||||||
|
{
|
||||||
|
protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Playfield playfield { get; set; }
|
||||||
|
|
||||||
|
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||||
|
{
|
||||||
|
var blueprint = moveEvent.Blueprint;
|
||||||
|
Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint);
|
||||||
|
Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta);
|
||||||
|
float deltaX = targetPosition.X - originalPosition.X;
|
||||||
|
|
||||||
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
|
{
|
||||||
|
if (!(h is CatchHitObject hitObject)) return;
|
||||||
|
|
||||||
|
if (hitObject is BananaShower) return;
|
||||||
|
|
||||||
|
// TODO: confine in bounds
|
||||||
|
hitObject.OriginalXBindable.Value += deltaX;
|
||||||
|
|
||||||
|
// Move the nested hit objects to give an instant result before nested objects are recreated.
|
||||||
|
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>())
|
||||||
|
nested.OriginalXBindable.Value += deltaX;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
Normal file
21
osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
||||||
|
{
|
||||||
|
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||||
|
: base(ruleset, beatmap, mods)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
Normal file
24
osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
public class FruitCompositionTool : HitObjectCompositionTool
|
||||||
|
{
|
||||||
|
public FruitCompositionTool()
|
||||||
|
: base(nameof(Fruit))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
|
public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,14 @@ using osu.Game.Rulesets.Catch.Beatmaps;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModHardRock : ModHardRock, IApplicableToBeatmap
|
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
public override bool Ranked => true;
|
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this);
|
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
||||||
|
{
|
||||||
|
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
|
||||||
|
catchProcessor.HardRockOffsets = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
Normal file
52
osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultCatcher : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Bindable<CatcherAnimationState> CurrentState { get; } = new Bindable<CatcherAnimationState>();
|
||||||
|
|
||||||
|
private readonly Sprite sprite;
|
||||||
|
|
||||||
|
private readonly Dictionary<CatcherAnimationState, Texture> textures = new Dictionary<CatcherAnimationState, Texture>();
|
||||||
|
|
||||||
|
public DefaultCatcher()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
InternalChild = sprite = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore store, Bindable<CatcherAnimationState> currentState)
|
||||||
|
{
|
||||||
|
CurrentState.BindTo(currentState);
|
||||||
|
|
||||||
|
textures[CatcherAnimationState.Idle] = store.Get(@"Gameplay/catch/fruit-catcher-idle");
|
||||||
|
textures[CatcherAnimationState.Fail] = store.Get(@"Gameplay/catch/fruit-catcher-fail");
|
||||||
|
textures[CatcherAnimationState.Kiai] = store.Get(@"Gameplay/catch/fruit-catcher-kiai");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
CurrentState.BindValueChanged(state => sprite.Texture = textures[state.NewValue], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool providesComboCounter => this.HasFont(LegacyFont.Combo);
|
private bool providesComboCounter => this.HasFont(LegacyFont.Combo);
|
||||||
|
|
||||||
public CatchLegacySkinTransformer(ISkinSource source)
|
public CatchLegacySkinTransformer(ISkin skin)
|
||||||
: base(source)
|
: base(skin)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
switch (targetComponent.Target)
|
switch (targetComponent.Target)
|
||||||
{
|
{
|
||||||
case SkinnableTarget.MainHUDComponents:
|
case SkinnableTarget.MainHUDComponents:
|
||||||
var components = Source.GetDrawableComponent(component) as SkinnableTargetComponentsContainer;
|
var components = base.GetDrawableComponent(component) as SkinnableTargetComponentsContainer;
|
||||||
|
|
||||||
if (providesComboCounter && components != null)
|
if (providesComboCounter && components != null)
|
||||||
{
|
{
|
||||||
@ -65,27 +65,31 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherIdle:
|
case CatchSkinComponents.Catcher:
|
||||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
var version = GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherFail:
|
if (version < 2.3m)
|
||||||
return this.GetAnimation("fruit-catcher-fail", true, true, true) ??
|
{
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
if (GetTexture(@"fruit-ryuuta") != null ||
|
||||||
|
GetTexture(@"fruit-ryuuta-0") != null)
|
||||||
|
return new LegacyCatcherOld();
|
||||||
|
}
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherKiai:
|
if (GetTexture(@"fruit-catcher-idle") != null ||
|
||||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
GetTexture(@"fruit-catcher-idle-0") != null)
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
return new LegacyCatcherNew();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.CatchComboCounter:
|
case CatchSkinComponents.CatchComboCounter:
|
||||||
if (providesComboCounter)
|
if (providesComboCounter)
|
||||||
return new LegacyCatchComboCounter(Source);
|
return new LegacyCatchComboCounter(Skin);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Source.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
@ -93,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case CatchSkinColour colour:
|
case CatchSkinColour colour:
|
||||||
var result = (Bindable<Color4>)Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
var result = (Bindable<Color4>)base.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||||
if (result == null)
|
if (result == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -101,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
return (IBindable<TValue>)result;
|
return (IBindable<TValue>)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Source.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
Normal file
69
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyCatcherNew : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<CatcherAnimationState> currentState { get; set; }
|
||||||
|
|
||||||
|
private readonly Dictionary<CatcherAnimationState, Drawable> drawables = new Dictionary<CatcherAnimationState, Drawable>();
|
||||||
|
|
||||||
|
private Drawable currentDrawable;
|
||||||
|
|
||||||
|
public LegacyCatcherNew()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast<CatcherAnimationState>())
|
||||||
|
{
|
||||||
|
AddInternal(drawables[state] = getDrawableFor(state).With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.TopCentre;
|
||||||
|
d.Origin = Anchor.TopCentre;
|
||||||
|
d.RelativeSizeAxes = Axes.Both;
|
||||||
|
d.Size = Vector2.One;
|
||||||
|
d.FillMode = FillMode.Fit;
|
||||||
|
d.Alpha = 0;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDrawable = drawables[CatcherAnimationState.Idle];
|
||||||
|
|
||||||
|
Drawable getDrawableFor(CatcherAnimationState state) =>
|
||||||
|
skin.GetAnimation(@$"fruit-catcher-{state.ToString().ToLowerInvariant()}", true, true, true) ??
|
||||||
|
skin.GetAnimation(@"fruit-catcher-idle", true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
currentState.BindValueChanged(state =>
|
||||||
|
{
|
||||||
|
currentDrawable.Alpha = 0;
|
||||||
|
currentDrawable = drawables[state.NewValue];
|
||||||
|
currentDrawable.Alpha = 1;
|
||||||
|
|
||||||
|
(currentDrawable as IFramedAnimation)?.GotoFrame(0);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
Normal file
32
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
Normal file
@ -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.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyCatcherOld : CompositeDrawable
|
||||||
|
{
|
||||||
|
public LegacyCatcherOld()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
InternalChild = skin.GetAnimation(@"fruit-ryuuta", true, true, true).With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.TopCentre;
|
||||||
|
d.Origin = Anchor.TopCentre;
|
||||||
|
d.RelativeSizeAxes = Axes.Both;
|
||||||
|
d.Size = Vector2.One;
|
||||||
|
d.FillMode = FillMode.Fit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
// only check the X position; handle all vertical space.
|
// only check the X position; handle all vertical space.
|
||||||
base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y));
|
base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y));
|
||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new Container<CaughtObject>
|
var droppedObjectContainer = new Container<CaughtObject>
|
||||||
{
|
{
|
||||||
|
@ -7,10 +7,8 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
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;
|
||||||
@ -25,7 +23,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
|
||||||
@ -53,6 +51,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>
|
||||||
@ -78,24 +81,23 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Container<CaughtObject> droppedObjectTarget;
|
private readonly Container<CaughtObject> droppedObjectTarget;
|
||||||
|
|
||||||
public CatcherAnimationState CurrentState { get; private set; }
|
public CatcherAnimationState CurrentState
|
||||||
|
{
|
||||||
|
get => body.AnimationState.Value;
|
||||||
|
private set => body.AnimationState.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
|
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The drawable catcher for <see cref="CurrentState"/>.
|
|
||||||
/// </summary>
|
|
||||||
internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable;
|
|
||||||
|
|
||||||
private bool dashing;
|
private bool dashing;
|
||||||
|
|
||||||
public bool Dashing
|
public bool Dashing
|
||||||
{
|
{
|
||||||
get => dashing;
|
get => dashing;
|
||||||
protected set
|
set
|
||||||
{
|
{
|
||||||
if (value == dashing) return;
|
if (value == dashing) return;
|
||||||
|
|
||||||
@ -105,22 +107,22 @@ 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>
|
||||||
private readonly float catchWidth;
|
private readonly float catchWidth;
|
||||||
|
|
||||||
private readonly CatcherSprite catcherIdle;
|
private readonly SkinnableCatcher body;
|
||||||
private readonly CatcherSprite catcherKiai;
|
|
||||||
private readonly CatcherSprite catcherFail;
|
|
||||||
|
|
||||||
private CatcherSprite currentCatcher;
|
|
||||||
|
|
||||||
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;
|
||||||
@ -156,21 +158,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
},
|
},
|
||||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
body = new SkinnableCatcher(),
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
catcherKiai = new CatcherSprite(CatcherAnimationState.Kiai)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
catcherFail = new CatcherSprite(CatcherAnimationState.Fail)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
hitExplosionContainer = new HitExplosionContainer
|
hitExplosionContainer = new HitExplosionContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -184,8 +172,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||||
trails = new CatcherTrailDisplay(this);
|
trails = new CatcherTrailDisplay(this);
|
||||||
|
|
||||||
updateCatcher();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -273,17 +259,16 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
SetHyperDashState();
|
SetHyperDashState();
|
||||||
|
|
||||||
if (result.IsHit)
|
if (result.IsHit)
|
||||||
updateState(hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
|
CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle;
|
||||||
else if (!(hitObject is Banana))
|
else if (!(hitObject is Banana))
|
||||||
updateState(CatcherAnimationState.Fail);
|
CurrentState = CatcherAnimationState.Fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
var catchResult = (CatchJudgementResult)result;
|
var catchResult = (CatchJudgementResult)result;
|
||||||
|
|
||||||
if (CurrentState != catchResult.CatcherAnimationState)
|
CurrentState = catchResult.CatcherAnimationState;
|
||||||
updateState(catchResult.CatcherAnimationState);
|
|
||||||
|
|
||||||
if (HyperDashing != catchResult.CatcherHyperDash)
|
if (HyperDashing != catchResult.CatcherHyperDash)
|
||||||
{
|
{
|
||||||
@ -328,55 +313,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>
|
||||||
@ -418,15 +354,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))
|
||||||
@ -436,38 +363,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCatcher()
|
|
||||||
{
|
|
||||||
currentCatcher?.Hide();
|
|
||||||
|
|
||||||
switch (CurrentState)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
currentCatcher = catcherIdle;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CatcherAnimationState.Fail:
|
|
||||||
currentCatcher = catcherFail;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CatcherAnimationState.Kiai:
|
|
||||||
currentCatcher = catcherKiai;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentCatcher.Show();
|
|
||||||
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState(CatcherAnimationState state)
|
|
||||||
{
|
|
||||||
if (CurrentState == state)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CurrentState = state;
|
|
||||||
updateCatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
|
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
|
||||||
{
|
{
|
||||||
var caughtObject = getCaughtObject(drawableObject.HitObject);
|
var caughtObject = getCaughtObject(drawableObject.HitObject);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
|
||||||
{
|
|
||||||
public class CatcherSprite : SkinnableDrawable
|
|
||||||
{
|
|
||||||
protected override bool ApplySizeRestrictionsToDefault => true;
|
|
||||||
|
|
||||||
public CatcherSprite(CatcherAnimationState state)
|
|
||||||
: base(new CatchSkinComponent(componentFromState(state)), _ =>
|
|
||||||
new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.None;
|
|
||||||
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
|
||||||
|
|
||||||
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
|
|
||||||
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CatchSkinComponents componentFromState(CatcherAnimationState state)
|
|
||||||
{
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case CatcherAnimationState.Fail:
|
|
||||||
return CatchSkinComponents.CatcherFail;
|
|
||||||
|
|
||||||
case CatcherAnimationState.Kiai:
|
|
||||||
return CatchSkinComponents.CatcherKiai;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return CatchSkinComponents.CatcherIdle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DefaultCatcherSprite : Sprite
|
|
||||||
{
|
|
||||||
private readonly CatcherAnimationState state;
|
|
||||||
|
|
||||||
public DefaultCatcherSprite(CatcherAnimationState state)
|
|
||||||
{
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(TextureStore textures)
|
|
||||||
{
|
|
||||||
Texture = textures.Get($"Gameplay/catch/fruit-catcher-{state.ToString().ToLower()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
43
osu.Game.Rulesets.Catch/UI/CatcherTrail.cs
Normal file
43
osu.Game.Rulesets.Catch/UI/CatcherTrail.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A trail of the catcher.
|
||||||
|
/// It also represents a hyper dash afterimage.
|
||||||
|
/// </summary>
|
||||||
|
public class CatcherTrail : PoolableDrawable
|
||||||
|
{
|
||||||
|
public CatcherAnimationState AnimationState
|
||||||
|
{
|
||||||
|
set => body.AnimationState.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SkinnableCatcher body;
|
||||||
|
|
||||||
|
public CatcherTrail()
|
||||||
|
{
|
||||||
|
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
InternalChild = body = new SkinnableCatcher
|
||||||
|
{
|
||||||
|
// Using a frozen clock because trails should not be animated when the skin has an animated catcher.
|
||||||
|
// TODO: The animation should be frozen at the animation frame at the time of the trail generation.
|
||||||
|
Clock = new FramedClock(new ManualClock()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FreeAfterUse()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
base.FreeAfterUse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -21,11 +19,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
private readonly Catcher catcher;
|
private readonly Catcher catcher;
|
||||||
|
|
||||||
private readonly DrawablePool<CatcherTrailSprite> trailPool;
|
private readonly DrawablePool<CatcherTrail> trailPool;
|
||||||
|
|
||||||
private readonly Container<CatcherTrailSprite> dashTrails;
|
private readonly Container<CatcherTrail> dashTrails;
|
||||||
private readonly Container<CatcherTrailSprite> hyperDashTrails;
|
private readonly Container<CatcherTrail> hyperDashTrails;
|
||||||
private readonly Container<CatcherTrailSprite> endGlowSprites;
|
private readonly Container<CatcherTrail> endGlowSprites;
|
||||||
|
|
||||||
private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
@ -85,10 +83,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
trailPool = new DrawablePool<CatcherTrailSprite>(30),
|
trailPool = new DrawablePool<CatcherTrail>(30),
|
||||||
dashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both },
|
dashTrails = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both },
|
||||||
hyperDashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
hyperDashTrails = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||||
endGlowSprites = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
endGlowSprites = new Container<CatcherTrail> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,17 +116,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
|
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CatcherTrailSprite createTrailSprite(Container<CatcherTrailSprite> target)
|
private CatcherTrail createTrailSprite(Container<CatcherTrail> target)
|
||||||
{
|
{
|
||||||
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
|
CatcherTrail sprite = trailPool.Get();
|
||||||
|
|
||||||
CatcherTrailSprite sprite = trailPool.Get();
|
sprite.AnimationState = catcher.CurrentState;
|
||||||
|
|
||||||
sprite.Texture = texture;
|
|
||||||
sprite.Anchor = catcher.Anchor;
|
|
||||||
sprite.Scale = catcher.Scale;
|
sprite.Scale = catcher.Scale;
|
||||||
sprite.Blending = BlendingParameters.Additive;
|
|
||||||
sprite.RelativePositionAxes = catcher.RelativePositionAxes;
|
|
||||||
sprite.Position = catcher.Position;
|
sprite.Position = catcher.Position;
|
||||||
|
|
||||||
target.Add(sprite);
|
target.Add(sprite);
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Pooling;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
|
||||||
{
|
|
||||||
public class CatcherTrailSprite : PoolableDrawable
|
|
||||||
{
|
|
||||||
public Texture Texture
|
|
||||||
{
|
|
||||||
set => sprite.Texture = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Sprite sprite;
|
|
||||||
|
|
||||||
public CatcherTrailSprite()
|
|
||||||
{
|
|
||||||
InternalChild = sprite = new Sprite
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
};
|
|
||||||
|
|
||||||
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
|
||||||
|
|
||||||
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
|
|
||||||
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FreeAfterUse()
|
|
||||||
{
|
|
||||||
ClearTransforms();
|
|
||||||
base.FreeAfterUse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +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.
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public enum HoldNotePosition
|
public enum Direction
|
||||||
{
|
{
|
||||||
Start,
|
Right = 1,
|
||||||
End
|
Left = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
|
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
|
||||||
|
|
||||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer();
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer();
|
||||||
|
|
||||||
|
33
osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
Normal file
33
osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The visual representation of the <see cref="Catcher"/>.
|
||||||
|
/// It includes the body part of the catcher and the catcher plate.
|
||||||
|
/// </summary>
|
||||||
|
public class SkinnableCatcher : SkinnableDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is used by skin elements to determine which texture of the catcher is used.
|
||||||
|
/// </summary>
|
||||||
|
[Cached]
|
||||||
|
public readonly Bindable<CatcherAnimationState> AnimationState = new Bindable<CatcherAnimationState>();
|
||||||
|
|
||||||
|
public SkinnableCatcher()
|
||||||
|
: base(new CatchSkinComponent(CatchSkinComponents.Catcher), _ => new DefaultCatcher())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
|
||||||
|
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +1,54 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
||||||
{
|
{
|
||||||
[Cached(Type = typeof(IAdjustableClock))]
|
protected override Container<Drawable> Content => blueprints ?? base.Content;
|
||||||
private readonly IAdjustableClock clock = new StopwatchClock();
|
|
||||||
|
|
||||||
protected ManiaSelectionBlueprintTestScene()
|
private readonly Container blueprints;
|
||||||
|
|
||||||
|
[Cached(typeof(Playfield))]
|
||||||
|
public Playfield Playfield { get; }
|
||||||
|
|
||||||
|
private readonly ScrollingTestContainer scrollingTestContainer;
|
||||||
|
|
||||||
|
protected ScrollingDirection Direction
|
||||||
{
|
{
|
||||||
Add(new Column(0)
|
set => scrollingTestContainer.Direction = value;
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
AccentColour = Color4.OrangeRed,
|
|
||||||
Clock = new FramedClock(new StopwatchClock()), // No scroll
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ManiaPlayfield Playfield => null;
|
protected ManiaSelectionBlueprintTestScene(int columns)
|
||||||
|
{
|
||||||
|
var stageDefinitions = new List<StageDefinition> { new StageDefinition { Columns = columns } };
|
||||||
|
base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Up)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
Playfield = new ManiaPlayfield(stageDefinitions)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
blueprints = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddToggleStep("Downward scroll", b => Direction = b ? ScrollingDirection.Down : ScrollingDirection.Up);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,32 @@
|
|||||||
// 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.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
||||||
{
|
{
|
||||||
private readonly DrawableHoldNote drawableObject;
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content ?? base.Content;
|
|
||||||
private readonly Container content;
|
|
||||||
|
|
||||||
public TestSceneHoldNoteSelectionBlueprint()
|
public TestSceneHoldNoteSelectionBlueprint()
|
||||||
|
: base(4)
|
||||||
{
|
{
|
||||||
var holdNote = new HoldNote { Column = 0, Duration = 1000 };
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var holdNote = new HoldNote
|
||||||
|
{
|
||||||
|
Column = i,
|
||||||
|
StartTime = i * 100,
|
||||||
|
Duration = 500
|
||||||
|
};
|
||||||
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
|
var drawableHitObject = new DrawableHoldNote(holdNote);
|
||||||
{
|
Playfield.Add(drawableHitObject);
|
||||||
Anchor = Anchor.Centre,
|
AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableHitObject);
|
||||||
Origin = Anchor.Centre,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Width = 50,
|
|
||||||
Child = drawableObject = new DrawableHoldNote(holdNote)
|
|
||||||
{
|
|
||||||
Height = 300,
|
|
||||||
AccentColour = { Value = OsuColour.Gray(0.3f) }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
foreach (var nested in drawableObject.NestedHitObjects)
|
|
||||||
{
|
|
||||||
double finalPosition = (nested.HitObject.StartTime - drawableObject.HitObject.StartTime) / drawableObject.HitObject.Duration;
|
|
||||||
nested.Y = (float)(-finalPosition * content.DrawHeight);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
|
||||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setScrollStep(ScrollingDirection direction)
|
private void setScrollStep(ScrollingDirection direction)
|
||||||
|
@ -1,40 +1,32 @@
|
|||||||
// 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.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
||||||
{
|
{
|
||||||
protected override Container<Drawable> Content => content ?? base.Content;
|
|
||||||
private readonly Container content;
|
|
||||||
|
|
||||||
public TestSceneNoteSelectionBlueprint()
|
public TestSceneNoteSelectionBlueprint()
|
||||||
|
: base(4)
|
||||||
{
|
{
|
||||||
var note = new Note { Column = 0 };
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var note = new Note
|
||||||
|
{
|
||||||
|
Column = i,
|
||||||
|
StartTime = i * 200,
|
||||||
|
};
|
||||||
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
DrawableNote drawableObject;
|
var drawableHitObject = new DrawableNote(note);
|
||||||
|
Playfield.Add(drawableHitObject);
|
||||||
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
|
AddBlueprint(new NoteSelectionBlueprint(note), drawableHitObject);
|
||||||
{
|
}
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Size = new Vector2(50, 20),
|
|
||||||
Child = drawableObject = new DrawableNote(note)
|
|
||||||
};
|
|
||||||
|
|
||||||
AddBlueprint(new NoteSelectionBlueprint(note), drawableObject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||||
|
|
||||||
if (mods.Any(m => !m.Ranked))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
IEnumerable<Mod> scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
|
IEnumerable<Mod> scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
|
||||||
|
|
||||||
double scoreMultiplier = 1.0;
|
double scoreMultiplier = 1.0;
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|
||||||
{
|
|
||||||
public class HoldNoteNoteOverlay : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly HoldNoteSelectionBlueprint holdNoteBlueprint;
|
|
||||||
private readonly HoldNotePosition position;
|
|
||||||
|
|
||||||
public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position)
|
|
||||||
{
|
|
||||||
this.holdNoteBlueprint = holdNoteBlueprint;
|
|
||||||
this.position = position;
|
|
||||||
|
|
||||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
var drawableObject = holdNoteBlueprint.DrawableObject;
|
|
||||||
|
|
||||||
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
|
||||||
if (drawableObject.IsLoaded)
|
|
||||||
{
|
|
||||||
DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail;
|
|
||||||
|
|
||||||
Anchor = note.Anchor;
|
|
||||||
Origin = note.Origin;
|
|
||||||
|
|
||||||
Size = note.DrawSize;
|
|
||||||
Position = note.DrawPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,14 +2,13 @@
|
|||||||
// 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.Allocation;
|
||||||
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.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -17,13 +16,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
||||||
{
|
{
|
||||||
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
private EditNotePiece head;
|
||||||
|
private EditNotePiece tail;
|
||||||
|
|
||||||
public HoldNoteSelectionBlueprint(HoldNote hold)
|
public HoldNoteSelectionBlueprint(HoldNote hold)
|
||||||
: base(hold)
|
: base(hold)
|
||||||
{
|
{
|
||||||
@ -32,12 +30,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new HoldNoteNoteOverlay(this, HoldNotePosition.Start),
|
head = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
||||||
new HoldNoteNoteOverlay(this, HoldNotePosition.End),
|
tail = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -58,21 +54,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
head.Y = HitObjectContainer.PositionAtTime(HitObject.Head.StartTime, HitObject.StartTime);
|
||||||
if (DrawableObject.IsLoaded)
|
tail.Y = HitObjectContainer.PositionAtTime(HitObject.Tail.StartTime, HitObject.StartTime);
|
||||||
{
|
Height = HitObjectContainer.LengthAtTime(HitObject.StartTime, HitObject.EndTime) + tail.DrawHeight;
|
||||||
Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
|
|
||||||
|
|
||||||
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
|
|
||||||
// When scrolling upwards our origin is already at the top of the head note (which is the intended location),
|
|
||||||
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
|
|
||||||
if (direction.Value == ScrollingDirection.Down)
|
|
||||||
Y -= DrawableObject.Tail.DrawHeight;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => head.ScreenSpaceDrawQuad.Centre;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,23 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract class ManiaSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
|
public abstract class ManiaSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
|
||||||
where T : ManiaHitObject
|
where T : ManiaHitObject
|
||||||
{
|
{
|
||||||
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
|
[Resolved]
|
||||||
|
private Playfield playfield { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IScrollingInfo scrollingInfo { get; set; }
|
private IScrollingInfo scrollingInfo { get; set; }
|
||||||
|
|
||||||
|
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||||
|
|
||||||
protected ManiaSelectionBlueprint(T hitObject)
|
protected ManiaSelectionBlueprint(T hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -29,19 +32,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero));
|
var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||||
}
|
Anchor = Origin = anchor;
|
||||||
|
foreach (var child in InternalChildren)
|
||||||
|
child.Anchor = child.Origin = anchor;
|
||||||
|
|
||||||
public override void Show()
|
Position = Parent.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||||
{
|
Width = HitObjectContainer.DrawWidth;
|
||||||
DrawableObject.AlwaysAlive = true;
|
|
||||||
base.Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Hide()
|
|
||||||
{
|
|
||||||
DrawableObject.AlwaysAlive = false;
|
|
||||||
base.Hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });
|
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
|
||||||
if (DrawableObject.IsLoaded)
|
|
||||||
Size = DrawableObject.DrawSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
|
||||||
|
|
||||||
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new ManiaLegacySkinTransformer(source, beatmap);
|
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap);
|
||||||
|
|
||||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public abstract int KeyCount { get; }
|
public abstract int KeyCount { get; }
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
|
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
|
||||||
public override bool Ranked => true;
|
|
||||||
|
|
||||||
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
||||||
{
|
{
|
||||||
|
@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
public override bool Ranked => false;
|
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
||||||
|
@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override string Description => "Notes are flipped horizontally.";
|
public override string Description => "Notes are flipped horizontally.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override bool Ranked => true;
|
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
@ -85,63 +85,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
AccentColour.UnbindFrom(ParentHitObject.AccentColour);
|
AccentColour.UnbindFrom(ParentHitObject.AccentColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double computedLifetimeStart;
|
|
||||||
|
|
||||||
public override double LifetimeStart
|
|
||||||
{
|
|
||||||
get => base.LifetimeStart;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
computedLifetimeStart = value;
|
|
||||||
|
|
||||||
if (!AlwaysAlive)
|
|
||||||
base.LifetimeStart = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private double computedLifetimeEnd;
|
|
||||||
|
|
||||||
public override double LifetimeEnd
|
|
||||||
{
|
|
||||||
get => base.LifetimeEnd;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
computedLifetimeEnd = value;
|
|
||||||
|
|
||||||
if (!AlwaysAlive)
|
|
||||||
base.LifetimeEnd = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool alwaysAlive;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this <see cref="DrawableManiaHitObject"/> should always remain alive.
|
|
||||||
/// </summary>
|
|
||||||
internal bool AlwaysAlive
|
|
||||||
{
|
|
||||||
get => alwaysAlive;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (alwaysAlive == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
alwaysAlive = value;
|
|
||||||
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
// Set the base lifetimes directly, to avoid mangling the computed lifetimes
|
|
||||||
base.LifetimeStart = double.MinValue;
|
|
||||||
base.LifetimeEnd = double.MaxValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LifetimeStart = computedLifetimeStart;
|
|
||||||
LifetimeEnd = computedLifetimeEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||||
{
|
{
|
||||||
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||||
|
@ -50,29 +50,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
{ HitResult.Miss, "mania-hit0" }
|
{ HitResult.Miss, "mania-hit0" }
|
||||||
};
|
};
|
||||||
|
|
||||||
private Lazy<bool> isLegacySkin;
|
private readonly Lazy<bool> isLegacySkin;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether texture for the keys exists.
|
/// Whether texture for the keys exists.
|
||||||
/// Used to determine if the mania ruleset is skinned.
|
/// Used to determine if the mania ruleset is skinned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Lazy<bool> hasKeyTexture;
|
private readonly Lazy<bool> hasKeyTexture;
|
||||||
|
|
||||||
public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap)
|
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||||
: base(source)
|
: base(skin)
|
||||||
{
|
{
|
||||||
this.beatmap = (ManiaBeatmap)beatmap;
|
this.beatmap = (ManiaBeatmap)beatmap;
|
||||||
|
|
||||||
Source.SourceChanged += sourceChanged;
|
isLegacySkin = new Lazy<bool>(() => GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
|
||||||
sourceChanged();
|
hasKeyTexture = new Lazy<bool>(() =>
|
||||||
}
|
|
||||||
|
|
||||||
private void sourceChanged()
|
|
||||||
{
|
{
|
||||||
isLegacySkin = new Lazy<bool>(() => FindProvider(s => s.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null) != null);
|
var keyImage = this.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1";
|
||||||
hasKeyTexture = new Lazy<bool>(() => FindProvider(s => s.GetAnimation(
|
return this.GetAnimation(keyImage, true, true) != null;
|
||||||
s.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value
|
});
|
||||||
?? "mania-key1", true, true) != null) != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
@ -125,7 +121,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Source.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getResult(HitResult result)
|
private Drawable getResult(HitResult result)
|
||||||
@ -146,15 +142,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)
|
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)
|
||||||
return new SampleVirtual();
|
return new SampleVirtual();
|
||||||
|
|
||||||
return Source.GetSample(sampleInfo);
|
return base.GetSample(sampleInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
return Source.GetConfig<LegacyManiaSkinConfigurationLookup, TValue>(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
|
return base.GetConfig<LegacyManiaSkinConfigurationLookup, TValue>(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
|
||||||
|
|
||||||
return Source.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,260 @@
|
|||||||
|
// 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 Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckLowDiffOverlapsTest
|
||||||
|
{
|
||||||
|
private CheckLowDiffOverlaps check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckLowDiffOverlaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoOverlapFarApart()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(200, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoOverlapClose()
|
||||||
|
{
|
||||||
|
assertShouldProbablyOverlap(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 167, Position = new Vector2(200, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoOverlapTooClose()
|
||||||
|
{
|
||||||
|
assertShouldOverlap(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 100, Position = new Vector2(200, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoOverlapTooCloseExpert()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 100, Position = new Vector2(200, 0) }
|
||||||
|
}
|
||||||
|
}, DifficultyRating.Expert);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlapClose()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 167, Position = new Vector2(20, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlapFarApart()
|
||||||
|
{
|
||||||
|
assertShouldNotOverlap(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(20, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAlmostOverlapFarApart()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
// Default circle diameter is 128 px, but part of that is the fade/border of the circle.
|
||||||
|
// We want this to only be a problem when it actually looks like an overlap.
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(125, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAlmostNotOverlapFarApart()
|
||||||
|
{
|
||||||
|
assertShouldNotOverlap(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(110, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlapFarApartExpert()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(20, 0) }
|
||||||
|
}
|
||||||
|
}, DifficultyRating.Expert);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlapTooFarApart()
|
||||||
|
{
|
||||||
|
// Far apart enough to where the objects are not visible at the same time, and so overlapping is fine.
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 2000, Position = new Vector2(20, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderTailOverlapFarApart()
|
||||||
|
{
|
||||||
|
assertShouldNotOverlap(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(startTime: 0, endTime: 500, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object,
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(120, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderTailOverlapClose()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(startTime: 0, endTime: 900, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object,
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(120, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderTailNoOverlapFarApart()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(startTime: 0, endTime: 500, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object,
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(300, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderTailNoOverlapClose()
|
||||||
|
{
|
||||||
|
// If these were circles they would need to overlap, but overlapping with slider tails is not required.
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
getSliderMock(startTime: 0, endTime: 900, startPosition: new Vector2(0, 0), endPosition: new Vector2(100, 0)).Object,
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(300, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<Slider> getSliderMock(double startTime, double endTime, Vector2 startPosition, Vector2 endPosition)
|
||||||
|
{
|
||||||
|
var mockSlider = new Mock<Slider>();
|
||||||
|
mockSlider.SetupGet(s => s.StartTime).Returns(startTime);
|
||||||
|
mockSlider.SetupGet(s => s.Position).Returns(startPosition);
|
||||||
|
mockSlider.SetupGet(s => s.EndPosition).Returns(endPosition);
|
||||||
|
mockSlider.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
|
||||||
|
|
||||||
|
return mockSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating = DifficultyRating.Easy)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||||
|
Assert.That(check.Run(context), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertShouldProbablyOverlap(IBeatmap beatmap, int count = 1)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy);
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(count));
|
||||||
|
Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldProbablyOverlap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertShouldOverlap(IBeatmap beatmap, int count = 1)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy);
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(count));
|
||||||
|
Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldOverlap));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertShouldNotOverlap(IBeatmap beatmap, int count = 1)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy);
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(count));
|
||||||
|
Assert.That(issues.All(issue => issue.Template is CheckLowDiffOverlaps.IssueTemplateShouldNotOverlap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,324 @@
|
|||||||
|
// 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 Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTimeDistanceEqualityTest
|
||||||
|
{
|
||||||
|
private CheckTimeDistanceEquality check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTimeDistanceEquality();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesEquidistant()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(100, 0) },
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(150, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesOneSlightlyOff()
|
||||||
|
{
|
||||||
|
assertWarning(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(80, 0) }, // Distance a quite low compared to previous.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(130, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesOneOff()
|
||||||
|
{
|
||||||
|
assertProblem(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(150, 0) }, // Twice the regular spacing.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesTwoOff()
|
||||||
|
{
|
||||||
|
assertProblem(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(150, 0) }, // Twice the regular spacing.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(250, 0) } // Also twice the regular spacing.
|
||||||
|
}
|
||||||
|
}, count: 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesStacked()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(50, 0) }, // Stacked, is fine.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesStacking()
|
||||||
|
{
|
||||||
|
assertWarning(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(50, 0), StackHeight = 1 },
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(50, 0), StackHeight = 2 },
|
||||||
|
new HitCircle { StartTime = 2000, Position = new Vector2(50, 0), StackHeight = 3 },
|
||||||
|
new HitCircle { StartTime = 2500, Position = new Vector2(50, 0), StackHeight = 4 }, // Ends up far from (50; 0), causing irregular spacing.
|
||||||
|
new HitCircle { StartTime = 3000, Position = new Vector2(100, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesHalfStack()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(55, 0) }, // Basically stacked, so is fine.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(105, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesPartialOverlap()
|
||||||
|
{
|
||||||
|
assertProblem(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(65, 0) }, // Really low distance compared to previous.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(115, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesSlightlyDifferent()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
// Does not need to be perfect, as long as the distance is approximately correct it's sight-readable.
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(52, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(97, 0) },
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(165, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesSlowlyChanging()
|
||||||
|
{
|
||||||
|
const float multiplier = 1.2f;
|
||||||
|
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(50 + 50 * multiplier, 0) },
|
||||||
|
// This gap would be a warning if it weren't for the previous pushing the average spacing up.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(50 + 50 * multiplier + 50 * multiplier * multiplier, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesQuicklyChanging()
|
||||||
|
{
|
||||||
|
const float multiplier = 1.6f;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(50 + 50 * multiplier, 0) }, // Warning
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(50 + 50 * multiplier + 50 * multiplier * multiplier, 0) } // Problem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy);
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.First().Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingWarning);
|
||||||
|
Assert.That(issues.Last().Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesTooFarApart()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 4000, Position = new Vector2(200, 0) }, // 2 seconds apart from previous, so can start from wherever.
|
||||||
|
new HitCircle { StartTime = 4500, Position = new Vector2(250, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCirclesOneOffExpert()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new HitCircle { StartTime = 1000, Position = new Vector2(150, 0) }, // Jumps are allowed in higher difficulties.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) }
|
||||||
|
}
|
||||||
|
}, DifficultyRating.Expert);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinner()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
new Spinner { StartTime = 500, EndTime = 1000 }, // Distance to and from the spinner should be ignored. If it isn't this should give a problem.
|
||||||
|
new HitCircle { StartTime = 1500, Position = new Vector2(100, 0) },
|
||||||
|
new HitCircle { StartTime = 2000, Position = new Vector2(150, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliders()
|
||||||
|
{
|
||||||
|
assertOk(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
getSliderMock(startTime: 1000, endTime: 1500, startPosition: new Vector2(100, 0), endPosition: new Vector2(150, 0)).Object,
|
||||||
|
getSliderMock(startTime: 2000, endTime: 2500, startPosition: new Vector2(200, 0), endPosition: new Vector2(250, 0)).Object,
|
||||||
|
new HitCircle { StartTime = 2500, Position = new Vector2(300, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSlidersOneOff()
|
||||||
|
{
|
||||||
|
assertProblem(new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 0, Position = new Vector2(0) },
|
||||||
|
new HitCircle { StartTime = 500, Position = new Vector2(50, 0) },
|
||||||
|
getSliderMock(startTime: 1000, endTime: 1500, startPosition: new Vector2(100, 0), endPosition: new Vector2(150, 0)).Object,
|
||||||
|
getSliderMock(startTime: 2000, endTime: 2500, startPosition: new Vector2(250, 0), endPosition: new Vector2(300, 0)).Object, // Twice the spacing.
|
||||||
|
new HitCircle { StartTime = 2500, Position = new Vector2(300, 0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mock<Slider> getSliderMock(double startTime, double endTime, Vector2 startPosition, Vector2 endPosition)
|
||||||
|
{
|
||||||
|
var mockSlider = new Mock<Slider>();
|
||||||
|
mockSlider.SetupGet(s => s.StartTime).Returns(startTime);
|
||||||
|
mockSlider.SetupGet(s => s.Position).Returns(startPosition);
|
||||||
|
mockSlider.SetupGet(s => s.EndPosition).Returns(endPosition);
|
||||||
|
mockSlider.As<IHasDuration>().Setup(d => d.EndTime).Returns(endTime);
|
||||||
|
|
||||||
|
return mockSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating = DifficultyRating.Easy)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||||
|
Assert.That(check.Run(context), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertWarning(IBeatmap beatmap, int count = 1)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy);
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(count));
|
||||||
|
Assert.That(issues.All(issue => issue.Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingWarning));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertProblem(IBeatmap beatmap, int count = 1)
|
||||||
|
{
|
||||||
|
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), DifficultyRating.Easy);
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(count));
|
||||||
|
Assert.That(issues.All(issue => issue.Template is CheckTimeDistanceEquality.IssueTemplateIrregularSpacingProblem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Transforms;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneOsuEditorHitAnimations : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleAnimationDisable()
|
||||||
|
{
|
||||||
|
HitCircle hitCircle = null;
|
||||||
|
DrawableHitCircle drawableHitCircle = null;
|
||||||
|
|
||||||
|
AddStep("retrieve first hit circle", () => hitCircle = getHitCircle(0));
|
||||||
|
toggleAnimations(true);
|
||||||
|
seekSmoothlyTo(() => hitCircle.StartTime + 10);
|
||||||
|
|
||||||
|
AddStep("retrieve drawable", () => drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle));
|
||||||
|
assertFutureTransforms(() => drawableHitCircle.CirclePiece, true);
|
||||||
|
|
||||||
|
AddStep("retrieve second hit circle", () => hitCircle = getHitCircle(1));
|
||||||
|
toggleAnimations(false);
|
||||||
|
seekSmoothlyTo(() => hitCircle.StartTime + 10);
|
||||||
|
|
||||||
|
AddStep("retrieve drawable", () => drawableHitCircle = (DrawableHitCircle)getDrawableObjectFor(hitCircle));
|
||||||
|
assertFutureTransforms(() => drawableHitCircle.CirclePiece, false);
|
||||||
|
AddAssert("hit circle has longer fade-out applied", () =>
|
||||||
|
{
|
||||||
|
var alphaTransform = drawableHitCircle.Transforms.Last(t => t.TargetMember == nameof(Alpha));
|
||||||
|
return alphaTransform.EndTime - alphaTransform.StartTime == DrawableOsuEditorRuleset.EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderAnimationDisable()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
DrawableSlider drawableSlider = null;
|
||||||
|
DrawableSliderRepeat sliderRepeat = null;
|
||||||
|
|
||||||
|
AddStep("retrieve first slider with repeats", () => slider = getSliderWithRepeats(0));
|
||||||
|
toggleAnimations(true);
|
||||||
|
seekSmoothlyTo(() => slider.StartTime + slider.SpanDuration + 10);
|
||||||
|
|
||||||
|
retrieveDrawables();
|
||||||
|
assertFutureTransforms(() => sliderRepeat, true);
|
||||||
|
|
||||||
|
AddStep("retrieve second slider with repeats", () => slider = getSliderWithRepeats(1));
|
||||||
|
toggleAnimations(false);
|
||||||
|
seekSmoothlyTo(() => slider.StartTime + slider.SpanDuration + 10);
|
||||||
|
|
||||||
|
retrieveDrawables();
|
||||||
|
assertFutureTransforms(() => sliderRepeat.Arrow, false);
|
||||||
|
seekSmoothlyTo(() => slider.GetEndTime());
|
||||||
|
AddAssert("slider has longer fade-out applied", () =>
|
||||||
|
{
|
||||||
|
var alphaTransform = drawableSlider.Transforms.Last(t => t.TargetMember == nameof(Alpha));
|
||||||
|
return alphaTransform.EndTime - alphaTransform.StartTime == DrawableOsuEditorRuleset.EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION;
|
||||||
|
});
|
||||||
|
|
||||||
|
void retrieveDrawables() =>
|
||||||
|
AddStep("retrieve drawables", () =>
|
||||||
|
{
|
||||||
|
drawableSlider = (DrawableSlider)getDrawableObjectFor(slider);
|
||||||
|
sliderRepeat = (DrawableSliderRepeat)getDrawableObjectFor(slider.NestedHitObjects.OfType<SliderRepeat>().First());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private HitCircle getHitCircle(int index)
|
||||||
|
=> EditorBeatmap.HitObjects.OfType<HitCircle>().ElementAt(index);
|
||||||
|
|
||||||
|
private Slider getSliderWithRepeats(int index)
|
||||||
|
=> EditorBeatmap.HitObjects.OfType<Slider>().Where(s => s.RepeatCount >= 1).ElementAt(index);
|
||||||
|
|
||||||
|
private DrawableHitObject getDrawableObjectFor(HitObject hitObject)
|
||||||
|
=> this.ChildrenOfType<DrawableHitObject>().Single(ho => ho.HitObject == hitObject);
|
||||||
|
|
||||||
|
private IEnumerable<Transform> getTransformsRecursively(Drawable drawable)
|
||||||
|
=> drawable.ChildrenOfType<Drawable>().SelectMany(d => d.Transforms);
|
||||||
|
|
||||||
|
private void toggleAnimations(bool enabled)
|
||||||
|
=> AddStep($"toggle animations {(enabled ? "on" : "off")}", () => config.SetValue(OsuSetting.EditorHitAnimations, enabled));
|
||||||
|
|
||||||
|
private void seekSmoothlyTo(Func<double> targetTime)
|
||||||
|
{
|
||||||
|
AddStep("seek smoothly", () => EditorClock.SeekSmoothlyTo(targetTime.Invoke()));
|
||||||
|
AddUntilStep("wait for seek", () => Precision.AlmostEquals(targetTime.Invoke(), EditorClock.CurrentTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertFutureTransforms(Func<Drawable> getDrawable, bool hasFutureTransforms)
|
||||||
|
=> AddAssert($"object {(hasFutureTransforms ? "has" : "has no")} future transforms",
|
||||||
|
() => getTransformsRecursively(getDrawable()).Any(t => t.EndTime >= EditorClock.CurrentTime) == hasFutureTransforms);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
[General]
|
[General]
|
||||||
Version: 1.0
|
// no version specified means v1
|
||||||
|
|
||||||
[Fonts]
|
[Fonts]
|
||||||
HitCircleOverlap: 3
|
HitCircleOverlap: 3
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
@ -112,7 +113,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
||||||
|
|
||||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
|
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : null;
|
||||||
|
|
||||||
|
public IEnumerable<ISkin> AllSources => new[] { this };
|
||||||
|
|
||||||
public event Action SourceChanged
|
public event Action SourceChanged
|
||||||
{
|
{
|
||||||
|
@ -21,20 +21,37 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private int depthIndex;
|
private int depthIndex;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestVariousHitCircles()
|
public void TestHits()
|
||||||
|
{
|
||||||
|
AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
|
||||||
|
AddStep("Hit Medium Single", () => SetContents(_ => testSingle(5, true)));
|
||||||
|
AddStep("Hit Small Single", () => SetContents(_ => testSingle(7, true)));
|
||||||
|
AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true)));
|
||||||
|
AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true)));
|
||||||
|
AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHittingEarly()
|
||||||
|
{
|
||||||
|
AddStep("Hit stream early", () => SetContents(_ => testStream(5, true, -150)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMisses()
|
||||||
{
|
{
|
||||||
AddStep("Miss Big Single", () => SetContents(_ => testSingle(2)));
|
AddStep("Miss Big Single", () => SetContents(_ => testSingle(2)));
|
||||||
AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5)));
|
AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5)));
|
||||||
AddStep("Miss Small Single", () => SetContents(_ => testSingle(7)));
|
AddStep("Miss Small Single", () => SetContents(_ => testSingle(7)));
|
||||||
AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
|
|
||||||
AddStep("Hit Medium Single", () => SetContents(_ => testSingle(5, true)));
|
|
||||||
AddStep("Hit Small Single", () => SetContents(_ => testSingle(7, true)));
|
|
||||||
AddStep("Miss Big Stream", () => SetContents(_ => testStream(2)));
|
AddStep("Miss Big Stream", () => SetContents(_ => testStream(2)));
|
||||||
AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5)));
|
AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5)));
|
||||||
AddStep("Miss Small Stream", () => SetContents(_ => testStream(7)));
|
AddStep("Miss Small Stream", () => SetContents(_ => testStream(7)));
|
||||||
AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true)));
|
}
|
||||||
AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true)));
|
|
||||||
AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true)));
|
[Test]
|
||||||
|
public void TestHittingLate()
|
||||||
|
{
|
||||||
|
AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||||
@ -46,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return playfield;
|
return playfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable testStream(float circleSize, bool auto = false)
|
private Drawable testStream(float circleSize, bool auto = false, double hitOffset = 0)
|
||||||
{
|
{
|
||||||
var playfield = new TestOsuPlayfield();
|
var playfield = new TestOsuPlayfield();
|
||||||
|
|
||||||
@ -54,14 +71,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
for (int i = 0; i <= 1000; i += 100)
|
for (int i = 0; i <= 1000; i += 100)
|
||||||
{
|
{
|
||||||
playfield.Add(createSingle(circleSize, auto, i, pos));
|
playfield.Add(createSingle(circleSize, auto, i, pos, hitOffset));
|
||||||
pos.X += 50;
|
pos.X += 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
return playfield;
|
return playfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset)
|
private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset, double hitOffset = 0)
|
||||||
{
|
{
|
||||||
positionOffset ??= Vector2.Zero;
|
positionOffset ??= Vector2.Zero;
|
||||||
|
|
||||||
@ -73,14 +90,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
||||||
|
|
||||||
var drawable = CreateDrawableHitCircle(circle, auto);
|
var drawable = CreateDrawableHitCircle(circle, auto, hitOffset);
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
mod.ApplyToDrawableHitObject(drawable);
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
|
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0) => new TestDrawableHitCircle(circle, auto, hitOffset)
|
||||||
{
|
{
|
||||||
Depth = depthIndex++
|
Depth = depthIndex++
|
||||||
};
|
};
|
||||||
@ -88,18 +105,20 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
protected class TestDrawableHitCircle : DrawableHitCircle
|
protected class TestDrawableHitCircle : DrawableHitCircle
|
||||||
{
|
{
|
||||||
private readonly bool auto;
|
private readonly bool auto;
|
||||||
|
private readonly double hitOffset;
|
||||||
|
|
||||||
public TestDrawableHitCircle(HitCircle h, bool auto)
|
public TestDrawableHitCircle(HitCircle h, bool auto, double hitOffset)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
this.auto = auto;
|
this.auto = auto;
|
||||||
|
this.hitOffset = hitOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerJudgement() => UpdateResult(true);
|
public void TriggerJudgement() => Schedule(() => UpdateResult(true));
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (auto && !userTriggered && timeOffset > 0)
|
if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false)
|
||||||
{
|
{
|
||||||
// force success
|
// force success
|
||||||
ApplyResult(r => r.Type = HitResult.Great);
|
ApplyResult(r => r.Type = HitResult.Great);
|
||||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
|
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0)
|
||||||
{
|
{
|
||||||
circle.ComboIndexBindable.BindTo(comboIndex);
|
circle.ComboIndexBindable.BindTo(comboIndex);
|
||||||
circle.IndexInCurrentComboBindable.BindTo(comboIndex);
|
circle.IndexInCurrentComboBindable.BindTo(comboIndex);
|
||||||
return base.CreateDrawableHitCircle(circle, auto);
|
return base.CreateDrawableHitCircle(circle, auto, hitOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return base.CreateBeatmapForSkinProvider();
|
return base.CreateBeatmapForSkinProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0)
|
||||||
{
|
{
|
||||||
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
|
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto, hitOffset);
|
||||||
|
|
||||||
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -164,9 +165,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => default;
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
||||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => null;
|
|
||||||
|
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : null;
|
||||||
|
|
||||||
|
public IEnumerable<ISkin> AllSources => new[] { this };
|
||||||
|
|
||||||
public event Action SourceChanged;
|
public event Action SourceChanged;
|
||||||
|
|
||||||
|
@ -335,8 +335,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
var drawable = CreateDrawableSlider(slider);
|
var drawable = CreateDrawableSlider(slider);
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
mod.ApplyToDrawableHitObject(drawable);
|
||||||
|
|
||||||
drawable.OnNewResult += onNewResult;
|
drawable.OnNewResult += onNewResult;
|
||||||
|
|
||||||
|
@ -85,8 +85,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Scale = new Vector2(0.75f)
|
Scale = new Vector2(0.75f)
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawableSpinner });
|
mod.ApplyToDrawableHitObject(drawableSpinner);
|
||||||
|
|
||||||
return drawableSpinner;
|
return drawableSpinner;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||||
|
|
||||||
// Don't count scores made with supposedly unranked mods
|
|
||||||
if (mods.Any(m => !m.Ranked))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Custom multipliers for NoFail and SpunOut.
|
// Custom multipliers for NoFail and SpunOut.
|
||||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||||
|
|
||||||
|
109
osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs
Normal file
109
osu.Game.Rulesets.Osu/Edit/Checks/CheckLowDiffOverlaps.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckLowDiffOverlaps : ICheck
|
||||||
|
{
|
||||||
|
// For the lowest difficulties, the osu! Ranking Criteria encourages overlapping ~180 BPM 1/2, but discourages ~180 BPM 1/1.
|
||||||
|
private const double should_overlap_threshold = 150; // 200 BPM 1/2
|
||||||
|
private const double should_probably_overlap_threshold = 175; // 170 BPM 1/2
|
||||||
|
private const double should_not_overlap_threshold = 250; // 120 BPM 1/2 = 240 BPM 1/1
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objects need to overlap this much before being treated as an overlap, else it may just be the borders slightly touching.
|
||||||
|
/// </summary>
|
||||||
|
private const double overlap_leniency = 5;
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Missing or unexpected overlaps");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateShouldOverlap(this),
|
||||||
|
new IssueTemplateShouldProbablyOverlap(this),
|
||||||
|
new IssueTemplateShouldNotOverlap(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
// TODO: This should also apply to *lowest difficulty* Normals - they are skipped for now.
|
||||||
|
if (context.InterpretedDifficulty > DifficultyRating.Easy)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var hitObjects = context.Beatmap.HitObjects;
|
||||||
|
|
||||||
|
for (int i = 0; i < hitObjects.Count - 1; ++i)
|
||||||
|
{
|
||||||
|
if (!(hitObjects[i] is OsuHitObject hitObject) || hitObject is Spinner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var deltaTime = nextHitObject.StartTime - hitObject.GetEndTime();
|
||||||
|
if (deltaTime >= hitObject.TimeFadeIn + hitObject.TimePreempt)
|
||||||
|
// The objects are not visible at the same time (without mods), hence skipping.
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var distanceSq = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthSquared;
|
||||||
|
var diameter = (hitObject.Radius - overlap_leniency) * 2;
|
||||||
|
var diameterSq = diameter * diameter;
|
||||||
|
|
||||||
|
bool areOverlapping = distanceSq < diameterSq;
|
||||||
|
|
||||||
|
// Slider ends do not need to be overlapped because of slider leniency.
|
||||||
|
if (!areOverlapping && !(hitObject is Slider))
|
||||||
|
{
|
||||||
|
if (deltaTime < should_overlap_threshold)
|
||||||
|
yield return new IssueTemplateShouldOverlap(this).Create(deltaTime, hitObject, nextHitObject);
|
||||||
|
else if (deltaTime < should_probably_overlap_threshold)
|
||||||
|
yield return new IssueTemplateShouldProbablyOverlap(this).Create(deltaTime, hitObject, nextHitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areOverlapping && deltaTime > should_not_overlap_threshold)
|
||||||
|
yield return new IssueTemplateShouldNotOverlap(this).Create(deltaTime, hitObject, nextHitObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IssueTemplateOverlap : IssueTemplate
|
||||||
|
{
|
||||||
|
protected IssueTemplateOverlap(ICheck check, IssueType issueType, string unformattedMessage)
|
||||||
|
: base(check, issueType, unformattedMessage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(double deltaTime, params HitObject[] hitObjects) => new Issue(hitObjects, this, deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateShouldOverlap : IssueTemplateOverlap
|
||||||
|
{
|
||||||
|
public IssueTemplateShouldOverlap(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "These are {0} ms apart and so should be overlapping.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateShouldProbablyOverlap : IssueTemplateOverlap
|
||||||
|
{
|
||||||
|
public IssueTemplateShouldProbablyOverlap(ICheck check)
|
||||||
|
: base(check, IssueType.Warning, "These are {0} ms apart and so should probably be overlapping.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateShouldNotOverlap : IssueTemplateOverlap
|
||||||
|
{
|
||||||
|
public IssueTemplateShouldNotOverlap(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "These are {0} ms apart and so should NOT be overlapping.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
179
osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs
Normal file
179
osu.Game.Rulesets.Osu/Edit/Checks/CheckTimeDistanceEquality.cs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckTimeDistanceEquality : ICheck
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Two objects this many ms apart or more are skipped. (200 BPM 2/1)
|
||||||
|
/// </summary>
|
||||||
|
private const double pattern_lifetime = 600;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Two objects this distance apart or less are skipped.
|
||||||
|
/// </summary>
|
||||||
|
private const double stack_leniency = 12;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long an observation is relevant for comparison. (120 BPM 8/1)
|
||||||
|
/// </summary>
|
||||||
|
private const double observation_lifetime = 4000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How different two delta times can be to still be compared. (240 BPM 1/16)
|
||||||
|
/// </summary>
|
||||||
|
private const double similar_time_leniency = 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many pixels are subtracted from the difference between current and expected distance.
|
||||||
|
/// </summary>
|
||||||
|
private const double distance_leniency_absolute_warning = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much of the current distance that the difference can make out.
|
||||||
|
/// </summary>
|
||||||
|
private const double distance_leniency_percent_warning = 0.15;
|
||||||
|
|
||||||
|
private const double distance_leniency_absolute_problem = 20;
|
||||||
|
private const double distance_leniency_percent_problem = 0.3;
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Spread, "Object too close or far away from previous");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateIrregularSpacingProblem(this),
|
||||||
|
new IssueTemplateIrregularSpacingWarning(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an observation of the time and distance between two objects.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct ObservedTimeDistance
|
||||||
|
{
|
||||||
|
public readonly double ObservationTime;
|
||||||
|
public readonly double DeltaTime;
|
||||||
|
public readonly double Distance;
|
||||||
|
|
||||||
|
public ObservedTimeDistance(double observationTime, double deltaTime, double distance)
|
||||||
|
{
|
||||||
|
ObservationTime = observationTime;
|
||||||
|
DeltaTime = deltaTime;
|
||||||
|
Distance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
if (context.InterpretedDifficulty > DifficultyRating.Normal)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var prevObservedTimeDistances = new List<ObservedTimeDistance>();
|
||||||
|
var hitObjects = context.Beatmap.HitObjects;
|
||||||
|
|
||||||
|
for (int i = 0; i < hitObjects.Count - 1; ++i)
|
||||||
|
{
|
||||||
|
if (!(hitObjects[i] is OsuHitObject hitObject) || hitObject is Spinner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!(hitObjects[i + 1] is OsuHitObject nextHitObject) || nextHitObject is Spinner)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var deltaTime = nextHitObject.StartTime - hitObject.GetEndTime();
|
||||||
|
|
||||||
|
// Ignore objects that are far enough apart in time to not be considered the same pattern.
|
||||||
|
if (deltaTime > pattern_lifetime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Relying on FastInvSqrt is probably good enough here. We'll be taking the difference between distances later, hence square not being sufficient.
|
||||||
|
var distance = (hitObject.StackedEndPosition - nextHitObject.StackedPosition).LengthFast;
|
||||||
|
|
||||||
|
// Ignore stacks and half-stacks, as these are close enough to where they can't be confused for being time-distanced.
|
||||||
|
if (distance < stack_leniency)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var observedTimeDistance = new ObservedTimeDistance(nextHitObject.StartTime, deltaTime, distance);
|
||||||
|
var expectedDistance = getExpectedDistance(prevObservedTimeDistances, observedTimeDistance);
|
||||||
|
|
||||||
|
if (expectedDistance == 0)
|
||||||
|
{
|
||||||
|
// There was nothing relevant to compare to.
|
||||||
|
prevObservedTimeDistances.Add(observedTimeDistance);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Math.Abs(expectedDistance - distance) - distance_leniency_absolute_problem) / distance > distance_leniency_percent_problem)
|
||||||
|
yield return new IssueTemplateIrregularSpacingProblem(this).Create(expectedDistance, distance, hitObject, nextHitObject);
|
||||||
|
else if ((Math.Abs(expectedDistance - distance) - distance_leniency_absolute_warning) / distance > distance_leniency_percent_warning)
|
||||||
|
yield return new IssueTemplateIrregularSpacingWarning(this).Create(expectedDistance, distance, hitObject, nextHitObject);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We use `else` here to prevent issues from cascading; an object spaced too far could cause regular spacing to be considered "too short" otherwise.
|
||||||
|
prevObservedTimeDistances.Add(observedTimeDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getExpectedDistance(IEnumerable<ObservedTimeDistance> prevObservedTimeDistances, ObservedTimeDistance observedTimeDistance)
|
||||||
|
{
|
||||||
|
var observations = prevObservedTimeDistances.Count();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
// Looping this in reverse allows us to break before going through all elements, as we're only interested in the most recent ones.
|
||||||
|
for (int i = observations - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
var prevObservedTimeDistance = prevObservedTimeDistances.ElementAt(i);
|
||||||
|
|
||||||
|
// Only consider observations within the last few seconds - this allows the map to build spacing up/down over time, but prevents it from being too sudden.
|
||||||
|
if (observedTimeDistance.ObservationTime - prevObservedTimeDistance.ObservationTime > observation_lifetime)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Only consider observations which have a similar time difference - this leniency allows handling of multi-BPM maps which speed up/down slowly.
|
||||||
|
if (Math.Abs(observedTimeDistance.DeltaTime - prevObservedTimeDistance.DeltaTime) > similar_time_leniency)
|
||||||
|
break;
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
sum += prevObservedTimeDistance.Distance / Math.Max(prevObservedTimeDistance.DeltaTime, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / Math.Max(count, 1) * observedTimeDistance.DeltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IssueTemplateIrregularSpacing : IssueTemplate
|
||||||
|
{
|
||||||
|
protected IssueTemplateIrregularSpacing(ICheck check, IssueType issueType)
|
||||||
|
: base(check, issueType, "Expected {0:0} px spacing like previous objects, currently {1:0}.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(double expected, double actual, params HitObject[] hitObjects) => new Issue(hitObjects, this, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateIrregularSpacingProblem : IssueTemplateIrregularSpacing
|
||||||
|
{
|
||||||
|
public IssueTemplateIrregularSpacingProblem(ICheck check)
|
||||||
|
: base(check, IssueType.Problem)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateIrregularSpacingWarning : IssueTemplateIrregularSpacing
|
||||||
|
{
|
||||||
|
public IssueTemplateIrregularSpacingWarning(ICheck check)
|
||||||
|
: base(check, IssueType.Warning)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public class DrawableOsuEditorRuleset : DrawableOsuRuleset
|
public class DrawableOsuEditorRuleset : DrawableOsuRuleset
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||||
|
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||||
|
/// </summary>
|
||||||
|
public const double EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION = 700;
|
||||||
|
|
||||||
public DrawableOsuEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public DrawableOsuEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
@ -46,12 +52,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
d.ApplyCustomUpdateState += updateState;
|
d.ApplyCustomUpdateState += updateState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
|
||||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
|
||||||
/// </summary>
|
|
||||||
private const double editor_hit_object_fade_out_extension = 700;
|
|
||||||
|
|
||||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
if (state == ArmedState.Idle || hitAnimations.Value)
|
if (state == ArmedState.Idle || hitAnimations.Value)
|
||||||
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (hitObject is DrawableHitCircle circle)
|
if (hitObject is DrawableHitCircle circle)
|
||||||
{
|
{
|
||||||
circle.ApproachCircle
|
circle.ApproachCircle
|
||||||
.FadeOutFromOne(editor_hit_object_fade_out_extension * 4)
|
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
|
||||||
.Expire();
|
.Expire();
|
||||||
|
|
||||||
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
|
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
|
||||||
@ -69,14 +69,20 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (hitObject is IHasMainCirclePiece mainPieceContainer)
|
if (hitObject is IHasMainCirclePiece mainPieceContainer)
|
||||||
{
|
{
|
||||||
// clear any explode animation logic.
|
// clear any explode animation logic.
|
||||||
mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true);
|
// this is scheduled after children to ensure that the clear happens after invocations of ApplyCustomUpdateState on the circle piece's nested skinnables.
|
||||||
mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true);
|
ScheduleAfterChildren(() =>
|
||||||
|
{
|
||||||
|
if (hitObject.HitObject == null) return;
|
||||||
|
|
||||||
|
mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.StateUpdateTime, true);
|
||||||
|
mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.StateUpdateTime, true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hitObject is DrawableSliderRepeat repeat)
|
if (hitObject is DrawableSliderRepeat repeat)
|
||||||
{
|
{
|
||||||
repeat.Arrow.ApplyTransformsAt(hitObject.HitStateUpdateTime, true);
|
repeat.Arrow.ApplyTransformsAt(hitObject.StateUpdateTime, true);
|
||||||
repeat.Arrow.ClearTransformsAfter(hitObject.HitStateUpdateTime, true);
|
repeat.Arrow.ClearTransformsAfter(hitObject.StateUpdateTime, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjust the visuals of top-level object types to make them stay on screen for longer than usual.
|
// adjust the visuals of top-level object types to make them stay on screen for longer than usual.
|
||||||
@ -93,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
hitObject.RemoveTransform(existing);
|
hitObject.RemoveTransform(existing);
|
||||||
|
|
||||||
using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime))
|
using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime))
|
||||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
hitObject.FadeOut(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION).Expire();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
private readonly List<ICheck> checks = new List<ICheck>
|
private readonly List<ICheck> checks = new List<ICheck>
|
||||||
{
|
{
|
||||||
new CheckOffscreenObjects()
|
// Compose
|
||||||
|
new CheckOffscreenObjects(),
|
||||||
|
|
||||||
|
// Spread
|
||||||
|
new CheckTimeDistanceEquality(),
|
||||||
|
new CheckLowDiffOverlaps()
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
12
osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs
Normal file
12
osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMutateApproachCircles
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
100
osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
Normal file
100
osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles
|
||||||
|
{
|
||||||
|
public override string Name => "Approach Different";
|
||||||
|
public override string Acronym => "AD";
|
||||||
|
public override string Description => "Never trust the approach circles...";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
|
||||||
|
|
||||||
|
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
||||||
|
public BindableFloat Scale { get; } = new BindableFloat(4)
|
||||||
|
{
|
||||||
|
Precision = 0.1f,
|
||||||
|
MinValue = 2,
|
||||||
|
MaxValue = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
[SettingSource("Style", "Change the animation style of the approach circles.", 1)]
|
||||||
|
public Bindable<AnimationStyle> Style { get; } = new Bindable<AnimationStyle>();
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
drawable.ApplyCustomUpdateState += (drawableObject, state) =>
|
||||||
|
{
|
||||||
|
if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return;
|
||||||
|
|
||||||
|
var hitCircle = drawableHitCircle.HitObject;
|
||||||
|
|
||||||
|
drawableHitCircle.ApproachCircle.ClearTransforms(targetMember: nameof(Scale));
|
||||||
|
|
||||||
|
using (drawableHitCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
|
||||||
|
drawableHitCircle.ApproachCircle.ScaleTo(Scale.Value).ScaleTo(1f, hitCircle.TimePreempt, getEasing(Style.Value));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Easing getEasing(AnimationStyle style)
|
||||||
|
{
|
||||||
|
switch (style)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return Easing.None;
|
||||||
|
|
||||||
|
case AnimationStyle.Accelerate1:
|
||||||
|
return Easing.In;
|
||||||
|
|
||||||
|
case AnimationStyle.Accelerate2:
|
||||||
|
return Easing.InCubic;
|
||||||
|
|
||||||
|
case AnimationStyle.Accelerate3:
|
||||||
|
return Easing.InQuint;
|
||||||
|
|
||||||
|
case AnimationStyle.Gravity:
|
||||||
|
return Easing.InBack;
|
||||||
|
|
||||||
|
case AnimationStyle.Decelerate1:
|
||||||
|
return Easing.Out;
|
||||||
|
|
||||||
|
case AnimationStyle.Decelerate2:
|
||||||
|
return Easing.OutCubic;
|
||||||
|
|
||||||
|
case AnimationStyle.Decelerate3:
|
||||||
|
return Easing.OutQuint;
|
||||||
|
|
||||||
|
case AnimationStyle.InOut1:
|
||||||
|
return Easing.InOutCubic;
|
||||||
|
|
||||||
|
case AnimationStyle.InOut2:
|
||||||
|
return Easing.InOutQuint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AnimationStyle
|
||||||
|
{
|
||||||
|
Gravity,
|
||||||
|
InOut1,
|
||||||
|
InOut2,
|
||||||
|
Accelerate1,
|
||||||
|
Accelerate2,
|
||||||
|
Accelerate3,
|
||||||
|
Decelerate1,
|
||||||
|
Decelerate2,
|
||||||
|
Decelerate3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.Collections.Generic;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -9,11 +8,9 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModBarrelRoll : ModBarrelRoll<OsuHitObject>, IApplicableToDrawableHitObjects
|
public class OsuModBarrelRoll : ModBarrelRoll<OsuHitObject>, IApplicableToDrawableHitObject
|
||||||
{
|
{
|
||||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public void ApplyToDrawableHitObject(DrawableHitObject d)
|
||||||
{
|
|
||||||
foreach (var d in drawables)
|
|
||||||
{
|
{
|
||||||
d.OnUpdate += _ =>
|
d.OnUpdate += _ =>
|
||||||
{
|
{
|
||||||
@ -26,5 +23,4 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
|
|
||||||
public override bool Ranked => false;
|
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
private DrawableOsuBlinds blinds;
|
private DrawableOsuBlinds blinds;
|
||||||
|
|
||||||
|
@ -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.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -15,7 +14,7 @@ using osu.Game.Rulesets.UI;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset<OsuHitObject>
|
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
{
|
{
|
||||||
[SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")]
|
[SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")]
|
||||||
public Bindable<bool> NoSliderHeadAccuracy { get; } = new BindableBool(true);
|
public Bindable<bool> NoSliderHeadAccuracy { get; } = new BindableBool(true);
|
||||||
@ -54,9 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
|
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public void ApplyToDrawableHitObject(DrawableHitObject obj)
|
||||||
{
|
|
||||||
foreach (var obj in drawables)
|
|
||||||
{
|
{
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
@ -74,5 +71,4 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// 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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
@ -19,7 +17,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObjects
|
public class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
|
|
||||||
@ -31,13 +29,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
|
public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
|
||||||
|
|
||||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
{
|
|
||||||
foreach (var s in drawables.OfType<DrawableSlider>())
|
|
||||||
{
|
{
|
||||||
|
if (drawable is DrawableSlider s)
|
||||||
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
|
s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public override void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => 1.06;
|
||||||
public override bool Ranked => true;
|
|
||||||
|
|
||||||
public void ApplyToHitObject(HitObject hitObject)
|
public void ApplyToHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
|
@ -11,15 +11,16 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModHidden : ModHidden
|
public class OsuModHidden : ModHidden, IMutateApproachCircles
|
||||||
{
|
{
|
||||||
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => 1.06;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) };
|
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
|
||||||
|
|
||||||
private const double fade_in_duration_multiplier = 0.4;
|
private const double fade_in_duration_multiplier = 0.4;
|
||||||
private const double fade_out_duration_multiplier = 0.3;
|
private const double fade_out_duration_multiplier = 0.3;
|
||||||
@ -110,6 +111,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
// hide elements we don't care about.
|
// hide elements we don't care about.
|
||||||
// todo: hide background
|
// todo: hide background
|
||||||
|
|
||||||
|
spinner.Body.OnSkinChanged += () => hideSpinnerApproachCircle(spinner);
|
||||||
|
hideSpinnerApproachCircle(spinner);
|
||||||
|
|
||||||
using (spinner.BeginAbsoluteSequence(fadeStartTime))
|
using (spinner.BeginAbsoluteSequence(fadeStartTime))
|
||||||
spinner.FadeOut(fadeDuration);
|
spinner.FadeOut(fadeDuration);
|
||||||
|
|
||||||
@ -160,5 +164,15 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void hideSpinnerApproachCircle(DrawableSpinner spinner)
|
||||||
|
{
|
||||||
|
var approachCircle = (spinner.Body.Drawable as IHasApproachCircle)?.ApproachCircle;
|
||||||
|
if (approachCircle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (spinner.BeginAbsoluteSequence(spinner.HitObject.StartTime - spinner.HitObject.TimePreempt))
|
||||||
|
approachCircle.Hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adjusts the size of hit objects during their fade in animation.
|
/// Adjusts the size of hit objects during their fade in animation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment
|
public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IMutateApproachCircles
|
||||||
{
|
{
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
protected virtual float EndScale => 1;
|
protected virtual float EndScale => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) };
|
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModRandom : ModRandom, IApplicableToBeatmap
|
public class OsuModRandom : ModRandom, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Description => "It never gets boring!";
|
public override string Description => "It never gets boring!";
|
||||||
public override bool Ranked => false;
|
|
||||||
|
|
||||||
// The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle.
|
// The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle.
|
||||||
// The closer the hit objects draw to the border, the sharper the turn
|
// The closer the hit objects draw to the border, the sharper the turn
|
||||||
|
@ -12,7 +12,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModSpinIn : ModWithVisibilityAdjustment
|
public class OsuModSpinIn : ModWithVisibilityAdjustment, IMutateApproachCircles
|
||||||
{
|
{
|
||||||
public override string Name => "Spin In";
|
public override string Name => "Spin In";
|
||||||
public override string Acronym => "SI";
|
public override string Acronym => "SI";
|
||||||
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
|
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
|
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
|
||||||
|
|
||||||
private const int rotate_offset = 360;
|
private const int rotate_offset = 360;
|
||||||
private const float rotate_starting_width = 2;
|
private const float rotate_starting_width = 2;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user