1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 10:00:42 +08:00

Compare commits

...

2669 Commits

1332 changed files with 35179 additions and 13816 deletions
+3 -3
View File
@@ -14,8 +14,8 @@
"jb"
]
},
"smoogipoo.nvika": {
"version": "1.0.1",
"nvika": {
"version": "2.2.0",
"commands": [
"nvika"
]
@@ -33,4 +33,4 @@
]
}
}
}
}
+1 -1
View File
@@ -113,7 +113,7 @@ dotnet_style_qualification_for_event = false:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
csharp_style_var_when_type_is_apparent = true:none
csharp_style_var_for_built_in_types = true:none
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_elsewhere = true:silent
#Style - modifiers
+1
View File
@@ -1 +1,2 @@
github: ppy
custom: https://osu.ppy.sh/home/support
-30
View File
@@ -1,30 +0,0 @@
---
name: Bug Report
about: Report a bug or crash to desktop
---
<!--
IMPORTANT: Your issue may already be reported.
Please check:
- Pinned issues, at the top of https://github.com/ppy/osu/issues
- Current priority 0 issues at https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0
- Search for your issue. If you find that it already exists, please respond with a reaction or add any further information that may be helpful.
-->
**Describe the bug:**
**Screenshots or videos showing encountered issue:**
**osu!lazer version:**
**Logs:**
<!--
*please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/data/sh.ppy.osulazer/files/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
-->
+4 -4
View File
@@ -1,12 +1,12 @@
blank_issues_enabled: false
contact_links:
- name: Suggestions or feature request
url: https://github.com/ppy/osu/discussions/categories/ideas
about: Got something you think should change or be added? Search for or start a new discussion!
- name: Help
url: https://github.com/ppy/osu/discussions/categories/q-a
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
- name: Suggestions or feature request
url: https://github.com/ppy/osu/discussions/categories/ideas
about: Got something you think should change or be added? Search for or start a new discussion!
- name: osu!stable issues
url: https://github.com/ppy/osu-stable-issues
about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
about: For osu!(stable) - ie. the current "live" game version, check out the dedicated repository. Note that this is for serious bug reports only, not tech support.
+50 -3
View File
@@ -50,6 +50,48 @@ jobs:
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx
build-only-android:
name: Build only (Android)
runs-on: windows-latest
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"
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1
- name: Build
run: msbuild osu.Android.slnf /restore /p:Configuration=Debug
build-only-ios:
# While this workflow technically *can* run, it fails as iOS builds are blocked by multiple issues.
# See https://github.com/ppy/osu-framework/issues/4677 for the details.
# The job can be unblocked once those issues are resolved and game deployments can happen again.
if: false
name: Build only (iOS)
runs-on: macos-latest
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"
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
# cannot accept .sln(f) files as arguments.
# Build just the main game for now.
- name: Build
run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug
inspect-code:
name: Code Quality
runs-on: ubuntu-latest
@@ -79,9 +121,14 @@ jobs:
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
exit_code=0
while read -r line; do
if [[ ! -z "$line" ]]; then
echo "::error::$line"
exit_code=1
fi
done <<< $(dotnet codefilesanity)
exit $exit_code
# Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded.
# - name: .NET Format (Dry Run)
+206
View File
@@ -0,0 +1,206 @@
# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master.
# Usage:
# !pp check 0 | Runs only the osu! ruleset.
# !pp check 0 2 | Runs only the osu! and catch rulesets.
#
name: Difficulty Calculation
on:
issue_comment:
types: [ created ]
env:
CONCURRENCY: 4
ALLOW_DOWNLOAD: 1
SAVE_DOWNLOADED: 1
SKIP_INSERT_ATTRIBUTES: 1
jobs:
metadata:
name: Check for requests
runs-on: self-hosted
if: github.event.issue.pull_request && contains(github.event.comment.body, '!pp check') && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
outputs:
matrix: ${{ steps.generate-matrix.outputs.matrix }}
continue: ${{ steps.generate-matrix.outputs.continue }}
steps:
- name: Construct build matrix
id: generate-matrix
run: |
if [[ "${{ github.event.comment.body }}" =~ "osu" ]] ; then
MATRIX_PROJECTS_JSON+='{ "name": "osu", "id": 0 },'
fi
if [[ "${{ github.event.comment.body }}" =~ "taiko" ]] ; then
MATRIX_PROJECTS_JSON+='{ "name": "taiko", "id": 1 },'
fi
if [[ "${{ github.event.comment.body }}" =~ "catch" ]] ; then
MATRIX_PROJECTS_JSON+='{ "name": "catch", "id": 2 },'
fi
if [[ "${{ github.event.comment.body }}" =~ "mania" ]] ; then
MATRIX_PROJECTS_JSON+='{ "name": "mania", "id": 3 },'
fi
if [[ "${MATRIX_PROJECTS_JSON}" != "" ]]; then
MATRIX_JSON="{ \"ruleset\": [ ${MATRIX_PROJECTS_JSON} ] }"
echo "${MATRIX_JSON}"
CONTINUE="yes"
else
CONTINUE="no"
fi
echo "::set-output name=continue::${CONTINUE}"
echo "::set-output name=matrix::${MATRIX_JSON}"
diffcalc:
name: Run
runs-on: self-hosted
timeout-minutes: 1440
if: needs.metadata.outputs.continue == 'yes'
needs: metadata
strategy:
matrix: ${{ fromJson(needs.metadata.outputs.matrix) }}
steps:
- name: Verify MySQL connection from host
run: |
mysql -e "SHOW DATABASES"
- name: Drop previous databases
run: |
for db in osu_master osu_pr
do
mysql -e "DROP DATABASE IF EXISTS $db"
done
- name: Create directory structure
run: |
mkdir -p $GITHUB_WORKSPACE/master/
mkdir -p $GITHUB_WORKSPACE/pr/
- name: Get upstream branch # https://akaimo.hatenablog.jp/entry/2020/05/16/101251
id: upstreambranch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"
echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')"
# Checkout osu
- name: Checkout osu (master)
uses: actions/checkout@v2
with:
path: 'master/osu'
- name: Checkout osu (pr)
uses: actions/checkout@v2
with:
path: 'pr/osu'
repository: ${{ steps.upstreambranch.outputs.repo }}
ref: ${{ steps.upstreambranch.outputs.branchname }}
- name: Checkout osu-difficulty-calculator (master)
uses: actions/checkout@v2
with:
repository: ppy/osu-difficulty-calculator
path: 'master/osu-difficulty-calculator'
- name: Checkout osu-difficulty-calculator (pr)
uses: actions/checkout@v2
with:
repository: ppy/osu-difficulty-calculator
path: 'pr/osu-difficulty-calculator'
- name: Install .NET 5.0.x
uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
# Sanity checks to make sure diffcalc is not run when incompatible.
- name: Build diffcalc (master)
run: |
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator
./UseLocalOsu.sh
dotnet build
- name: Build diffcalc (pr)
run: |
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator
./UseLocalOsu.sh
dotnet build
- name: Download + import data
run: |
PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top_1000 | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
# Set env variable for further steps.
echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV
cd $GITHUB_WORKSPACE
echo "Downloading database dump $PERFORMANCE_DATA_NAME.."
wget -q -nc https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2
echo "Extracting.."
tar -xf $PERFORMANCE_DATA_NAME.tar.bz2
echo "Downloading beatmap dump $BEATMAPS_DATA_NAME.."
wget -q -nc https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2
echo "Extracting.."
tar -xf $BEATMAPS_DATA_NAME.tar.bz2
cd $PERFORMANCE_DATA_NAME
for db in osu_master osu_pr
do
echo "Setting up database $db.."
mysql -e "CREATE DATABASE $db"
echo "Importing beatmaps.."
cat osu_beatmaps.sql | mysql $db
echo "Importing beatmapsets.."
cat osu_beatmapsets.sql | mysql $db
echo "Creating table structure.."
mysql $db -e 'CREATE TABLE `osu_beatmap_difficulty` (
`beatmap_id` int unsigned NOT NULL,
`mode` tinyint NOT NULL DEFAULT 0,
`mods` int unsigned NOT NULL,
`diff_unified` float NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`beatmap_id`,`mode`,`mods`),
KEY `diff_sort` (`mode`,`mods`,`diff_unified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;'
done
- name: Run diffcalc (master)
env:
DB_NAME: osu_master
run: |
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
- name: Run diffcalc (pr)
env:
DB_NAME: osu_pr
run: |
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
- name: Print diffs
run: |
mysql -e "
SELECT
m.beatmap_id,
m.mods,
b.filename,
m.diff_unified as 'sr_master',
p.diff_unified as 'sr_pr',
(p.diff_unified - m.diff_unified) as 'diff'
FROM osu_master.osu_beatmap_difficulty m
JOIN osu_pr.osu_beatmap_difficulty p
ON m.beatmap_id = p.beatmap_id
AND m.mode = p.mode
AND m.mods = p.mods
JOIN osu_pr.osu_beatmaps b
ON b.beatmap_id = p.beatmap_id
WHERE abs(m.diff_unified - p.diff_unified) > 0.1
ORDER BY abs(m.diff_unified - p.diff_unified)
DESC
LIMIT 10000;"
# Todo: Run ppcalc
+2
View File
@@ -30,3 +30,5 @@ jobs:
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
path: "*.trx"
reporter: dotnet-trx
list-suites: 'failed'
list-tests: 'failed'
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
<option name="PROGRAM_PARAMETERS" value="--filter *" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0/osu.Game.Benchmarks.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -14,7 +14,7 @@
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net5.0" />
<method v="2">
<option name="Build" enabled="true" />
<option name="Build" />
</method>
</configuration>
</component>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelUserStore">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
+1 -1
View File
@@ -16,7 +16,7 @@
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup>
+1 -2
View File
@@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
## Developing a custom ruleset
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
game
};
AddGame(new OsuGame());
}
}
}
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<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="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
game
};
AddGame(new OsuGame());
}
}
}
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<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="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
game
};
AddGame(new OsuGame());
}
}
}
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<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="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
game
};
AddGame(new OsuGame());
}
}
}
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<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="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osuTK;
@@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Pippidon.UI
}
}
public bool OnPressed(PippidonAction action)
public bool OnPressed(KeyBindingPressEvent<PippidonAction> e)
{
switch (action)
switch (e.Action)
{
case PippidonAction.MoveUp:
changeLane(-1);
@@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Pippidon.UI
}
}
public void OnReleased(PippidonAction action)
public void OnReleased(KeyBindingReleaseEvent<PippidonAction> e)
{
}
+3 -3
View File
@@ -51,11 +51,11 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.813.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1104.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" />
<PackageReference Include="Realm" Version="10.6.0" />
</ItemGroup>
</Project>
+18 -2
View File
@@ -20,8 +20,22 @@ 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)]
[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", 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.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]
[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",
// newer official mime types (see https://osu.ppy.sh/wiki/en/osu%21_File_Formats).
"application/x-osu-beatmap-archive",
"application/x-osu-skin-archive",
"application/x-osu-replay",
})]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
public class OsuGameActivity : AndroidGameActivity
{
@@ -66,12 +80,14 @@ namespace osu.Android
case Intent.ActionSendMultiple:
{
var uris = new List<Uri>();
for (int i = 0; i < intent.ClipData?.ItemCount; i++)
{
var content = intent.ClipData?.GetItemAt(i);
if (content != null)
uris.Add(content.Uri);
}
handleImportFromUris(uris.ToArray());
break;
}
+3 -3
View File
@@ -139,11 +139,11 @@ namespace osu.Desktop
{
switch (activity)
{
case UserActivity.SoloGame solo:
return solo.Beatmap.ToString();
case UserActivity.InGame game:
return game.BeatmapInfo.ToString();
case UserActivity.Editing edit:
return edit.Beatmap.ToString();
return edit.BeatmapInfo.ToString();
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
+2 -2
View File
@@ -156,7 +156,7 @@ namespace osu.Desktop
{
lock (importableFiles)
{
var firstExtension = Path.GetExtension(filePaths.First());
string firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
@@ -177,7 +177,7 @@ namespace osu.Desktop
{
Logger.Log($"Handling batch import of {importableFiles.Count} files");
var paths = importableFiles.ToArray();
string[] paths = importableFiles.ToArray();
importableFiles.Clear();
Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
+9 -6
View File
@@ -22,17 +22,17 @@ namespace osu.Desktop
public static int Main(string[] args)
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
string cwd = Environment.CurrentDirectory;
string gameName = base_game_name;
bool tournamentClient = false;
foreach (var arg in args)
foreach (string arg in args)
{
var split = arg.Split('=');
string[] split = arg.Split('=');
var key = split[0];
var val = split.Length > 1 ? split[1] : string.Empty;
string key = split[0];
string val = split.Length > 1 ? split[1] : string.Empty;
switch (key)
{
@@ -62,7 +62,7 @@ namespace osu.Desktop
{
var importer = new ArchiveImportIPCChannel(host);
foreach (var file in args)
foreach (string file in args)
{
Console.WriteLine(@"Importing {0}", file);
if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
@@ -74,7 +74,10 @@ namespace osu.Desktop
// we want to allow multiple instances to be started when in debug.
if (!DebugUtils.IsDebugBuild)
{
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
return 0;
}
}
if (tournamentClient)
+4 -4
View File
@@ -5,23 +5,23 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Game;
using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Desktop.Windows
{
public class GameplayWinKeyBlocker : Component
{
private Bindable<bool> disableWinKey;
private Bindable<bool> localUserPlaying;
private IBindable<bool> localUserPlaying;
[Resolved]
private GameHost host { get; set; }
[BackgroundDependencyLoader]
private void load(OsuGame game, OsuConfigManager config)
private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
{
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking());
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
+34
View File
@@ -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 System;
using BenchmarkDotNet.Attributes;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Benchmarks
{
public class BenchmarkMod : BenchmarkTest
{
private OsuModDoubleTime mod;
[Params(1, 10, 100)]
public int Times { get; set; }
public override void SetUp()
{
base.SetUp();
mod = new OsuModDoubleTime();
}
[Benchmark]
public int ModHashCode()
{
var hashCode = new HashCode();
for (int i = 0; i < Times; i++)
hashCode.Add(mod);
return hashCode.ToHashCode();
}
}
}
+62
View File
@@ -0,0 +1,62 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Benchmarks
{
public class BenchmarkRuleset : BenchmarkTest
{
private OsuRuleset ruleset;
private APIMod apiModDoubleTime;
private APIMod apiModDifficultyAdjust;
public override void SetUp()
{
base.SetUp();
ruleset = new OsuRuleset();
apiModDoubleTime = new APIMod { Acronym = "DT" };
apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
}
[Benchmark]
public void BenchmarkToModDoubleTime()
{
apiModDoubleTime.ToMod(ruleset);
}
[Benchmark]
public void BenchmarkToModDifficultyAdjust()
{
apiModDifficultyAdjust.ToMod(ruleset);
}
[Benchmark]
public void BenchmarkGetAllMods()
{
ruleset.CreateAllMods().Consume(new Consumer());
}
[Benchmark]
public void BenchmarkGetAllModsForReference()
{
ruleset.AllMods.Consume(new Consumer());
}
[Benchmark]
public void BenchmarkGetForAcronym()
{
ruleset.CreateModFromAcronym("DT");
}
[Benchmark]
public void BenchmarkGetForType()
{
ruleset.CreateMod<ModDoubleTime>();
}
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
namespace osu.Game.Benchmarks
@@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
{
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
.Run(args);
.Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
}
}
}
@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
</ItemGroup>
@@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Catch.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase(4.050601681491468d, "diffcalc-test")]
[TestCase(4.0505463516206195d, "diffcalc-test")]
public void Test(double expected, string name)
=> base.Test(expected, name);
[TestCase(5.169743871843191d, "diffcalc-test")]
[TestCase(5.1696411260785498d, "diffcalc-test")]
public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new CatchModDoubleTime());
@@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected CatchSelectionBlueprintTestScene()
{
EditorBeatmap = new EditorBeatmap(new CatchBeatmap());
EditorBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = 0;
EditorBeatmap = new EditorBeatmap(new CatchBeatmap()) { Difficulty = { CircleSize = 0 } };
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
{
BeatLength = 100
@@ -5,6 +5,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
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);
hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
base.AddHitObject(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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Edit;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests.Editor
{
public class TestSceneCatchDistanceSnapGrid : OsuManualInputManagerTestScene
{
private readonly ManualClock manualClock = new ManualClock();
[Cached(typeof(Playfield))]
private readonly CatchPlayfield playfield;
private ScrollingHitObjectContainer hitObjectContainer => playfield.HitObjectContainer;
private readonly CatchDistanceSnapGrid distanceGrid;
private readonly FruitOutline fruitOutline;
private readonly Fruit fruit = new Fruit();
public TestSceneCatchDistanceSnapGrid()
{
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 500,
Children = new Drawable[]
{
new ScrollingTestContainer(ScrollingDirection.Down)
{
RelativeSizeAxes = Axes.Both,
Child = playfield = new CatchPlayfield(new BeatmapDifficulty())
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(manualClock)
}
},
distanceGrid = new CatchDistanceSnapGrid(new double[] { 0, -1, 1 }),
fruitOutline = new FruitOutline()
},
};
}
protected override void Update()
{
base.Update();
distanceGrid.StartTime = 100;
distanceGrid.StartX = 250;
Vector2 screenSpacePosition = InputManager.CurrentState.Mouse.Position;
var result = distanceGrid.GetSnappedPosition(screenSpacePosition);
if (result != null)
{
fruit.OriginalX = hitObjectContainer.ToLocalSpace(result.ScreenSpacePosition).X;
if (result.Time != null)
fruit.StartTime = result.Time.Value;
}
fruitOutline.Position = CatchHitObjectUtils.GetStartPosition(hitObjectContainer, fruit);
fruitOutline.UpdateFrom(fruit);
}
protected override bool OnScroll(ScrollEvent e)
{
manualClock.CurrentTime -= e.ScrollDelta.Y * 50;
return true;
}
}
}
@@ -4,9 +4,9 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
@@ -23,11 +23,12 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
private JuiceStream lastObject => LastObject?.HitObject as JuiceStream;
[BackgroundDependencyLoader]
private void load()
protected override IBeatmap GetPlayableBeatmap()
{
Beatmap.Value.BeatmapInfo.BaseDifficulty.SliderTickRate = 5;
Beatmap.Value.BeatmapInfo.BaseDifficulty.SliderMultiplier = velocity * 10;
var playable = base.GetPlayableBeatmap();
playable.Difficulty.SliderTickRate = 5;
playable.Difficulty.SliderMultiplier = velocity * 10;
return playable;
}
[Test]
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
AddAssert("correct outline count", () =>
{
var expected = hitObject.NestedHitObjects.Count(h => !(h is TinyDroplet));
int expected = hitObject.NestedHitObjects.Count(h => !(h is TinyDroplet));
return this.ChildrenOfType<FruitOutline>().Count() == expected;
});
AddAssert("correct vertex piece count", () =>
@@ -210,9 +210,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
new Vector2(50, 200),
}), 0.5);
AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count);
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type.Value == PathType.PerfectCurve);
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve);
addAddVertexSteps(150, 150);
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type.Value == PathType.Linear);
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear);
}
private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () =>
@@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
X = x,
Path = sliderPath,
};
EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = velocity;
EditorBeatmap.Difficulty.SliderMultiplier = velocity;
EditorBeatmap.Add(hitObject);
EditorBeatmap.Update(hitObject);
Assert.That(hitObject.Velocity, Is.EqualTo(velocity));
@@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests
} 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;
sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier;
} while (rng.Next(3) != 0);
if (rng.Next(5) == 0)
@@ -210,13 +210,13 @@ namespace osu.Game.Rulesets.Catch.Tests
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));
Assert.That(sliderPath.ControlPoints[0].Position.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));
Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null);
Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
}
for (int i = 0; i < 10; i++)
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestCatcherCatchWidth()
{
var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
float halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
AddStep("catch fruit", () =>
{
attemptCatch(new Fruit { X = -halfWidth + 1 });
@@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void attemptCatch(Func<CatchHitObject> hitObject, int count)
{
for (var i = 0; i < count; i++)
for (int i = 0; i < count; i++)
attemptCatch(hitObject(), out _, out _);
}
@@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
public TestCatcher(DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty)
public TestCatcher(DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty)
: base(droppedObjectTarget, difficulty)
{
}
@@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public class TestKiaiFruit : Fruit
{
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private ScheduledDelegate addManyFruit;
private BeatmapDifficulty beatmapDifficulty;
private IBeatmapDifficultyInfo beatmapDifficulty;
public TestSceneCatcherArea()
{
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private class TestCatcherArea : CatcherArea
{
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
public TestCatcherArea(IBeatmapDifficultyInfo beatmapDifficulty)
{
var droppedObjectContainer = new DroppedObjectContainer();
Add(droppedObjectContainer);
@@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void spawnJuiceStream(bool hit = false)
{
var xCoords = getXCoords(hit);
float xCoords = getXCoords(hit);
var juice = new JuiceStream
{
@@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<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="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream:
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.X;
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
lastStartTime = juiceStream.StartTime;
@@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2;
// Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins.
// This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible.
@@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
float distanceToHyper = (float)(timeToNext * Catcher.BASE_DASH_SPEED - distanceToNext);
if (distanceToHyper < 0)
{
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
return new CatchDifficultyAttributes
{
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
foreach (var hitObject in beatmap.HitObjects
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj })
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj })
.Cast<CatchHitObject>()
.OrderBy(x => x.StartTime))
{
@@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
{
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f);
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f);
return new Skill[]
{
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
: base(hitObject, lastObject, clockRate)
{
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
float scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{
public class Movement : StrainSkill
public class Movement : StrainDecaySkill
{
private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f;
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
path.ConvertFromSliderPath(sliderPath);
// If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices.
if (sliderPath.ControlPoints.Any(p => p.Type.Value != null && p.Type.Value != PathType.Linear))
if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear))
{
path.ResampleVertices(hitObject.NestedHitObjects
.Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used.
@@ -0,0 +1,141 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Catch.Edit
{
/// <summary>
/// The guide lines used in the osu!catch editor to compose patterns that can be caught with constant speed.
/// Currently, only forward placement (an object is snapped based on the previous object, not the opposite) is supported.
/// </summary>
public class CatchDistanceSnapGrid : CompositeDrawable
{
public double StartTime { get; set; }
public float StartX { get; set; }
private const double max_vertical_line_length_in_time = CatchPlayfield.WIDTH / Catcher.BASE_WALK_SPEED;
private readonly double[] velocities;
private readonly List<Path> verticalPaths = new List<Path>();
private readonly List<Vector2[]> verticalLineVertices = new List<Vector2[]>();
[Resolved]
private Playfield playfield { get; set; }
private ScrollingHitObjectContainer hitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
public CatchDistanceSnapGrid(double[] velocities)
{
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.BottomLeft;
this.velocities = velocities;
for (int i = 0; i < velocities.Length; i++)
{
verticalPaths.Add(new SmoothPath
{
PathRadius = 2,
Alpha = 0.5f,
});
verticalLineVertices.Add(new[] { Vector2.Zero, Vector2.Zero });
}
AddRangeInternal(verticalPaths);
}
protected override void Update()
{
base.Update();
double currentTime = hitObjectContainer.Time.Current;
for (int i = 0; i < velocities.Length; i++)
{
double velocity = velocities[i];
// The line ends at the top of the playfield.
double endTime = hitObjectContainer.TimeAtPosition(-hitObjectContainer.DrawHeight, currentTime);
// Non-vertical lines are cut at the sides of the playfield.
// Vertical lines are cut at some reasonable length.
if (velocity > 0)
endTime = Math.Min(endTime, StartTime + (CatchPlayfield.WIDTH - StartX) / velocity);
else if (velocity < 0)
endTime = Math.Min(endTime, StartTime + StartX / -velocity);
else
endTime = Math.Min(endTime, StartTime + max_vertical_line_length_in_time);
Vector2[] lineVertices = verticalLineVertices[i];
lineVertices[0] = calculatePosition(velocity, StartTime);
lineVertices[1] = calculatePosition(velocity, endTime);
var verticalPath = verticalPaths[i];
verticalPath.Vertices = verticalLineVertices[i];
verticalPath.OriginPosition = verticalPath.PositionInBoundingBox(Vector2.Zero);
}
Vector2 calculatePosition(double velocity, double time)
{
// Don't draw inverted lines.
time = Math.Max(time, StartTime);
float x = StartX + (float)((time - StartTime) * velocity);
float y = hitObjectContainer.PositionAtTime(time, currentTime);
return new Vector2(x, y);
}
}
[CanBeNull]
public SnapResult GetSnappedPosition(Vector2 screenSpacePosition)
{
double time = hitObjectContainer.TimeAtScreenSpacePosition(screenSpacePosition);
// If the cursor is below the distance snap grid, snap to the origin.
// Not returning `null` to retain the continuous snapping behavior when the cursor is slightly below the origin.
// This behavior is not currently visible in the editor because editor chooses the snap start time based on the mouse position.
if (time <= StartTime)
{
float y = hitObjectContainer.PositionAtTime(StartTime);
Vector2 originPosition = hitObjectContainer.ToScreenSpace(new Vector2(StartX, y));
return new SnapResult(originPosition, StartTime);
}
return enumerateSnappingCandidates(time)
.OrderBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition))
.FirstOrDefault();
}
private IEnumerable<SnapResult> enumerateSnappingCandidates(double time)
{
float y = hitObjectContainer.PositionAtTime(time);
foreach (double velocity in velocities)
{
float x = (float)(StartX + (time - StartTime) * velocity);
Vector2 screenSpacePosition = hitObjectContainer.ToScreenSpace(new Vector2(x, y + hitObjectContainer.DrawHeight));
yield return new SnapResult(screenSpacePosition, time);
}
}
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
}
}
@@ -9,7 +9,7 @@ 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)
public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty)
: base(difficulty)
{
}
@@ -2,14 +2,23 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -17,6 +26,14 @@ namespace osu.Game.Rulesets.Catch.Edit
{
public class CatchHitObjectComposer : HitObjectComposer<CatchHitObject>
{
private const float distance_snap_radius = 50;
private CatchDistanceSnapGrid distanceSnapGrid;
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
private InputManager inputManager;
public CatchHitObjectComposer(CatchRuleset ruleset)
: base(ruleset)
{
@@ -30,6 +47,27 @@ namespace osu.Game.Rulesets.Catch.Edit
RelativeSizeAxes = Axes.Both,
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
});
LayerBelowRuleset.Add(distanceSnapGrid = new CatchDistanceSnapGrid(new[]
{
0.0,
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
}));
}
protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
}
protected override void Update()
{
base.Update();
updateDistanceSnapGrid();
}
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
@@ -42,14 +80,95 @@ namespace osu.Game.Rulesets.Catch.Edit
new BananaShowerCompositionTool()
};
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
{
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
});
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
// TODO: implement position snap
result.ScreenSpacePosition.X = screenSpacePosition.X;
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
{
result = snapResult;
}
return result;
}
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
[CanBeNull]
private PalpableCatchHitObject getLastSnappableHitObject(double time)
{
var hitObject = EditorBeatmap.HitObjects.OfType<CatchHitObject>().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
switch (hitObject)
{
case Fruit fruit:
return fruit;
case JuiceStream juiceStream:
return juiceStream.NestedHitObjects.OfType<PalpableCatchHitObject>().LastOrDefault(h => !(h is TinyDroplet));
default:
return null;
}
}
[CanBeNull]
private PalpableCatchHitObject getDistanceSnapGridSourceHitObject()
{
switch (BlueprintContainer.CurrentTool)
{
case SelectTool _:
if (EditorBeatmap.SelectedHitObjects.Count == 0)
return null;
double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime);
return getLastSnappableHitObject(minTime);
case FruitCompositionTool _:
case JuiceStreamCompositionTool _:
if (!CursorInPlacementArea)
return null;
if (EditorBeatmap.PlacementObject.Value is JuiceStream)
{
// Juice stream path is not subject to snapping.
return null;
}
double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(inputManager.CurrentState.Mouse.Position);
return getLastSnappableHitObject(timeAtCursor);
default:
return null;
}
}
private void updateDistanceSnapGrid()
{
if (distanceSnapToggle.Value != TernaryState.True)
{
distanceSnapGrid.Hide();
return;
}
var sourceHitObject = getDistanceSnapGridSourceHitObject();
if (sourceHitObject == null)
{
distanceSnapGrid.Hide();
return;
}
distanceSnapGrid.Show();
distanceSnapGrid.StartTime = sourceHitObject.GetEndTime();
distanceSnapGrid.StartX = sourceHitObject.EffectiveX;
}
}
}
@@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Catch.Edit
juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX);
foreach (var point in juiceStream.Path.ControlPoints)
point.Position.Value *= new Vector2(-1, 1);
point.Position *= new Vector2(-1, 1);
EditorBeatmap.Update(juiceStream);
return true;
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch.Edit
{
}
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
}
}
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
Replay = new CatchAutoGenerator(beatmap).Generate(),
};
}
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
Replay = new CatchAutoGenerator(beatmap).Generate(),
};
}
@@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Mods
{
var hitObject = drawable.HitObject;
var offset = hitObject.TimePreempt * fade_out_offset_multiplier;
var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier;
double offset = hitObject.TimePreempt * fade_out_offset_multiplier;
double duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier;
using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset))
drawable.FadeOut(duration);
@@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Catch.Mods
/// </summary>
private static void mirrorJuiceStreamPath(JuiceStream juiceStream)
{
var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
foreach (var point in controlPoints)
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y);
point.Position = new Vector2(-point.Position.X, point.Position.Y);
juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value);
}
@@ -44,9 +44,9 @@ namespace osu.Game.Rulesets.Catch.Mods
}
// disable keyboard controls
public bool OnPressed(CatchAction action) => true;
public bool OnPressed(KeyBindingPressEvent<CatchAction> e) => true;
public void OnReleased(CatchAction action)
public void OnReleased(KeyBindingReleaseEvent<CatchAction> e)
{
}
@@ -128,11 +128,11 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary>
public int RandomSeed => (int)StartTime;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
@@ -37,14 +37,13 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary>
public double SpanDuration => Duration / this.SpanCount();
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;
@@ -138,7 +137,7 @@ namespace osu.Game.Rulesets.Catch.Objects
if (value != null)
{
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
}
@@ -146,7 +145,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance => Path.Distance;
public List<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
public double? LegacyLastTickOffset { get; set; }
}
@@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Catch.Objects
for (int i = 1; i < vertices.Count; i++)
{
sliderPath.ControlPoints[^1].Type.Value = PathType.Linear;
sliderPath.ControlPoints[^1].Type = PathType.Linear;
float deltaX = vertices[i].X - lastPosition.X;
double length = vertices[i].Distance - currentDistance;
@@ -26,9 +26,6 @@ namespace osu.Game.Rulesets.Catch.Replays
if (Beatmap.HitObjects.Count == 0)
return;
// todo: add support for HT DT
const double dash_speed = Catcher.BASE_SPEED;
const double movement_speed = dash_speed / 2;
float lastPosition = CatchPlayfield.CENTER_X;
double lastTime = 0;
@@ -47,8 +44,8 @@ namespace osu.Game.Rulesets.Catch.Replays
// The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable;
bool dashRequired = speedRequired > movement_speed;
bool impossibleJump = speedRequired > movement_speed * 2;
bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED;
bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED;
// todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f;
@@ -73,7 +70,7 @@ namespace osu.Game.Rulesets.Catch.Replays
else if (dashRequired)
{
// we do a movement in two parts - the dash part then the normal part...
double timeAtNormalSpeed = positionChange / movement_speed;
double timeAtNormalSpeed = positionChange / Catcher.BASE_WALK_SPEED;
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
double timeAtDashSpeed = timeWeNeedToSave / 2;
@@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.Replays
}
else
{
double timeBefore = positionChange / movement_speed;
double timeBefore = positionChange / Catcher.BASE_WALK_SPEED;
addFrame(h.StartTime - timeBefore, lastPosition);
addFrame(h.StartTime, h.EffectiveX);
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Replays
public override void CollectPendingInputs(List<IInput> inputs)
{
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
float position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
inputs.Add(new CatchReplayState
{
@@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
return null;
case CatchSkinComponents.Catcher:
var version = GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value ?? 1;
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
if (version < 2.3m)
{
+2 -2
View File
@@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Catch.UI
internal CatcherArea CatcherArea { get; private set; }
private readonly BeatmapDifficulty difficulty;
private readonly IBeatmapDifficultyInfo difficulty;
public CatchPlayfield(BeatmapDifficulty difficulty)
public CatchPlayfield(IBeatmapDifficultyInfo difficulty)
{
this.difficulty = difficulty;
}
+16 -11
View File
@@ -57,14 +57,19 @@ namespace osu.Game.Rulesets.Catch.UI
public bool CatchFruitOnPlate { get; set; } = true;
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// The speed of the catcher when the catcher is dashing.
/// </summary>
public const double BASE_SPEED = 1.0;
public const double BASE_DASH_SPEED = 1.0;
/// <summary>
/// The current speed of the catcher.
/// The speed of the catcher when the catcher is not dashing.
/// </summary>
public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier;
public const double BASE_WALK_SPEED = 0.5;
/// <summary>
/// The current speed of the catcher with the hyper-dash modifier applied.
/// </summary>
public double Speed => (Dashing ? BASE_DASH_SPEED : BASE_WALK_SPEED) * hyperDashModifier;
/// <summary>
/// The amount by which caught fruit should be scaled down to fit on the plate.
@@ -124,7 +129,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null)
public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty = null)
{
this.droppedObjectTarget = droppedObjectTarget;
@@ -172,7 +177,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary>
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
/// </summary>
private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
/// <summary>
/// Calculates the width of the area used for attempting catches in gameplay.
@@ -184,7 +189,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// Calculates the width of the area used for attempting catches in gameplay.
/// </summary>
/// <param name="difficulty">The beatmap difficulty.</param>
public static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty));
public static float CalculateCatchWidth(IBeatmapDifficultyInfo difficulty) => CalculateCatchWidth(calculateScale(difficulty));
/// <summary>
/// Determine if this catcher can catch a <see cref="CatchHitObject"/> in the current position.
@@ -226,11 +231,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (result.IsHit && hitObject.HyperDash)
{
var target = hitObject.HyperDashTarget;
var timeDifference = target.StartTime - hitObject.StartTime;
double timeDifference = target.StartTime - hitObject.StartTime;
double positionDifference = target.EffectiveX - X;
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperDashState(Math.Abs(velocity), target.EffectiveX);
SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX);
}
else
SetHyperDashState();
@@ -266,7 +271,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
{
var wasHyperDashing = HyperDashing;
bool wasHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition)
{
+5 -4
View File
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
@@ -144,9 +145,9 @@ namespace osu.Game.Rulesets.Catch.UI
Catcher.VisualDirection = Direction.Left;
}
public bool OnPressed(CatchAction action)
public bool OnPressed(KeyBindingPressEvent<CatchAction> e)
{
switch (action)
switch (e.Action)
{
case CatchAction.MoveLeft:
currentDirection--;
@@ -164,9 +165,9 @@ namespace osu.Game.Rulesets.Catch.UI
return false;
}
public void OnReleased(CatchAction action)
public void OnReleased(KeyBindingReleaseEvent<CatchAction> e)
{
switch (action)
switch (e.Action)
{
case CatchAction.MoveLeft:
currentDirection++;
@@ -27,14 +27,14 @@ namespace osu.Game.Rulesets.Catch.UI
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
}
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.Difficulty);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer();
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
{
var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
var pos = column.ScreenSpacePositionAtTime(time);
return new SnapResult(pos, time, column);
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -101,27 +102,27 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
throw new System.NotImplementedException();
}
public override float GetBeatSnapDistanceAt(double referenceTime)
public override float GetBeatSnapDistanceAt(HitObject referenceObject)
{
throw new System.NotImplementedException();
}
public override float DurationToDistance(double referenceTime, double duration)
public override float DurationToDistance(HitObject referenceObject, double duration)
{
throw new System.NotImplementedException();
}
public override double DistanceToDuration(double referenceTime, float distance)
public override double DistanceToDuration(HitObject referenceObject, float distance)
{
throw new System.NotImplementedException();
}
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
{
throw new System.NotImplementedException();
}
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
{
throw new System.NotImplementedException();
}
@@ -0,0 +1,64 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Editor
{
public class TestSceneManiaComposeScreen : EditorClockTestScene
{
[Resolved]
private SkinManager skins { get; set; }
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup compose screen", () =>
{
var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
};
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(EditorBeatmap), editorBeatmap),
(typeof(IBeatSnapProvider), editorBeatmap),
},
Child = new ComposeScreen { State = { Value = Visibility.Visible } },
};
});
AddUntilStep("wait for composer", () => this.ChildrenOfType<HitObjectComposer>().SingleOrDefault()?.IsLoaded == true);
}
[Test]
public void TestDefaultSkin()
{
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = SkinInfo.Default);
}
[Test]
public void TestLegacySkin()
{
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info);
}
}
}
@@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Mania.Tests
public int CompareTo(ConvertValue other)
{
var result = StartTime.CompareTo(other.StartTime);
int result = StartTime.CompareTo(other.StartTime);
if (result != 0)
return result;
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private IList<string> getSampleNames(IList<HitSampleInfo> hitSampleInfo)
=> hitSampleInfo.Select(sample => sample.LookupNames.First()).ToList();
private IList<IList<string>> getNodeSampleNames(List<IList<HitSampleInfo>> hitSampleInfo)
private IList<IList<string>> getNodeSampleNames(IList<IList<HitSampleInfo>> hitSampleInfo)
=> hitSampleInfo?.Select(getSampleNames)
.ToList();
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private IEnumerable<ColumnType> getResults(StageDefinition definition)
{
for (var i = 0; i < definition.Columns; i++)
for (int i = 0; i < definition.Columns; i++)
yield return definition.GetTypeOfColumn(i);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(CreateHitObject().With(h =>
{
h.HitObject.StartTime = START_TIME;
h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(CreateHitObject().With(h =>
{
h.HitObject.StartTime = START_TIME;
h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange;
}));
})
@@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
/// </summary>
public abstract class ManiaSkinnableTestScene : SkinnableTestScene
{
protected const double START_TIME = 1000000000;
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
@@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm();
}
private class ZeroScrollAlgorithm : IScrollAlgorithm
{
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
=> double.MinValue;
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
=> scrollLength;
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
=> (float)((time - START_TIME) / timeRange) * scrollLength;
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
=> 0;
public void Reset()
{
}
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(5000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
}
}
}
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("Hold key", () =>
{
clock.CurrentTime = 0;
note.OnPressed(ManiaAction.Key1);
note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager().CurrentState, ManiaAction.Key1));
});
AddStep("progress time", () => clock.CurrentTime = 500);
AddAssert("head is visible", () => note.Head.Alpha == 1);
@@ -388,7 +388,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
};
beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
}
AddStep("load player", () =>
@@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
});
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
@@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Tests
{
@@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
[Resolved]
private RulesetConfigCache configCache { get; set; }
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>();
private Bindable<bool> configTimingBasedNoteColouring;
protected override void LoadComplete()
private ManualClock clock;
private DrawableManiaRuleset drawableRuleset;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup hierarchy", () => Child = new Container
{
Clock = new FramedClock(clock = new ManualClock()),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
}
});
AddStep("retrieve config bindable", () =>
{
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
configTimingBasedNoteColouring = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring);
});
}
[Test]
public void TestSimple()
{
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
}
[Test]
public void TestToggleOffScreen()
{
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
seekTo(10000);
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
seekTo(0);
AddAssert("all notes not coloured", () => this.ChildrenOfType<DrawableNote>().All(note => note.Colour == Colour4.White));
seekTo(10000);
AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
seekTo(0);
AddAssert("some notes coloured", () => this.ChildrenOfType<DrawableNote>().Any(note => note.Colour != Colour4.White));
}
private void seekTo(double time)
{
AddStep($"seek to {time}", () => clock.CurrentTime = time);
AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
}
private ManiaBeatmap createTestBeatmap()
{
const double beat_length = 500;
var ruleset = new ManiaRuleset();
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
{
HitObjects =
@@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
new Note { StartTime = beat_length }
},
ControlPointInfo = new ControlPointInfo(),
BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
BeatmapInfo = { Ruleset = Ruleset.Value },
};
foreach (var note in beatmap.HitObjects)
@@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
BeatLength = beat_length
});
Child = new Container
{
Clock = new FramedClock(new ManualClock()),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
ruleset.CreateDrawableRulesetWith(beatmap)
}
};
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
return beatmap;
}
}
}
@@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<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="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
@@ -42,8 +42,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
double roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize);
double roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty);
if (IsForCurrentRuleset)
{
@@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
originalTargetColumns = TargetColumns;
}
public static int GetColumnCountForNonConvert(BeatmapInfo beatmap)
public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo)
{
var roundedCircleSize = Math.Round(beatmap.BaseDifficulty.CircleSize);
double roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize);
}
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
IBeatmapDifficultyInfo difficulty = original.Difficulty;
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
Random = new FastRandom(seed);
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Debug.Assert(distanceData != null);
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
double beatLength;
#pragma warning disable 618
@@ -55,13 +55,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
#pragma warning restore 618
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
SpanCount = repeatsData?.SpanCount() ?? 1;
StartTime = (int)Math.Round(hitObject.StartTime);
// This matches stable's calculation.
EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier);
EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier);
SegmentDuration = (EndTime - StartTime) / SpanCount;
}
@@ -488,12 +488,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to retrieve node samples at.</param>
private List<IList<HitSampleInfo>> nodeSamplesAt(int time)
private IList<IList<HitSampleInfo>> nodeSamplesAt(int time)
{
if (!(HitObject is IHasPathWithRepeats curveData))
return null;
var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration;
int index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration;
// avoid slicing the list & creating copies, if at all possible.
return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
@@ -302,7 +302,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out var addToCentre);
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out bool addToCentre);
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
int nextColumn = GetRandomColumn(upperBound: columnLimit);
@@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (drainTime == 0)
drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
IBeatmapDifficultyInfo difficulty = OriginalBeatmap.Difficulty;
conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
@@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
lowerBound ??= RandomStart;
upperBound ??= TotalColumns;
nextColumn ??= (_ => GetRandomColumn(lowerBound, upperBound));
nextColumn ??= _ => GetRandomColumn(lowerBound, upperBound);
// Check for the initial column
if (isValid(initialColumn))
@@ -176,7 +176,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return initialColumn;
bool isValid(int column) => validation?.Invoke(column) != false && !patterns.Any(p => p.ColumnHasObject(column));
bool isValid(int column)
{
if (validation?.Invoke(column) == false)
return false;
foreach (var p in patterns)
{
if (p.ColumnHasObject(column))
return false;
}
return true;
}
}
/// <summary>
@@ -12,46 +12,68 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
/// </summary>
internal class Pattern
{
private readonly List<ManiaHitObject> hitObjects = new List<ManiaHitObject>();
private List<ManiaHitObject> hitObjects;
private HashSet<int> containedColumns;
/// <summary>
/// All the hit objects contained in this pattern.
/// </summary>
public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
public IEnumerable<ManiaHitObject> HitObjects => hitObjects ?? Enumerable.Empty<ManiaHitObject>();
/// <summary>
/// Check whether a column of this patterns contains a hit object.
/// </summary>
/// <param name="column">The column index.</param>
/// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns>
public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column);
public bool ColumnHasObject(int column) => containedColumns?.Contains(column) == true;
/// <summary>
/// Amount of columns taken up by hit objects in this pattern.
/// </summary>
public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count();
public int ColumnWithObjects => containedColumns?.Count ?? 0;
/// <summary>
/// Adds a hit object to this pattern.
/// </summary>
/// <param name="hitObject">The hit object to add.</param>
public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject);
public void Add(ManiaHitObject hitObject)
{
prepareStorage();
hitObjects.Add(hitObject);
containedColumns.Add(hitObject.Column);
}
/// <summary>
/// Copies hit object from another pattern to this one.
/// </summary>
/// <param name="other">The other pattern.</param>
public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects);
public void Add(Pattern other)
{
prepareStorage();
if (other.hitObjects != null)
{
hitObjects.AddRange(other.hitObjects);
foreach (var h in other.hitObjects)
containedColumns.Add(h.Column);
}
}
/// <summary>
/// Clears this pattern, removing all hit objects.
/// </summary>
public void Clear() => hitObjects.Clear();
public void Clear()
{
hitObjects?.Clear();
containedColumns?.Clear();
}
/// <summary>
/// Removes a hit object from this pattern.
/// </summary>
/// <param name="hitObject">The hit object to remove.</param>
public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject);
private void prepareStorage()
{
hitObjects ??= new List<ManiaHitObject>();
containedColumns ??= new HashSet<int>();
}
}
}
@@ -28,7 +28,12 @@ namespace osu.Game.Rulesets.Mania.Configuration
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime,
v => new SettingDescription(v, "Scroll Speed", $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / v)} ({v}ms)"))
scrollTime => new SettingDescription(
rawValue: scrollTime,
name: "Scroll Speed",
value: $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)} ({scrollTime}ms)"
)
)
};
}
@@ -41,15 +41,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
HitWindows hitWindows = new ManiaHitWindows();
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
return new ManiaDifficultyAttributes
{
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate),
ScoreMultiplier = getScoreMultiplier(beatmap, mods),
GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate),
ScoreMultiplier = getScoreMultiplier(mods),
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
Skills = skills
};
@@ -70,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
{
new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
new Strain(mods, ((ManiaBeatmap)Beatmap).TotalColumns)
};
protected override Mod[] DifficultyAdjustmentMods
@@ -138,7 +137,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods)
private double getScoreMultiplier(Mod[] mods)
{
double scoreMultiplier = 1;
@@ -154,7 +153,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
var maniaBeatmap = (ManiaBeatmap)beatmap;
var maniaBeatmap = (ManiaBeatmap)Beatmap;
int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns;
if (diff > 0)
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
public class Strain : StrainSkill
public class Strain : StrainDecaySkill
{
private const double individual_decay_base = 0.125;
private const double overall_decay_base = 0.30;
@@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
var endTime = maniaCurrent.EndTime;
var column = maniaCurrent.BaseObject.Column;
double endTime = maniaCurrent.EndTime;
int column = maniaCurrent.BaseObject.Column;
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
return individualStrain + overallStrain - CurrentStrain;
}
protected override double GetPeakStrain(double offset)
protected override double CalculateInitialStrain(double offset)
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
@@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Screens.Edit.Setup;
namespace osu.Game.Rulesets.Mania.Edit.Setup
{
public class ManiaSetupSection : RulesetSetupSection
{
private LabelledSwitchButton specialStyle;
public ManiaSetupSection()
: base(new ManiaRuleset().RulesetInfo)
{
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
specialStyle = new LabelledSwitchButton
{
Label = "Use special (N+1) style",
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 5k (4+1) or 8key (7+1) configurations.",
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
specialStyle.Current.BindValueChanged(_ => updateBeatmap());
}
private void updateBeatmap()
{
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
}
}
}
@@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Mania
{
private FilterCriteria.OptionalRange<float> keys;
public bool Matches(BeatmapInfo beatmap)
public bool Matches(BeatmapInfo beatmapInfo)
{
return !keys.HasFilter || (beatmap.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmap)));
return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
+5 -1
View File
@@ -27,11 +27,13 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Edit.Setup;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets.Mania
@@ -314,7 +316,7 @@ namespace osu.Game.Rulesets.Mania
case PlayfieldType.Dual:
{
var keys = getDualStageKeyCount(variant);
int keys = getDualStageKeyCount(variant);
return $"{keys}K + {keys}K";
}
}
@@ -390,6 +392,8 @@ namespace osu.Game.Rulesets.Mania
{
return new ManiaFilterCriteria();
}
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
}
public enum PlayfieldType

Some files were not shown because too many files have changed in this diff Show More