mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 08:39:54 +08:00
Compare commits
2637 Commits
@@ -14,8 +14,8 @@
|
||||
"jb"
|
||||
]
|
||||
},
|
||||
"smoogipoo.nvika": {
|
||||
"version": "1.0.1",
|
||||
"nvika": {
|
||||
"version": "2.2.0",
|
||||
"commands": [
|
||||
"nvika"
|
||||
]
|
||||
@@ -33,4 +33,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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 +1,2 @@
|
||||
github: ppy
|
||||
custom: https://osu.ppy.sh/home/support
|
||||
|
||||
@@ -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)
|
||||
-->
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ updates:
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "17:00"
|
||||
open-pull-requests-limit: 99
|
||||
open-pull-requests-limit: 0 # disabled until https://github.com/dependabot/dependabot-core/issues/369 is resolved.
|
||||
ignore:
|
||||
- dependency-name: Microsoft.EntityFrameworkCore.Design
|
||||
versions:
|
||||
|
||||
@@ -50,6 +50,51 @@ 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: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Pin Xamarin.Android version to 11.2 for now to avoid build failures caused by a Xamarin-side regression.
|
||||
# See: https://github.com/xamarin/xamarin-android/issues/6284
|
||||
# This can be removed/reverted when the fix makes it to upstream and is deployed on github runners.
|
||||
- name: Set default Xamarin SDK version
|
||||
run: |
|
||||
$VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2
|
||||
|
||||
- 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.Android/osu.Android.csproj /restore /p:Configuration=Debug
|
||||
|
||||
build-only-ios:
|
||||
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 +124,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)
|
||||
|
||||
@@ -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
|
||||
@@ -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,163 +0,0 @@
|
||||
# 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: Diffcalc Consistency Checks
|
||||
on:
|
||||
issue_comment:
|
||||
types: [ created ]
|
||||
|
||||
env:
|
||||
DB_USER: root
|
||||
DB_HOST: 127.0.0.1
|
||||
CONCURRENCY: 4
|
||||
ALLOW_DOWNLOAD: 1
|
||||
SAVE_DOWNLOADED: 1
|
||||
|
||||
jobs:
|
||||
diffcalc:
|
||||
name: Diffcalc
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
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')
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruleset:
|
||||
- { name: osu, id: 0 }
|
||||
- { name: taiko, id: 1 }
|
||||
- { name: catch, id: 2 }
|
||||
- { name: mania, id: 3 }
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Verify ruleset
|
||||
if: contains(github.event.comment.body, matrix.ruleset.id) == false
|
||||
run: |
|
||||
echo "${{ github.event.comment.body }} doesn't contain ${{ matrix.ruleset.id }}"
|
||||
exit 1
|
||||
|
||||
- name: Verify MySQL connection from host
|
||||
run: |
|
||||
sudo apt-get install -y mysql-client
|
||||
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "SHOW DATABASES"
|
||||
|
||||
- name: Create directory structure
|
||||
run: |
|
||||
mkdir -p $GITHUB_WORKSPACE/master/
|
||||
mkdir -p $GITHUB_WORKSPACE/pr/
|
||||
|
||||
# Checkout osu
|
||||
- name: Checkout osu (master)
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ppy/osu
|
||||
path: 'master/osu'
|
||||
- name: Checkout osu (pr)
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'pr/osu'
|
||||
|
||||
# Checkout osu-difficulty-calculator
|
||||
- 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
|
||||
|
||||
# Initial data imports
|
||||
- name: Download + import data
|
||||
run: |
|
||||
PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top | 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
|
||||
|
||||
wget https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2
|
||||
wget https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2
|
||||
tar -xf $PERFORMANCE_DATA_NAME.tar.bz2
|
||||
tar -xf $BEATMAPS_DATA_NAME.tar.bz2
|
||||
|
||||
cd $GITHUB_WORKSPACE/$PERFORMANCE_DATA_NAME
|
||||
|
||||
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_master"
|
||||
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_pr"
|
||||
|
||||
cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_master
|
||||
cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_pr
|
||||
|
||||
# Run diffcalc
|
||||
- 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 }}
|
||||
|
||||
# Print diffs
|
||||
- name: Print diffs
|
||||
run: |
|
||||
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "
|
||||
SELECT
|
||||
m.beatmap_id,
|
||||
m.mods,
|
||||
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
|
||||
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
|
||||
Generated
+1
@@ -0,0 +1 @@
|
||||
osu.iOS
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
|
||||
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ContentModelUserStore">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
|
||||
@@ -8,4 +8,8 @@ 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)
|
||||
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
|
||||
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
|
||||
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.
|
||||
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
|
||||
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.
|
||||
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead.
|
||||
|
||||
@@ -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>
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.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 Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+2
-2
@@ -13,14 +13,14 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
||||
{
|
||||
public class EmptyFreeformDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public EmptyFreeformDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public EmptyFreeformDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
return new DifficultyAttributes(mods, skills, 0);
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
+2
-2
@@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
|
||||
new EmptyFreeformBeatmapConverter(beatmap, this);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
|
||||
new EmptyFreeformDifficultyCalculator(this, beatmap);
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) =>
|
||||
new EmptyFreeformDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.EmptyFreeform.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
||||
{
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
User = new User { Username = "sample" },
|
||||
User = new APIUser { Username = "sample" },
|
||||
},
|
||||
Replay = new EmptyFreeformAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.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 Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Pippidon.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Pippidon.Mods
|
||||
{
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
User = new User { Username = "sample" },
|
||||
User = new APIUser { Username = "sample" },
|
||||
},
|
||||
Replay = new PippidonAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
|
||||
+2
-2
@@ -13,14 +13,14 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
{
|
||||
public class PippidonDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
return new DifficultyAttributes(mods, skills, 0);
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
@@ -26,8 +26,8 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
|
||||
new PippidonBeatmapConverter(beatmap, this);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
|
||||
new PippidonDifficultyCalculator(this, beatmap);
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) =>
|
||||
new PippidonDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.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 Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+2
-2
@@ -13,14 +13,14 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
||||
{
|
||||
public class EmptyScrollingDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public EmptyScrollingDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public EmptyScrollingDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
return new DifficultyAttributes(mods, skills, 0);
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new EmptyScrollingBeatmapConverter(beatmap, this);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new EmptyScrollingDifficultyCalculator(this, beatmap);
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new EmptyScrollingDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.EmptyScrolling.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
||||
{
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
User = new User { Username = "sample" },
|
||||
User = new APIUser { Username = "sample" },
|
||||
},
|
||||
Replay = new EmptyScrollingAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.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 Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Pippidon.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Pippidon.Mods
|
||||
{
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
User = new User { Username = "sample" },
|
||||
User = new APIUser { Username = "sample" },
|
||||
},
|
||||
Replay = new PippidonAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
|
||||
+2
-2
@@ -13,14 +13,14 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
{
|
||||
public class PippidonDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
return new DifficultyAttributes(mods, skills, 0);
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PippidonBeatmapConverter(beatmap, this);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new PippidonDifficultyCalculator(this, beatmap);
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new PippidonDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
|
||||
+3
-3
@@ -51,11 +51,11 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.913.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.916.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1203.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.5.0" />
|
||||
<PackageReference Include="Realm" Version="10.7.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -20,6 +20,7 @@ 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", 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")]
|
||||
|
||||
@@ -11,10 +11,10 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
using LogLevel = osu.Framework.Logging.LogLevel;
|
||||
using User = osu.Game.Users.User;
|
||||
|
||||
namespace osu.Desktop
|
||||
{
|
||||
@@ -27,7 +27,7 @@ namespace osu.Desktop
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
private IBindable<User> user;
|
||||
private IBindable<APIUser> user;
|
||||
|
||||
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
||||
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
@@ -108,7 +108,10 @@ namespace osu.Desktop
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
||||
|
||||
// update ruleset
|
||||
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
|
||||
int onlineID = ruleset.Value.OnlineID;
|
||||
bool isLegacyRuleset = onlineID >= 0 && onlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID;
|
||||
|
||||
presence.Assets.SmallImageKey = isLegacyRuleset ? $"mode_{onlineID}" : "mode_custom";
|
||||
presence.Assets.SmallImageText = ruleset.Value.Name;
|
||||
|
||||
client.SetPresence(presence);
|
||||
@@ -140,10 +143,10 @@ namespace osu.Desktop
|
||||
switch (activity)
|
||||
{
|
||||
case UserActivity.InGame game:
|
||||
return game.Beatmap.ToString();
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Desktop.LegacyIpc
|
||||
{
|
||||
/// <summary>
|
||||
/// A difficulty calculation request from the legacy client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Synchronise any changes with osu!stable.
|
||||
/// </remarks>
|
||||
public class LegacyIpcDifficultyCalculationRequest
|
||||
{
|
||||
public string BeatmapFile { get; set; }
|
||||
public int RulesetId { get; set; }
|
||||
public int Mods { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.Desktop.LegacyIpc
|
||||
{
|
||||
/// <summary>
|
||||
/// A difficulty calculation response returned to the legacy client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Synchronise any changes with osu!stable.
|
||||
/// </remarks>
|
||||
public class LegacyIpcDifficultyCalculationResponse
|
||||
{
|
||||
public double StarRating { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.Platform;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace osu.Desktop.LegacyIpc
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IpcMessage"/> that can be used to communicate to and from legacy clients.
|
||||
/// <para>
|
||||
/// In order to deserialise types at either end, types must be serialised as their <see cref="System.Type.AssemblyQualifiedName"/>,
|
||||
/// however this cannot be done since osu!stable and osu!lazer live in two different assemblies.
|
||||
/// <br />
|
||||
/// To get around this, this class exists which serialises a payload (<see cref="LegacyIpcMessage.Data"/>) as an <see cref="System.Object"/> type,
|
||||
/// which can be deserialised at either end because it is part of the core library (mscorlib / System.Private.CorLib).
|
||||
/// The payload contains the data to be sent over the IPC channel.
|
||||
/// <br />
|
||||
/// At either end, Json.NET deserialises the payload into a <see cref="JObject"/> which is manually converted back into the expected <see cref="LegacyIpcMessage.Data"/> type,
|
||||
/// which then further contains another <see cref="JObject"/> representing the data sent over the IPC channel whose type can likewise be lazily matched through
|
||||
/// <see cref="LegacyIpcMessage.Data.MessageType"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Synchronise any changes with osu-stable.
|
||||
/// </remarks>
|
||||
public class LegacyIpcMessage : IpcMessage
|
||||
{
|
||||
public LegacyIpcMessage()
|
||||
{
|
||||
// Types/assemblies are not inter-compatible, so always serialise/deserialise into objects.
|
||||
base.Type = typeof(object).FullName;
|
||||
}
|
||||
|
||||
public new string Type => base.Type; // Hide setter.
|
||||
|
||||
public new object Value
|
||||
{
|
||||
get => base.Value;
|
||||
set => base.Value = new Data
|
||||
{
|
||||
MessageType = value.GetType().Name,
|
||||
MessageData = value
|
||||
};
|
||||
}
|
||||
|
||||
public class Data
|
||||
{
|
||||
public string MessageType { get; set; }
|
||||
public object MessageData { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// 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 Newtonsoft.Json.Linq;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Desktop.LegacyIpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides IPC to legacy osu! clients.
|
||||
/// </summary>
|
||||
public class LegacyTcpIpcProvider : TcpIpcProvider
|
||||
{
|
||||
private static readonly Logger logger = Logger.GetLogger("legacy-ipc");
|
||||
|
||||
public LegacyTcpIpcProvider()
|
||||
: base(45357)
|
||||
{
|
||||
MessageReceived += msg =>
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Add("Processing legacy IPC message...");
|
||||
logger.Add($" {msg.Value}", LogLevel.Debug);
|
||||
|
||||
// See explanation in LegacyIpcMessage for why this is done this way.
|
||||
var legacyData = ((JObject)msg.Value).ToObject<LegacyIpcMessage.Data>();
|
||||
object value = parseObject((JObject)legacyData!.MessageData, legacyData.MessageType);
|
||||
|
||||
return new LegacyIpcMessage
|
||||
{
|
||||
Value = onLegacyIpcMessageReceived(value)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Add($"Processing IPC message failed: {msg.Value}", exception: ex);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private object parseObject(JObject value, string type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case nameof(LegacyIpcDifficultyCalculationRequest):
|
||||
return value.ToObject<LegacyIpcDifficultyCalculationRequest>()
|
||||
?? throw new InvalidOperationException($"Failed to parse request {value}");
|
||||
|
||||
case nameof(LegacyIpcDifficultyCalculationResponse):
|
||||
return value.ToObject<LegacyIpcDifficultyCalculationResponse>()
|
||||
?? throw new InvalidOperationException($"Failed to parse request {value}");
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported object type {type}");
|
||||
}
|
||||
}
|
||||
|
||||
private object onLegacyIpcMessageReceived(object message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case LegacyIpcDifficultyCalculationRequest req:
|
||||
try
|
||||
{
|
||||
var ruleset = getLegacyRulesetFromID(req.RulesetId);
|
||||
|
||||
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
|
||||
WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset);
|
||||
|
||||
return new LegacyIpcDifficultyCalculationResponse
|
||||
{
|
||||
StarRating = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods).StarRating
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new LegacyIpcDifficultyCalculationResponse();
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported message type {message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static Ruleset getLegacyRulesetFromID(int rulesetId)
|
||||
{
|
||||
switch (rulesetId)
|
||||
{
|
||||
case 0:
|
||||
return new OsuRuleset();
|
||||
|
||||
case 1:
|
||||
return new TaikoRuleset();
|
||||
|
||||
case 2:
|
||||
return new CatchRuleset();
|
||||
|
||||
case 3:
|
||||
return new ManiaRuleset();
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Invalid ruleset id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,11 @@ namespace osu.Desktop
|
||||
|
||||
protected override UpdateManager CreateUpdateManager()
|
||||
{
|
||||
string packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
|
||||
|
||||
if (!string.IsNullOrEmpty(packageManaged))
|
||||
return new NoActionUpdateManager();
|
||||
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
@@ -156,7 +161,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 +182,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);
|
||||
|
||||
+30
-11
@@ -5,6 +5,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Desktop.LegacyIpc;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Logging;
|
||||
@@ -18,21 +19,23 @@ namespace osu.Desktop
|
||||
{
|
||||
private const string base_game_name = @"osu";
|
||||
|
||||
private static LegacyTcpIpcProvider legacyIpc;
|
||||
|
||||
[STAThread]
|
||||
public static int Main(string[] args)
|
||||
public static void 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,27 +65,43 @@ 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))
|
||||
throw new TimeoutException(@"IPC took too long to send");
|
||||
}
|
||||
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// we want to allow multiple instances to be started when in debug.
|
||||
if (!DebugUtils.IsDebugBuild)
|
||||
return 0;
|
||||
{
|
||||
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (host.IsPrimaryInstance)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Log("Starting legacy IPC provider...");
|
||||
legacyIpc = new LegacyTcpIpcProvider();
|
||||
legacyIpc.Bind();
|
||||
legacyIpc.StartAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Failed to start legacy IPC provider");
|
||||
}
|
||||
}
|
||||
|
||||
if (tournamentClient)
|
||||
host.Run(new TournamentGame());
|
||||
else
|
||||
host.Run(new OsuGameDesktop(args));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,10 @@ namespace osu.Desktop.Updater
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the case of an error, a separate notification will be displayed.
|
||||
notification.State = ProgressNotificationState.Cancelled;
|
||||
notification.Close();
|
||||
|
||||
Logger.Error(e, @"update failed!");
|
||||
}
|
||||
}
|
||||
@@ -183,6 +186,19 @@ namespace osu.Desktop.Updater
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
// cancelling updates is not currently supported by the underlying updater.
|
||||
// only allow dismissing for now.
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case ProgressNotificationState.Cancelled:
|
||||
base.Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SquirrelLogger : Splat.ILogger, IDisposable
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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 osu.Framework.iOS;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
@@ -9,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, "GameUIApplication", "AppDelegate");
|
||||
UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,15 @@ 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());
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset().RulesetInfo, beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Edit.Checks;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCheckBananaShowerGap
|
||||
{
|
||||
private CheckBananaShowerGap check;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckBananaShowerGap();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllowedSpinnerGaps()
|
||||
{
|
||||
assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Easy);
|
||||
assertOk(mockBeatmap(250, 1000, 1250), DifficultyRating.Normal);
|
||||
assertOk(mockBeatmap(125, 1000, 1250), DifficultyRating.Hard);
|
||||
assertOk(mockBeatmap(125, 1000, 1125), DifficultyRating.Insane);
|
||||
assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.Expert);
|
||||
assertOk(mockBeatmap(62, 1000, 1125), DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisallowedSpinnerGapStart()
|
||||
{
|
||||
assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Easy);
|
||||
assertTooShortSpinnerStart(mockBeatmap(249, 1000, 1250), DifficultyRating.Normal);
|
||||
assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Hard);
|
||||
assertTooShortSpinnerStart(mockBeatmap(124, 1000, 1250), DifficultyRating.Insane);
|
||||
assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.Expert);
|
||||
assertTooShortSpinnerStart(mockBeatmap(61, 1000, 1250), DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisallowedSpinnerGapEnd()
|
||||
{
|
||||
assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Easy);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(250, 1000, 1249), DifficultyRating.Normal);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1249), DifficultyRating.Hard);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(125, 1000, 1124), DifficultyRating.Insane);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.Expert);
|
||||
assertTooShortSpinnerEnd(mockBeatmap(62, 1000, 1124), DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConsecutiveSpinners()
|
||||
{
|
||||
var spinnerConsecutiveBeatmap = new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new BananaShower { StartTime = 0, EndTime = 100, X = 0 },
|
||||
new BananaShower { StartTime = 101, EndTime = 200, X = 0 },
|
||||
new BananaShower { StartTime = 201, EndTime = 300, X = 0 }
|
||||
}
|
||||
};
|
||||
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Easy);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Normal);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Hard);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Insane);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.Expert);
|
||||
assertOk(spinnerConsecutiveBeatmap, DifficultyRating.ExpertPlus);
|
||||
}
|
||||
|
||||
private Beatmap<HitObject> mockBeatmap(double bananaShowerStart, double bananaShowerEnd, double nextFruitStart)
|
||||
{
|
||||
return new Beatmap<HitObject>
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit { StartTime = 0, X = 0 },
|
||||
new BananaShower { StartTime = bananaShowerStart, EndTime = bananaShowerEnd, X = 0 },
|
||||
new Fruit { StartTime = nextFruitStart, X = 0 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void assertOk(IBeatmap beatmap, DifficultyRating difficultyRating)
|
||||
{
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
private void assertTooShortSpinnerStart(IBeatmap beatmap, DifficultyRating difficultyRating)
|
||||
{
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerStartGap));
|
||||
}
|
||||
|
||||
private void assertTooShortSpinnerEnd(IBeatmap beatmap, DifficultyRating difficultyRating)
|
||||
{
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.All(issue => issue.Template is CheckBananaShowerGap.IssueTemplateBananaShowerEndGap));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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", () =>
|
||||
@@ -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));
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
public class TestSceneCatchModNoScope : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestVisibleDuringBreak()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModNoScope
|
||||
{
|
||||
HiddenComboCount = { Value = 0 },
|
||||
},
|
||||
Autoplay = true,
|
||||
PassCondition = () => true,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 1000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0));
|
||||
AddUntilStep("wait for start of break", isBreak);
|
||||
AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1));
|
||||
AddUntilStep("wait for end of break", () => !isBreak());
|
||||
AddUntilStep("wait for catcher to hide", () => catcherAlphaAlmostEquals(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibleAfterComboBreak()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModNoScope
|
||||
{
|
||||
HiddenComboCount = { Value = 2 },
|
||||
},
|
||||
Autoplay = true,
|
||||
PassCondition = () => true,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit
|
||||
{
|
||||
X = 0,
|
||||
StartTime = 1000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 3000,
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.WIDTH,
|
||||
StartTime = 5000,
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("catcher must start visible", () => catcherAlphaAlmostEquals(1));
|
||||
AddUntilStep("wait for combo", () => Player.ScoreProcessor.Combo.Value >= 2);
|
||||
AddAssert("catcher must dim after combo", () => !catcherAlphaAlmostEquals(1));
|
||||
AddStep("break combo", () => Player.ScoreProcessor.Combo.Value = 0);
|
||||
AddUntilStep("wait for catcher to show", () => catcherAlphaAlmostEquals(1));
|
||||
}
|
||||
|
||||
private bool isBreak() => Player.IsBreakTime.Value;
|
||||
|
||||
private bool catcherAlphaAlmostEquals(float alpha)
|
||||
{
|
||||
var playfield = (CatchPlayfield)Player.DrawableRuleset.Playfield;
|
||||
return Precision.AlmostEquals(playfield.CatcherArea.Alpha, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
@@ -168,6 +168,28 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
checkHyperDash(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastBananaShouldClearPlateOnMiss()
|
||||
{
|
||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||
checkPlate(1);
|
||||
AddStep("miss banana", () => attemptCatch(new Banana { X = 100 }));
|
||||
checkPlate(1);
|
||||
AddStep("miss last banana", () => attemptCatch(new Banana { LastInCombo = true, X = 100 }));
|
||||
checkPlate(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastBananaShouldClearPlateOnCatch()
|
||||
{
|
||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||
checkPlate(1);
|
||||
AddStep("catch banana", () => attemptCatch(new Banana()));
|
||||
checkPlate(2);
|
||||
AddStep("catch last banana", () => attemptCatch(new Banana { LastInCombo = true }));
|
||||
checkPlate(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCatcherRandomStacking()
|
||||
{
|
||||
@@ -237,7 +259,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 +312,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 +320,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);
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||
IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject> { new Fruit() },
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
@@ -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,9 +2,9 @@
|
||||
<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.11.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 Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -133,6 +133,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||
new CatchModFloatingFruits(),
|
||||
new CatchModMuted(),
|
||||
new CatchModNoScope(),
|
||||
};
|
||||
|
||||
default:
|
||||
@@ -177,7 +178,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
return base.GetDisplayNameForHitResult(result);
|
||||
}
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new CatchDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new CatchLegacySkinTransformer(skin);
|
||||
|
||||
@@ -188,5 +189,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
[JsonProperty("approach_rate")]
|
||||
public double ApproachRate { get; set; }
|
||||
|
||||
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
|
||||
{
|
||||
foreach (var v in base.ToDatabaseAttributes())
|
||||
yield return v;
|
||||
|
||||
// Todo: osu!catch should not output star rating in the 'aim' attribute.
|
||||
yield return (ATTRIB_ID_AIM, StarRating);
|
||||
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
|
||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||
}
|
||||
|
||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
|
||||
{
|
||||
base.FromDatabaseAttributes(values);
|
||||
|
||||
StarRating = values[ATTRIB_ID_AIM];
|
||||
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
|
||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
private float halfCatcherWidth;
|
||||
|
||||
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
@@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
return new CatchDifficultyAttributes { Mods = mods };
|
||||
|
||||
// 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
|
||||
{
|
||||
@@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
Mods = mods,
|
||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,7 +51,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 +68,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;
|
||||
|
||||
@@ -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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Catch.Edit.Checks;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public class CatchBeatmapVerifier : IBeatmapVerifier
|
||||
{
|
||||
private readonly List<ICheck> checks = new List<ICheck>
|
||||
{
|
||||
new CheckBananaShowerGap()
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
return checks.SelectMany(check => check.Run(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Checks
|
||||
{
|
||||
/// <summary>
|
||||
/// Check the spinner/banana shower gaps specified in the osu!catch difficulty specific ranking criteria.
|
||||
/// </summary>
|
||||
public class CheckBananaShowerGap : ICheck
|
||||
{
|
||||
private static readonly Dictionary<DifficultyRating, (int startGap, int endGap)> spinner_delta_threshold = new Dictionary<DifficultyRating, (int, int)>
|
||||
{
|
||||
[DifficultyRating.Easy] = (250, 250),
|
||||
[DifficultyRating.Normal] = (250, 250),
|
||||
[DifficultyRating.Hard] = (125, 250),
|
||||
[DifficultyRating.Insane] = (125, 125),
|
||||
[DifficultyRating.Expert] = (62, 125),
|
||||
[DifficultyRating.ExpertPlus] = (62, 125)
|
||||
};
|
||||
|
||||
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Too short spinner gap");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateBananaShowerStartGap(this),
|
||||
new IssueTemplateBananaShowerEndGap(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
var hitObjects = context.Beatmap.HitObjects;
|
||||
(int expectedStartDelta, int expectedEndDelta) = spinner_delta_threshold[context.InterpretedDifficulty];
|
||||
|
||||
for (int i = 0; i < hitObjects.Count - 1; ++i)
|
||||
{
|
||||
if (!(hitObjects[i] is BananaShower bananaShower))
|
||||
continue;
|
||||
|
||||
// Skip if the previous hitobject is a banana shower, consecutive spinners are allowed
|
||||
if (i != 0 && hitObjects[i - 1] is CatchHitObject previousHitObject && !(previousHitObject is BananaShower))
|
||||
{
|
||||
double spinnerStartDelta = bananaShower.StartTime - previousHitObject.GetEndTime();
|
||||
|
||||
if (spinnerStartDelta < expectedStartDelta)
|
||||
{
|
||||
yield return new IssueTemplateBananaShowerStartGap(this)
|
||||
.Create(spinnerStartDelta, expectedStartDelta, bananaShower, previousHitObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if the next hitobject is a banana shower, consecutive spinners are allowed
|
||||
if (hitObjects[i + 1] is CatchHitObject nextHitObject && !(nextHitObject is BananaShower))
|
||||
{
|
||||
double spinnerEndDelta = nextHitObject.StartTime - bananaShower.EndTime;
|
||||
|
||||
if (spinnerEndDelta < expectedEndDelta)
|
||||
{
|
||||
yield return new IssueTemplateBananaShowerEndGap(this)
|
||||
.Create(spinnerEndDelta, expectedEndDelta, bananaShower, nextHitObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class IssueTemplateBananaShowerGap : IssueTemplate
|
||||
{
|
||||
protected IssueTemplateBananaShowerGap(ICheck check, IssueType issueType, string unformattedMessage)
|
||||
: base(check, issueType, unformattedMessage)
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(double deltaTime, int expectedDeltaTime, params HitObject[] hitObjects)
|
||||
{
|
||||
return new Issue(hitObjects, this, Math.Floor(deltaTime), expectedDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateBananaShowerStartGap : IssueTemplateBananaShowerGap
|
||||
{
|
||||
public IssueTemplateBananaShowerStartGap(ICheck check)
|
||||
: base(check, IssueType.Problem, "There is only {0} ms between the start of the spinner and the last object, it should not be less than {1} ms.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateBananaShowerEndGap : IssueTemplateBananaShowerGap
|
||||
{
|
||||
public IssueTemplateBananaShowerEndGap(ICheck check)
|
||||
: base(check, IssueType.Problem, "There is only {0} ms between the end of the spinner and the next object, it should not be less than {1} ms.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
@@ -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 APIUser { Username = "osu!salad" } },
|
||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
@@ -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 APIUser { 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);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModNoScope : ModNoScope, IUpdatableByPlayfield
|
||||
{
|
||||
public override string Description => "Where's the catcher?";
|
||||
|
||||
[SettingSource(
|
||||
"Hidden at combo",
|
||||
"The combo count at which the catcher becomes completely hidden",
|
||||
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
||||
)]
|
||||
public override BindableInt HiddenComboCount { get; } = new BindableInt
|
||||
{
|
||||
Default = 10,
|
||||
Value = 10,
|
||||
MinValue = 0,
|
||||
MaxValue = 50,
|
||||
};
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
var catchPlayfield = (CatchPlayfield)playfield;
|
||||
bool shouldAlwaysShowCatcher = IsBreakTime.Value;
|
||||
float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha;
|
||||
catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -205,6 +210,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
catchResult.CatcherAnimationState = CurrentState;
|
||||
catchResult.CatcherHyperDash = HyperDashing;
|
||||
|
||||
// Ignore JuiceStreams and BananaShowers
|
||||
if (!(drawableObject is DrawablePalpableCatchHitObject palpableObject)) return;
|
||||
|
||||
var hitObject = palpableObject.HitObject;
|
||||
@@ -226,11 +232,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();
|
||||
@@ -239,6 +245,14 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle;
|
||||
else if (!(hitObject is Banana))
|
||||
CurrentState = CatcherAnimationState.Fail;
|
||||
|
||||
if (palpableObject.HitObject.LastInCombo)
|
||||
{
|
||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||
Explode();
|
||||
else
|
||||
Drop();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||
@@ -266,7 +280,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)
|
||||
{
|
||||
|
||||
@@ -6,11 +6,9 @@ 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;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
@@ -72,18 +70,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
||||
{
|
||||
Catcher.OnNewResult(hitObject, result);
|
||||
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
|
||||
if (hitObject.HitObject.LastInCombo)
|
||||
{
|
||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||
Catcher.Explode();
|
||||
else
|
||||
Catcher.Drop();
|
||||
}
|
||||
|
||||
comboDisplay.OnNewResult(hitObject, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 osu.Framework.iOS;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
@@ -9,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, "GameUIApplication", "AppDelegate");
|
||||
UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
[Cached]
|
||||
private EditorClipboard clipboard = new EditorClipboard();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new ManiaModDoubleTime());
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<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.11.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 Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
|
||||
@@ -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)"
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
[JsonProperty("great_hit_window")]
|
||||
public double GreatHitWindow { get; set; }
|
||||
|
||||
[JsonProperty("score_multiplier")]
|
||||
public double ScoreMultiplier { get; set; }
|
||||
|
||||
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
|
||||
{
|
||||
foreach (var v in base.ToDatabaseAttributes())
|
||||
yield return v;
|
||||
|
||||
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
|
||||
yield return (ATTRIB_ID_STRAIN, StarRating);
|
||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
||||
}
|
||||
|
||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values)
|
||||
{
|
||||
base.FromDatabaseAttributes(values);
|
||||
|
||||
StarRating = values[ATTRIB_ID_STRAIN];
|
||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user