mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Merge branch 'master' into diffspike-balance
This commit is contained in:
commit
84fd59777c
@ -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.705.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -157,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
|
|||||||
|
|
||||||
#Style - variable declaration
|
#Style - variable declaration
|
||||||
csharp_style_inlined_variable_declaration = true:warning
|
csharp_style_inlined_variable_declaration = true:warning
|
||||||
csharp_style_deconstructed_variable_declaration = true:warning
|
csharp_style_deconstructed_variable_declaration = false:silent
|
||||||
|
|
||||||
#Style - other C# 7.x features
|
#Style - other C# 7.x features
|
||||||
dotnet_style_prefer_inferred_tuple_names = true:warning
|
dotnet_style_prefer_inferred_tuple_names = true:warning
|
||||||
@ -168,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
|||||||
#Style - C# 8 features
|
#Style - C# 8 features
|
||||||
csharp_prefer_static_local_function = true:warning
|
csharp_prefer_static_local_function = true:warning
|
||||||
csharp_prefer_simple_using_statement = true:silent
|
csharp_prefer_simple_using_statement = true:silent
|
||||||
csharp_style_prefer_index_operator = true:warning
|
csharp_style_prefer_index_operator = false:silent
|
||||||
csharp_style_prefer_range_operator = true:warning
|
csharp_style_prefer_range_operator = false:silent
|
||||||
csharp_style_prefer_switch_expression = false:none
|
csharp_style_prefer_switch_expression = false:none
|
||||||
|
|
||||||
#Supressing roslyn built-in analyzers
|
#Supressing roslyn built-in analyzers
|
||||||
|
94
.github/workflows/ci.yml
vendored
Normal file
94
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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']
|
||||||
|
timeout-minutes: 60
|
||||||
|
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
|
32
.github/workflows/report-nunit.yml
vendored
Normal file
32
.github/workflows/report-nunit.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 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']
|
||||||
|
timeout-minutes: 5
|
||||||
|
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
|
||||||
|
@ -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,6 +3,7 @@ M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Us
|
|||||||
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable<T> or EqualityComparer<T>.Default instead.
|
||||||
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
||||||
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
||||||
|
T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> instead.
|
||||||
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
||||||
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
||||||
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
|
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
|
||||||
|
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 DisableAnalytics="true" />
|
||||||
|
</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
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||||
|
|
||||||
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the codename "*lazer*". As in sharper than cutting-edge.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ We are accepting bug reports (please report with as much detail as possible and
|
|||||||
|
|
||||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||||
- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where lazer is currently and the roadmap going forward.
|
- Read peppy's [blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward.
|
||||||
|
|
||||||
## Running osu!
|
## Running osu!
|
||||||
|
|
||||||
@ -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!
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<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="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
|||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.EmptyFreeform.Objects;
|
|
||||||
using osu.Game.Rulesets.EmptyFreeform.Replays;
|
using osu.Game.Rulesets.EmptyFreeform.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -11,7 +10,7 @@ using osu.Game.Users;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
||||||
{
|
{
|
||||||
public class EmptyFreeformModAutoplay : ModAutoplay<EmptyFreeformHitObject>
|
public class EmptyFreeformModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<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="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Pippidon.Objects;
|
|
||||||
using osu.Game.Rulesets.Pippidon.Replays;
|
using osu.Game.Rulesets.Pippidon.Replays;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||||
{
|
{
|
||||||
public class PippidonModAutoplay : ModAutoplay<PippidonHitObject>
|
public class PippidonModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Pippidon
|
|||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<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="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
|||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.EmptyScrolling.Objects;
|
|
||||||
using osu.Game.Rulesets.EmptyScrolling.Replays;
|
using osu.Game.Rulesets.EmptyScrolling.Replays;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@ -11,7 +10,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
||||||
{
|
{
|
||||||
public class EmptyScrollingModAutoplay : ModAutoplay<EmptyScrollingHitObject>
|
public class EmptyScrollingModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<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="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Pippidon.Objects;
|
|
||||||
using osu.Game.Rulesets.Pippidon.Replays;
|
using osu.Game.Rulesets.Pippidon.Replays;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||||
{
|
{
|
||||||
public class PippidonModAutoplay : ModAutoplay<PippidonHitObject>
|
public class PippidonModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Pippidon
|
|||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.706.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.601.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.714.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.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -20,7 +20,8 @@ namespace osu.Android
|
|||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-archive")]
|
||||||
|
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed", "application/x-osu-archive" })]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
|
||||||
public class OsuGameActivity : AndroidGameActivity
|
public class OsuGameActivity : AndroidGameActivity
|
||||||
{
|
{
|
||||||
|
@ -68,6 +68,8 @@ namespace osu.Desktop.Updater
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleRecheck = false;
|
||||||
|
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
|
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
|
||||||
@ -98,7 +100,6 @@ namespace osu.Desktop.Updater
|
|||||||
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
|
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
|
||||||
// try again without deltas.
|
// try again without deltas.
|
||||||
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
||||||
scheduleRecheck = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -110,13 +111,14 @@ namespace osu.Desktop.Updater
|
|||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||||
|
scheduleRecheck = true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (scheduleRecheck)
|
if (scheduleRecheck)
|
||||||
{
|
{
|
||||||
// check again in 30 minutes.
|
// check again in 30 minutes.
|
||||||
Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
|
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +143,7 @@ namespace osu.Desktop.Updater
|
|||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
updateManager.PrepareUpdateAsync()
|
updateManager.PrepareUpdateAsync()
|
||||||
.ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
|
.ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit()));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||||
<AssemblyName>osu!</AssemblyName>
|
<AssemblyName>osu!</AssemblyName>
|
||||||
<Title>osu!lazer</Title>
|
<Title>osu!</Title>
|
||||||
<Product>osu!lazer</Product>
|
<Product>osu!(lazer)</Product>
|
||||||
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<Version>0.0.0</Version>
|
<Version>0.0.0</Version>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<metadata>
|
<metadata>
|
||||||
<id>osulazer</id>
|
<id>osulazer</id>
|
||||||
<version>0.0.0</version>
|
<version>0.0.0</version>
|
||||||
<title>osu!lazer</title>
|
<title>osu!</title>
|
||||||
<authors>ppy Pty Ltd</authors>
|
<authors>ppy Pty Ltd</authors>
|
||||||
<owners>Dean Herbert</owners>
|
<owners>Dean Herbert</owners>
|
||||||
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
||||||
@ -20,4 +20,3 @@
|
|||||||
<file src="**.config" target="lib\net45\"/>
|
<file src="**.config" target="lib\net45\"/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
|
||||||
<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="4.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class CatchEditorTestSceneContainer : Container
|
||||||
|
{
|
||||||
|
[Cached(typeof(Playfield))]
|
||||||
|
public readonly ScrollingPlayfield Playfield;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
public CatchEditorTestSceneContainer()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Width = CatchPlayfield.WIDTH;
|
||||||
|
Height = 1000;
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Bottom = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
TimeRange = 1000,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Playfield = new TestCatchPlayfield
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PlayfieldBorder
|
||||||
|
{
|
||||||
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Full },
|
||||||
|
Clock = new FramedClock(new StopwatchClock(true))
|
||||||
|
},
|
||||||
|
Content = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestCatchPlayfield : CatchEditorPlayfield
|
||||||
|
{
|
||||||
|
public TestCatchPlayfield()
|
||||||
|
: base(new BeatmapDifficulty { CircleSize = 0 })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public abstract class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected const double TIME_SNAP = 100;
|
||||||
|
|
||||||
|
protected DrawableCatchHitObject LastObject;
|
||||||
|
|
||||||
|
protected new ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => contentContainer;
|
||||||
|
|
||||||
|
private readonly CatchEditorTestSceneContainer contentContainer;
|
||||||
|
|
||||||
|
protected CatchPlacementBlueprintTestScene()
|
||||||
|
{
|
||||||
|
base.Content.Add(contentContainer = new CatchEditorTestSceneContainer());
|
||||||
|
|
||||||
|
contentContainer.Playfield.Clock = new FramedClock(new ManualClock());
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
HitObjectContainer.Clear();
|
||||||
|
ResetPlacement();
|
||||||
|
LastObject = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected void AddMoveStep(double time, float x) => AddStep($"move to time={time}, x={x}", () =>
|
||||||
|
{
|
||||||
|
float y = HitObjectContainer.PositionAtTime(time);
|
||||||
|
Vector2 pos = HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight));
|
||||||
|
InputManager.MoveMouseTo(pos);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected void AddClickStep(MouseButton button) => AddStep($"click {button}", () =>
|
||||||
|
{
|
||||||
|
InputManager.Click(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected IEnumerable<FruitOutline> FruitOutlines => Content.ChildrenOfType<FruitOutline>();
|
||||||
|
|
||||||
|
// Unused because AddHitObject is overriden
|
||||||
|
protected override Container CreateHitObjectContainer() => new Container();
|
||||||
|
|
||||||
|
protected override void AddHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
LastObject = (DrawableCatchHitObject)hitObject;
|
||||||
|
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
||||||
|
{
|
||||||
|
var result = base.SnapForBlueprint(blueprint);
|
||||||
|
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public abstract class CatchSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => contentContainer;
|
||||||
|
|
||||||
|
private readonly CatchEditorTestSceneContainer contentContainer;
|
||||||
|
|
||||||
|
protected CatchSelectionBlueprintTestScene()
|
||||||
|
{
|
||||||
|
base.Content.Add(contentContainer = new CatchEditorTestSceneContainer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneBananaShowerPlacementBlueprint : CatchPlacementBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
|
||||||
|
|
||||||
|
protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
|
|
||||||
|
protected override void AddHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
// Create nested bananas (but positions are not randomized because beatmap processing is not done).
|
||||||
|
hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), Beatmap.Value.BeatmapInfo.BaseDifficulty);
|
||||||
|
|
||||||
|
base.AddHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicPlacement()
|
||||||
|
{
|
||||||
|
const double start_time = 100;
|
||||||
|
const double end_time = 500;
|
||||||
|
|
||||||
|
AddMoveStep(start_time, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddMoveStep(end_time, 0);
|
||||||
|
AddClickStep(MouseButton.Right);
|
||||||
|
AddAssert("banana shower is placed", () => LastObject is DrawableBananaShower);
|
||||||
|
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
||||||
|
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReversePlacement()
|
||||||
|
{
|
||||||
|
const double start_time = 100;
|
||||||
|
const double end_time = 500;
|
||||||
|
|
||||||
|
AddMoveStep(end_time, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddMoveStep(start_time, 0);
|
||||||
|
AddClickStep(MouseButton.Right);
|
||||||
|
AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time));
|
||||||
|
AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFinishWithZeroDuration()
|
||||||
|
{
|
||||||
|
AddMoveStep(100, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddClickStep(MouseButton.Right);
|
||||||
|
AddAssert("banana shower is not placed", () => LastObject == null);
|
||||||
|
AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOpacity()
|
||||||
|
{
|
||||||
|
AddMoveStep(100, 0);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha));
|
||||||
|
AddMoveStep(200, 0);
|
||||||
|
AddUntilStep("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1));
|
||||||
|
AddMoveStep(100, 0);
|
||||||
|
AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpanOutline timeSpanOutline => Content.ChildrenOfType<TimeSpanOutline>().Single();
|
||||||
|
}
|
||||||
|
}
|
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,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneFruitPlacementBlueprint : CatchPlacementBlueprintTestScene
|
||||||
|
{
|
||||||
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
|
||||||
|
|
||||||
|
protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitPlacementPosition()
|
||||||
|
{
|
||||||
|
const double time = 300;
|
||||||
|
const float x = CatchPlayfield.CENTER_X;
|
||||||
|
|
||||||
|
AddMoveStep(time, x);
|
||||||
|
AddClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
AddAssert("outline position is correct", () =>
|
||||||
|
{
|
||||||
|
var outline = FruitOutlines.Single();
|
||||||
|
return Precision.AlmostEquals(outline.X, x) &&
|
||||||
|
Precision.AlmostEquals(outline.Y, HitObjectContainer.PositionAtTime(time));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("fruit time is correct", () => Precision.AlmostEquals(LastObject.StartTimeBindable.Value, time));
|
||||||
|
AddAssert("fruit position is correct", () => Precision.AlmostEquals(LastObject.X, x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneJuiceStreamSelectionBlueprint : CatchSelectionBlueprintTestScene
|
||||||
|
{
|
||||||
|
public TestSceneJuiceStreamSelectionBlueprint()
|
||||||
|
{
|
||||||
|
var hitObject = new JuiceStream
|
||||||
|
{
|
||||||
|
OriginalX = 100,
|
||||||
|
StartTime = 100,
|
||||||
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(200, 100),
|
||||||
|
new Vector2(0, 200),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
var controlPoint = new ControlPointInfo();
|
||||||
|
controlPoint.Add(0, new TimingControlPoint
|
||||||
|
{
|
||||||
|
BeatLength = 100
|
||||||
|
});
|
||||||
|
hitObject.ApplyDefaults(controlPoint, new BeatmapDifficulty { CircleSize = 0 });
|
||||||
|
AddBlueprint(new JuiceStreamSelectionBlueprint(hitObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
288
osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
Normal file
288
osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
// 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.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class JuiceStreamPathTest
|
||||||
|
{
|
||||||
|
[TestCase(1e3, true, false)]
|
||||||
|
// When the coordinates are large, the slope invariant fails within the specified absolute allowance due to the floating-number precision.
|
||||||
|
[TestCase(1e9, false, false)]
|
||||||
|
// Using discrete values sometimes discover more edge cases.
|
||||||
|
[TestCase(10, true, true)]
|
||||||
|
public void TestRandomInsertSetPosition(double scale, bool checkSlope, bool integralValues)
|
||||||
|
{
|
||||||
|
var rng = new Random(1);
|
||||||
|
var path = new JuiceStreamPath();
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < 100000; iteration++)
|
||||||
|
{
|
||||||
|
if (rng.Next(10) == 0)
|
||||||
|
path.Clear();
|
||||||
|
|
||||||
|
int vertexCount = path.Vertices.Count;
|
||||||
|
|
||||||
|
switch (rng.Next(2))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
double distance = rng.NextDouble() * scale * 2 - scale;
|
||||||
|
if (integralValues)
|
||||||
|
distance = Math.Round(distance);
|
||||||
|
|
||||||
|
float oldX = path.PositionAtDistance(distance);
|
||||||
|
int index = path.InsertVertex(distance);
|
||||||
|
Assert.That(path.Vertices.Count, Is.EqualTo(vertexCount + 1));
|
||||||
|
Assert.That(path.Vertices[index].Distance, Is.EqualTo(distance));
|
||||||
|
Assert.That(path.Vertices[index].X, Is.EqualTo(oldX));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
int index = rng.Next(path.Vertices.Count);
|
||||||
|
double distance = path.Vertices[index].Distance;
|
||||||
|
float newX = (float)(rng.NextDouble() * scale * 2 - scale);
|
||||||
|
if (integralValues)
|
||||||
|
newX = MathF.Round(newX);
|
||||||
|
|
||||||
|
path.SetVertexPosition(index, newX);
|
||||||
|
Assert.That(path.Vertices.Count, Is.EqualTo(vertexCount));
|
||||||
|
Assert.That(path.Vertices[index].Distance, Is.EqualTo(distance));
|
||||||
|
Assert.That(path.Vertices[index].X, Is.EqualTo(newX));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertInvariants(path.Vertices, checkSlope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveVertices()
|
||||||
|
{
|
||||||
|
var path = new JuiceStreamPath();
|
||||||
|
path.Add(10, 5);
|
||||||
|
path.Add(20, -5);
|
||||||
|
|
||||||
|
int removeCount = path.RemoveVertices((v, i) => v.Distance == 10 && i == 1);
|
||||||
|
Assert.That(removeCount, Is.EqualTo(1));
|
||||||
|
Assert.That(path.Vertices, Is.EqualTo(new[]
|
||||||
|
{
|
||||||
|
new JuiceStreamPathVertex(0, 0),
|
||||||
|
new JuiceStreamPathVertex(20, -5)
|
||||||
|
}));
|
||||||
|
|
||||||
|
removeCount = path.RemoveVertices((_, i) => i == 0);
|
||||||
|
Assert.That(removeCount, Is.EqualTo(1));
|
||||||
|
Assert.That(path.Vertices, Is.EqualTo(new[]
|
||||||
|
{
|
||||||
|
new JuiceStreamPathVertex(20, -5)
|
||||||
|
}));
|
||||||
|
|
||||||
|
removeCount = path.RemoveVertices((_, i) => true);
|
||||||
|
Assert.That(removeCount, Is.EqualTo(1));
|
||||||
|
Assert.That(path.Vertices, Is.EqualTo(new[]
|
||||||
|
{
|
||||||
|
new JuiceStreamPathVertex()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResampleVertices()
|
||||||
|
{
|
||||||
|
var path = new JuiceStreamPath();
|
||||||
|
path.Add(-100, -10);
|
||||||
|
path.Add(100, 50);
|
||||||
|
path.ResampleVertices(new double[]
|
||||||
|
{
|
||||||
|
-50,
|
||||||
|
0,
|
||||||
|
70,
|
||||||
|
120
|
||||||
|
});
|
||||||
|
Assert.That(path.Vertices, Is.EqualTo(new[]
|
||||||
|
{
|
||||||
|
new JuiceStreamPathVertex(-100, -10),
|
||||||
|
new JuiceStreamPathVertex(-50, -5),
|
||||||
|
new JuiceStreamPathVertex(0, 0),
|
||||||
|
new JuiceStreamPathVertex(70, 35),
|
||||||
|
new JuiceStreamPathVertex(100, 50),
|
||||||
|
new JuiceStreamPathVertex(100, 50),
|
||||||
|
}));
|
||||||
|
|
||||||
|
path.Clear();
|
||||||
|
path.SetVertexPosition(0, 10);
|
||||||
|
path.ResampleVertices(Array.Empty<double>());
|
||||||
|
Assert.That(path.Vertices, Is.EqualTo(new[]
|
||||||
|
{
|
||||||
|
new JuiceStreamPathVertex(0, 10)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRandomConvertFromSliderPath()
|
||||||
|
{
|
||||||
|
var rng = new Random(1);
|
||||||
|
var path = new JuiceStreamPath();
|
||||||
|
var sliderPath = new SliderPath();
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < 10000; iteration++)
|
||||||
|
{
|
||||||
|
sliderPath.ControlPoints.Clear();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int start = sliderPath.ControlPoints.Count;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
float x = (float)(rng.NextDouble() * 1e3);
|
||||||
|
float y = (float)(rng.NextDouble() * 1e3);
|
||||||
|
sliderPath.ControlPoints.Add(new PathControlPoint(new Vector2(x, y)));
|
||||||
|
} while (rng.Next(2) != 0);
|
||||||
|
|
||||||
|
int length = sliderPath.ControlPoints.Count - start + 1;
|
||||||
|
sliderPath.ControlPoints[start].Type.Value = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier;
|
||||||
|
} while (rng.Next(3) != 0);
|
||||||
|
|
||||||
|
if (rng.Next(5) == 0)
|
||||||
|
sliderPath.ExpectedDistance.Value = rng.NextDouble() * 3e3;
|
||||||
|
else
|
||||||
|
sliderPath.ExpectedDistance.Value = null;
|
||||||
|
|
||||||
|
path.ConvertFromSliderPath(sliderPath);
|
||||||
|
Assert.That(path.Vertices[0].Distance, Is.EqualTo(0));
|
||||||
|
Assert.That(path.Distance, Is.EqualTo(sliderPath.Distance).Within(1e-3));
|
||||||
|
assertInvariants(path.Vertices, true);
|
||||||
|
|
||||||
|
double[] sampleDistances = Enumerable.Range(0, 10)
|
||||||
|
.Select(_ => rng.NextDouble() * sliderPath.Distance)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (double distance in sampleDistances)
|
||||||
|
{
|
||||||
|
float expected = sliderPath.PositionAt(distance / sliderPath.Distance).X;
|
||||||
|
Assert.That(path.PositionAtDistance(distance), Is.EqualTo(expected).Within(1e-3));
|
||||||
|
}
|
||||||
|
|
||||||
|
path.ResampleVertices(sampleDistances);
|
||||||
|
assertInvariants(path.Vertices, true);
|
||||||
|
|
||||||
|
foreach (double distance in sampleDistances)
|
||||||
|
{
|
||||||
|
float expected = sliderPath.PositionAt(distance / sliderPath.Distance).X;
|
||||||
|
Assert.That(path.PositionAtDistance(distance), Is.EqualTo(expected).Within(1e-3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRandomConvertToSliderPath()
|
||||||
|
{
|
||||||
|
var rng = new Random(1);
|
||||||
|
var path = new JuiceStreamPath();
|
||||||
|
var sliderPath = new SliderPath();
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < 10000; iteration++)
|
||||||
|
{
|
||||||
|
path.Clear();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
double distance = rng.NextDouble() * 1e3;
|
||||||
|
float x = (float)(rng.NextDouble() * 1e3);
|
||||||
|
path.Add(distance, x);
|
||||||
|
} while (rng.Next(5) != 0);
|
||||||
|
|
||||||
|
float sliderStartY = (float)(rng.NextDouble() * JuiceStreamPath.OSU_PLAYFIELD_HEIGHT);
|
||||||
|
|
||||||
|
path.ConvertToSliderPath(sliderPath, sliderStartY);
|
||||||
|
Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3));
|
||||||
|
Assert.That(sliderPath.ControlPoints[0].Position.Value.X, Is.EqualTo(path.Vertices[0].X));
|
||||||
|
assertInvariants(path.Vertices, true);
|
||||||
|
|
||||||
|
foreach (var point in sliderPath.ControlPoints)
|
||||||
|
{
|
||||||
|
Assert.That(point.Type.Value, Is.EqualTo(PathType.Linear).Or.Null);
|
||||||
|
Assert.That(sliderStartY + point.Position.Value.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
double distance = rng.NextDouble() * path.Distance;
|
||||||
|
float expected = path.PositionAtDistance(distance);
|
||||||
|
Assert.That(sliderPath.PositionAt(distance / sliderPath.Distance).X, Is.EqualTo(expected).Within(1e-3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInvalidation()
|
||||||
|
{
|
||||||
|
var path = new JuiceStreamPath();
|
||||||
|
Assert.That(path.InvalidationID, Is.EqualTo(1));
|
||||||
|
int previousId = path.InvalidationID;
|
||||||
|
|
||||||
|
path.InsertVertex(10);
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
path.SetVertexPosition(1, 5);
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
path.Add(20, 0);
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
path.RemoveVertices((v, _) => v.Distance == 20);
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
path.ResampleVertices(new double[] { 5, 10, 15 });
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
path.Clear();
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
path.ConvertFromSliderPath(new SliderPath());
|
||||||
|
checkNewId();
|
||||||
|
|
||||||
|
void checkNewId()
|
||||||
|
{
|
||||||
|
Assert.That(path.InvalidationID, Is.Not.EqualTo(previousId));
|
||||||
|
previousId = path.InvalidationID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInvariants(IReadOnlyList<JuiceStreamPathVertex> vertices, bool checkSlope)
|
||||||
|
{
|
||||||
|
Assert.That(vertices, Is.Not.Empty);
|
||||||
|
|
||||||
|
for (int i = 0; i < vertices.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.That(double.IsFinite(vertices[i].Distance));
|
||||||
|
Assert.That(float.IsFinite(vertices[i].X));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < vertices.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.That(vertices[i].Distance, Is.GreaterThanOrEqualTo(vertices[i - 1].Distance));
|
||||||
|
|
||||||
|
if (!checkSlope) continue;
|
||||||
|
|
||||||
|
float xDiff = Math.Abs(vertices[i].X - vertices[i - 1].X);
|
||||||
|
double distanceDiff = vertices[i].Distance - vertices[i - 1].Distance;
|
||||||
|
Assert.That(xDiff, Is.LessThanOrEqualTo(distanceDiff).Within(Precision.FLOAT_EPSILON));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
[General]
|
||||||
|
// no version specified means v1
|
@ -4,10 +4,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
@ -21,12 +19,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneCatchModHidden : ModTestScene
|
public class TestSceneCatchModHidden : ModTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestJuiceStream()
|
public void TestJuiceStream()
|
||||||
{
|
{
|
||||||
|
114
osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs
Normal file
114
osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs
Normal file
@ -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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using Direction = osu.Game.Rulesets.Catch.UI.Direction;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneCatchSkinConfiguration : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly DroppedObjectContainer droppedObjectContainer;
|
||||||
|
|
||||||
|
private Catcher catcher;
|
||||||
|
|
||||||
|
private readonly Container container;
|
||||||
|
|
||||||
|
public TestSceneCatchSkinConfiguration()
|
||||||
|
{
|
||||||
|
Add(droppedObjectContainer = new DroppedObjectContainer());
|
||||||
|
Add(container = new Container { RelativeSizeAxes = Axes.Both });
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(false)]
|
||||||
|
[TestCase(true)]
|
||||||
|
public void TestCatcherPlateFlipping(bool flip)
|
||||||
|
{
|
||||||
|
AddStep("setup catcher", () =>
|
||||||
|
{
|
||||||
|
var skin = new TestSkin { FlipCatcherPlate = flip };
|
||||||
|
container.Child = new SkinProvidingContainer(skin)
|
||||||
|
{
|
||||||
|
Child = catcher = new Catcher(new Container())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Fruit fruit = new Fruit();
|
||||||
|
|
||||||
|
AddStep("catch fruit", () => catchFruit(fruit, 20));
|
||||||
|
|
||||||
|
float position = 0;
|
||||||
|
|
||||||
|
AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit));
|
||||||
|
|
||||||
|
AddStep("face left", () => catcher.VisualDirection = Direction.Left);
|
||||||
|
|
||||||
|
if (flip)
|
||||||
|
AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
|
||||||
|
else
|
||||||
|
AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
|
||||||
|
|
||||||
|
AddStep("face right", () => catcher.VisualDirection = Direction.Right);
|
||||||
|
|
||||||
|
AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getCaughtObjectPosition(Fruit fruit)
|
||||||
|
{
|
||||||
|
var caughtObject = catcher.ChildrenOfType<CaughtObject>().Single(c => c.HitObject == fruit);
|
||||||
|
return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void catchFruit(Fruit fruit, float x)
|
||||||
|
{
|
||||||
|
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
var drawableFruit = new DrawableFruit(fruit) { X = x };
|
||||||
|
var judgement = fruit.CreateJudgement();
|
||||||
|
catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement)
|
||||||
|
{
|
||||||
|
Type = judgement.MaxResult
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSkin : DefaultSkin
|
||||||
|
{
|
||||||
|
public bool FlipCatcherPlate { get; set; }
|
||||||
|
|
||||||
|
public TestSkin()
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
if (lookup is CatchSkinConfiguration config)
|
||||||
|
{
|
||||||
|
if (config == CatchSkinConfiguration.FlipCatcherPlate)
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<bool>(FlipCatcherPlate));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Container<CaughtObject> droppedObjectContainer;
|
|
||||||
|
|
||||||
private TestCatcher catcher;
|
private TestCatcher catcher;
|
||||||
|
|
||||||
|
private DroppedObjectContainer droppedObjectContainer;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -43,19 +43,24 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
CircleSize = 0,
|
CircleSize = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
var trailContainer = new Container();
|
var trailContainer = new Container
|
||||||
droppedObjectContainer = new Container<CaughtObject>();
|
|
||||||
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
|
||||||
|
|
||||||
Child = new Container
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
};
|
||||||
|
droppedObjectContainer = new DroppedObjectContainer();
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
CachedDependencies = new (Type, object)[]
|
||||||
|
{
|
||||||
|
(typeof(DroppedObjectContainer), droppedObjectContainer),
|
||||||
|
},
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
trailContainer,
|
|
||||||
droppedObjectContainer,
|
droppedObjectContainer,
|
||||||
catcher
|
catcher = new TestCatcher(trailContainer, difficulty),
|
||||||
}
|
trailContainer
|
||||||
|
},
|
||||||
|
Anchor = Anchor.Centre
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -188,9 +193,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9));
|
AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9));
|
||||||
checkPlate(10);
|
checkPlate(10);
|
||||||
AddAssert("caught objects are stacked", () =>
|
AddAssert("caught objects are stacked", () =>
|
||||||
catcher.CaughtObjects.All(obj => obj.Y <= Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) &&
|
catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
|
||||||
catcher.CaughtObjects.Any(obj => obj.Y == Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) &&
|
catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
|
||||||
catcher.CaughtObjects.Any(obj => obj.Y < -25));
|
catcher.CaughtObjects.Any(obj => obj.Y < 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -216,7 +221,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
AddAssert("correct hit lighting colour", () =>
|
AddAssert("correct hit lighting colour", () =>
|
||||||
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
|
catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == fruitColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -293,8 +298,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
|
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
|
||||||
|
|
||||||
public TestCatcher(Container trailsTarget, Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty)
|
public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty)
|
||||||
: base(trailsTarget, droppedObjectTarget, difficulty)
|
: base(trailsTarget, difficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -97,18 +96,12 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new Container<CaughtObject>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
};
|
|
||||||
|
|
||||||
return new CatchInputManager(catchRuleset)
|
return new CatchInputManager(catchRuleset)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
droppedObjectContainer,
|
new TestCatcherArea(beatmapDifficulty)
|
||||||
new TestCatcherArea(droppedObjectContainer, beatmapDifficulty)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
@ -126,9 +119,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private class TestCatcherArea : CatcherArea
|
private class TestCatcherArea : CatcherArea
|
||||||
{
|
{
|
||||||
public TestCatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
[Cached]
|
||||||
: base(droppedObjectContainer, beatmapDifficulty)
|
private readonly DroppedObjectContainer droppedObjectContainer;
|
||||||
|
|
||||||
|
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
|
||||||
|
: base(beatmapDifficulty)
|
||||||
{
|
{
|
||||||
|
AddInternal(droppedObjectContainer = new DroppedObjectContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
|
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -118,11 +118,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("create hyper-dashing catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container<CaughtObject>())
|
Child = setupSkinHierarchy(catcherArea = new TestCatcherArea
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre
|
||||||
Scale = new Vector2(4f),
|
|
||||||
}, skin);
|
}, skin);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("finish hyper-dashing", () =>
|
AddStep("finish hyper-dashing", () =>
|
||||||
{
|
{
|
||||||
catcherArea.MovableCatcher.SetHyperDashState(1);
|
catcherArea.MovableCatcher.SetHyperDashState();
|
||||||
catcherArea.MovableCatcher.FinishTransforms();
|
catcherArea.MovableCatcher.FinishTransforms();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -206,5 +205,18 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestCatcherArea : CatcherArea
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly DroppedObjectContainer droppedObjectContainer;
|
||||||
|
|
||||||
|
public TestCatcherArea()
|
||||||
|
{
|
||||||
|
Scale = new Vector2(4f);
|
||||||
|
|
||||||
|
AddInternal(droppedObjectContainer = new DroppedObjectContainer());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<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="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken)
|
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var positionData = obj as IHasXPosition;
|
var xPositionData = obj as IHasXPosition;
|
||||||
|
var yPositionData = obj as IHasYPosition;
|
||||||
var comboData = obj as IHasCombo;
|
var comboData = obj as IHasCombo;
|
||||||
|
|
||||||
switch (obj)
|
switch (obj)
|
||||||
@ -36,10 +37,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
Path = curveData.Path,
|
Path = curveData.Path,
|
||||||
NodeSamples = curveData.NodeSamples,
|
NodeSamples = curveData.NodeSamples,
|
||||||
RepeatCount = curveData.RepeatCount,
|
RepeatCount = curveData.RepeatCount,
|
||||||
X = positionData?.X ?? 0,
|
X = xPositionData?.X ?? 0,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
|
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
|
||||||
|
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
case IHasDuration endTime:
|
case IHasDuration endTime:
|
||||||
@ -59,7 +61,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
X = positionData?.X ?? 0
|
X = xPositionData?.X ?? 0,
|
||||||
|
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
|
||||||
}.Yield();
|
}.Yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||||
{
|
{
|
||||||
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
|
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
return new Skill[]
|
return new Skill[]
|
||||||
{
|
{
|
||||||
new Movement(mods, halfCatcherWidth),
|
new Movement(mods, halfCatcherWidth, clockRate),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -33,15 +32,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
|
|
||||||
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Great);
|
fruitsHit = Score.Statistics.GetValueOrDefault(HitResult.Great);
|
||||||
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
|
ticksHit = Score.Statistics.GetValueOrDefault(HitResult.LargeTickHit);
|
||||||
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
|
tinyTicksHit = Score.Statistics.GetValueOrDefault(HitResult.SmallTickHit);
|
||||||
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
tinyTicksMissed = Score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
|
||||||
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
|
misses = Score.Statistics.GetValueOrDefault(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,8 +24,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly double StrainTime;
|
public readonly double StrainTime;
|
||||||
|
|
||||||
public readonly double ClockRate;
|
|
||||||
|
|
||||||
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
||||||
: base(hitObject, lastObject, clockRate)
|
: base(hitObject, lastObject, clockRate)
|
||||||
{
|
{
|
||||||
@ -37,7 +35,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
|
|
||||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||||
StrainTime = Math.Max(40, DeltaTime);
|
StrainTime = Math.Max(40, DeltaTime);
|
||||||
ClockRate = clockRate;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,21 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
private float lastDistanceMoved;
|
private float lastDistanceMoved;
|
||||||
private double lastStrainTime;
|
private double lastStrainTime;
|
||||||
|
|
||||||
public Movement(Mod[] mods, float halfCatcherWidth)
|
/// <summary>
|
||||||
|
/// The speed multiplier applied to the player's catcher.
|
||||||
|
/// </summary>
|
||||||
|
private readonly double catcherSpeedMultiplier;
|
||||||
|
|
||||||
|
public Movement(Mod[] mods, float halfCatcherWidth, double clockRate)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
HalfCatcherWidth = halfCatcherWidth;
|
HalfCatcherWidth = halfCatcherWidth;
|
||||||
|
|
||||||
|
// In catch, clockrate adjustments do not only affect the timings of hitobjects,
|
||||||
|
// but also the speed of the player's catcher, which has an impact on difficulty
|
||||||
|
// TODO: Support variable clockrates caused by mods such as ModTimeRamp
|
||||||
|
// (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing)
|
||||||
|
catcherSpeedMultiplier = clockRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
@ -48,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
|
|
||||||
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
||||||
|
|
||||||
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate);
|
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier);
|
||||||
|
|
||||||
double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
|
double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
|
||||||
double sqrtStrain = Math.Sqrt(weightedStrainTime);
|
double sqrtStrain = Math.Sqrt(weightedStrainTime);
|
||||||
@ -81,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
playerPosition = catchCurrent.NormalizedPosition;
|
playerPosition = catchCurrent.NormalizedPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catcherSpeedMultiplier, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPlayerPosition = playerPosition;
|
lastPlayerPosition = playerPosition;
|
||||||
|
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,30 @@
|
|||||||
|
// 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 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())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.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
|
||||||
|
{
|
||||||
|
protected override bool AlwaysShowWhenSelected => true;
|
||||||
|
|
||||||
|
public override Vector2 ScreenSpaceSelectionPoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Vector2 position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
|
||||||
|
return HitObjectContainer.ToScreenSpace(position + new Vector2(0, 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,34 @@
|
|||||||
|
// 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 osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class FruitOutline : CompositeDrawable
|
||||||
|
{
|
||||||
|
public FruitOutline()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
InternalChild = new BorderPiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour osuColour)
|
||||||
|
{
|
||||||
|
Colour = osuColour.Yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateFrom(CatchHitObject hitObject)
|
||||||
|
{
|
||||||
|
Scale = new Vector2(hitObject.Scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class NestedOutlineContainer : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly List<CatchHitObject> nestedHitObjects = new List<CatchHitObject>();
|
||||||
|
|
||||||
|
public NestedOutlineContainer()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateNestedObjectsFrom(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject parentHitObject)
|
||||||
|
{
|
||||||
|
nestedHitObjects.Clear();
|
||||||
|
nestedHitObjects.AddRange(parentHitObject.NestedHitObjects
|
||||||
|
.OfType<CatchHitObject>()
|
||||||
|
.Where(h => !(h is TinyDroplet)));
|
||||||
|
|
||||||
|
while (nestedHitObjects.Count < InternalChildren.Count)
|
||||||
|
RemoveInternal(InternalChildren[^1]);
|
||||||
|
|
||||||
|
while (InternalChildren.Count < nestedHitObjects.Count)
|
||||||
|
AddInternal(new FruitOutline());
|
||||||
|
|
||||||
|
for (int i = 0; i < nestedHitObjects.Count; i++)
|
||||||
|
{
|
||||||
|
var hitObject = nestedHitObjects[i];
|
||||||
|
var outline = (FruitOutline)InternalChildren[i];
|
||||||
|
outline.Position = CatchHitObjectUtils.GetStartPosition(hitObjectContainer, hitObject) - Position;
|
||||||
|
outline.UpdateFrom(hitObject);
|
||||||
|
outline.Scale *= hitObject is Droplet ? 0.5f : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Lines;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public class ScrollingPath : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Path drawablePath;
|
||||||
|
|
||||||
|
private readonly List<(double Distance, float X)> vertices = new List<(double, float)>();
|
||||||
|
|
||||||
|
public ScrollingPath()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
drawablePath = new SmoothPath
|
||||||
|
{
|
||||||
|
PathRadius = 2,
|
||||||
|
Alpha = 0.5f
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject)
|
||||||
|
{
|
||||||
|
double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity);
|
||||||
|
|
||||||
|
computeDistanceXs(hitObject);
|
||||||
|
drawablePath.Vertices = vertices
|
||||||
|
.Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor)))
|
||||||
|
.ToArray();
|
||||||
|
drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeDistanceXs(JuiceStream hitObject)
|
||||||
|
{
|
||||||
|
vertices.Clear();
|
||||||
|
|
||||||
|
var sliderVertices = new List<Vector2>();
|
||||||
|
hitObject.Path.GetPathToProgress(sliderVertices, 0, 1);
|
||||||
|
|
||||||
|
if (sliderVertices.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double distance = 0;
|
||||||
|
Vector2 lastPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++)
|
||||||
|
{
|
||||||
|
foreach (var position in sliderVertices)
|
||||||
|
{
|
||||||
|
distance += Vector2.Distance(lastPosition, position);
|
||||||
|
lastPosition = position;
|
||||||
|
|
||||||
|
vertices.Add((distance, position.X));
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderVertices.Reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because this has 0x0 size, the contents are otherwise masked away if the start position is outside the screen.
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
}
|
||||||
|
}
|
@ -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,51 @@
|
|||||||
|
// 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.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
|
||||||
|
outline.UpdateFrom(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,29 @@
|
|||||||
|
// 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) return;
|
||||||
|
|
||||||
|
outline.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
|
||||||
|
outline.UpdateFrom(HitObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
// 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.Caching;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
|
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;
|
||||||
|
|
||||||
|
private readonly ScrollingPath scrollingPath;
|
||||||
|
|
||||||
|
private readonly NestedOutlineContainer nestedOutlineContainer;
|
||||||
|
|
||||||
|
private readonly Cached pathCache = new Cached();
|
||||||
|
|
||||||
|
public JuiceStreamSelectionBlueprint(JuiceStream hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
scrollingPath = new ScrollingPath(),
|
||||||
|
nestedOutlineContainer = new NestedOutlineContainer()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
HitObject.DefaultsApplied += onDefaultsApplied;
|
||||||
|
computeObjectBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!IsSelected) return;
|
||||||
|
|
||||||
|
nestedOutlineContainer.Position = scrollingPath.Position = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
|
||||||
|
|
||||||
|
if (pathCache.IsValid) return;
|
||||||
|
|
||||||
|
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
||||||
|
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
||||||
|
|
||||||
|
pathCache.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDefaultsApplied(HitObject _)
|
||||||
|
{
|
||||||
|
computeObjectBounds();
|
||||||
|
pathCache.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
Normal file
54
osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
Normal file
24
osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.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.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility functions used by the editor.
|
||||||
|
/// </summary>
|
||||||
|
public static class CatchHitObjectUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the position of the hit object in the playfield based on <see cref="CatchHitObject.OriginalX"/> and <see cref="HitObject.StartTime"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2 GetStartPosition(ScrollingHitObjectContainer hitObjectContainer, CatchHitObject hitObject)
|
||||||
|
{
|
||||||
|
return new Vector2(hitObject.OriginalX, hitObjectContainer.PositionAtTime(hitObject.StartTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
Normal file
116
osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// 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.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
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;
|
||||||
|
deltaX = limitMovement(deltaX, EditorBeatmap.SelectedHitObjects);
|
||||||
|
|
||||||
|
if (deltaX == 0)
|
||||||
|
{
|
||||||
|
// Even if there is no positional change, there may be a time change.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
|
{
|
||||||
|
if (!(h is CatchHitObject hitObject)) return;
|
||||||
|
|
||||||
|
hitObject.OriginalX += 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.OriginalX += deltaX;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limit positional movement of the objects by the constraint that moved objects should stay in bounds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deltaX">The positional movement.</param>
|
||||||
|
/// <param name="movingObjects">The objects to be moved.</param>
|
||||||
|
/// <returns>The positional movement with the restriction applied.</returns>
|
||||||
|
private float limitMovement(float deltaX, IEnumerable<HitObject> movingObjects)
|
||||||
|
{
|
||||||
|
float minX = float.PositiveInfinity;
|
||||||
|
float maxX = float.NegativeInfinity;
|
||||||
|
|
||||||
|
foreach (float x in movingObjects.SelectMany(getOriginalPositions))
|
||||||
|
{
|
||||||
|
minX = Math.Min(minX, x);
|
||||||
|
maxX = Math.Max(maxX, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To make an object with position `x` stay in bounds after `deltaX` movement, `0 <= x + deltaX <= WIDTH` should be satisfied.
|
||||||
|
// Subtracting `x`, we get `-x <= deltaX <= WIDTH - x`.
|
||||||
|
// We only need to apply the inequality to extreme values of `x`.
|
||||||
|
float lowerBound = -minX;
|
||||||
|
float upperBound = CatchPlayfield.WIDTH - maxX;
|
||||||
|
// The inequality may be unsatisfiable if the objects were already out of bounds.
|
||||||
|
// In that case, don't move objects at all.
|
||||||
|
if (lowerBound > upperBound)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return Math.Clamp(deltaX, lowerBound, upperBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerate X positions that should be contained in-bounds after move offset is applied.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<float> getOriginalPositions(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Fruit fruit:
|
||||||
|
yield return fruit.OriginalX;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JuiceStream juiceStream:
|
||||||
|
foreach (var nested in juiceStream.NestedHitObjects.OfType<CatchHitObject>())
|
||||||
|
{
|
||||||
|
// Even if `OriginalX` is outside the playfield, tiny droplets can be moved inside the playfield after the random offset application.
|
||||||
|
if (!(nested is TinyDroplet))
|
||||||
|
yield return nested.OriginalX;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BananaShower _:
|
||||||
|
// A banana shower occupies the whole screen width.
|
||||||
|
// If the selection contains a banana shower, the selection cannot be moved horizontally.
|
||||||
|
yield return 0;
|
||||||
|
yield return CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -11,7 +10,7 @@ using osu.Game.Users;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModAutoplay : ModAutoplay<CatchHitObject>
|
public class CatchModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
|
@ -5,39 +5,35 @@ using System.Linq;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModDifficultyAdjust : ModDifficultyAdjust
|
public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor
|
||||||
{
|
{
|
||||||
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
|
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> CircleSize { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Default = 5,
|
ExtendedMaxValue = 11,
|
||||||
Value = 5,
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
|
[SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
|
||||||
public BindableNumber<float> ApproachRate { get; } = new BindableFloatWithLimitExtension
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Default = 5,
|
ExtendedMaxValue = 11,
|
||||||
Value = 5,
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override void ApplyLimits(bool extended)
|
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||||
{
|
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
||||||
base.ApplyLimits(extended);
|
|
||||||
|
|
||||||
CircleSize.MaxValue = extended ? 11 : 10;
|
|
||||||
ApproachRate.MaxValue = extended ? 11 : 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string SettingDescription
|
public override string SettingDescription
|
||||||
{
|
{
|
||||||
@ -45,30 +41,30 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
|
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
|
||||||
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
|
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
|
||||||
|
string spicyPatterns = HardRockOffsets.IsDefault ? string.Empty : "Spicy patterns";
|
||||||
|
|
||||||
return string.Join(", ", new[]
|
return string.Join(", ", new[]
|
||||||
{
|
{
|
||||||
circleSize,
|
circleSize,
|
||||||
base.SettingDescription,
|
base.SettingDescription,
|
||||||
approachRate
|
approachRate,
|
||||||
|
spicyPatterns,
|
||||||
}.Where(s => !string.IsNullOrEmpty(s)));
|
}.Where(s => !string.IsNullOrEmpty(s)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void TransferSettings(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
base.TransferSettings(difficulty);
|
|
||||||
|
|
||||||
TransferSetting(CircleSize, difficulty.CircleSize);
|
|
||||||
TransferSetting(ApproachRate, difficulty.ApproachRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
protected override void ApplySettings(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value;
|
||||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
||||||
|
{
|
||||||
|
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
|
||||||
|
catchProcessor.HardRockOffsets = HardRockOffsets.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
=> ApplyNormalVisibilityState(hitObject, state);
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Game.Audio;
|
|||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
// override any external colour changes with banananana
|
// override any external colour changes with banananana
|
||||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => getBananaColour();
|
Color4 IHasComboInformation.GetComboColour(ISkin skin) => getBananaColour();
|
||||||
|
|
||||||
private Color4 getBananaColour()
|
private Color4 getBananaColour()
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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 Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -8,10 +9,11 @@ using osu.Game.Rulesets.Catch.UI;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
|
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
|
||||||
{
|
{
|
||||||
public const float OBJECT_RADIUS = 64;
|
public const float OBJECT_RADIUS = 64;
|
||||||
|
|
||||||
@ -20,13 +22,16 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only setter is exposed.
|
||||||
|
/// Use <see cref="OriginalX"/> or <see cref="EffectiveX"/> to get the horizontal position.
|
||||||
|
/// </remarks>
|
||||||
|
[JsonIgnore]
|
||||||
public float X
|
public float X
|
||||||
{
|
{
|
||||||
set => OriginalXBindable.Value = value;
|
set => OriginalXBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
float IHasXPosition.X => OriginalXBindable.Value;
|
|
||||||
|
|
||||||
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -34,6 +39,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float XOffset
|
public float XOffset
|
||||||
{
|
{
|
||||||
|
get => XOffsetBindable.Value;
|
||||||
set => XOffsetBindable.Value = value;
|
set => XOffsetBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +50,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
|
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
|
||||||
/// Use <see cref="EffectiveX"/> for a gameplay.
|
/// Use <see cref="EffectiveX"/> for a gameplay.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float OriginalX => OriginalXBindable.Value;
|
public float OriginalX
|
||||||
|
{
|
||||||
|
get => OriginalXBindable.Value;
|
||||||
|
set => OriginalXBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
@ -53,9 +63,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
||||||
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
|
public float EffectiveX => OriginalX + XOffset;
|
||||||
|
|
||||||
public double TimePreempt = 1000;
|
public double TimePreempt { get; set; } = 1000;
|
||||||
|
|
||||||
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
||||||
|
|
||||||
@ -120,5 +130,24 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
|
||||||
|
#region Hit object conversion
|
||||||
|
|
||||||
|
// The half of the height of the osu! playfield.
|
||||||
|
public const float DEFAULT_LEGACY_CONVERT_Y = 192;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Y position of the hit object is not used in the normal osu!catch gameplay.
|
||||||
|
/// It is preserved to maximize the backward compatibility with the legacy editor, in which the mappers use the Y position to organize the patterns.
|
||||||
|
/// </summary>
|
||||||
|
public float LegacyConvertedY { get; set; } = DEFAULT_LEGACY_CONVERT_Y;
|
||||||
|
|
||||||
|
float IHasXPosition.X => OriginalX;
|
||||||
|
|
||||||
|
float IHasYPosition.Y => LegacyConvertedY;
|
||||||
|
|
||||||
|
Vector2 IHasPosition.Position => new Vector2(OriginalX, LegacyConvertedY);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 osu.Framework.Bindables;
|
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
@ -9,21 +8,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a <see cref="Fruit"/> caught by the catcher.
|
/// Represents a <see cref="Fruit"/> caught by the catcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CaughtFruit : CaughtObject, IHasFruitState
|
public class CaughtFruit : CaughtObject
|
||||||
{
|
{
|
||||||
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
|
||||||
|
|
||||||
public CaughtFruit()
|
public CaughtFruit()
|
||||||
: base(CatchSkinComponents.Fruit, _ => new FruitPiece())
|
: base(CatchSkinComponents.Fruit, _ => new FruitPiece())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CopyStateFrom(IHasCatchObjectState objectState)
|
|
||||||
{
|
|
||||||
base.CopyStateFrom(objectState);
|
|
||||||
|
|
||||||
var fruitState = (IHasFruitState)objectState;
|
|
||||||
VisualRepresentation.Value = fruitState.VisualRepresentation.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
public PalpableCatchHitObject HitObject { get; private set; }
|
public PalpableCatchHitObject HitObject { get; private set; }
|
||||||
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
|
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||||
|
|
||||||
public Vector2 DisplaySize => Size * Scale;
|
public Vector2 DisplaySize => Size * Scale;
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Rotation = objectState.DisplayRotation;
|
Rotation = objectState.DisplayRotation;
|
||||||
AccentColour.Value = objectState.AccentColour.Value;
|
AccentColour.Value = objectState.AccentColour.Value;
|
||||||
HyperDash.Value = objectState.HyperDash.Value;
|
HyperDash.Value = objectState.HyperDash.Value;
|
||||||
|
IndexInBeatmap.Value = objectState.IndexInBeatmap.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void FreeAfterUse()
|
protected override void FreeAfterUse()
|
||||||
|
@ -3,17 +3,14 @@
|
|||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState
|
public class DrawableFruit : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
|
||||||
|
|
||||||
public DrawableFruit()
|
public DrawableFruit()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -27,11 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
IndexInBeatmap.BindValueChanged(change =>
|
|
||||||
{
|
|
||||||
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
ScalingContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
||||||
_ => new FruitPiece());
|
_ => new FruitPiece());
|
||||||
@ -44,12 +36,4 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FruitVisualRepresentation
|
|
||||||
{
|
|
||||||
Pear,
|
|
||||||
Grape,
|
|
||||||
Pineapple,
|
|
||||||
Raspberry,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
Bindable<bool> HyperDash { get; }
|
Bindable<bool> HyperDash { get; }
|
||||||
|
|
||||||
|
Bindable<int> IndexInBeatmap { get; }
|
||||||
|
|
||||||
Vector2 DisplaySize { get; }
|
Vector2 DisplaySize { get; }
|
||||||
|
|
||||||
float DisplayRotation { get; }
|
float DisplayRotation { get; }
|
||||||
|
@ -1,15 +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.Bindables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a visual state of a <see cref="Fruit"/>.
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasFruitState : IHasCatchObjectState
|
|
||||||
{
|
|
||||||
Bindable<FruitVisualRepresentation> VisualRepresentation { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,5 +9,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public class Fruit : PalpableCatchHitObject
|
public class Fruit : PalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override Judgement CreateJudgement() => new CatchJudgement();
|
public override Judgement CreateJudgement() => new CatchJudgement();
|
||||||
|
|
||||||
|
public static FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
Normal file
13
osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
|
{
|
||||||
|
public enum FruitVisualRepresentation
|
||||||
|
{
|
||||||
|
Pear,
|
||||||
|
Grape,
|
||||||
|
Pineapple,
|
||||||
|
Raspberry,
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -25,7 +26,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int RepeatCount { get; set; }
|
public int RepeatCount { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public double Velocity { get; private set; }
|
public double Velocity { get; private set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public double TickDistance { get; private set; }
|
public double TickDistance { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public double Duration
|
public double Duration
|
||||||
{
|
{
|
||||||
get => this.SpanCount() * Path.Distance / Velocity;
|
get => this.SpanCount() * Path.Distance / Velocity;
|
||||||
|
340
osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs
Normal file
340
osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
// 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.Utils;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the path of a juice stream.
|
||||||
|
/// <para>
|
||||||
|
/// A <see cref="JuiceStream"/> holds a legacy <see cref="SliderPath"/> as the representation of the path.
|
||||||
|
/// However, the <see cref="SliderPath"/> representation is difficult to work with.
|
||||||
|
/// This <see cref="JuiceStreamPath"/> represents the path in a more convenient way, a polyline connecting list of <see cref="JuiceStreamPathVertex"/>s.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The path can be regarded as a function from the closed interval <c>[Vertices[0].Distance, Vertices[^1].Distance]</c> to the x position, given by <see cref="PositionAtDistance"/>.
|
||||||
|
/// To ensure the path is convertible to a <see cref="SliderPath"/>, the slope of the function must not be more than <c>1</c> everywhere,
|
||||||
|
/// and this slope condition is always maintained as an invariant.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public class JuiceStreamPath
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The height of legacy osu!standard playfield.
|
||||||
|
/// The sliders converted by <see cref="ConvertToSliderPath"/> are vertically contained in this height.
|
||||||
|
/// </summary>
|
||||||
|
internal const float OSU_PLAYFIELD_HEIGHT = 384;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of vertices of the path, which is represented as a polyline connecting the vertices.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<JuiceStreamPathVertex> Vertices => vertices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current version number.
|
||||||
|
/// This starts from <c>1</c> and incremented whenever this <see cref="JuiceStreamPath"/> is modified.
|
||||||
|
/// </summary>
|
||||||
|
public int InvalidationID { get; private set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The difference between first vertex's <see cref="JuiceStreamPathVertex.Distance"/> and last vertex's <see cref="JuiceStreamPathVertex.Distance"/>.
|
||||||
|
/// </summary>
|
||||||
|
public double Distance => vertices[^1].Distance - vertices[0].Distance;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// This list should always be non-empty.
|
||||||
|
/// </remarks>
|
||||||
|
private readonly List<JuiceStreamPathVertex> vertices = new List<JuiceStreamPathVertex>
|
||||||
|
{
|
||||||
|
new JuiceStreamPathVertex()
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute the x-position of the path at the given <paramref name="distance"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When the given distance is outside of the path, the x position at the corresponding endpoint is returned,
|
||||||
|
/// </remarks>
|
||||||
|
public float PositionAtDistance(double distance)
|
||||||
|
{
|
||||||
|
int index = vertexIndexAtDistance(distance);
|
||||||
|
return positionAtDistance(distance, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove all vertices of this path, then add a new vertex <c>(0, 0)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
vertices.Clear();
|
||||||
|
vertices.Add(new JuiceStreamPathVertex());
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Insert a vertex at given <paramref name="distance"/>.
|
||||||
|
/// The <see cref="PositionAtDistance"/> is used as the position of the new vertex.
|
||||||
|
/// Thus, the set of points of the path is not changed (up to floating-point precision).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The index of the new vertex.</returns>
|
||||||
|
public int InsertVertex(double distance)
|
||||||
|
{
|
||||||
|
if (!double.IsFinite(distance))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(distance));
|
||||||
|
|
||||||
|
int index = vertexIndexAtDistance(distance);
|
||||||
|
float x = positionAtDistance(distance, index);
|
||||||
|
vertices.Insert(index, new JuiceStreamPathVertex(distance, x));
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move the vertex of given <paramref name="index"/> to the given position <paramref name="newX"/>.
|
||||||
|
/// When the distances between vertices are too small for the new vertex positions, the adjacent vertices are moved towards <paramref name="newX"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void SetVertexPosition(int index, float newX)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= vertices.Count)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
|
||||||
|
if (!float.IsFinite(newX))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(newX));
|
||||||
|
|
||||||
|
var newVertex = new JuiceStreamPathVertex(vertices[index].Distance, newX);
|
||||||
|
|
||||||
|
for (int i = index - 1; i >= 0 && !canConnect(vertices[i], newVertex); i--)
|
||||||
|
{
|
||||||
|
float clampedX = clampToConnectablePosition(newVertex, vertices[i]);
|
||||||
|
vertices[i] = new JuiceStreamPathVertex(vertices[i].Distance, clampedX);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = index + 1; i < vertices.Count; i++)
|
||||||
|
{
|
||||||
|
float clampedX = clampToConnectablePosition(newVertex, vertices[i]);
|
||||||
|
vertices[i] = new JuiceStreamPathVertex(vertices[i].Distance, clampedX);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices[index] = newVertex;
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new vertex at given <paramref name="distance"/> and position.
|
||||||
|
/// Adjacent vertices are moved when necessary in the same way as <see cref="SetVertexPosition"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Add(double distance, float x)
|
||||||
|
{
|
||||||
|
int index = InsertVertex(distance);
|
||||||
|
SetVertexPosition(index, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove all vertices that satisfy the given <paramref name="predicate"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If all vertices are removed, a new vertex <c>(0, 0)</c> is added.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="predicate">The predicate to determine whether a vertex should be removed given the vertex and its index in the path.</param>
|
||||||
|
/// <returns>The number of removed vertices.</returns>
|
||||||
|
public int RemoveVertices(Func<JuiceStreamPathVertex, int, bool> predicate)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
int removeCount = vertices.RemoveAll(vertex => predicate(vertex, index++));
|
||||||
|
|
||||||
|
if (vertices.Count == 0)
|
||||||
|
vertices.Add(new JuiceStreamPathVertex());
|
||||||
|
|
||||||
|
if (removeCount != 0)
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
return removeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recreate this path by using difference set of vertices at given distances.
|
||||||
|
/// In addition to the given <paramref name="sampleDistances"/>, the first vertex and the last vertex are always added to the new path.
|
||||||
|
/// New vertices use the positions on the original path. Thus, <see cref="PositionAtDistance"/>s at <paramref name="sampleDistances"/> are preserved.
|
||||||
|
/// </summary>
|
||||||
|
public void ResampleVertices(IEnumerable<double> sampleDistances)
|
||||||
|
{
|
||||||
|
var sampledVertices = new List<JuiceStreamPathVertex>();
|
||||||
|
|
||||||
|
foreach (double distance in sampleDistances)
|
||||||
|
{
|
||||||
|
if (!double.IsFinite(distance))
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sampleDistances));
|
||||||
|
|
||||||
|
double clampedDistance = Math.Clamp(distance, vertices[0].Distance, vertices[^1].Distance);
|
||||||
|
float x = PositionAtDistance(clampedDistance);
|
||||||
|
sampledVertices.Add(new JuiceStreamPathVertex(clampedDistance, x));
|
||||||
|
}
|
||||||
|
|
||||||
|
sampledVertices.Sort();
|
||||||
|
|
||||||
|
// The first vertex and the last vertex are always used in the result.
|
||||||
|
vertices.RemoveRange(1, vertices.Count - (vertices.Count == 1 ? 1 : 2));
|
||||||
|
vertices.InsertRange(1, sampledVertices);
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SliderPath"/> to list of vertices and write the result to this <see cref="JuiceStreamPath"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Duplicated vertices are automatically removed.
|
||||||
|
/// </remarks>
|
||||||
|
public void ConvertFromSliderPath(SliderPath sliderPath)
|
||||||
|
{
|
||||||
|
var sliderPathVertices = new List<Vector2>();
|
||||||
|
sliderPath.GetPathToProgress(sliderPathVertices, 0, 1);
|
||||||
|
|
||||||
|
double distance = 0;
|
||||||
|
|
||||||
|
vertices.Clear();
|
||||||
|
vertices.Add(new JuiceStreamPathVertex(0, sliderPathVertices.FirstOrDefault().X));
|
||||||
|
|
||||||
|
for (int i = 1; i < sliderPathVertices.Count; i++)
|
||||||
|
{
|
||||||
|
distance += Vector2.Distance(sliderPathVertices[i - 1], sliderPathVertices[i]);
|
||||||
|
|
||||||
|
if (!Precision.AlmostEquals(vertices[^1].Distance, distance))
|
||||||
|
vertices.Add(new JuiceStreamPathVertex(distance, sliderPathVertices[i].X));
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert the path of this <see cref="JuiceStreamPath"/> to a <see cref="SliderPath"/> and write the result to <paramref name="sliderPath"/>.
|
||||||
|
/// The resulting slider is "folded" to make it vertically contained in the playfield `(0..<see cref="OSU_PLAYFIELD_HEIGHT"/>)` assuming the slider start position is <paramref name="sliderStartY"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void ConvertToSliderPath(SliderPath sliderPath, float sliderStartY)
|
||||||
|
{
|
||||||
|
const float margin = 1;
|
||||||
|
|
||||||
|
// Note: these two variables and `sliderPath` are modified by the local functions.
|
||||||
|
double currentDistance = 0;
|
||||||
|
Vector2 lastPosition = new Vector2(vertices[0].X, 0);
|
||||||
|
|
||||||
|
sliderPath.ControlPoints.Clear();
|
||||||
|
sliderPath.ControlPoints.Add(new PathControlPoint(lastPosition));
|
||||||
|
|
||||||
|
for (int i = 1; i < vertices.Count; i++)
|
||||||
|
{
|
||||||
|
sliderPath.ControlPoints[^1].Type.Value = PathType.Linear;
|
||||||
|
|
||||||
|
float deltaX = vertices[i].X - lastPosition.X;
|
||||||
|
double length = vertices[i].Distance - currentDistance;
|
||||||
|
|
||||||
|
// Should satisfy `deltaX^2 + deltaY^2 = length^2`.
|
||||||
|
// By invariants, the expression inside the `sqrt` is (almost) non-negative.
|
||||||
|
double deltaY = Math.Sqrt(Math.Max(0, length * length - (double)deltaX * deltaX));
|
||||||
|
|
||||||
|
// When `deltaY` is small, one segment is always enough.
|
||||||
|
// This case is handled separately to prevent divide-by-zero.
|
||||||
|
if (deltaY <= OSU_PLAYFIELD_HEIGHT / 2 - margin)
|
||||||
|
{
|
||||||
|
float nextX = vertices[i].X;
|
||||||
|
float nextY = (float)(lastPosition.Y + getYDirection() * deltaY);
|
||||||
|
addControlPoint(nextX, nextY);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When `deltaY` is large or when the slider velocity is fast, the segment must be partitioned to subsegments to stay in bounds.
|
||||||
|
for (double currentProgress = 0; currentProgress < deltaY;)
|
||||||
|
{
|
||||||
|
double nextProgress = Math.Min(currentProgress + getMaxDeltaY(), deltaY);
|
||||||
|
float nextX = (float)(vertices[i - 1].X + nextProgress / deltaY * deltaX);
|
||||||
|
float nextY = (float)(lastPosition.Y + getYDirection() * (nextProgress - currentProgress));
|
||||||
|
addControlPoint(nextX, nextY);
|
||||||
|
currentProgress = nextProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getYDirection()
|
||||||
|
{
|
||||||
|
float lastSliderY = sliderStartY + lastPosition.Y;
|
||||||
|
return lastSliderY < OSU_PLAYFIELD_HEIGHT / 2 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getMaxDeltaY()
|
||||||
|
{
|
||||||
|
float lastSliderY = sliderStartY + lastPosition.Y;
|
||||||
|
return Math.Max(lastSliderY, OSU_PLAYFIELD_HEIGHT - lastSliderY) - margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addControlPoint(float nextX, float nextY)
|
||||||
|
{
|
||||||
|
Vector2 nextPosition = new Vector2(nextX, nextY);
|
||||||
|
sliderPath.ControlPoints.Add(new PathControlPoint(nextPosition));
|
||||||
|
currentDistance += Vector2.Distance(lastPosition, nextPosition);
|
||||||
|
lastPosition = nextPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the index at which a new vertex with <paramref name="distance"/> can be inserted.
|
||||||
|
/// </summary>
|
||||||
|
private int vertexIndexAtDistance(double distance)
|
||||||
|
{
|
||||||
|
// The position of `(distance, Infinity)` is uniquely determined because infinite positions are not allowed.
|
||||||
|
int i = vertices.BinarySearch(new JuiceStreamPathVertex(distance, float.PositiveInfinity));
|
||||||
|
return i < 0 ? ~i : i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute the position at the given <paramref name="distance"/>, assuming <paramref name="index"/> is the vertex index returned by <see cref="vertexIndexAtDistance"/>.
|
||||||
|
/// </summary>
|
||||||
|
private float positionAtDistance(double distance, int index)
|
||||||
|
{
|
||||||
|
if (index <= 0)
|
||||||
|
return vertices[0].X;
|
||||||
|
if (index >= vertices.Count)
|
||||||
|
return vertices[^1].X;
|
||||||
|
|
||||||
|
double length = vertices[index].Distance - vertices[index - 1].Distance;
|
||||||
|
if (Precision.AlmostEquals(length, 0))
|
||||||
|
return vertices[index].X;
|
||||||
|
|
||||||
|
float deltaX = vertices[index].X - vertices[index - 1].X;
|
||||||
|
|
||||||
|
return (float)(vertices[index - 1].X + deltaX * ((distance - vertices[index - 1].Distance) / length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check the two vertices can connected directly while satisfying the slope condition.
|
||||||
|
/// </summary>
|
||||||
|
private bool canConnect(JuiceStreamPathVertex vertex1, JuiceStreamPathVertex vertex2, float allowance = 0)
|
||||||
|
{
|
||||||
|
double xDistance = Math.Abs((double)vertex2.X - vertex1.X);
|
||||||
|
float length = (float)Math.Abs(vertex2.Distance - vertex1.Distance);
|
||||||
|
return xDistance <= length + allowance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move the position of <paramref name="movableVertex"/> towards the position of <paramref name="fixedVertex"/>
|
||||||
|
/// until the vertex pair satisfies the condition <see cref="canConnect"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The resulting position of <paramref name="movableVertex"/>.</returns>
|
||||||
|
private float clampToConnectablePosition(JuiceStreamPathVertex fixedVertex, JuiceStreamPathVertex movableVertex)
|
||||||
|
{
|
||||||
|
float length = (float)Math.Abs(movableVertex.Distance - fixedVertex.Distance);
|
||||||
|
return Math.Clamp(movableVertex.X, fixedVertex.X - length, fixedVertex.X + length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invalidate() => InvalidationID++;
|
||||||
|
}
|
||||||
|
}
|
33
osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs
Normal file
33
osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.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 System;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A vertex of a <see cref="JuiceStreamPath"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct JuiceStreamPathVertex : IComparable<JuiceStreamPathVertex>
|
||||||
|
{
|
||||||
|
public readonly double Distance;
|
||||||
|
|
||||||
|
public readonly float X;
|
||||||
|
|
||||||
|
public JuiceStreamPathVertex(double distance, float x)
|
||||||
|
{
|
||||||
|
Distance = distance;
|
||||||
|
X = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(JuiceStreamPathVertex other)
|
||||||
|
{
|
||||||
|
int c = Distance.CompareTo(other.Distance);
|
||||||
|
return c != 0 ? c : X.CompareTo(other.X);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"({Distance}, {X})";
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +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.Collections.Generic;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The target fruit if we are to initiate a hyperdash.
|
/// The target fruit if we are to initiate a hyperdash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public CatchHitObject HyperDashTarget
|
public CatchHitObject HyperDashTarget
|
||||||
{
|
{
|
||||||
get => hyperDashTarget;
|
get => hyperDashTarget;
|
||||||
@ -43,6 +45,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count];
|
Color4 IHasComboInformation.GetComboColour(ISkin skin) => IHasComboInformation.GetSkinComboColour(this, skin, IndexInBeatmap + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Scoring
|
|
||||||
{
|
|
||||||
public class CatchHitWindows : HitWindows
|
|
||||||
{
|
|
||||||
public override bool IsHitResultAllowed(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case HitResult.Great:
|
|
||||||
case HitResult.Miss:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
13
osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs
Normal file
13
osu.Game.Rulesets.Catch/Skinning/CatchSkinConfiguration.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
|
{
|
||||||
|
public enum CatchSkinConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
|
||||||
|
/// </summary>
|
||||||
|
FlipCatcherPlate
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
public readonly Bindable<int> IndexInBeatmap = new Bindable<int>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IHasCatchObjectState ObjectState { get; private set; }
|
protected IHasCatchObjectState ObjectState { get; private set; }
|
||||||
@ -37,6 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
|
|
||||||
AccentColour.BindTo(ObjectState.AccentColour);
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
HyperDash.BindTo(ObjectState.HyperDash);
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
|
IndexInBeatmap.BindTo(ObjectState.IndexInBeatmap);
|
||||||
|
|
||||||
HyperDash.BindValueChanged(hyper =>
|
HyperDash.BindValueChanged(hyper =>
|
||||||
{
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user