diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 1f937e1837..3cecb0d07c 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -15,13 +15,13 @@
]
},
"codefilesanity": {
- "version": "0.0.36",
+ "version": "0.0.37",
"commands": [
"CodeFileSanity"
]
},
"ppy.localisationanalyser.tools": {
- "version": "2022.809.0",
+ "version": "2023.712.0",
"commands": [
"localisation"
]
diff --git a/.editorconfig b/.editorconfig
index 67c47000d3..c249e5e9b3 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,6 +9,9 @@ indent_style = space
indent_size = 2
trim_trailing_whitespace = true
+[g_*.cs]
+generated_code = true
+
[*.cs]
end_of_line = crlf
insert_final_newline = true
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index b85862270b..d35d4be412 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -6,3 +6,5 @@
212d78865a6b5f091173a347bad5686834d1d5fe
# Add partial specs in mobile projects too
00c11b2b4e389e48f3995d63484a6bc66a7afbdb
+# Mass NRT enabling
+0ab0c52ad577b3e7b406d09fa6056a56ff997c3e
diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml
index 2c6ec17e18..e7c628e365 100644
--- a/.github/workflows/diffcalc.yml
+++ b/.github/workflows/diffcalc.yml
@@ -1,206 +1,380 @@
-# 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.
+# ## Description
#
+# Uses [diffcalc-sheet-generator](https://github.com/smoogipoo/diffcalc-sheet-generator) to run two builds of osu and generate an SR/PP/Score comparison spreadsheet.
+#
+# ## Requirements
+#
+# Self-hosted runner with installed:
+# - `docker >= 20.10.16`
+# - `docker-compose >= 2.5.1`
+# - `lbzip2`
+# - `jq`
+#
+# ## Usage
+#
+# The workflow can be run in two ways:
+# 1. Via workflow dispatch.
+# 2. By an owner of the repository posting a pull request or issue comment containing `!diffcalc`.
+# For pull requests, the workflow will assume the pull request as the target to compare against (i.e. the `OSU_B` variable).
+# Any lines in the comment of the form `KEY=VALUE` are treated as variables for the generator.
+#
+# ## Google Service Account
+#
+# Spreadsheets are uploaded to a Google Service Account, and exposed with read-only permissions to the wider audience.
+#
+# 1. Create a project at https://console.cloud.google.com
+# 2. Enable the `Google Sheets` and `Google Drive` APIs.
+# 3. Create a Service Account
+# 4. Generate a key in the JSON format.
+# 5. Encode the key as base64 and store as an **actions secret** with name **`DIFFCALC_GOOGLE_CREDENTIALS`**
+#
+# ## Environment variables
+#
+# The default environment may be configured via **actions variables**.
+#
+# Refer to [the sample environment](https://github.com/smoogipoo/diffcalc-sheet-generator/blob/master/.env.sample), and prefix each variable with `DIFFCALC_` (e.g. `DIFFCALC_THREADS`, `DIFFCALC_INNODB_BUFFER_SIZE`, etc...).
+
+name: Run difficulty calculation comparison
+
+run-name: "${{ github.event_name == 'workflow_dispatch' && format('Manual run: {0}', inputs.osu-b) || 'Automatic comment trigger' }}"
-name: Difficulty Calculation
on:
issue_comment:
types: [ created ]
+ workflow_dispatch:
+ inputs:
+ osu-b:
+ description: "The target build of ppy/osu"
+ type: string
+ required: true
+ ruleset:
+ description: "The ruleset to process"
+ type: choice
+ required: true
+ options:
+ - osu
+ - taiko
+ - catch
+ - mania
+ converts:
+ description: "Include converted beatmaps"
+ type: boolean
+ required: false
+ default: true
+ ranked-only:
+ description: "Only ranked beatmaps"
+ type: boolean
+ required: false
+ default: true
+ generators:
+ description: "Comma-separated list of generators (available: [sr, pp, score])"
+ type: string
+ required: false
+ default: 'pp,sr'
+ osu-a:
+ description: "The source build of ppy/osu"
+ type: string
+ required: false
+ default: 'latest'
+ difficulty-calculator-a:
+ description: "The source build of ppy/osu-difficulty-calculator"
+ type: string
+ required: false
+ default: 'latest'
+ difficulty-calculator-b:
+ description: "The target build of ppy/osu-difficulty-calculator"
+ type: string
+ required: false
+ default: 'latest'
+ score-processor-a:
+ description: "The source build of ppy/osu-queue-score-statistics"
+ type: string
+ required: false
+ default: 'latest'
+ score-processor-b:
+ description: "The target build of ppy/osu-queue-score-statistics"
+ type: string
+ required: false
+ default: 'latest'
+
+permissions:
+ pull-requests: write
env:
- CONCURRENCY: 4
- ALLOW_DOWNLOAD: 1
- SAVE_DOWNLOADED: 1
- SKIP_INSERT_ATTRIBUTES: 1
+ EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
jobs:
- metadata:
- name: Check for requests
+ check-permissions:
+ name: Check permissions
+ runs-on: ubuntu-latest
+ if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }}
+ steps:
+ - name: Check permissions
+ if: ${{ github.event_name != 'workflow_dispatch' }}
+ uses: actions-cool/check-user-permission@a0668c9aec87f3875fc56170b6452a453e9dd819 # v2.2.0
+ with:
+ require: 'write'
+
+ create-comment:
+ name: Create PR comment
+ needs: check-permissions
+ runs-on: ubuntu-latest
+ if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
+ steps:
+ - name: Create comment
+ uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
+ with:
+ comment_tag: ${{ env.EXECUTION_ID }}
+ message: |
+ Difficulty calculation queued -- please wait! (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
+
+ *This comment will update on completion*
+
+ directory:
+ name: Prepare directory
+ needs: check-permissions
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 }}
+ GENERATOR_DIR: ${{ steps.set-outputs.outputs.GENERATOR_DIR }}
+ GENERATOR_ENV: ${{ steps.set-outputs.outputs.GENERATOR_ENV }}
+ GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
steps:
- - name: Construct build matrix
- id: generate-matrix
+ - name: Checkout diffcalc-sheet-generator
+ uses: actions/checkout@v3
+ with:
+ path: ${{ env.EXECUTION_ID }}
+ repository: 'smoogipoo/diffcalc-sheet-generator'
+
+ - name: Set outputs
+ id: set-outputs
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
+ echo "GENERATOR_DIR=${{ github.workspace }}/${{ env.EXECUTION_ID }}" >> "${GITHUB_OUTPUT}"
+ echo "GENERATOR_ENV=${{ github.workspace }}/${{ env.EXECUTION_ID }}/.env" >> "${GITHUB_OUTPUT}"
+ echo "GOOGLE_CREDS_FILE=${{ github.workspace }}/${{ env.EXECUTION_ID }}/google-credentials.json" >> "${GITHUB_OUTPUT}"
- if [[ "${MATRIX_PROJECTS_JSON}" != "" ]]; then
- MATRIX_JSON="{ \"ruleset\": [ ${MATRIX_PROJECTS_JSON} ] }"
- echo "${MATRIX_JSON}"
- CONTINUE="yes"
- else
- CONTINUE="no"
- fi
-
- echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
- echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
- diffcalc:
- name: Run
+ environment:
+ name: Setup environment
+ needs: directory
runs-on: self-hosted
- timeout-minutes: 1440
- if: needs.metadata.outputs.continue == 'yes'
- needs: metadata
- strategy:
- matrix: ${{ fromJson(needs.metadata.outputs.matrix) }}
+ env:
+ VARS_JSON: ${{ toJSON(vars) }}
steps:
- - name: Verify MySQL connection from host
+ - name: Add base environment
run: |
- mysql -e "SHOW DATABASES"
+ # Required by diffcalc-sheet-generator
+ cp '${{ needs.directory.outputs.GENERATOR_DIR }}/.env.sample' "${{ needs.directory.outputs.GENERATOR_ENV }}"
- - name: Drop previous databases
- run: |
- for db in osu_master osu_pr
- do
- mysql -e "DROP DATABASE IF EXISTS $db"
+ # Add Google credentials
+ echo '${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}' | base64 -d > "${{ needs.directory.outputs.GOOGLE_CREDS_FILE }}"
+
+ # Add repository variables
+ echo "${VARS_JSON}" | jq -c '. | to_entries | .[]' | while read -r line; do
+ opt=$(jq -r '.key' <<< ${line})
+ val=$(jq -r '.value' <<< ${line})
+
+ if [[ "${opt}" =~ ^DIFFCALC_ ]]; then
+ optNoPrefix=$(echo "${opt}" | cut -d '_' -f2-)
+ sed -i "s;^${optNoPrefix}=.*$;${optNoPrefix}=${val};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
done
- - name: Create directory structure
+ - name: Add pull-request environment
+ if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
run: |
- mkdir -p $GITHUB_WORKSPACE/master/
- mkdir -p $GITHUB_WORKSPACE/pr/
+ sed -i "s;^OSU_B=.*$;OSU_B=${{ github.event.issue.pull_request.html_url }};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
- - name: Get upstream branch # https://akaimo.hatenablog.jp/entry/2020/05/16/101251
- id: upstreambranch
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Add comment environment
+ if: ${{ github.event_name == 'issue_comment' }}
run: |
- echo "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
- echo "repo=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
-
- # Checkout osu
- - name: Checkout osu (master)
- uses: actions/checkout@v3
- with:
- path: 'master/osu'
- - name: Checkout osu (pr)
- uses: actions/checkout@v3
- with:
- path: 'pr/osu'
- repository: ${{ steps.upstreambranch.outputs.repo }}
- ref: ${{ steps.upstreambranch.outputs.branchname }}
-
- - name: Checkout osu-difficulty-calculator (master)
- uses: actions/checkout@v3
- with:
- repository: ppy/osu-difficulty-calculator
- path: 'master/osu-difficulty-calculator'
- - name: Checkout osu-difficulty-calculator (pr)
- uses: actions/checkout@v3
- with:
- repository: ppy/osu-difficulty-calculator
- path: 'pr/osu-difficulty-calculator'
-
- - name: Install .NET 5.0.x
- uses: actions/setup-dotnet@v3
- 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;'
+ # Add comment environment
+ echo '${{ github.event.comment.body }}' | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do
+ opt=$(echo ${line} | cut -d '=' -f1)
+ sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
done
- - name: Run diffcalc (master)
- env:
- DB_NAME: osu_master
+ - name: Add dispatch environment
+ if: ${{ github.event_name == 'workflow_dispatch' }}
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 }}
+ sed -i 's;^OSU_B=.*$;OSU_B=${{ inputs.osu-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ sed -i 's/^RULESET=.*$/RULESET=${{ inputs.ruleset }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ sed -i 's/^GENERATORS=.*$/GENERATORS=${{ inputs.generators }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
- - 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;"
+ if [[ '${{ inputs.osu-a }}' != 'latest' ]]; then
+ sed -i 's;^OSU_A=.*$;OSU_A=${{ inputs.osu-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
- # Todo: Run ppcalc
+ if [[ '${{ inputs.difficulty-calculator-a }}' != 'latest' ]]; then
+ sed -i 's;^DIFFICULTY_CALCULATOR_A=.*$;DIFFICULTY_CALCULATOR_A=${{ inputs.difficulty-calculator-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
+
+ if [[ '${{ inputs.difficulty-calculator-b }}' != 'latest' ]]; then
+ sed -i 's;^DIFFICULTY_CALCULATOR_B=.*$;DIFFICULTY_CALCULATOR_B=${{ inputs.difficulty-calculator-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
+
+ if [[ '${{ inputs.score-processor-a }}' != 'latest' ]]; then
+ sed -i 's;^SCORE_PROCESSOR_A=.*$;SCORE_PROCESSOR_A=${{ inputs.score-processor-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
+
+ if [[ '${{ inputs.score-processor-b }}' != 'latest' ]]; then
+ sed -i 's;^SCORE_PROCESSOR_B=.*$;SCORE_PROCESSOR_B=${{ inputs.score-processor-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
+
+ if [[ '${{ inputs.converts }}' == 'true' ]]; then
+ sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=0/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ else
+ sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=1/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
+
+ if [[ '${{ inputs.ranked-only }}' == 'true' ]]; then
+ sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=1/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ else
+ sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=0/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+ fi
+
+ scores:
+ name: Setup scores
+ needs: [ directory, environment ]
+ runs-on: self-hosted
+ steps:
+ - name: Query latest data
+ id: query
+ run: |
+ ruleset=$(cat ${{ needs.directory.outputs.GENERATOR_ENV }} | grep -E '^RULESET=' | cut -d '=' -f2-)
+ performance_data_name=$(curl -s "https://data.ppy.sh/" | grep "performance_${ruleset}_top_1000\b" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g')
+
+ echo "TARGET_DIR=${{ needs.directory.outputs.GENERATOR_DIR }}/sql/${ruleset}" >> "${GITHUB_OUTPUT}"
+ echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}"
+
+ - name: Restore cache
+ id: restore-cache
+ uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1
+ with:
+ path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
+ key: ${{ steps.query.outputs.DATA_NAME }}
+
+ - name: Download
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ run: |
+ wget -q -nc "https://data.ppy.sh/${{ steps.query.outputs.DATA_NAME }}.tar.bz2"
+
+ - name: Extract
+ run: |
+ tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_NAME }}.tar.bz2"
+ rm -r "${{ steps.query.outputs.TARGET_DIR }}"
+ mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}"
+
+ beatmaps:
+ name: Setup beatmaps
+ needs: directory
+ runs-on: self-hosted
+ steps:
+ - name: Query latest data
+ id: query
+ run: |
+ beatmaps_data_name=$(curl -s "https://data.ppy.sh/" | grep "osu_files" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g')
+
+ echo "TARGET_DIR=${{ needs.directory.outputs.GENERATOR_DIR }}/beatmaps" >> "${GITHUB_OUTPUT}"
+ echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}"
+
+ - name: Restore cache
+ id: restore-cache
+ uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1
+ with:
+ path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
+ key: ${{ steps.query.outputs.DATA_NAME }}
+
+ - name: Download
+ if: steps.restore-cache.outputs.cache-hit != 'true'
+ run: |
+ wget -q -nc "https://data.ppy.sh/${{ steps.query.outputs.DATA_NAME }}.tar.bz2"
+
+ - name: Extract
+ run: |
+ tar -I lbzip2 -xf "${{ steps.query.outputs.DATA_NAME }}.tar.bz2"
+ rm -r "${{ steps.query.outputs.TARGET_DIR }}"
+ mv "${{ steps.query.outputs.DATA_NAME }}" "${{ steps.query.outputs.TARGET_DIR }}"
+
+ generator:
+ name: Run generator
+ needs: [ directory, environment, scores, beatmaps ]
+ runs-on: self-hosted
+ timeout-minutes: 720
+ outputs:
+ TARGET: ${{ steps.run.outputs.TARGET }}
+ SPREADSHEET_LINK: ${{ steps.run.outputs.SPREADSHEET_LINK }}
+ steps:
+ - name: Run
+ id: run
+ run: |
+ # Add the GitHub token. This needs to be done here because it's unique per-job.
+ sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
+
+ cd "${{ needs.directory.outputs.GENERATOR_DIR }}"
+ docker-compose up --build generator
+
+ link=$(docker-compose logs generator -n 10 | grep 'http' | sed -E 's/^.*(http.*)$/\1/')
+ target=$(cat "${{ needs.directory.outputs.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-)
+
+ echo "TARGET=${target}" >> "${GITHUB_OUTPUT}"
+ echo "SPREADSHEET_LINK=${link}" >> "${GITHUB_OUTPUT}"
+
+ - name: Shutdown
+ if: ${{ always() }}
+ run: |
+ cd "${{ needs.directory.outputs.GENERATOR_DIR }}"
+ docker-compose down -v
+
+ output-cli:
+ name: Output info
+ needs: generator
+ runs-on: ubuntu-latest
+ steps:
+ - name: Output info
+ run: |
+ echo "Target: ${{ needs.generator.outputs.TARGET }}"
+ echo "Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }}"
+
+ cleanup:
+ name: Cleanup
+ needs: [ directory, generator ]
+ if: ${{ always() && needs.directory.result == 'success' }}
+ runs-on: self-hosted
+ steps:
+ - name: Cleanup
+ run: |
+ rm -rf "${{ needs.directory.outputs.GENERATOR_DIR }}"
+
+ update-comment:
+ name: Update PR comment
+ needs: [ create-comment, generator ]
+ runs-on: ubuntu-latest
+ if: ${{ always() && needs.create-comment.result == 'success' }}
+ steps:
+ - name: Update comment on success
+ if: ${{ needs.generator.result == 'success' }}
+ uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
+ with:
+ comment_tag: ${{ env.EXECUTION_ID }}
+ mode: upsert
+ create_if_not_exists: false
+ message: |
+ Target: ${{ needs.generator.outputs.TARGET }}
+ Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }}
+
+ - name: Update comment on failure
+ if: ${{ needs.generator.result == 'failure' }}
+ uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
+ with:
+ comment_tag: ${{ env.EXECUTION_ID }}
+ mode: upsert
+ create_if_not_exists: false
+ message: |
+ Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+
+ - name: Update comment on cancellation
+ if: ${{ needs.generator.result == 'cancelled' }}
+ uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
+ with:
+ comment_tag: ${{ env.EXECUTION_ID }}
+ mode: delete
+ message: '.' # Appears to be required by this action for non-error status code.
diff --git a/.gitignore b/.gitignore
index 0c7a18b437..525b3418cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -339,6 +339,5 @@ inspectcode
# Fody (pulled in by Realm) - schema file
FodyWeavers.xsd
-**/FodyWeavers.xml
.idea/.idea.osu.Desktop/.idea/misc.xml
\ No newline at end of file
diff --git a/README.md b/README.md
index cf7ce35791..d5dc0723af 100644
--- a/README.md
+++ b/README.md
@@ -12,45 +12,48 @@
A free-to-win rhythm game. Rhythm is just a *click* away!
-The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
+This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
## Status
-This project is under constant development, but we aim to keep things in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
+This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release.
-**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to a [stable release](https://osu.ppy.sh/home/download) of osu!. We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
-
-We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
+A few resources are available as starting points to getting involved and understanding the project:
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
+- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc).
## Running osu!
-If you are looking to install or test osu! without setting up a development environment, you can consume our [releases](https://github.com/ppy/osu/releases). You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download). Failing that, you may use the links below to download the latest version for your operating system of choice:
+If you are just looking to give the game a whirl, you can grab the latest release for your platform:
-**Latest release:**
+### Latest release:
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
| ------------- | ------------- | ------------- | ------------- | ------------- |
-- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
+You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
+**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores in early 2024.
+
## Developing a custom ruleset
-osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
+osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
## Developing osu!
+### Prerequisites
+
Please make sure you have the following prerequisites:
- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
-When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
+When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) plugin installed.
### Downloading the source code
@@ -69,9 +72,19 @@ git pull
### Building
-Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
+#### From an IDE
-- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will allow access to template run configurations.
+You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are:
+
+- `osu.Desktop.slnf` (most common)
+- `osu.Android.slnf`
+- `osu.iOS.slnf`
+
+Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing).
+
+To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build.
+
+#### From CLI
You can also build and run *osu!* from the command-line with a single command:
@@ -79,12 +92,10 @@ You can also build and run *osu!* from the command-line with a single command:
dotnet run --project osu.Desktop
```
-If you are not interested in debugging *osu!*, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document.
+When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
If the build fails, try to restore NuGet packages with `dotnet restore`.
-_Due to a historical feature gap between .NET Core and Xamarin, running `dotnet` CLI from the root directory will not work for most commands. This can be resolved by specifying a target `.csproj` or the helper project at `build/Desktop.proj`. Configurations have been provided to work around this issue for all supported IDEs mentioned above._
-
### Testing with resource/framework modifications
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
diff --git a/Templates/README.md b/Templates/README.md
index cf25a89273..28aaee3290 100644
--- a/Templates/README.md
+++ b/Templates/README.md
@@ -7,7 +7,7 @@ Templates for use when creating osu! dependent projects. Create a fully-testable
```bash
# install (or update) templates package.
# this only needs to be done once
-dotnet new -i ppy.osu.Game.Templates
+dotnet new install ppy.osu.Game.Templates
# create an empty freeform ruleset
dotnet new ruleset -n MyCoolRuleset
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index a1c53ece03..2baa7ee0e0 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 683e9fd5e8..a2308e6dfc 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index b7a7fff18a..e839d2657c 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 683e9fd5e8..a2308e6dfc 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/app.manifest b/app.manifest
index 533c6ff208..088ad1dde7 100644
--- a/app.manifest
+++ b/app.manifest
@@ -1,6 +1,7 @@
+ 1
@@ -14,33 +15,10 @@
-
-
-
-
-
-
-
+
-
-
- true
-
-
-
-
-
-
-
diff --git a/osu.Android.props b/osu.Android.props
index c88bea8265..2870696c03 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -8,13 +8,9 @@
true
true
- manifestmerger.jar
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs
index 3c39a820cc..e5fc354db7 100644
--- a/osu.Android/GameplayScreenRotationLocker.cs
+++ b/osu.Android/GameplayScreenRotationLocker.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using Android.Content.PM;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -13,10 +11,10 @@ namespace osu.Android
{
public partial class GameplayScreenRotationLocker : Component
{
- private Bindable localUserPlaying;
+ private Bindable localUserPlaying = null!;
[Resolved]
- private OsuGameActivity gameActivity { get; set; }
+ private OsuGameActivity gameActivity { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(OsuGame game)
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index f0a6e4733c..33ffed432e 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -15,6 +13,7 @@ using Android.Graphics;
using Android.OS;
using Android.Views;
using osu.Framework.Android;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Database;
using Debug = System.Diagnostics.Debug;
using Uri = Android.Net.Uri;
@@ -51,11 +50,11 @@ namespace osu.Android
/// Adjusted on startup to match expected UX for the current device type (phone/tablet).
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
- private OsuGameAndroid game;
+ private OsuGameAndroid game = null!;
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
- protected override void OnCreate(Bundle savedInstanceState)
+ protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
@@ -92,15 +91,15 @@ namespace osu.Android
Assembly.Load("osu.Game.Rulesets.Mania");
}
- protected override void OnNewIntent(Intent intent) => handleIntent(intent);
+ protected override void OnNewIntent(Intent? intent) => handleIntent(intent);
- private void handleIntent(Intent intent)
+ private void handleIntent(Intent? intent)
{
- switch (intent.Action)
+ switch (intent?.Action)
{
case Intent.ActionDefault:
if (intent.Scheme == ContentResolver.SchemeContent)
- handleImportFromUris(intent.Data);
+ handleImportFromUris(intent.Data.AsNonNull());
else if (osu_url_schemes.Contains(intent.Scheme))
game.HandleLink(intent.DataString);
break;
@@ -114,7 +113,7 @@ namespace osu.Android
{
var content = intent.ClipData?.GetItemAt(i);
if (content != null)
- uris.Add(content.Uri);
+ uris.Add(content.Uri.AsNonNull());
}
handleImportFromUris(uris.ToArray());
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 0227d2aec2..dea70e6b27 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -1,13 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using Android.App;
using Microsoft.Maui.Devices;
using osu.Framework.Allocation;
using osu.Framework.Android.Input;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Input.Handlers;
using osu.Framework.Platform;
using osu.Game;
@@ -32,7 +31,7 @@ namespace osu.Android
{
get
{
- var packageInfo = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0);
+ var packageInfo = Application.Context.ApplicationContext!.PackageManager!.GetPackageInfo(Application.Context.ApplicationContext.PackageName!, 0).AsNonNull();
try
{
@@ -45,7 +44,7 @@ namespace osu.Android
// Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060
// https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
- string versionName = string.Empty;
+ string versionName;
if (OperatingSystem.IsAndroidVersionAtLeast(28))
{
@@ -68,7 +67,7 @@ namespace osu.Android
{
}
- return new Version(packageInfo.VersionName);
+ return new Version(packageInfo.VersionName.AsNonNull());
}
}
diff --git a/osu.Android/Properties/AndroidManifestOverlay.xml b/osu.Android/Properties/AndroidManifestOverlay.xml
deleted file mode 100644
index 815f935383..0000000000
--- a/osu.Android/Properties/AndroidManifestOverlay.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/osu.Android/Properties/AssemblyInfo.cs b/osu.Android/Properties/AssemblyInfo.cs
index f65b1b239f..1632087fb1 100644
--- a/osu.Android/Properties/AssemblyInfo.cs
+++ b/osu.Android/Properties/AssemblyInfo.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using Android;
using Android.App;
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index fe3e08537e..caf0a1d9fd 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -54,9 +54,6 @@ namespace osu.Desktop
client.OnReady += onReady;
- // safety measure for now, until we performance test / improve backoff for failed connections.
- client.OnConnectionFailed += (_, _) => client.Deinitialize();
-
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
@@ -187,7 +184,7 @@ namespace osu.Desktop
return edit.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.WatchingReplay watching:
- return watching.BeatmapInfo.ToString();
+ return watching.BeatmapInfo?.ToString() ?? string.Empty;
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
diff --git a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
index 5d950eef55..d1ac42f22b 100644
--- a/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
+++ b/osu.Desktop/LegacyIpc/LegacyTcpIpcProvider.cs
@@ -75,7 +75,7 @@ namespace osu.Desktop.LegacyIpc
case LegacyIpcDifficultyCalculationRequest req:
try
{
- WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile);
+ WorkingBeatmap beatmap = new FlatWorkingBeatmap(req.BeatmapFile);
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index d92fea27bf..a0db896f46 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -2,12 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
-using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Security;
using osu.Framework.Platform;
@@ -17,9 +15,9 @@ using osu.Framework;
using osu.Framework.Logging;
using osu.Game.Updater;
using osu.Desktop.Windows;
-using osu.Framework.Threading;
using osu.Game.IO;
using osu.Game.IPC;
+using osu.Game.Online.Multiplayer;
using osu.Game.Utils;
using SDL2;
@@ -111,6 +109,25 @@ namespace osu.Desktop
}
}
+ public override bool RestartAppWhenExited()
+ {
+ switch (RuntimeInfo.OS)
+ {
+ case RuntimeInfo.Platform.Windows:
+ Debug.Assert(OperatingSystem.IsWindows());
+
+ // Of note, this is an async method in squirrel that adds an arbitrary delay before returning
+ // likely to ensure the external process is in a good state.
+ //
+ // We're not waiting on that here, but the outro playing before the actual exit should be enough
+ // to cover this.
+ Squirrel.UpdateManager.RestartAppWhenExited().FireAndForget();
+ return true;
+ }
+
+ return base.RestartAppWhenExited();
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -130,60 +147,16 @@ namespace osu.Desktop
{
base.SetHost(host);
- var desktopWindow = (SDL2DesktopWindow)host.Window;
-
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
if (iconStream != null)
- desktopWindow.SetIconFromStream(iconStream);
+ host.Window.SetIconFromStream(iconStream);
- desktopWindow.CursorState |= CursorState.Hidden;
- desktopWindow.Title = Name;
- desktopWindow.DragDrop += f =>
- {
- // on macOS, URL associations are handled via SDL_DROPFILE events.
- if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
- {
- HandleLink(f);
- return;
- }
-
- fileDrop(new[] { f });
- };
+ host.Window.CursorState |= CursorState.Hidden;
+ host.Window.Title = Name;
}
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
- private readonly List importableFiles = new List();
- private ScheduledDelegate? importSchedule;
-
- private void fileDrop(string[] filePaths)
- {
- lock (importableFiles)
- {
- importableFiles.AddRange(filePaths);
-
- Logger.Log($"Adding {filePaths.Length} files for import");
-
- // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
- // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
- importSchedule?.Cancel();
- importSchedule = Scheduler.AddDelayed(handlePendingImports, 100);
- }
- }
-
- private void handlePendingImports()
- {
- lock (importableFiles)
- {
- Logger.Log($"Handling batch import of {importableFiles.Count} files");
-
- string[] paths = importableFiles.ToArray();
- importableFiles.Clear();
-
- Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
- }
- }
-
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 5a1373e040..a33e845f5b 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -85,7 +85,7 @@ namespace osu.Desktop
}
}
- using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true }))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = !tournamentClient }))
{
if (!host.IsPrimaryInstance)
{
diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest
deleted file mode 100644
index a11cee132c..0000000000
--- a/osu.Desktop/app.manifest
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index f1b9c92429..1d43e118a3 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -8,7 +8,6 @@
osu!
osu!(lazer)
lazer.ico
- app.manifest
0.0.0
0.0.0
@@ -27,7 +26,7 @@
-
+
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 4719d54138..5de21a68d0 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -7,9 +7,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml
index bf7c0bfeca..52b34959b9 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
index 64c71c9ecd..d8b729576d 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
+++ b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using Android.App;
using osu.Framework.Android;
using osu.Game.Tests;
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
index 5ace6c07f5..f87043e1d1 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
@@ -5,7 +5,7 @@
CFBundleName
osu.Game.Rulesets.Catch.Tests.iOS
CFBundleIdentifier
- ppy.osu-Game-Rulesets-Catch-Tests-iOS
+ sh.ppy.catch-ruleset-tests
CFBundleShortVersionString
1.0
CFBundleVersion
@@ -42,4 +42,4 @@
CADisableMinimumFrameDurationOnPhone
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index b6cb351c1e..baca8166d1 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
index cf030f6e13..880316f177 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
index b9d6f28228..dacfd649ef 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
@@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using NUnit.Framework;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
@@ -26,7 +25,8 @@ namespace osu.Game.Rulesets.Catch.Tests
new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } },
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
- new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }
+ new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } },
+ new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
};
[TestCaseSource(nameof(catch_mod_mapping))]
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
index f30b216d8d..72011042bc 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.IO.Stores;
using osu.Game.Rulesets.Catch.Skinning;
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
index 2af851a561..4306cc7d9d 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs
index 39508359a4..058d4eb6b9 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
index 033dca587e..6dfc74e75c 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
index 2db4102513..ed37ff4ef3 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs
index 1e057cf3fb..8052b8e3f7 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchDistanceSnapGrid.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs
index 5593f3d319..c9ba127569 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneEditor.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Tests.Visual;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs
index 93b24d92fb..75d3c3753a 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
index 2426f8c886..d010bb02ad 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1]));
- AddAssert("default slider velocity", () => lastObject.SliderVelocityBindable.IsDefault);
+ AddAssert("default slider velocity", () => lastObject.SliderVelocityMultiplierBindable.IsDefault);
}
[Test]
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
addPlacementSteps(times, positions);
addPathCheckStep(times, positions);
- AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault);
+ AddAssert("slider velocity changed", () => !lastObject.SliderVelocityMultiplierBindable.IsDefault);
}
[Test]
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs
index beba5811fe..05d7a38a95 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs
@@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
double[] times = { 100, 300 };
float[] positions = { 200, 300 };
addBlueprintStep(times, positions);
- AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault);
+ AddAssert("default slider velocity", () => hitObject.SliderVelocityMultiplierBindable.IsDefault);
addDragStartStep(times[1], positions[1]);
AddMouseMoveStep(times[1], 400);
- AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault);
+ AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
}
[Test]
diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
index 0de992c1df..95b4fdc07e 100644
--- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModFloatingFruits.cs
new file mode 100644
index 0000000000..73579d1c22
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModFloatingFruits.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests.Mods
+{
+ public partial class TestSceneCatchModFloatingFruits : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+
+ [Test]
+ public void TestFloating() => CreateModTest(new ModTestData
+ {
+ Mod = new CatchModFloatingFruits(),
+ PassCondition = () => true
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-0.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-1.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-2.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-3.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-4.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle-5.png
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png
similarity index 100%
rename from osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai@2x.png
rename to osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index f21825668f..3261fb656e 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index 402f8f548d..569c69a633 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
index 05d3361dc3..a44575a46e 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
index 01cce88d9d..a82edc1df8 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Tests.Visual;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 4c1ba33aa2..5406230359 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs
index cbf900ebc0..1d2ea4610d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs
index 75ab4ad9d2..e2fc31d869 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private float getCaughtObjectPosition(Fruit fruit)
{
var caughtObject = catcher.ChildrenOfType().Single(c => c.HitObject == fruit);
- return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
+ return caughtObject.Parent!.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
}
private void catchFruit(Fruit fruit, float x)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index c8979381fe..af38956002 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
index 007f309f3f..23fcd49863 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index 223c4e57fc..fda4136a37 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
index 995daaceb1..8e7f77285c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
@@ -28,6 +26,8 @@ namespace osu.Game.Rulesets.Catch.Tests
AddSliderStep("start time", 500, 600, 0, x =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
+ drawableFruit.RefreshStateTransforms();
+ drawableBanana.RefreshStateTransforms();
});
}
@@ -46,6 +46,8 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("Initialize start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
+ drawableFruit.RefreshStateTransforms();
+ drawableBanana.RefreshStateTransforms();
fruitRotation = drawableFruit.DisplayRotation;
bananaRotation = drawableBanana.DisplayRotation;
@@ -56,6 +58,8 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("change start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
+ drawableFruit.RefreshStateTransforms();
+ drawableBanana.RefreshStateTransforms();
});
AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
@@ -66,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("reset start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
+ drawableFruit.RefreshStateTransforms();
+ drawableBanana.RefreshStateTransforms();
});
AddAssert("rotation and size restored", () =>
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
index 4b2873e0a8..1534d91e77 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Bindables;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index f8c43a221e..3c222662f5 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
index c91f07891c..c31a7ca99f 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
index aa66fc8741..871da28142 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public partial class TestSceneLegacyBeatmapSkin : LegacyBeatmapSkinColourTest
{
[Resolved]
- private AudioManager audio { get; set; }
+ private AudioManager audio { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs
new file mode 100644
index 0000000000..dfdde0a325
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs
@@ -0,0 +1,157 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Judgements;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual.Gameplay;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public partial class TestSceneScoring : ScoringTestScene
+ {
+ public TestSceneScoring()
+ : base(supportsNonPerfectJudgements: false)
+ {
+ }
+
+ private Bindable scoreMultiplier { get; } = new BindableDouble
+ {
+ Default = 4,
+ Value = 4
+ };
+
+ protected override IBeatmap CreateBeatmap(int maxCombo)
+ {
+ var beatmap = new CatchBeatmap();
+ for (int i = 0; i < maxCombo; ++i)
+ beatmap.HitObjects.Add(new Fruit());
+ return beatmap;
+ }
+
+ protected override IScoringAlgorithm CreateScoreV1() => new ScoreV1 { ScoreMultiplier = { BindTarget = scoreMultiplier } };
+
+ protected override IScoringAlgorithm CreateScoreV2(int maxCombo) => new ScoreV2(maxCombo);
+
+ protected override ProcessorBasedScoringAlgorithm CreateScoreAlgorithm(IBeatmap beatmap, ScoringMode mode) => new CatchProcessorBasedScoringAlgorithm(beatmap, mode);
+
+ [Test]
+ public void TestBasicScenarios()
+ {
+ AddStep("set up score multiplier", () =>
+ {
+ scoreMultiplier.BindValueChanged(_ => Rerun());
+ });
+ AddStep("set max combo to 100", () => MaxCombo.Value = 100);
+ AddStep("set perfect score", () =>
+ {
+ NonPerfectLocations.Clear();
+ MissLocations.Clear();
+ });
+ AddStep("set score with misses", () =>
+ {
+ NonPerfectLocations.Clear();
+ MissLocations.Clear();
+ MissLocations.AddRange(new[] { 24d, 49 });
+ });
+ AddSliderStep("adjust score multiplier", 0, 10, (int)scoreMultiplier.Default, multiplier => scoreMultiplier.Value = multiplier);
+ }
+
+ private const int base_great = 300;
+
+ private class ScoreV1 : IScoringAlgorithm
+ {
+ private int currentCombo;
+
+ public BindableDouble ScoreMultiplier { get; } = new BindableDouble();
+
+ public void ApplyHit() => applyHitV1(base_great);
+
+ public void ApplyNonPerfect() => throw new NotSupportedException("catch does not have \"non-perfect\" judgements.");
+
+ public void ApplyMiss() => applyHitV1(0);
+
+ private void applyHitV1(int baseScore)
+ {
+ if (baseScore == 0)
+ {
+ currentCombo = 0;
+ return;
+ }
+
+ TotalScore += baseScore;
+
+ // combo multiplier
+ // ReSharper disable once PossibleLossOfFraction
+ TotalScore += (int)(Math.Max(0, currentCombo - 1) * (baseScore / 25 * ScoreMultiplier.Value));
+
+ currentCombo++;
+ }
+
+ public long TotalScore { get; private set; }
+ }
+
+ private class ScoreV2 : IScoringAlgorithm
+ {
+ private int currentCombo;
+ private double comboPortion;
+
+ private readonly double comboPortionMax;
+
+ private const double combo_base = 4;
+ private const int combo_cap = 200;
+
+ public ScoreV2(int maxCombo)
+ {
+ for (int i = 0; i < maxCombo; i++)
+ ApplyHit();
+
+ comboPortionMax = comboPortion;
+
+ currentCombo = 0;
+ comboPortion = 0;
+ }
+
+ public void ApplyHit() => applyHitV2(base_great);
+
+ public void ApplyNonPerfect() => throw new NotSupportedException("catch does not have \"non-perfect\" judgements.");
+
+ private void applyHitV2(int baseScore)
+ {
+ comboPortion += baseScore * Math.Min(Math.Max(0.5, Math.Log(++currentCombo, combo_base)), Math.Log(combo_cap, combo_base));
+ }
+
+ public void ApplyMiss()
+ {
+ currentCombo = 0;
+ }
+
+ public long TotalScore
+ => (int)Math.Round(1000000 * comboPortion / comboPortionMax); // vast simplification, as we're not doing ticks here.
+ }
+
+ private class CatchProcessorBasedScoringAlgorithm : ProcessorBasedScoringAlgorithm
+ {
+ public CatchProcessorBasedScoringAlgorithm(IBeatmap beatmap, ScoringMode mode)
+ : base(beatmap, mode)
+ {
+ }
+
+ protected override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
+
+ protected override JudgementResult CreatePerfectJudgementResult() => new CatchJudgementResult(new Fruit(), new CatchJudgement()) { Type = HitResult.Great };
+
+ protected override JudgementResult CreateNonPerfectJudgementResult() => throw new NotSupportedException("catch does not have \"non-perfect\" judgements.");
+
+ protected override JudgementResult CreateMissJudgementResult() => new CatchJudgementResult(new Fruit(), new CatchJudgement()) { Type = HitResult.Miss };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 01922b2a96..c45c85833c 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 2c8ef9eae0..6a24c26844 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -41,9 +41,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
X = xPositionData?.X ?? 0,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
- LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
- SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1
+ SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
}.Yield();
case IHasDuration endTime:
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 8a0b8250d5..9ceb78893e 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -25,7 +25,10 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.UI;
+using osu.Game.Scoring;
+using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
@@ -51,7 +54,7 @@ namespace osu.Game.Rulesets.Catch
new KeyBinding(InputKey.X, CatchAction.MoveRight),
new KeyBinding(InputKey.Right, CatchAction.MoveRight),
new KeyBinding(InputKey.Shift, CatchAction.Dash),
- new KeyBinding(InputKey.Shift, CatchAction.Dash),
+ new KeyBinding(InputKey.MouseLeft, CatchAction.Dash),
};
public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
@@ -91,6 +94,9 @@ namespace osu.Game.Rulesets.Catch
if (mods.HasFlagFast(LegacyMods.Relax))
yield return new CatchModRelax();
+
+ if (mods.HasFlagFast(LegacyMods.ScoreV2))
+ yield return new ModScoreV2();
}
public override IEnumerable GetModsFor(ModType type)
@@ -140,6 +146,12 @@ namespace osu.Game.Rulesets.Catch
new CatchModNoScope(),
};
+ case ModType.System:
+ return new Mod[]
+ {
+ new ModScoreV2(),
+ };
+
default:
return Array.Empty();
}
@@ -202,10 +214,24 @@ namespace osu.Game.Rulesets.Catch
public int LegacyID => 2;
+ public ILegacyScoreSimulator CreateLegacyScoreSimulator() => new CatchLegacyScoreSimulator();
+
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
+
+ public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
+ {
+ return new[]
+ {
+ new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y
+ }),
+ };
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
index 2d01153f98..5c64643fd4 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
@@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// 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 values, IBeatmapOnlineInfo onlineInfo)
@@ -36,7 +35,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
- MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 42cfde268e..b826c1f546 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -38,13 +38,15 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
- return new CatchDifficultyAttributes
+ CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
{
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
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().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
};
+
+ return attributes;
}
protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs
new file mode 100644
index 0000000000..746f5713e4
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs
@@ -0,0 +1,192 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Scoring.Legacy;
+
+namespace osu.Game.Rulesets.Catch.Difficulty
+{
+ internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
+ {
+ private int legacyBonusScore;
+ private int standardisedBonusScore;
+ private int combo;
+
+ private double scoreMultiplier;
+
+ public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
+ {
+ IBeatmap baseBeatmap = workingBeatmap.Beatmap;
+
+ int countNormal = 0;
+ int countSlider = 0;
+ int countSpinner = 0;
+
+ foreach (HitObject obj in baseBeatmap.HitObjects)
+ {
+ switch (obj)
+ {
+ case IHasPath:
+ countSlider++;
+ break;
+
+ case IHasDuration:
+ countSpinner++;
+ break;
+
+ default:
+ countNormal++;
+ break;
+ }
+ }
+
+ int objectCount = countNormal + countSlider + countSpinner;
+
+ int drainLength = 0;
+
+ if (baseBeatmap.HitObjects.Count > 0)
+ {
+ int breakLength = baseBeatmap.Breaks.Select(b => (int)Math.Round(b.EndTime) - (int)Math.Round(b.StartTime)).Sum();
+ drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
+ }
+
+ int difficultyPeppyStars = (int)Math.Round(
+ (baseBeatmap.Difficulty.DrainRate
+ + baseBeatmap.Difficulty.OverallDifficulty
+ + baseBeatmap.Difficulty.CircleSize
+ + Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);
+
+ scoreMultiplier = difficultyPeppyStars;
+
+ LegacyScoreAttributes attributes = new LegacyScoreAttributes();
+
+ foreach (var obj in playableBeatmap.HitObjects)
+ simulateHit(obj, ref attributes);
+
+ attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
+
+ return attributes;
+ }
+
+ private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attributes)
+ {
+ bool increaseCombo = true;
+ bool addScoreComboMultiplier = false;
+
+ bool isBonus = false;
+ HitResult bonusResult = HitResult.None;
+
+ int scoreIncrease = 0;
+
+ switch (hitObject)
+ {
+ case TinyDroplet:
+ scoreIncrease = 10;
+ increaseCombo = false;
+ break;
+
+ case Droplet:
+ scoreIncrease = 100;
+ break;
+
+ case Fruit:
+ scoreIncrease = 300;
+ addScoreComboMultiplier = true;
+ increaseCombo = true;
+ break;
+
+ case Banana:
+ scoreIncrease = 1100;
+ increaseCombo = false;
+ isBonus = true;
+ bonusResult = HitResult.LargeBonus;
+ break;
+
+ case JuiceStream:
+ foreach (var nested in hitObject.NestedHitObjects)
+ simulateHit(nested, ref attributes);
+ return;
+
+ case BananaShower:
+ foreach (var nested in hitObject.NestedHitObjects)
+ simulateHit(nested, ref attributes);
+ return;
+ }
+
+ if (addScoreComboMultiplier)
+ {
+ // ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
+ attributes.ComboScore += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * scoreMultiplier));
+ }
+
+ if (isBonus)
+ {
+ legacyBonusScore += scoreIncrease;
+ standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
+ }
+ else
+ attributes.AccuracyScore += scoreIncrease;
+
+ if (increaseCombo)
+ combo++;
+ }
+
+ public double GetLegacyScoreMultiplier(IReadOnlyList mods, LegacyBeatmapConversionDifficultyInfo difficulty)
+ {
+ bool scoreV2 = mods.Any(m => m is ModScoreV2);
+
+ double multiplier = 1.0;
+
+ foreach (var mod in mods)
+ {
+ switch (mod)
+ {
+ case CatchModNoFail:
+ multiplier *= scoreV2 ? 1.0 : 0.5;
+ break;
+
+ case CatchModEasy:
+ multiplier *= 0.5;
+ break;
+
+ case CatchModHalfTime:
+ case CatchModDaycore:
+ multiplier *= 0.3;
+ break;
+
+ case CatchModHidden:
+ multiplier *= scoreV2 ? 1.0 : 1.06;
+ break;
+
+ case CatchModHardRock:
+ multiplier *= 1.12;
+ break;
+
+ case CatchModDoubleTime:
+ case CatchModNightcore:
+ multiplier *= 1.06;
+ break;
+
+ case CatchModFlashlight:
+ multiplier *= 1.12;
+ break;
+
+ case CatchModRelax:
+ return 0;
+ }
+ }
+
+ return multiplier;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
index d2d605a6fe..1a2990e4ac 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
@@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
{
@@ -24,7 +23,5 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
: base(new THitObject())
{
}
-
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
}
}
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs
index 7a577f8a83..df76bf0a8c 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
public void UpdateHitObjectFromPath(JuiceStream hitObject)
{
// The SV setting may need to be changed for the current path.
- var svBindable = hitObject.SliderVelocityBindable;
+ var svBindable = hitObject.SliderVelocityMultiplierBindable;
double svToVelocityFactor = hitObject.Velocity / svBindable.Value;
double requiredVelocity = path.ComputeRequiredVelocity();
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs
index 6862696b3a..40bd08455f 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs
@@ -1,180 +1,19 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Caching;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.UI.Scrolling;
-using osu.Game.Screens.Edit;
-using osuTK.Graphics;
+using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Catch.Edit
{
- ///
- /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
- ///
- ///
- /// This class heavily borrows from osu!mania's implementation (ManiaBeatSnapGrid).
- /// If further changes are to be made, they should also be applied there.
- /// If the scale of the changes are large enough, abstracting may be a good path.
- ///
- public partial class CatchBeatSnapGrid : Component
+ public partial class CatchBeatSnapGrid : BeatSnapGrid
{
- private const double visible_range = 750;
-
- ///
- /// The range of time values of the current selection.
- ///
- public (double start, double end)? SelectionTimeRange
+ protected override IEnumerable GetTargetContainers(HitObjectComposer composer) => new[]
{
- set
- {
- if (value == selectionTimeRange)
- return;
-
- selectionTimeRange = value;
- lineCache.Invalidate();
- }
- }
-
- [Resolved]
- private EditorBeatmap beatmap { get; set; } = null!;
-
- [Resolved]
- private OsuColour colours { get; set; } = null!;
-
- [Resolved]
- private BindableBeatDivisor beatDivisor { get; set; } = null!;
-
- private readonly Cached lineCache = new Cached();
-
- private (double start, double end)? selectionTimeRange;
-
- private ScrollingHitObjectContainer lineContainer = null!;
-
- [BackgroundDependencyLoader]
- private void load(HitObjectComposer composer)
- {
- lineContainer = new ScrollingHitObjectContainer();
-
- ((CatchPlayfield)composer.Playfield).UnderlayElements.Add(lineContainer);
-
- beatDivisor.BindValueChanged(_ => createLines(), true);
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (!lineCache.IsValid)
- {
- lineCache.Validate();
- createLines();
- }
- }
-
- private readonly Stack availableLines = new Stack();
-
- private void createLines()
- {
- foreach (var line in lineContainer.Objects.OfType())
- availableLines.Push(line);
-
- lineContainer.Clear();
-
- if (selectionTimeRange == null)
- return;
-
- var range = selectionTimeRange.Value;
-
- var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
-
- double time = timingPoint.Time;
- int beat = 0;
-
- // progress time until in the visible range.
- while (time < range.start - visible_range)
- {
- time += timingPoint.BeatLength / beatDivisor.Value;
- beat++;
- }
-
- while (time < range.end + visible_range)
- {
- var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
-
- // switch to the next timing point if we have reached it.
- if (nextTimingPoint.Time > timingPoint.Time)
- {
- beat = 0;
- time = nextTimingPoint.Time;
- timingPoint = nextTimingPoint;
- }
-
- Color4 colour = BindableBeatDivisor.GetColourFor(
- BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours);
-
- if (!availableLines.TryPop(out var line))
- line = new DrawableGridLine();
-
- line.HitObject.StartTime = time;
- line.Colour = colour;
-
- lineContainer.Add(line);
-
- beat++;
- time += timingPoint.BeatLength / beatDivisor.Value;
- }
-
- // required to update ScrollingHitObjectContainer's cache.
- lineContainer.UpdateSubTree();
-
- foreach (var line in lineContainer.Objects.OfType())
- {
- time = line.HitObject.StartTime;
-
- if (time >= range.start && time <= range.end)
- line.Alpha = 1;
- else
- {
- double timeSeparation = time < range.start ? range.start - time : time - range.end;
- line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
- }
- }
- }
-
- private partial class DrawableGridLine : DrawableHitObject
- {
- public DrawableGridLine()
- : base(new HitObject())
- {
- RelativeSizeAxes = Axes.X;
- Height = 2;
-
- AddInternal(new Box { RelativeSizeAxes = Axes.Both });
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Origin = Anchor.BottomLeft;
- Anchor = Anchor.BottomLeft;
- }
-
- protected override void UpdateInitialTransforms()
- {
- // don't perform any fading – we are handling that ourselves.
- LifetimeEnd = HitObject.StartTime + visible_range;
- }
- }
+ ((CatchPlayfield)composer.Playfield).UnderlayElements
+ };
}
}
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs
new file mode 100644
index 0000000000..c3103bd204
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Catch.Edit
+{
+ public partial class CatchDistanceSnapProvider : ComposerDistanceSnapProvider
+ {
+ protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
+ {
+ // osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
+ // Therefore this functionality is not currently used.
+ //
+ // The implementation below is probably correct but should be checked if/when exposed via controls.
+
+ float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
+ float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
+
+ return actualDistance / expectedDistance;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfieldAdjustmentContainer.cs
index 0271005dd1..36aee792a8 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfieldAdjustmentContainer.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Edit
{
base.Update();
- Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
+ Scale = new Vector2(Math.Min(Parent!.ChildSize.X / CatchPlayfield.WIDTH, Parent!.ChildSize.Y / CatchPlayfield.HEIGHT));
Height = 1 / Scale.Y;
}
}
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index f2877572e8..4172720ada 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -1,14 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
@@ -20,27 +19,27 @@ 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;
namespace osu.Game.Rulesets.Catch.Edit
{
- public partial class CatchHitObjectComposer : DistancedHitObjectComposer
+ public partial class CatchHitObjectComposer : ScrollingHitObjectComposer, IKeyBindingHandler
{
private const float distance_snap_radius = 50;
private CatchDistanceSnapGrid distanceSnapGrid = null!;
- private InputManager inputManager = null!;
-
- private CatchBeatSnapGrid beatSnapGrid = null!;
-
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
{
MinValue = 1,
MaxValue = 10,
};
+ [Cached(typeof(IDistanceSnapProvider))]
+ protected readonly CatchDistanceSnapProvider DistanceSnapProvider = new CatchDistanceSnapProvider();
+
public CatchHitObjectComposer(CatchRuleset ruleset)
: base(ruleset)
{
@@ -49,8 +48,11 @@ namespace osu.Game.Rulesets.Catch.Edit
[BackgroundDependencyLoader]
private void load()
{
+ AddInternal(DistanceSnapProvider);
+ DistanceSnapProvider.AttachToToolbox(RightToolbox);
+
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
- DistanceSpacingMultiplier.Disabled = true;
+ DistanceSnapProvider.DistanceSpacingMultiplier.Disabled = true;
LayerBelowRuleset.Add(new PlayfieldBorder
{
@@ -67,61 +69,30 @@ namespace osu.Game.Rulesets.Catch.Edit
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
}));
-
- AddInternal(beatSnapGrid = new CatchBeatSnapGrid());
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
+ protected override IEnumerable CreateTernaryButtons()
+ => base.CreateTernaryButtons()
+ .Concat(DistanceSnapProvider.CreateTernaryButtons());
- inputManager = GetContainingInputManager();
- }
-
- protected override void UpdateAfterChildren()
- {
- base.UpdateAfterChildren();
-
- if (BlueprintContainer.CurrentTool is SelectTool)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) =>
+ new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
{
- if (EditorBeatmap.SelectedHitObjects.Any())
- {
- beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
- }
- else
- beatSnapGrid.SelectionTimeRange = null;
- }
- else
- {
- var result = FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
- if (result.Time is double time)
- beatSnapGrid.SelectionTimeRange = (time, time);
- else
- beatSnapGrid.SelectionTimeRange = null;
- }
- }
+ TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
+ };
- protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
+ protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
+
+ protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
+
+ protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
- // osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
- // Therefore this functionality is not currently used.
- //
- // The implementation below is probably correct but should be checked if/when exposed via controls.
+ new FruitCompositionTool(),
+ new JuiceStreamCompositionTool(),
+ new BananaShowerCompositionTool()
+ };
- float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
- float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
-
- return actualDistance / expectedDistance;
- }
-
- protected override void Update()
- {
- base.Update();
-
- updateDistanceSnapGrid();
- }
-
- public override bool OnPressed(KeyBindingPressEvent e)
+ public bool OnPressed(KeyBindingPressEvent e)
{
switch (e.Action)
{
@@ -130,28 +101,19 @@ namespace osu.Game.Rulesets.Catch.Edit
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
case GlobalAction.IncreaseScrollSpeed:
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
- break;
+ return true;
case GlobalAction.DecreaseScrollSpeed:
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
- break;
+ return true;
}
- return base.OnPressed(e);
+ return false;
}
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) =>
- new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
- {
- TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
- };
-
- protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- new FruitCompositionTool(),
- new JuiceStreamCompositionTool(),
- new BananaShowerCompositionTool()
- };
+ }
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
{
@@ -171,8 +133,6 @@ namespace osu.Game.Rulesets.Catch.Edit
return result;
}
- protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
-
private PalpableCatchHitObject? getLastSnappableHitObject(double time)
{
var hitObject = EditorBeatmap.HitObjects.OfType().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
@@ -213,7 +173,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return null;
}
- double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(inputManager.CurrentState.Mouse.Position);
+ double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
return getLastSnappableHitObject(timeAtCursor);
default:
@@ -221,9 +181,16 @@ namespace osu.Game.Rulesets.Catch.Edit
}
}
+ protected override void Update()
+ {
+ base.Update();
+
+ updateDistanceSnapGrid();
+ }
+
private void updateDistanceSnapGrid()
{
- if (DistanceSnapToggle.Value != TernaryState.True)
+ if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True)
{
distanceSnapGrid.Hide();
return;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs
index e12181d051..9d88c90576 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Catch.Objects;
@@ -21,10 +20,8 @@ namespace osu.Game.Rulesets.Catch.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- drawableRuleset.Anchor = Anchor.Centre;
- drawableRuleset.Origin = Anchor.Centre;
-
- drawableRuleset.Scale = new Vector2(1, -1);
+ drawableRuleset.PlayfieldAdjustmentContainer.Scale = new Vector2(1, -1);
+ drawableRuleset.PlayfieldAdjustmentContainer.Y = 1 - drawableRuleset.PlayfieldAdjustmentContainer.Y;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 93eadcc13e..62fded0980 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Game.Rulesets.Mods;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps;
@@ -16,5 +17,13 @@ namespace osu.Game.Rulesets.Catch.Mods
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
catchProcessor.HardRockOffsets = true;
}
+
+ public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
+ {
+ base.ApplyToDifficulty(difficulty);
+
+ difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
+ difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index f4bd515995..b9fef6bf8c 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osuTK;
@@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Catch.Objects
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
- Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
+ Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize);
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 169e99c90c..fb1a86d8c0 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -28,17 +28,17 @@ namespace osu.Game.Rulesets.Catch.Objects
public int RepeatCount { get; set; }
- public BindableNumber SliderVelocityBindable { get; } = new BindableDouble(1)
+ public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1)
{
Precision = 0.01,
MinValue = 0.1,
MaxValue = 10
};
- public double SliderVelocity
+ public double SliderVelocityMultiplier
{
- get => SliderVelocityBindable.Value;
- set => SliderVelocityBindable.Value = value;
+ get => SliderVelocityMultiplierBindable.Value;
+ set => SliderVelocityMultiplierBindable.Value = value;
}
[JsonIgnore]
@@ -48,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Objects
private double tickDistanceFactor;
[JsonIgnore]
- public double Velocity => velocityFactor * SliderVelocity;
+ public double Velocity => velocityFactor * SliderVelocityMultiplier;
[JsonIgnore]
- public double TickDistance => tickDistanceFactor * SliderVelocity;
+ public double TickDistance => tickDistanceFactor * SliderVelocityMultiplier;
///
/// The length of one span of this .
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Objects
int nodeIndex = 0;
SliderEventDescriptor? lastEvent = null;
- foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
+ foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), cancellationToken))
{
// generate tiny droplets since the last point
if (lastEvent != null)
@@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- // this also includes LegacyLastTick and this is used for TinyDroplet generation above.
- // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied.
+ // this also includes LastTick and this is used for TinyDroplet generation above.
+ // this means that the final segment of TinyDroplets are increasingly mistimed where LastTick is being applied.
lastEvent = e;
switch (e.Type)
@@ -162,7 +162,5 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance => Path.Distance;
public IList> NodeSamples { get; set; } = new List>();
-
- public double? LegacyLastTickOffset { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
index 82374085c8..5a788a26fb 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
@@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon
[BackgroundDependencyLoader]
private void load()
{
- RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.Centre;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
index 72208b763b..bcd4c73f04 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultCatcher.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Catch.UI;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
@@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
public DefaultCatcher()
{
+ Anchor = Anchor.TopCentre;
RelativeSizeAxes = Axes.Both;
InternalChild = sprite = new Sprite
{
@@ -32,6 +34,15 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
};
}
+ protected override void Update()
+ {
+ base.Update();
+
+ // matches stable's origin position since we're using the same catcher sprite.
+ // see LegacyCatcher for more information.
+ OriginPosition = new Vector2(DrawWidth / 2, 16f);
+ }
+
[BackgroundDependencyLoader]
private void load(TextureStore store, Bindable currentState)
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs
index 26832b7271..ae530e94fc 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyBananaPiece.cs
@@ -2,17 +2,21 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Textures;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece
{
+ private static readonly Vector2 banana_max_size = new Vector2(160);
+
protected override void LoadComplete()
{
base.LoadComplete();
- Texture? texture = Skin.GetTexture("fruit-bananas");
- Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay");
+ Texture? texture = Skin.GetTexture("fruit-bananas")?.WithMaximumSize(banana_max_size);
+ Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay")?.WithMaximumSize(banana_max_size);
SetTexture(texture, overlayTexture);
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
index eba837a52d..f38b9b430e 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
@@ -2,8 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
///
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
///
- public partial class LegacyCatchComboCounter : CompositeDrawable, ICatchComboCounter
+ public partial class LegacyCatchComboCounter : UprightAspectMaintainingContainer, ICatchComboCounter
{
private readonly LegacyRollingCounter counter;
@@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
lastDisplayedCombo = combo;
- if (Time.Elapsed < 0)
+ if ((Clock as IGameplayClock)?.IsRewinding == true)
{
// needs more work to make rewind somehow look good.
// basically we want the previous increment to play... or turning off RemoveCompletedTransforms (not feasible from a performance angle).
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs
new file mode 100644
index 0000000000..6cd8b14191
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs
@@ -0,0 +1,33 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
+{
+ public abstract partial class LegacyCatcher : CompositeDrawable
+ {
+ protected LegacyCatcher()
+ {
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+
+ // in stable, catcher sprites are displayed in their raw size. stable also has catcher sprites displayed with the following scale factors applied:
+ // 1. 0.5x, affecting all sprites in the playfield, computed here based on lazer's catch playfield dimensions (see WIDTH/HEIGHT constants in CatchPlayfield),
+ // source: https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/GameplayElements/HitObjectManager.cs#L483-L494
+ // 2. 0.7x, a constant scale applied to all catcher sprites on construction.
+ AutoSizeAxes = Axes.Both;
+ Scale = new Vector2(0.5f * 0.7f);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // stable sets the Y origin position of the catcher to 16px in order for the catching range and OD scaling to align with the top of the catcher's plate in the default skin.
+ OriginPosition = new Vector2(DrawWidth / 2, 16f);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
index f6b2c52498..54d555b22a 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
@@ -7,14 +7,12 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
-using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
- public partial class LegacyCatcherNew : CompositeDrawable
+ public partial class LegacyCatcherNew : LegacyCatcher
{
[Resolved]
private Bindable currentState { get; set; } = null!;
@@ -23,25 +21,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
private Drawable currentDrawable = null!;
- public LegacyCatcherNew()
- {
- RelativeSizeAxes = Axes.Both;
- }
-
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
foreach (var state in Enum.GetValues())
{
- AddInternal(drawables[state] = getDrawableFor(state).With(d =>
- {
- d.Anchor = Anchor.TopCentre;
- d.Origin = Anchor.TopCentre;
- d.RelativeSizeAxes = Axes.Both;
- d.Size = Vector2.One;
- d.FillMode = FillMode.Fit;
- d.Alpha = 0;
- }));
+ AddInternal(drawables[state] = getDrawableFor(state).With(d => d.Alpha = 0));
}
currentDrawable = drawables[CatcherAnimationState.Idle];
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
index 1e21d8eab1..012200eedf 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherOld.cs
@@ -3,30 +3,21 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
- public partial class LegacyCatcherOld : CompositeDrawable
+ public partial class LegacyCatcherOld : LegacyCatcher
{
public LegacyCatcherOld()
{
- RelativeSizeAxes = Axes.Both;
+ AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
- InternalChild = (skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? Empty()).With(d =>
- {
- d.Anchor = Anchor.TopCentre;
- d.Origin = Anchor.TopCentre;
- d.RelativeSizeAxes = Axes.Both;
- d.Size = Vector2.One;
- d.FillMode = FillMode.Fit;
- });
+ InternalChild = skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? Empty();
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs
index 7ffd682698..a121d20d3d 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyDropletPiece.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Textures;
+using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece
{
+ private static readonly Vector2 droplet_max_size = new Vector2(160);
+
public LegacyDropletPiece()
{
Scale = new Vector2(0.8f);
@@ -17,8 +20,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
base.LoadComplete();
- Texture? texture = Skin.GetTexture("fruit-drop");
- Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay");
+ Texture? texture = Skin.GetTexture("fruit-drop")?.WithMaximumSize(droplet_max_size);
+ Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay")?.WithMaximumSize(droplet_max_size);
SetTexture(texture, overlayTexture);
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
index 85b60561dd..3a8b5b427a 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
@@ -2,11 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece
{
+ private static readonly Vector2 fruit_max_size = new Vector2(160);
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -22,21 +26,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (visualRepresentation)
{
case FruitVisualRepresentation.Pear:
- SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay"));
+ setTextures("pear");
break;
case FruitVisualRepresentation.Grape:
- SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay"));
+ setTextures("grapes");
break;
case FruitVisualRepresentation.Pineapple:
- SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay"));
+ setTextures("apple");
break;
case FruitVisualRepresentation.Raspberry:
- SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay"));
+ setTextures("orange");
break;
}
+
+ void setTextures(string fruitName) => SetTexture(
+ Skin.GetTexture($"fruit-{fruitName}")?.WithMaximumSize(fruit_max_size),
+ Skin.GetTexture($"fruit-{fruitName}-overlay")?.WithMaximumSize(fruit_max_size)
+ );
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
index 74cbc665c0..11531011ee 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
@@ -17,12 +17,13 @@ namespace osu.Game.Rulesets.Catch.UI
public CatchPlayfieldAdjustmentContainer()
{
- // because we are using centre anchor/origin, we will need to limit visibility in the future
- // to ensure tall windows do not get a readability advantage.
- // it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values
- // which are compatible with TopCentre alignment.
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+
+ // playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
+ // we can match that in lazer by using relative coordinates for Y and considering window height to be 1, and playfield height to be 0.8.
+ RelativePositionAxes = Axes.Y;
+ Y = (1 - playfield_size_adjust) / 4 * 3;
Size = new Vector2(playfield_size_adjust);
@@ -42,18 +43,28 @@ namespace osu.Game.Rulesets.Catch.UI
///
private partial class ScalingContainer : Container
{
+ public ScalingContainer()
+ {
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ }
+
protected override void Update()
{
base.Update();
- // in stable, fruit fall vertically from -100 to 340.
- // to emulate this, we want to make our playfield 440 gameplay pixels high.
- // we then offset it -100 vertically in the position set below.
- const float stable_v_offset_ratio = 440 / 384f;
+ // in stable, fruit fall vertically from 100 pixels above the playfield top down to the catcher's Y position (i.e. -100 to 340),
+ // see: https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/GameplayElements/HitObjects/Fruits/HitCircleFruits.cs#L65
+ // we already have the playfield positioned similar to stable (see CatchPlayfieldAdjustmentContainer constructor),
+ // so we only need to increase this container's height 100 pixels above the playfield, and offset it to have the bottom at 340 rather than 384.
+ const float stable_fruit_start_position = -100;
+ const float stable_catcher_y_position = 340;
+ const float playfield_v_size_adjustment = (stable_catcher_y_position - stable_fruit_start_position) / CatchPlayfield.HEIGHT;
+ const float playfield_v_catcher_offset = stable_catcher_y_position - CatchPlayfield.HEIGHT;
- Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
- Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X);
- Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale);
+ Scale = new Vector2(Parent!.ChildSize.X / CatchPlayfield.WIDTH);
+ Position = new Vector2(0f, playfield_v_catcher_offset * Scale.Y);
+ Size = Vector2.Divide(new Vector2(1, playfield_v_size_adjustment), Scale);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index f77dab56c8..0c2c157d10 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -29,6 +30,13 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// The size of the catcher at 1x scale.
///
+ ///
+ /// This is mainly used to compute catching range, the actual catcher size may differ based on skin implementation and sprite textures.
+ /// This is also equivalent to the "catcherWidth" property in osu-stable when the game field and beatmap difficulty are set to default values.
+ ///
+ ///
+ ///
+ ///
public const float BASE_SIZE = 106.75f;
///
@@ -175,11 +183,6 @@ namespace osu.Game.Rulesets.Catch.UI
///
public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
- ///
- /// Calculates the scale of the catcher based off the provided beatmap difficulty.
- ///
- private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
-
///
/// Calculates the width of the area used for attempting catches in gameplay.
///
@@ -464,6 +467,11 @@ namespace osu.Game.Rulesets.Catch.UI
d.Expire();
}
+ ///
+ /// Calculates the scale of the catcher based off the provided beatmap difficulty.
+ ///
+ private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize) * 2);
+
private enum DroppedObjectAnimation
{
Drop,
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 1b99270b65..567c288b47 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
@@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.X = Catcher.X;
- if (Time.Elapsed <= 0)
+ if ((Clock as IGameplayClock)?.IsRewinding == true)
{
// This is probably a wrong value, but currently the true value is not recorded.
// Setting `true` will prevent generation of false-positive after-images (with more false-negatives).
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index 7930a07551..f0a327d7ac 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Catch.UI
{
public partial class DrawableCatchRuleset : DrawableScrollingRuleset
{
- protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant;
-
protected override bool UserScrollSpeedAdjustment => false;
public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null)
@@ -30,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
Direction.Value = ScrollingDirection.Down;
TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
+ VisualisationMethod = ScrollVisualisationMethod.Constant;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
index bcc59a5e4f..107b79c88e 100644
--- a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
@@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
-using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -26,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.UI
: base(new CatchSkinComponentLookup(CatchSkinComponents.Catcher), _ => new DefaultCatcher())
{
Anchor = Anchor.TopCentre;
- // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
- OriginPosition = new Vector2(0.5f, 0.06f) * Catcher.BASE_SIZE;
+ Origin = Anchor.TopCentre;
+ CentreComponent = false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml
index 4a1545a423..f5a49210ea 100644
--- a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
index 789fc9e22d..518071fd49 100644
--- a/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
+++ b/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using Android.App;
using osu.Framework.Android;
using osu.Game.Tests;
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
index ff5dde856e..740036309f 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
@@ -5,7 +5,7 @@
CFBundleName
osu.Game.Rulesets.Mania.Tests.iOS
CFBundleIdentifier
- ppy.osu-Game-Rulesets-Mania-Tests-iOS
+ sh.ppy.mania-ruleset-tests
CFBundleShortVersionString
1.0
CFBundleVersion
@@ -42,4 +42,4 @@
CADisableMinimumFrameDurationOnPhone
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
index 2fda012f07..80e1b753ea 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -19,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
protected override Container Content => blueprints ?? base.Content;
- private readonly Container blueprints;
+ private readonly Container? blueprints;
[Cached(typeof(Playfield))]
public Playfield Playfield { get; }
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs
index 0a21098d0d..762238be47 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs
index 4b332c3faa..b79bcb7682 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs
index a65f949cec..c75095237e 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
index aca555552f..fbc0ed1785 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
index a1f4b234c4..8f623d1fc6 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
@@ -25,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
public partial class TestSceneManiaComposeScreen : EditorClockTestScene
{
[Resolved]
- private SkinManager skins { get; set; }
+ private SkinManager skins { get; set; } = null!;
[Cached]
private EditorClipboard clipboard = new EditorClipboard();
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs
index 86e87e7486..9d56d31329 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
index 4ae6cb9c7c..7b0171a9ee 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
index c85583c1fd..62591ce4ca 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -14,7 +12,8 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public abstract partial class ManiaInputTestScene : OsuTestScene
{
- private readonly Container content;
+ private readonly Container? content;
+
protected override Container Content => content ?? base.Content;
protected ManiaInputTestScene(int keys)
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
index 9dee861e66..cb2abc1595 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
@@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using NUnit.Framework;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
@@ -38,7 +37,8 @@ namespace osu.Game.Rulesets.Mania.Tests
new object[] { LegacyMods.Key3, new[] { typeof(ManiaModKey3) } },
new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } },
new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } },
- new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } }
+ new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } },
+ new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
};
[TestCaseSource(nameof(mania_mod_mapping))]
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
index 7d1a934456..641631d05e 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Replays;
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs
index 3bd654e75e..ff1f9e6894 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Beatmaps;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs
index dc4f660a45..474430414c 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
PassCondition = () =>
{
var hitObject = Player.ChildrenOfType().FirstOrDefault();
- return hitObject?.Dependencies.Get().Algorithm is ConstantScrollAlgorithm;
+ return hitObject?.Dependencies.Get().Algorithm.Value is ConstantScrollAlgorithm;
}
});
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs
new file mode 100644
index 0000000000..c717f03f51
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs
@@ -0,0 +1,74 @@
+// Copyright (c) ppy Pty Ltd . 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.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public partial class TestSceneManiaModDoubleTime : ModTestScene
+ {
+ private const double offset = 18;
+
+ protected override bool AllowFail => true;
+
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [Test]
+ public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData
+ {
+ PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
+ && Player.ScoreProcessor.Accuracy.Value == 1
+ && Player.ScoreProcessor.TotalScore.Value == 1_000_000,
+ Autoplay = false,
+ Beatmap = new Beatmap
+ {
+ BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
+ Difficulty = { OverallDifficulty = 10 },
+ HitObjects = new List
+ {
+ new Note { StartTime = 1000 }
+ },
+ },
+ ReplayFrames = new List
+ {
+ new ManiaReplayFrame(1000 + offset, ManiaAction.Key1)
+ }
+ });
+
+ [Test]
+ public void TestHitWindowWithDoubleTime()
+ {
+ var doubleTime = new ManiaModDoubleTime();
+
+ CreateModTest(new ModTestData
+ {
+ Mod = doubleTime,
+ PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
+ && Player.ScoreProcessor.Accuracy.Value == 1
+ && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_010 * doubleTime.ScoreMultiplier),
+ Autoplay = false,
+ Beatmap = new Beatmap
+ {
+ BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
+ Difficulty = { OverallDifficulty = 10 },
+ HitObjects = new List
+ {
+ new Note { StartTime = 1000 }
+ },
+ },
+ ReplayFrames = new List
+ {
+ new ManiaReplayFrame(1000 + offset, ManiaAction.Key1)
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
index 3011a93755..f5117b61af 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
@@ -6,7 +6,6 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Tests.Visual;
-using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -24,21 +23,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
Assert.False(testBeatmap.HitObjects.OfType().Any());
}
- [Test]
- public void TestCorrectNoteValues()
- {
- var testBeatmap = createRawBeatmap();
- var noteValues = new List(testBeatmap.HitObjects.OfType().Count());
-
- foreach (HoldNote h in testBeatmap.HitObjects.OfType())
- {
- noteValues.Add(ManiaModHoldOff.GetNoteDurationInBeatLength(h, testBeatmap));
- }
-
- noteValues.Sort();
- Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 });
- }
-
[Test]
public void TestCorrectObjectCount()
{
@@ -47,25 +31,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
var rawBeatmap = createRawBeatmap();
var testBeatmap = createModdedBeatmap();
- // Calculate expected number of objects
- int expectedObjectCount = 0;
-
- foreach (ManiaHitObject h in rawBeatmap.HitObjects)
- {
- // Both notes and hold notes account for at least one object
- expectedObjectCount++;
-
- if (h.GetType() == typeof(HoldNote))
- {
- double noteValue = ManiaModHoldOff.GetNoteDurationInBeatLength((HoldNote)h, rawBeatmap);
-
- if (noteValue >= ManiaModHoldOff.END_NOTE_ALLOW_THRESHOLD)
- {
- // Should generate an end note if it's longer than the minimum note value
- expectedObjectCount++;
- }
- }
- }
+ // Both notes and hold notes account for at least one object
+ int expectedObjectCount = rawBeatmap.HitObjects.Count;
Assert.That(testBeatmap.HitObjects.Count == expectedObjectCount);
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-0.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-0.png
new file mode 100644
index 0000000000..5044c2d15f
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-0.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-1.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-1.png
new file mode 100644
index 0000000000..0a3d614739
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-1.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-2.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-2.png
new file mode 100644
index 0000000000..f0aa0b3a02
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-2.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-3.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-3.png
new file mode 100644
index 0000000000..8f9155b8f7
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-light-3.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
index 7c51036d69..3a9d465f8d 100644
--- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -4,6 +4,7 @@ Version: 2.5
[Mania]
Keys: 4
ColumnLineWidth: 3,1,3,1,1
+LightFramePerSecond: 15
// some skins found in the wild had configuration keys where the @2x suffix was included in the values.
// the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything
// if @2x assets are present.
@@ -15,5 +16,6 @@ Hit300: mania/hit300@2x
Hit300g: mania/hit300g@2x
StageLeft: mania/stage-left
StageRight: mania/stage-right
+StageLight: mania/stage-light
NoteImage0L: LongNoteTailWang
NoteImage1L: LongNoteTailWang
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
index 0c55cebf0d..465d4a49f0 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
index 25e120edc5..dd494dfc82 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
index 30bd600d9d..abf01aa4a4 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -60,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
IBindable IScrollingInfo.Direction => Direction;
IBindable IScrollingInfo.TimeRange { get; } = new Bindable(5000);
- IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
+ IBindable IScrollingInfo.Algorithm { get; } = new Bindable(new ConstantScrollAlgorithm());
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
index 3881aae22e..47923d0733 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnBackground.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
index 9cccc2dd86..d4bbc8acb6 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneColumnHitObjectArea.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 2a9727dbd4..c993ba0e0a 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Extensions;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
index 30dd83123d..a0833ff91f 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -41,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
c.Add(hitExplosionPools[poolIndex].Get(e =>
{
- e.Apply(new JudgementResult(new HitObject(), runCount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
+ e.Apply(new JudgementResult(new HitObject(), new ManiaJudgement()));
e.Anchor = Anchor.Centre;
e.Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs
index 0b9ca42af8..a9d18ba401 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs
index d049d88ea8..2c978c1148 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneNote.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
index f85e303940..29c47ca93a 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -1,13 +1,12 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
+using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
@@ -25,22 +24,35 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
new StageDefinition(2)
};
- SetContents(_ => new ManiaPlayfield(stageDefinitions));
+ SetContents(_ => new ManiaInputManager(new ManiaRuleset().RulesetInfo, 2)
+ {
+ Child = new ManiaPlayfield(stageDefinitions)
+ });
});
}
- [Test]
- public void TestDualStages()
+ [TestCase(2)]
+ [TestCase(3)]
+ [TestCase(5)]
+ public void TestDualStages(int columnCount)
{
AddStep("create stage", () =>
{
stageDefinitions = new List
{
- new StageDefinition(2),
- new StageDefinition(2)
+ new StageDefinition(columnCount),
+ new StageDefinition(columnCount)
};
- SetContents(_ => new ManiaPlayfield(stageDefinitions));
+ SetContents(_ => new ManiaInputManager(new ManiaRuleset().RulesetInfo, (int)PlayfieldType.Dual + 2 * columnCount)
+ {
+ Child = new ManiaPlayfield(stageDefinitions)
+ {
+ // bit of a hack to make sure the dual stages fit on screen without overlapping each other.
+ Size = new Vector2(1.5f),
+ Scale = new Vector2(1 / 1.5f)
+ }
+ });
});
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
index 25e24929c9..d44a38fdec 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
index 0557a201c8..11c3ab3cd3 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI.Components;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
index 9fdd93bcc9..e3846e8213 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
index b96fab9ec0..cb9fcca5b0 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using NUnit.Framework;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 77db1b0bd8..044ce37832 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -54,7 +54,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
assertNoteJudgement(HitResult.IgnoreMiss);
}
@@ -73,7 +72,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Perfect);
assertNoteJudgement(HitResult.IgnoreHit);
}
@@ -92,7 +90,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss);
assertNoteJudgement(HitResult.IgnoreMiss);
}
@@ -111,7 +108,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
}
@@ -129,7 +125,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
}
@@ -149,7 +144,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss);
}
@@ -169,7 +163,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Perfect);
}
@@ -188,10 +181,33 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
}
+ ///
+ /// -----[ ]-----
+ /// xox o
+ ///
+ [Test]
+ public void TestPressAtStartThenReleaseAndImmediatelyRepress()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 1),
+ new ManiaReplayFrame(time_head + 2, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ // judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
+ assertComboAtJudgement(1, 1);
+ assertTailJudgement(HitResult.Meh);
+ assertComboAtJudgement(2, 0);
+ // judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
+ assertComboAtJudgement(4, 1);
+ }
+
///
/// -----[ ]-----
/// xo x o
@@ -208,7 +224,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss);
}
@@ -228,7 +243,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Meh);
}
@@ -246,7 +260,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss);
}
@@ -264,7 +277,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Meh);
}
@@ -358,7 +370,6 @@ namespace osu.Game.Rulesets.Mania.Tests
}, beatmap);
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
assertHitObjectJudgement(note, HitResult.Good);
@@ -371,7 +382,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[Test]
public void TestPressAndReleaseJustAfterTailWithNearbyNote()
{
- Note note;
+ // Next note within tail lenience
+ Note note = new Note { StartTime = time_tail + 50 };
var beatmap = new Beatmap
{
@@ -383,13 +395,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Duration = time_tail - time_head,
Column = 0,
},
- {
- // Next note within tail lenience
- note = new Note
- {
- StartTime = time_tail + 50
- }
- }
+ note
},
BeatmapInfo =
{
@@ -405,7 +411,6 @@ namespace osu.Game.Rulesets.Mania.Tests
}, beatmap);
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
assertHitObjectJudgement(note, HitResult.Great);
@@ -425,7 +430,6 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Miss);
- assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Meh);
}
@@ -476,42 +480,6 @@ namespace osu.Game.Rulesets.Mania.Tests
.All(j => j.Type.IsHit()));
}
- [Test]
- public void TestHitTailBeforeLastTick()
- {
- const int tick_rate = 8;
- const double tick_spacing = TimingControlPoint.DEFAULT_BEAT_LENGTH / tick_rate;
- const double time_last_tick = time_head + tick_spacing * (int)((time_tail - time_head) / tick_spacing - 1);
-
- var beatmap = new Beatmap
- {
- HitObjects =
- {
- new HoldNote
- {
- StartTime = time_head,
- Duration = time_tail - time_head,
- Column = 0,
- }
- },
- BeatmapInfo =
- {
- Difficulty = new BeatmapDifficulty { SliderTickRate = tick_rate },
- Ruleset = new ManiaRuleset().RulesetInfo
- },
- };
-
- performTest(new List
- {
- new ManiaReplayFrame(time_head, ManiaAction.Key1),
- new ManiaReplayFrame(time_last_tick - 5)
- }, beatmap);
-
- assertHeadJudgement(HitResult.Perfect);
- assertLastTickJudgement(HitResult.LargeTickMiss);
- assertTailJudgement(HitResult.Ok);
- }
-
[Test]
public void TestZeroLength()
{
@@ -551,11 +519,8 @@ namespace osu.Game.Rulesets.Mania.Tests
private void assertNoteJudgement(HitResult result)
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
- private void assertTickJudgement(HitResult result)
- => AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Select(j => j.Type), () => Does.Contain(result));
-
- private void assertLastTickJudgement(HitResult result)
- => AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(result));
+ private void assertComboAtJudgement(int judgementIndex, int combo)
+ => AddAssert($"combo at judgement {judgementIndex} is {combo}", () => judgementResults.ElementAt(judgementIndex).ComboAfterJudgement, () => Is.EqualTo(combo));
private ScoreAccessibleReplayPlayer currentPlayer = null!;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs
index 7021c081b7..36ecbdb098 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Reflection;
using NUnit.Framework;
using osu.Framework.IO.Stores;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
index 4e50fd924c..073bef5061 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs
new file mode 100644
index 0000000000..edf866952b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneMaximumScore.cs
@@ -0,0 +1,147 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public partial class TestSceneMaximumScore : RateAdjustedBeatmapTestScene
+ {
+ private ScoreAccessibleReplayPlayer currentPlayer = null!;
+
+ private List judgementResults = new List();
+
+ [Test]
+ public void TestSimultaneousTickAndNote()
+ {
+ performTest(
+ new List
+ {
+ new HoldNote
+ {
+ StartTime = 1000,
+ Duration = 2000,
+ Column = 0,
+ },
+ new Note
+ {
+ StartTime = 2000,
+ Column = 1
+ }
+ },
+ new List
+ {
+ new ManiaReplayFrame(1000, ManiaAction.Key1),
+ new ManiaReplayFrame(2000, ManiaAction.Key1, ManiaAction.Key2),
+ new ManiaReplayFrame(2001, ManiaAction.Key1),
+ new ManiaReplayFrame(3000)
+ });
+
+ AddAssert("all objects perfectly judged",
+ () => judgementResults.Select(result => result.Type),
+ () => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
+ AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_030));
+ }
+
+ [Test]
+ public void TestSimultaneousLongNotes()
+ {
+ performTest(
+ new List
+ {
+ new HoldNote
+ {
+ StartTime = 1000,
+ Duration = 2000,
+ Column = 0,
+ },
+ new HoldNote
+ {
+ StartTime = 2000,
+ Duration = 2000,
+ Column = 1
+ }
+ },
+ new List
+ {
+ new ManiaReplayFrame(1000, ManiaAction.Key1),
+ new ManiaReplayFrame(2000, ManiaAction.Key1, ManiaAction.Key2),
+ new ManiaReplayFrame(3000, ManiaAction.Key2),
+ new ManiaReplayFrame(4000)
+ });
+
+ AddAssert("all objects perfectly judged",
+ () => judgementResults.Select(result => result.Type),
+ () => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
+ AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_040));
+ }
+
+ private void performTest(List hitObjects, List frames)
+ {
+ var beatmap = new Beatmap
+ {
+ HitObjects = hitObjects,
+ BeatmapInfo =
+ {
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Ruleset = new ManiaRuleset().RulesetInfo
+ },
+ };
+
+ beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
+
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(beatmap);
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults.Add(result);
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+
+ AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
+ }
+
+ private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score, new PlayerConfiguration
+ {
+ AllowPause = false,
+ ShowResults = false,
+ })
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs
index f497c88bcc..2a8dc715f9 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayfieldCoveringContainer.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneScoring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneScoring.cs
new file mode 100644
index 0000000000..ae3ea861ea
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneScoring.cs
@@ -0,0 +1,175 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Judgements;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual.Gameplay;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public partial class TestSceneScoring : ScoringTestScene
+ {
+ protected override IBeatmap CreateBeatmap(int maxCombo)
+ {
+ var beatmap = new ManiaBeatmap(new StageDefinition(5));
+ for (int i = 0; i < maxCombo; ++i)
+ beatmap.HitObjects.Add(new Note());
+ return beatmap;
+ }
+
+ protected override IScoringAlgorithm CreateScoreV1() => new ScoreV1(MaxCombo.Value);
+ protected override IScoringAlgorithm CreateScoreV2(int maxCombo) => new ScoreV2(maxCombo);
+ protected override ProcessorBasedScoringAlgorithm CreateScoreAlgorithm(IBeatmap beatmap, ScoringMode mode) => new ManiaProcessorBasedScoringAlgorithm(beatmap, mode);
+
+ [Test]
+ public void TestBasicScenarios()
+ {
+ AddStep("set max combo to 100", () => MaxCombo.Value = 100);
+ AddStep("set perfect score", () =>
+ {
+ NonPerfectLocations.Clear();
+ MissLocations.Clear();
+ });
+ AddStep("set score with misses", () =>
+ {
+ NonPerfectLocations.Clear();
+ MissLocations.Clear();
+ MissLocations.AddRange(new[] { 24d, 49 });
+ });
+ AddStep("set score with misses and OKs", () =>
+ {
+ NonPerfectLocations.Clear();
+ MissLocations.Clear();
+
+ NonPerfectLocations.AddRange(new[] { 9d, 19, 29, 39, 59, 69, 79, 89, 99 });
+ MissLocations.AddRange(new[] { 24d, 49 });
+ });
+ }
+
+ private class ScoreV1 : IScoringAlgorithm
+ {
+ private int currentCombo;
+ private double comboAddition = 100;
+ private double totalScoreDouble;
+ private readonly double scoreMultiplier;
+
+ public ScoreV1(int maxCombo)
+ {
+ scoreMultiplier = 500000d / maxCombo;
+ }
+
+ public void ApplyHit() => applyHitV1(320, add => add + 2, 32);
+ public void ApplyNonPerfect() => applyHitV1(100, add => add - 24, 8);
+ public void ApplyMiss() => applyHitV1(0, _ => -56, 0);
+
+ private void applyHitV1(int scoreIncrease, Func comboAdditionFunc, int delta)
+ {
+ comboAddition = comboAdditionFunc(comboAddition);
+ if (currentCombo != 0 && currentCombo % 384 == 0)
+ comboAddition = 100;
+ comboAddition = Math.Max(0, Math.Min(comboAddition, 100));
+ double scoreIncreaseD = Math.Sqrt(comboAddition) * delta * scoreMultiplier / 320;
+
+ TotalScore = (long)totalScoreDouble;
+
+ scoreIncreaseD += scoreIncrease * scoreMultiplier / 320;
+ scoreIncrease = (int)scoreIncreaseD;
+
+ TotalScore += scoreIncrease;
+ totalScoreDouble += scoreIncreaseD;
+
+ if (scoreIncrease > 0)
+ currentCombo++;
+ }
+
+ public long TotalScore { get; private set; }
+ }
+
+ private class ScoreV2 : IScoringAlgorithm
+ {
+ private int currentCombo;
+ private double comboPortion;
+ private double currentBaseScore;
+ private double maxBaseScore;
+ private int currentHits;
+
+ private readonly double comboPortionMax;
+ private readonly int maxCombo;
+
+ private const double combo_base = 4;
+
+ public ScoreV2(int maxCombo)
+ {
+ this.maxCombo = maxCombo;
+
+ for (int i = 0; i < this.maxCombo; i++)
+ ApplyHit();
+
+ comboPortionMax = comboPortion;
+
+ currentCombo = 0;
+ comboPortion = 0;
+ currentBaseScore = 0;
+ maxBaseScore = 0;
+ currentHits = 0;
+ }
+
+ public void ApplyHit() => applyHitV2(305, 300);
+ public void ApplyNonPerfect() => applyHitV2(100, 100);
+
+ private void applyHitV2(int hitValue, int baseHitValue)
+ {
+ maxBaseScore += 305;
+ currentBaseScore += hitValue;
+ comboPortion += baseHitValue * Math.Min(Math.Max(0.5, Math.Log(++currentCombo, combo_base)), Math.Log(400, combo_base));
+
+ currentHits++;
+ }
+
+ public void ApplyMiss()
+ {
+ currentHits++;
+ maxBaseScore += 305;
+ currentCombo = 0;
+ }
+
+ public long TotalScore
+ {
+ get
+ {
+ float accuracy = (float)(currentBaseScore / maxBaseScore);
+
+ return (int)Math.Round
+ (
+ 200000 * comboPortion / comboPortionMax +
+ 800000 * Math.Pow(accuracy, 2 + 2 * accuracy) * ((double)currentHits / maxCombo)
+ );
+ }
+ }
+ }
+
+ private class ManiaProcessorBasedScoringAlgorithm : ProcessorBasedScoringAlgorithm
+ {
+ public ManiaProcessorBasedScoringAlgorithm(IBeatmap beatmap, ScoringMode mode)
+ : base(beatmap, mode)
+ {
+ }
+
+ protected override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
+
+ protected override JudgementResult CreatePerfectJudgementResult() => new JudgementResult(new Note(), new ManiaJudgement()) { Type = HitResult.Perfect };
+
+ protected override JudgementResult CreateNonPerfectJudgementResult() => new JudgementResult(new Note(), new ManiaJudgement()) { Type = HitResult.Ok };
+
+ protected override JudgementResult CreateMissJudgementResult() => new JudgementResult(new Note(), new ManiaJudgement()) { Type = HitResult.Miss };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
index 86499a7c6e..fee3ba3e39 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -117,18 +117,16 @@ namespace osu.Game.Rulesets.Mania.Tests
private void createBarLine(bool major)
{
- foreach (var stage in stages)
+ var obj = new BarLine
{
- var obj = new BarLine
- {
- StartTime = Time.Current + 2000,
- Major = major,
- };
+ StartTime = Time.Current + 2000,
+ Major = major,
+ };
- obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ foreach (var stage in stages)
stage.Add(obj);
- }
}
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index 027bf60a0c..b991db408c 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index b5655a4579..28cdf8907e 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 632b7cdcc7..aaef69f119 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
+using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Utils;
using osuTK;
@@ -43,39 +44,41 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
: base(beatmap, ruleset)
{
IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
+ TargetColumns = GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap));
- double roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize);
- double roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty);
-
- if (IsForCurrentRuleset)
+ if (IsForCurrentRuleset && TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
{
- TargetColumns = GetColumnCountForNonConvert(beatmap.BeatmapInfo);
-
- if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
- {
- TargetColumns /= 2;
- Dual = true;
- }
- }
- else
- {
- float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
- if (percentSliderOrSpinner < 0.2)
- TargetColumns = 7;
- else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
- TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6;
- else if (percentSliderOrSpinner > 0.6)
- TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4;
- else
- TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
+ TargetColumns /= 2;
+ Dual = true;
}
originalTargetColumns = TargetColumns;
}
- public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo)
+ public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty)
{
- double roundedCircleSize = Math.Round(beatmapInfo.Difficulty.CircleSize);
+ if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
+ return GetColumnCountForNonConvert(difficulty);
+
+ double roundedCircleSize = Math.Round(difficulty.CircleSize);
+ double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty);
+
+ int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount;
+ float percentSpecialObjects = (float)countSliderOrSpinner / difficulty.TotalObjectCount;
+
+ if (percentSpecialObjects < 0.2)
+ return 7;
+ if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5)
+ return roundedOverallDifficulty > 5 ? 7 : 6;
+ if (percentSpecialObjects > 0.6)
+ return roundedOverallDifficulty > 4 ? 5 : 4;
+
+ return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
+ }
+
+ public static int GetColumnCountForNonConvert(IBeatmapDifficultyInfo difficulty)
+ {
+ double roundedCircleSize = Math.Round(difficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize);
}
@@ -119,14 +122,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
yield return obj;
}
- private readonly List prevNoteTimes = new List(max_notes_for_density);
+ private readonly LimitedCapacityQueue prevNoteTimes = new LimitedCapacityQueue(max_notes_for_density);
private double density = int.MaxValue;
private void computeDensity(double newNoteTime)
{
- if (prevNoteTimes.Count == max_notes_for_density)
- prevNoteTimes.RemoveAt(0);
- prevNoteTimes.Add(newNoteTime);
+ prevNoteTimes.Enqueue(newNoteTime);
if (prevNoteTimes.Count >= 2)
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 91b7be6e8f..cce0944564 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -10,10 +10,11 @@ using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Audio;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Legacy;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
@@ -50,10 +51,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
double beatLength;
- if (hitObject.LegacyBpmMultiplier.HasValue)
- beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value;
- else if (hitObject is IHasSliderVelocity hasSliderVelocity)
- beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
+
+ if (hitObject is IHasSliderVelocity hasSliderVelocity)
+ beatLength = LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(hasSliderVelocity, timingPoint, ManiaRuleset.SHORT_NAME);
else
beatLength = timingPoint.BeatLength;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 630fdf7ae2..2265d3d347 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 912cac4fe4..27cb681300 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
index bf54dc3179..e4a28167ec 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
index 931673f337..3d3c35773b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
index 898b558eb3..48b3ce010f 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Game.Rulesets.Mania.UI;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
index d259c2af8e..db60e757e1 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
foreach (var v in base.ToDatabaseAttributes())
yield return v;
- yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
@@ -33,7 +32,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
base.FromDatabaseAttributes(values, onlineInfo);
- MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 63e61f17e3..6bb6879052 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -48,15 +46,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty
HitWindows hitWindows = new ManiaHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
- return new ManiaDifficultyAttributes
+ ManiaDifficultyAttributes attributes = new ManiaDifficultyAttributes
{
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
Mods = mods,
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
- MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
+ MaxCombo = beatmap.HitObjects.Sum(maxComboForObject),
};
+
+ return attributes;
}
private static int maxComboForObject(HitObject hitObject)
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs
new file mode 100644
index 0000000000..ddb4b868a3
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs
@@ -0,0 +1,66 @@
+// Copyright (c) ppy Pty Ltd . 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.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring.Legacy;
+
+namespace osu.Game.Rulesets.Mania.Difficulty
+{
+ internal class ManiaLegacyScoreSimulator : ILegacyScoreSimulator
+ {
+ public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
+ {
+ return new LegacyScoreAttributes { ComboScore = 1000000 };
+ }
+
+ public double GetLegacyScoreMultiplier(IReadOnlyList mods, LegacyBeatmapConversionDifficultyInfo difficulty)
+ {
+ bool scoreV2 = mods.Any(m => m is ModScoreV2);
+
+ double multiplier = 1.0;
+
+ foreach (var mod in mods)
+ {
+ switch (mod)
+ {
+ case ManiaModNoFail:
+ multiplier *= scoreV2 ? 1.0 : 0.5;
+ break;
+
+ case ManiaModEasy:
+ multiplier *= 0.5;
+ break;
+
+ case ManiaModHalfTime:
+ case ManiaModDaycore:
+ multiplier *= 0.5;
+ break;
+ }
+ }
+
+ if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
+ return multiplier;
+
+ // Apply key mod multipliers.
+
+ int originalColumns = ManiaBeatmapConverter.GetColumnCount(difficulty);
+ int actualColumns = originalColumns;
+
+ actualColumns = mods.OfType().SingleOrDefault()?.KeyCount ?? actualColumns;
+ if (mods.Any(m => m is ManiaModDualStages))
+ actualColumns *= 2;
+
+ if (actualColumns > originalColumns)
+ multiplier *= 0.9;
+ else if (actualColumns < originalColumns)
+ multiplier *= 0.9 - 0.04 * (originalColumns - actualColumns);
+
+ return multiplier;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
index 01474e6e00..64f8b026c2 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 440dec82af..d9f9479247 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs
index df95654319..a67d38b29f 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
index 2c7c84de97..a24fcaad8d 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty.Preprocessing;
@@ -16,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
private const double individual_decay_base = 0.125;
private const double overall_decay_base = 0.30;
- private const double release_threshold = 24;
+ private const double release_threshold = 30;
protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 1;
@@ -52,10 +50,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
for (int i = 0; i < endTimes.Length; ++i)
{
// The current note is overlapped if a previous note or end is overlapping the current note body
- isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1);
+ isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) &&
+ Precision.DefinitelyBigger(endTime, endTimes[i], 1) &&
+ Precision.DefinitelyBigger(startTime, startTimes[i], 1);
// We give a slight bonus to everything if something is held meanwhile
- if (Precision.DefinitelyBigger(endTimes[i], endTime, 1))
+ if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) &&
+ Precision.DefinitelyBigger(startTime, startTimes[i], 1))
holdFactor = 1.25;
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i]));
@@ -72,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
// 0.0 +--------+-+---------------> Release Difference / ms
// release_threshold
if (isOverlapping)
- holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime)));
+ holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime)));
// Decay and increase individualStrains in own column
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
diff --git a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
index 262247e244..e9d26b4aa1 100644
--- a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
+++ b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input.Bindings;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
index be1cc9a7fe..6a12ec5088 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
index ef7ce9073c..48dde29a9f 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index 381af8be7f..02ad1655b5 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -23,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private readonly EditNotePiece tailPiece;
[Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
+ private IScrollingInfo scrollingInfo { get; set; } = null!;
protected override bool IsValidForPlacement => HitObject.Duration > 0;
@@ -46,8 +44,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column != null)
{
- headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
- tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
+ headPiece.Y = Parent!.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
+ tailPiece.Y = Parent!.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
switch (scrollingInfo.Direction.Value)
{
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
index cf4bca0030..1ae65dd8c0 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit;
@@ -17,10 +15,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
where T : ManiaHitObject
{
[Resolved]
- private Playfield playfield { get; set; }
+ private Playfield playfield { get; set; } = null!;
[Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
+ private IScrollingInfo scrollingInfo { get; set; } = null!;
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
@@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
foreach (var child in InternalChildren)
child.Anchor = child.Origin = anchor;
- Position = Parent.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
+ Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
Width = HitObjectContainer.DrawWidth;
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
index d77abca350..b3ec3ef3e4 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
index a1392f09fa..01c7bd502a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs
index 4a070e70b4..8d34373f82 100644
--- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs
@@ -1,28 +1,37 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osuTK;
using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
- public partial class DrawableManiaEditorRuleset : DrawableManiaRuleset
+ public partial class DrawableManiaEditorRuleset : DrawableManiaRuleset, ISupportConstantAlgorithmToggle
{
+ public BindableBool ShowSpeedChanges { get; } = new BindableBool();
+
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods)
: base(ruleset, beatmap, mods)
{
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Sequential : ScrollVisualisationMethod.Constant, true);
+ }
+
protected override Playfield CreatePlayfield() => new ManiaEditorPlayfield(Beatmap.Stages)
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
index 960a08eeeb..99e1ce04b1 100644
--- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
+++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
index 2d4b5f718c..61c730912f 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
@@ -1,206 +1,23 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Caching;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.UI.Scrolling;
-using osu.Game.Screens.Edit;
-using osuTK.Graphics;
+using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Mania.Edit
{
- ///
- /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
- ///
- public partial class ManiaBeatSnapGrid : Component
+ public partial class ManiaBeatSnapGrid : BeatSnapGrid
{
- private const double visible_range = 750;
-
- ///
- /// The range of time values of the current selection.
- ///
- public (double start, double end)? SelectionTimeRange
+ protected override IEnumerable GetTargetContainers(HitObjectComposer composer)
{
- set
- {
- if (value == selectionTimeRange)
- return;
-
- selectionTimeRange = value;
- lineCache.Invalidate();
- }
- }
-
- [Resolved]
- private EditorBeatmap beatmap { get; set; }
-
- [Resolved]
- private OsuColour colours { get; set; }
-
- [Resolved]
- private BindableBeatDivisor beatDivisor { get; set; }
-
- private readonly List grids = new List();
-
- private readonly Cached lineCache = new Cached();
-
- private (double start, double end)? selectionTimeRange;
-
- [BackgroundDependencyLoader]
- private void load(HitObjectComposer composer)
- {
- foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages)
- {
- foreach (var column in stage.Columns)
- {
- var lineContainer = new ScrollingHitObjectContainer();
-
- grids.Add(lineContainer);
- column.UnderlayElements.Add(lineContainer);
- }
- }
-
- beatDivisor.BindValueChanged(_ => createLines(), true);
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (!lineCache.IsValid)
- {
- lineCache.Validate();
- createLines();
- }
- }
-
- private readonly Stack availableLines = new Stack();
-
- private void createLines()
- {
- foreach (var grid in grids)
- {
- foreach (var line in grid.Objects.OfType())
- availableLines.Push(line);
-
- grid.Clear();
- }
-
- if (selectionTimeRange == null)
- return;
-
- var range = selectionTimeRange.Value;
-
- var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
-
- double time = timingPoint.Time;
- int beat = 0;
-
- // progress time until in the visible range.
- while (time < range.start - visible_range)
- {
- time += timingPoint.BeatLength / beatDivisor.Value;
- beat++;
- }
-
- while (time < range.end + visible_range)
- {
- var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
-
- // switch to the next timing point if we have reached it.
- if (nextTimingPoint.Time > timingPoint.Time)
- {
- beat = 0;
- time = nextTimingPoint.Time;
- timingPoint = nextTimingPoint;
- }
-
- Color4 colour = BindableBeatDivisor.GetColourFor(
- BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours);
-
- foreach (var grid in grids)
- {
- if (!availableLines.TryPop(out var line))
- line = new DrawableGridLine();
-
- line.HitObject.StartTime = time;
- line.Colour = colour;
-
- grid.Add(line);
- }
-
- beat++;
- time += timingPoint.BeatLength / beatDivisor.Value;
- }
-
- foreach (var grid in grids)
- {
- // required to update ScrollingHitObjectContainer's cache.
- grid.UpdateSubTree();
-
- foreach (var line in grid.Objects.OfType())
- {
- time = line.HitObject.StartTime;
-
- if (time >= range.start && time <= range.end)
- line.Alpha = 1;
- else
- {
- double timeSeparation = time < range.start ? range.start - time : time - range.end;
- line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
- }
- }
- }
- }
-
- private partial class DrawableGridLine : DrawableHitObject
- {
- [Resolved]
- private IScrollingInfo scrollingInfo { get; set; }
-
- private readonly IBindable direction = new Bindable();
-
- public DrawableGridLine()
- : base(new HitObject())
- {
- RelativeSizeAxes = Axes.X;
- Height = 2;
-
- AddInternal(new Box { RelativeSizeAxes = Axes.Both });
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(onDirectionChanged, true);
- }
-
- private void onDirectionChanged(ValueChangedEvent direction)
- {
- Origin = Anchor = direction.NewValue == ScrollingDirection.Up
- ? Anchor.TopLeft
- : Anchor.BottomLeft;
- }
-
- protected override void UpdateInitialTransforms()
- {
- // don't perform any fading – we are handling that ourselves.
- LifetimeEnd = HitObject.StartTime + visible_range;
- }
+ return ((ManiaPlayfield)composer.Playfield)
+ .Stages
+ .SelectMany(stage => stage.Columns)
+ .Select(column => column.UnderlayElements);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
index 05d8ccc73f..d0eb8c1e6e 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
@@ -18,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
}
- public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
+ public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
{
switch (hitObject)
{
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs
index 0a697ca986..77e372d1d6 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using System.Collections.Generic;
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 5e577a2964..b9db4168f4 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -5,15 +5,12 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
@@ -21,35 +18,15 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
- public partial class ManiaHitObjectComposer : HitObjectComposer
+ public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer
{
private DrawableManiaEditorRuleset drawableRuleset;
- private ManiaBeatSnapGrid beatSnapGrid;
- private InputManager inputManager;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
{
}
- [BackgroundDependencyLoader]
- private void load()
- {
- AddInternal(beatSnapGrid = new ManiaBeatSnapGrid());
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- inputManager = GetContainingInputManager();
- }
-
- private DependencyContainer dependencies;
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
-
public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
@@ -57,48 +34,20 @@ namespace osu.Game.Rulesets.Mania.Edit
protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) =>
Playfield.GetColumnByPosition(screenSpacePosition);
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
- {
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) =>
drawableRuleset = new DrawableManiaEditorRuleset(ruleset, beatmap, mods);
- // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
- dependencies.CacheAs(drawableRuleset.ScrollingInfo);
-
- return drawableRuleset;
- }
-
protected override ComposeBlueprintContainer CreateBlueprintContainer()
=> new ManiaBlueprintContainer(this);
+ protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
+
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
new NoteCompositionTool(),
new HoldNoteCompositionTool()
};
- protected override void UpdateAfterChildren()
- {
- base.UpdateAfterChildren();
-
- if (BlueprintContainer.CurrentTool is SelectTool)
- {
- if (EditorBeatmap.SelectedHitObjects.Any())
- {
- beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
- }
- else
- beatSnapGrid.SelectionTimeRange = null;
- }
- else
- {
- var result = FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
- if (result.Time is double time)
- beatSnapGrid.SelectionTimeRange = (time, time);
- else
- beatSnapGrid.SelectionTimeRange = null;
- }
- }
-
public override string ConvertSelectionToString()
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}"));
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 5e6ae9bb11..8fdbada04f 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd