Merge branch 'master' into beat-divisor-select-text-on-click
@ -15,13 +15,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"codefilesanity": {
|
"codefilesanity": {
|
||||||
"version": "0.0.36",
|
"version": "0.0.37",
|
||||||
"commands": [
|
"commands": [
|
||||||
"CodeFileSanity"
|
"CodeFileSanity"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2022.809.0",
|
"version": "2023.712.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -9,6 +9,9 @@ indent_style = space
|
|||||||
indent_size = 2
|
indent_size = 2
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[g_*.cs]
|
||||||
|
generated_code = true
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
end_of_line = crlf
|
end_of_line = crlf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
@ -6,3 +6,5 @@
|
|||||||
212d78865a6b5f091173a347bad5686834d1d5fe
|
212d78865a6b5f091173a347bad5686834d1d5fe
|
||||||
# Add partial specs in mobile projects too
|
# Add partial specs in mobile projects too
|
||||||
00c11b2b4e389e48f3995d63484a6bc66a7afbdb
|
00c11b2b4e389e48f3995d63484a6bc66a7afbdb
|
||||||
|
# Mass NRT enabling
|
||||||
|
0ab0c52ad577b3e7b406d09fa6056a56ff997c3e
|
||||||
|
530
.github/workflows/diffcalc.yml
vendored
@ -1,206 +1,380 @@
|
|||||||
# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master.
|
# ## Description
|
||||||
# Usage:
|
|
||||||
# !pp check 0 | Runs only the osu! ruleset.
|
|
||||||
# !pp check 0 2 | Runs only the osu! and catch rulesets.
|
|
||||||
#
|
#
|
||||||
|
# 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:
|
on:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [ created ]
|
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:
|
env:
|
||||||
CONCURRENCY: 4
|
EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||||
ALLOW_DOWNLOAD: 1
|
|
||||||
SAVE_DOWNLOADED: 1
|
|
||||||
SKIP_INSERT_ATTRIBUTES: 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
metadata:
|
check-permissions:
|
||||||
name: Check for requests
|
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
|
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:
|
outputs:
|
||||||
matrix: ${{ steps.generate-matrix.outputs.matrix }}
|
GENERATOR_DIR: ${{ steps.set-outputs.outputs.GENERATOR_DIR }}
|
||||||
continue: ${{ steps.generate-matrix.outputs.continue }}
|
GENERATOR_ENV: ${{ steps.set-outputs.outputs.GENERATOR_ENV }}
|
||||||
|
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
||||||
steps:
|
steps:
|
||||||
- name: Construct build matrix
|
- name: Checkout diffcalc-sheet-generator
|
||||||
id: generate-matrix
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: ${{ env.EXECUTION_ID }}
|
||||||
|
repository: 'smoogipoo/diffcalc-sheet-generator'
|
||||||
|
|
||||||
|
- name: Set outputs
|
||||||
|
id: set-outputs
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ github.event.comment.body }}" =~ "osu" ]] ; then
|
echo "GENERATOR_DIR=${{ github.workspace }}/${{ env.EXECUTION_ID }}" >> "${GITHUB_OUTPUT}"
|
||||||
MATRIX_PROJECTS_JSON+='{ "name": "osu", "id": 0 },'
|
echo "GENERATOR_ENV=${{ github.workspace }}/${{ env.EXECUTION_ID }}/.env" >> "${GITHUB_OUTPUT}"
|
||||||
fi
|
echo "GOOGLE_CREDS_FILE=${{ github.workspace }}/${{ env.EXECUTION_ID }}/google-credentials.json" >> "${GITHUB_OUTPUT}"
|
||||||
if [[ "${{ github.event.comment.body }}" =~ "taiko" ]] ; then
|
|
||||||
MATRIX_PROJECTS_JSON+='{ "name": "taiko", "id": 1 },'
|
|
||||||
fi
|
|
||||||
if [[ "${{ github.event.comment.body }}" =~ "catch" ]] ; then
|
|
||||||
MATRIX_PROJECTS_JSON+='{ "name": "catch", "id": 2 },'
|
|
||||||
fi
|
|
||||||
if [[ "${{ github.event.comment.body }}" =~ "mania" ]] ; then
|
|
||||||
MATRIX_PROJECTS_JSON+='{ "name": "mania", "id": 3 },'
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${MATRIX_PROJECTS_JSON}" != "" ]]; then
|
environment:
|
||||||
MATRIX_JSON="{ \"ruleset\": [ ${MATRIX_PROJECTS_JSON} ] }"
|
name: Setup environment
|
||||||
echo "${MATRIX_JSON}"
|
needs: directory
|
||||||
CONTINUE="yes"
|
|
||||||
else
|
|
||||||
CONTINUE="no"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
|
|
||||||
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
|
|
||||||
diffcalc:
|
|
||||||
name: Run
|
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
timeout-minutes: 1440
|
env:
|
||||||
if: needs.metadata.outputs.continue == 'yes'
|
VARS_JSON: ${{ toJSON(vars) }}
|
||||||
needs: metadata
|
|
||||||
strategy:
|
|
||||||
matrix: ${{ fromJson(needs.metadata.outputs.matrix) }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Verify MySQL connection from host
|
- name: Add base environment
|
||||||
run: |
|
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
|
# Add Google credentials
|
||||||
run: |
|
echo '${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}' | base64 -d > "${{ needs.directory.outputs.GOOGLE_CREDS_FILE }}"
|
||||||
for db in osu_master osu_pr
|
|
||||||
do
|
# Add repository variables
|
||||||
mysql -e "DROP DATABASE IF EXISTS $db"
|
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
|
done
|
||||||
|
|
||||||
- name: Create directory structure
|
- name: Add pull-request environment
|
||||||
|
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p $GITHUB_WORKSPACE/master/
|
sed -i "s;^OSU_B=.*$;OSU_B=${{ github.event.issue.pull_request.html_url }};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
mkdir -p $GITHUB_WORKSPACE/pr/
|
|
||||||
|
|
||||||
- name: Get upstream branch # https://akaimo.hatenablog.jp/entry/2020/05/16/101251
|
- name: Add comment environment
|
||||||
id: upstreambranch
|
if: ${{ github.event_name == 'issue_comment' }}
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
# Add comment environment
|
||||||
echo "repo=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
echo '${{ github.event.comment.body }}' | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do
|
||||||
|
opt=$(echo ${line} | cut -d '=' -f1)
|
||||||
# Checkout osu
|
sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
- 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;'
|
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Run diffcalc (master)
|
- name: Add dispatch environment
|
||||||
env:
|
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
DB_NAME: osu_master
|
|
||||||
run: |
|
run: |
|
||||||
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator
|
sed -i 's;^OSU_B=.*$;OSU_B=${{ inputs.osu-b }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
|
sed -i 's/^RULESET=.*$/RULESET=${{ inputs.ruleset }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
- name: Run diffcalc (pr)
|
sed -i 's/^GENERATORS=.*$/GENERATORS=${{ inputs.generators }}/' "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
env:
|
|
||||||
DB_NAME: osu_pr
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator
|
|
||||||
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
|
|
||||||
|
|
||||||
- name: Print diffs
|
if [[ '${{ inputs.osu-a }}' != 'latest' ]]; then
|
||||||
run: |
|
sed -i 's;^OSU_A=.*$;OSU_A=${{ inputs.osu-a }};' "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
mysql -e "
|
fi
|
||||||
SELECT
|
|
||||||
m.beatmap_id,
|
|
||||||
m.mods,
|
|
||||||
b.filename,
|
|
||||||
m.diff_unified as 'sr_master',
|
|
||||||
p.diff_unified as 'sr_pr',
|
|
||||||
(p.diff_unified - m.diff_unified) as 'diff'
|
|
||||||
FROM osu_master.osu_beatmap_difficulty m
|
|
||||||
JOIN osu_pr.osu_beatmap_difficulty p
|
|
||||||
ON m.beatmap_id = p.beatmap_id
|
|
||||||
AND m.mode = p.mode
|
|
||||||
AND m.mods = p.mods
|
|
||||||
JOIN osu_pr.osu_beatmaps b
|
|
||||||
ON b.beatmap_id = p.beatmap_id
|
|
||||||
WHERE abs(m.diff_unified - p.diff_unified) > 0.1
|
|
||||||
ORDER BY abs(m.diff_unified - p.diff_unified)
|
|
||||||
DESC
|
|
||||||
LIMIT 10000;"
|
|
||||||
|
|
||||||
# Todo: Run ppcalc
|
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.
|
||||||
|
1
.gitignore
vendored
@ -339,6 +339,5 @@ inspectcode
|
|||||||
|
|
||||||
# Fody (pulled in by Realm) - schema file
|
# Fody (pulled in by Realm) - schema file
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
**/FodyWeavers.xml
|
|
||||||
|
|
||||||
.idea/.idea.osu.Desktop/.idea/misc.xml
|
.idea/.idea.osu.Desktop/.idea/misc.xml
|
41
README.md
@ -12,45 +12,48 @@
|
|||||||
|
|
||||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||||
|
|
||||||
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! 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
|
## 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.
|
A few resources are available as starting points to getting involved and understanding the project:
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||||
|
- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc).
|
||||||
|
|
||||||
## Running osu!
|
## 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) |
|
| [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.
|
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
|
## 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).
|
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
|
||||||
|
|
||||||
## Developing osu!
|
## Developing osu!
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
Please make sure you have the following prerequisites:
|
Please make sure you have the following prerequisites:
|
||||||
|
|
||||||
- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
|
- 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
|
### Downloading the source code
|
||||||
|
|
||||||
@ -69,9 +72,19 @@ git pull
|
|||||||
|
|
||||||
### Building
|
### 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:
|
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
|
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`.
|
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
|
### 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:
|
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:
|
||||||
|
@ -7,7 +7,7 @@ Templates for use when creating osu! dependent projects. Create a fully-testable
|
|||||||
```bash
|
```bash
|
||||||
# install (or update) templates package.
|
# install (or update) templates package.
|
||||||
# this only needs to be done once
|
# 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
|
# create an empty freeform ruleset
|
||||||
dotnet new ruleset -n MyCoolRuleset
|
dotnet new ruleset -n MyCoolRuleset
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
26
app.manifest
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
<assemblyIdentity version="1.0.0.0" name="osu!" />
|
<assemblyIdentity version="1.0.0.0" name="osu!" />
|
||||||
|
<SquirrelAwareVersion xmlns="urn:schema-squirrel-com:asm.v1">1</SquirrelAwareVersion>
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
@ -14,33 +15,10 @@
|
|||||||
</trustInfo>
|
</trustInfo>
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
<application>
|
<application>
|
||||||
<!-- Windows Vista -->
|
|
||||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
|
||||||
<!-- Windows 7 -->
|
|
||||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
|
||||||
<!-- Windows 8 -->
|
|
||||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
|
||||||
<!-- Windows 8.1 -->
|
<!-- Windows 8.1 -->
|
||||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||||
<!-- Windows 10 -->
|
<!-- Windows 10 and Windows 11 -->
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</compatibility>
|
||||||
<asmv3:application>
|
|
||||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
|
||||||
<dpiAware>true</dpiAware>
|
|
||||||
</asmv3:windowsSettings>
|
|
||||||
</asmv3:application>
|
|
||||||
<dependency>
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity
|
|
||||||
type="win32"
|
|
||||||
name="Microsoft.Windows.Common-Controls"
|
|
||||||
version="6.0.0.0"
|
|
||||||
processorArchitecture="*"
|
|
||||||
publicKeyToken="6595b64144ccf1df"
|
|
||||||
language="*"
|
|
||||||
/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</dependency>
|
|
||||||
</asmv1:assembly>
|
</asmv1:assembly>
|
||||||
|
@ -8,13 +8,9 @@
|
|||||||
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
|
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
|
||||||
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
|
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
|
||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.531.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1030.0" />
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
||||||
|
<!-- for editor usage -->
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
</manifest>
|
</manifest>
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -13,10 +11,10 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
public partial class GameplayScreenRotationLocker : Component
|
public partial class GameplayScreenRotationLocker : Component
|
||||||
{
|
{
|
||||||
private Bindable<bool> localUserPlaying;
|
private Bindable<bool> localUserPlaying = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameActivity gameActivity { get; set; }
|
private OsuGameActivity gameActivity { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGame game)
|
private void load(OsuGame game)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -15,6 +13,7 @@ using Android.Graphics;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using osu.Framework.Android;
|
using osu.Framework.Android;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using Debug = System.Diagnostics.Debug;
|
using Debug = System.Diagnostics.Debug;
|
||||||
using Uri = Android.Net.Uri;
|
using Uri = Android.Net.Uri;
|
||||||
@ -51,11 +50,11 @@ namespace osu.Android
|
|||||||
/// <remarks>Adjusted on startup to match expected UX for the current device type (phone/tablet).</remarks>
|
/// <remarks>Adjusted on startup to match expected UX for the current device type (phone/tablet).</remarks>
|
||||||
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
|
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
|
||||||
|
|
||||||
private OsuGameAndroid game;
|
private OsuGameAndroid game = null!;
|
||||||
|
|
||||||
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle? savedInstanceState)
|
||||||
{
|
{
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
|
|
||||||
@ -92,15 +91,15 @@ namespace osu.Android
|
|||||||
Assembly.Load("osu.Game.Rulesets.Mania");
|
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:
|
case Intent.ActionDefault:
|
||||||
if (intent.Scheme == ContentResolver.SchemeContent)
|
if (intent.Scheme == ContentResolver.SchemeContent)
|
||||||
handleImportFromUris(intent.Data);
|
handleImportFromUris(intent.Data.AsNonNull());
|
||||||
else if (osu_url_schemes.Contains(intent.Scheme))
|
else if (osu_url_schemes.Contains(intent.Scheme))
|
||||||
game.HandleLink(intent.DataString);
|
game.HandleLink(intent.DataString);
|
||||||
break;
|
break;
|
||||||
@ -114,7 +113,7 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
var content = intent.ClipData?.GetItemAt(i);
|
var content = intent.ClipData?.GetItemAt(i);
|
||||||
if (content != null)
|
if (content != null)
|
||||||
uris.Add(content.Uri);
|
uris.Add(content.Uri.AsNonNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImportFromUris(uris.ToArray());
|
handleImportFromUris(uris.ToArray());
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Microsoft.Maui.Devices;
|
using Microsoft.Maui.Devices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Android.Input;
|
using osu.Framework.Android.Input;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Input.Handlers;
|
using osu.Framework.Input.Handlers;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
@ -32,7 +31,7 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
get
|
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
|
try
|
||||||
{
|
{
|
||||||
@ -45,7 +44,7 @@ namespace osu.Android
|
|||||||
// Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060
|
// 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
|
// https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
|
||||||
string versionName = string.Empty;
|
string versionName;
|
||||||
|
|
||||||
if (OperatingSystem.IsAndroidVersionAtLeast(28))
|
if (OperatingSystem.IsAndroidVersionAtLeast(28))
|
||||||
{
|
{
|
||||||
@ -68,7 +67,7 @@ namespace osu.Android
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Version(packageInfo.VersionName);
|
return new Version(packageInfo.VersionName.AsNonNull());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="https" />
|
|
||||||
</intent>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="mailto" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
</manifest>
|
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Android;
|
using Android;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
|
||||||
|
@ -54,9 +54,6 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
client.OnReady += onReady;
|
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);
|
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
||||||
|
|
||||||
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
||||||
@ -187,7 +184,7 @@ namespace osu.Desktop
|
|||||||
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
||||||
|
|
||||||
case UserActivity.WatchingReplay watching:
|
case UserActivity.WatchingReplay watching:
|
||||||
return watching.BeatmapInfo.ToString();
|
return watching.BeatmapInfo?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
case UserActivity.InLobby lobby:
|
case UserActivity.InLobby lobby:
|
||||||
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Desktop.LegacyIpc
|
|||||||
case LegacyIpcDifficultyCalculationRequest req:
|
case LegacyIpcDifficultyCalculationRequest req:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile);
|
WorkingBeatmap beatmap = new FlatWorkingBeatmap(req.BeatmapFile);
|
||||||
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
|
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
|
||||||
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
|
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
|
||||||
|
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using osu.Desktop.Security;
|
using osu.Desktop.Security;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -17,9 +15,9 @@ using osu.Framework;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Desktop.Windows;
|
using osu.Desktop.Windows;
|
||||||
using osu.Framework.Threading;
|
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using SDL2;
|
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()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -130,60 +147,16 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
|
||||||
|
|
||||||
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
||||||
if (iconStream != null)
|
if (iconStream != null)
|
||||||
desktopWindow.SetIconFromStream(iconStream);
|
host.Window.SetIconFromStream(iconStream);
|
||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
host.Window.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
host.Window.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 });
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||||
|
|
||||||
private readonly List<string> importableFiles = new List<string>();
|
|
||||||
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)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -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)
|
if (!host.IsPrimaryInstance)
|
||||||
{
|
{
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<assemblyIdentity version="1.0.0.0" name="osu!" />
|
|
||||||
<SquirrelAwareVersion xmlns="urn:schema-squirrel-com:asm.v1">1</SquirrelAwareVersion>
|
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
|
||||||
<security>
|
|
||||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
|
||||||
</requestedPrivileges>
|
|
||||||
<applicationRequestMinimum>
|
|
||||||
<defaultAssemblyRequest permissionSetReference="Custom" />
|
|
||||||
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
|
|
||||||
</applicationRequestMinimum>
|
|
||||||
</security>
|
|
||||||
</trustInfo>
|
|
||||||
<asmv3:application>
|
|
||||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
|
||||||
<dpiAware>true</dpiAware>
|
|
||||||
</asmv3:windowsSettings>
|
|
||||||
</asmv3:application>
|
|
||||||
</asmv1:assembly>
|
|
@ -8,7 +8,6 @@
|
|||||||
<Title>osu!</Title>
|
<Title>osu!</Title>
|
||||||
<Product>osu!(lazer)</Product>
|
<Product>osu!(lazer)</Product>
|
||||||
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
|
||||||
<Version>0.0.0</Version>
|
<Version>0.0.0</Version>
|
||||||
<FileVersion>0.0.0</FileVersion>
|
<FileVersion>0.0.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -27,7 +26,7 @@
|
|||||||
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- using a different name because package name cannot contain 'catch' -->
|
<!-- using a different name because package name cannot contain 'catch' -->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch_Tests.Android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch_Tests.Android" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using osu.Framework.Android;
|
using osu.Framework.Android;
|
||||||
using osu.Game.Tests;
|
using osu.Game.Tests;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>osu.Game.Rulesets.Catch.Tests.iOS</string>
|
<string>osu.Game.Rulesets.Catch.Tests.iOS</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>ppy.osu-Game-Rulesets-Catch-Tests-iOS</string>
|
<string>sh.ppy.catch-ruleset-tests</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Difficulty;
|
using osu.Game.Rulesets.Catch.Difficulty;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
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.HalfTime, new[] { typeof(CatchModHalfTime) } },
|
||||||
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
|
new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
|
||||||
new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
|
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))]
|
[TestCaseSource(nameof(catch_mod_mapping))]
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
|
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
|
||||||
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
|
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
|
||||||
AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1]));
|
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]
|
[Test]
|
||||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
addPlacementSteps(times, positions);
|
addPlacementSteps(times, positions);
|
||||||
addPathCheckStep(times, positions);
|
addPathCheckStep(times, positions);
|
||||||
|
|
||||||
AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault);
|
AddAssert("slider velocity changed", () => !lastObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
double[] times = { 100, 300 };
|
double[] times = { 100, 300 };
|
||||||
float[] positions = { 200, 300 };
|
float[] positions = { 200, 300 };
|
||||||
addBlueprintStep(times, positions);
|
addBlueprintStep(times, positions);
|
||||||
AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault);
|
AddAssert("default slider velocity", () => hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||||
|
|
||||||
addDragStartStep(times[1], positions[1]);
|
addDragStartStep(times[1], positions[1]);
|
||||||
AddMouseMoveStep(times[1], 400);
|
AddMouseMoveStep(times[1], 400);
|
||||||
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault);
|
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
private float getCaughtObjectPosition(Fruit fruit)
|
private float getCaughtObjectPosition(Fruit fruit)
|
||||||
{
|
{
|
||||||
var caughtObject = catcher.ChildrenOfType<CaughtObject>().Single(c => c.HitObject == fruit);
|
var caughtObject = catcher.ChildrenOfType<CaughtObject>().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)
|
private void catchFruit(Fruit fruit, float x)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
@ -28,6 +26,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddSliderStep("start time", 500, 600, 0, x =>
|
AddSliderStep("start time", 500, 600, 0, x =>
|
||||||
{
|
{
|
||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = 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", () =>
|
AddStep("Initialize start time", () =>
|
||||||
{
|
{
|
||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
drawableFruit.RefreshStateTransforms();
|
||||||
|
drawableBanana.RefreshStateTransforms();
|
||||||
|
|
||||||
fruitRotation = drawableFruit.DisplayRotation;
|
fruitRotation = drawableFruit.DisplayRotation;
|
||||||
bananaRotation = drawableBanana.DisplayRotation;
|
bananaRotation = drawableBanana.DisplayRotation;
|
||||||
@ -56,6 +58,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddStep("change start time", () =>
|
AddStep("change start time", () =>
|
||||||
{
|
{
|
||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
||||||
|
drawableFruit.RefreshStateTransforms();
|
||||||
|
drawableBanana.RefreshStateTransforms();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
|
AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
|
||||||
@ -66,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddStep("reset start time", () =>
|
AddStep("reset start time", () =>
|
||||||
{
|
{
|
||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
drawableFruit.RefreshStateTransforms();
|
||||||
|
drawableBanana.RefreshStateTransforms();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("rotation and size restored", () =>
|
AddAssert("rotation and size restored", () =>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
public partial class TestSceneLegacyBeatmapSkin : LegacyBeatmapSkinColourTest
|
public partial class TestSceneLegacyBeatmapSkin : LegacyBeatmapSkinColourTest
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audio { get; set; }
|
private AudioManager audio { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
|
157
osu.Game.Rulesets.Catch.Tests/TestSceneScoring.cs
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using 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<double> 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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -41,9 +41,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
X = xPositionData?.X ?? 0,
|
X = xPositionData?.X ?? 0,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
|
|
||||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||||
SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1
|
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
case IHasDuration endTime:
|
case IHasDuration endTime:
|
||||||
|
@ -25,7 +25,10 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
@ -51,7 +54,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
new KeyBinding(InputKey.X, CatchAction.MoveRight),
|
new KeyBinding(InputKey.X, CatchAction.MoveRight),
|
||||||
new KeyBinding(InputKey.Right, CatchAction.MoveRight),
|
new KeyBinding(InputKey.Right, CatchAction.MoveRight),
|
||||||
new KeyBinding(InputKey.Shift, CatchAction.Dash),
|
new KeyBinding(InputKey.Shift, CatchAction.Dash),
|
||||||
new KeyBinding(InputKey.Shift, CatchAction.Dash),
|
new KeyBinding(InputKey.MouseLeft, CatchAction.Dash),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||||
@ -91,6 +94,9 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||||
yield return new CatchModRelax();
|
yield return new CatchModRelax();
|
||||||
|
|
||||||
|
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||||
|
yield return new ModScoreV2();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||||
@ -140,6 +146,12 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
new CatchModNoScope(),
|
new CatchModNoScope(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case ModType.System:
|
||||||
|
return new Mod[]
|
||||||
|
{
|
||||||
|
new ModScoreV2(),
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Array.Empty<Mod>();
|
return Array.Empty<Mod>();
|
||||||
}
|
}
|
||||||
@ -202,10 +214,24 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public int LegacyID => 2;
|
public int LegacyID => 2;
|
||||||
|
|
||||||
|
public ILegacyScoreSimulator CreateLegacyScoreSimulator() => new CatchLegacyScoreSimulator();
|
||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
|
||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||||
|
|
||||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
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
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
// Todo: osu!catch should not output star rating in the 'aim' attribute.
|
// Todo: osu!catch should not output star rating in the 'aim' attribute.
|
||||||
yield return (ATTRIB_ID_AIM, StarRating);
|
yield return (ATTRIB_ID_AIM, StarRating);
|
||||||
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
|
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
|
||||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||||
@ -36,7 +35,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
StarRating = values[ATTRIB_ID_AIM];
|
StarRating = values[ATTRIB_ID_AIM];
|
||||||
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
|
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
|
||||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// 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;
|
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,
|
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
|
192
osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.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<Mod> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
{
|
{
|
||||||
@ -24,7 +23,5 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
: base(new THitObject())
|
: base(new THitObject())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
public void UpdateHitObjectFromPath(JuiceStream hitObject)
|
public void UpdateHitObjectFromPath(JuiceStream hitObject)
|
||||||
{
|
{
|
||||||
// The SV setting may need to be changed for the current path.
|
// 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 svToVelocityFactor = hitObject.Velocity / svBindable.Value;
|
||||||
double requiredVelocity = path.ComputeRequiredVelocity();
|
double requiredVelocity = path.ComputeRequiredVelocity();
|
||||||
|
|
||||||
|
@ -1,180 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Caching;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
|
||||||
using osu.Game.Screens.Edit;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
/// <summary>
|
public partial class CatchBeatSnapGrid : BeatSnapGrid
|
||||||
/// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 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.
|
|
||||||
/// </remarks>
|
|
||||||
public partial class CatchBeatSnapGrid : Component
|
|
||||||
{
|
{
|
||||||
private const double visible_range = 750;
|
protected override IEnumerable<Container> GetTargetContainers(HitObjectComposer composer) => new[]
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The range of time values of the current selection.
|
|
||||||
/// </summary>
|
|
||||||
public (double start, double end)? SelectionTimeRange
|
|
||||||
{
|
{
|
||||||
set
|
((CatchPlayfield)composer.Playfield).UnderlayElements
|
||||||
{
|
};
|
||||||
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<DrawableGridLine> availableLines = new Stack<DrawableGridLine>();
|
|
||||||
|
|
||||||
private void createLines()
|
|
||||||
{
|
|
||||||
foreach (var line in lineContainer.Objects.OfType<DrawableGridLine>())
|
|
||||||
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<DrawableGridLine>())
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
base.Update();
|
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;
|
Height = 1 / Scale.Y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -20,27 +19,27 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public partial class CatchHitObjectComposer : DistancedHitObjectComposer<CatchHitObject>
|
public partial class CatchHitObjectComposer : ScrollingHitObjectComposer<CatchHitObject>, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
private const float distance_snap_radius = 50;
|
private const float distance_snap_radius = 50;
|
||||||
|
|
||||||
private CatchDistanceSnapGrid distanceSnapGrid = null!;
|
private CatchDistanceSnapGrid distanceSnapGrid = null!;
|
||||||
|
|
||||||
private InputManager inputManager = null!;
|
|
||||||
|
|
||||||
private CatchBeatSnapGrid beatSnapGrid = null!;
|
|
||||||
|
|
||||||
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
|
protected readonly CatchDistanceSnapProvider DistanceSnapProvider = new CatchDistanceSnapProvider();
|
||||||
|
|
||||||
public CatchHitObjectComposer(CatchRuleset ruleset)
|
public CatchHitObjectComposer(CatchRuleset ruleset)
|
||||||
: base(ruleset)
|
: base(ruleset)
|
||||||
{
|
{
|
||||||
@ -49,8 +48,11 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
AddInternal(DistanceSnapProvider);
|
||||||
|
DistanceSnapProvider.AttachToToolbox(RightToolbox);
|
||||||
|
|
||||||
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
// 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
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||||
{
|
{
|
||||||
@ -67,61 +69,30 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
||||||
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AddInternal(beatSnapGrid = new CatchBeatSnapGrid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
||||||
|
=> base.CreateTernaryButtons()
|
||||||
|
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
||||||
|
|
||||||
|
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) =>
|
||||||
|
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
|
||||||
|
};
|
||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
|
||||||
|
|
||||||
|
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
new FruitCompositionTool(),
|
||||||
|
new JuiceStreamCompositionTool(),
|
||||||
|
new BananaShowerCompositionTool()
|
||||||
|
};
|
||||||
|
|
||||||
if (BlueprintContainer.CurrentTool is SelectTool)
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
updateDistanceSnapGrid();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
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.
|
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
|
||||||
case GlobalAction.IncreaseScrollSpeed:
|
case GlobalAction.IncreaseScrollSpeed:
|
||||||
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
||||||
break;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.DecreaseScrollSpeed:
|
case GlobalAction.DecreaseScrollSpeed:
|
||||||
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnPressed(e);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) =>
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
|
|
||||||
{
|
{
|
||||||
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
|
}
|
||||||
};
|
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
|
||||||
{
|
|
||||||
new FruitCompositionTool(),
|
|
||||||
new JuiceStreamCompositionTool(),
|
|
||||||
new BananaShowerCompositionTool()
|
|
||||||
};
|
|
||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||||
{
|
{
|
||||||
@ -171,8 +133,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
|
|
||||||
|
|
||||||
private PalpableCatchHitObject? getLastSnappableHitObject(double time)
|
private PalpableCatchHitObject? getLastSnappableHitObject(double time)
|
||||||
{
|
{
|
||||||
var hitObject = EditorBeatmap.HitObjects.OfType<CatchHitObject>().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
|
var hitObject = EditorBeatmap.HitObjects.OfType<CatchHitObject>().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
|
||||||
@ -213,7 +173,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(inputManager.CurrentState.Mouse.Position);
|
double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||||
return getLastSnappableHitObject(timeAtCursor);
|
return getLastSnappableHitObject(timeAtCursor);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -221,9 +181,16 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
updateDistanceSnapGrid();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateDistanceSnapGrid()
|
private void updateDistanceSnapGrid()
|
||||||
{
|
{
|
||||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True)
|
||||||
{
|
{
|
||||||
distanceSnapGrid.Hide();
|
distanceSnapGrid.Hide();
|
||||||
return;
|
return;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -21,10 +20,8 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Anchor = Anchor.Centre;
|
drawableRuleset.PlayfieldAdjustmentContainer.Scale = new Vector2(1, -1);
|
||||||
drawableRuleset.Origin = Anchor.Centre;
|
drawableRuleset.PlayfieldAdjustmentContainer.Y = 1 - drawableRuleset.PlayfieldAdjustmentContainer.Y;
|
||||||
|
|
||||||
drawableRuleset.Scale = new Vector2(1, -1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
@ -16,5 +17,13 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
|
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
|
||||||
catchProcessor.HardRockOffsets = true;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
|
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
|
||||||
|
|
||||||
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
|
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
@ -28,17 +28,17 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int RepeatCount { get; set; }
|
public int RepeatCount { get; set; }
|
||||||
|
|
||||||
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
public BindableNumber<double> SliderVelocityMultiplierBindable { get; } = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
Precision = 0.01,
|
Precision = 0.01,
|
||||||
MinValue = 0.1,
|
MinValue = 0.1,
|
||||||
MaxValue = 10
|
MaxValue = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
public double SliderVelocity
|
public double SliderVelocityMultiplier
|
||||||
{
|
{
|
||||||
get => SliderVelocityBindable.Value;
|
get => SliderVelocityMultiplierBindable.Value;
|
||||||
set => SliderVelocityBindable.Value = value;
|
set => SliderVelocityMultiplierBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
@ -48,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
private double tickDistanceFactor;
|
private double tickDistanceFactor;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double Velocity => velocityFactor * SliderVelocity;
|
public double Velocity => velocityFactor * SliderVelocityMultiplier;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double TickDistance => tickDistanceFactor * SliderVelocity;
|
public double TickDistance => tickDistanceFactor * SliderVelocityMultiplier;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||||
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
int nodeIndex = 0;
|
int nodeIndex = 0;
|
||||||
SliderEventDescriptor? lastEvent = null;
|
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
|
// generate tiny droplets since the last point
|
||||||
if (lastEvent != null)
|
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 also includes LastTick 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 means that the final segment of TinyDroplets are increasingly mistimed where LastTick is being applied.
|
||||||
lastEvent = e;
|
lastEvent = e;
|
||||||
|
|
||||||
switch (e.Type)
|
switch (e.Type)
|
||||||
@ -162,7 +162,5 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public double Distance => Path.Distance;
|
public double Distance => Path.Distance;
|
||||||
|
|
||||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||||
|
|
||||||
public double? LegacyLastTickOffset { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
{
|
{
|
||||||
@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
|
|
||||||
public DefaultCatcher()
|
public DefaultCatcher()
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
InternalChild = sprite = new Sprite
|
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]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore store, Bindable<CatcherAnimationState> currentState)
|
private void load(TextureStore store, Bindable<CatcherAnimationState> currentState)
|
||||||
{
|
{
|
||||||
|
@ -2,17 +2,21 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
public partial class LegacyBananaPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 banana_max_size = new Vector2(160);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Texture? texture = Skin.GetTexture("fruit-bananas");
|
Texture? texture = Skin.GetTexture("fruit-bananas")?.WithMaximumSize(banana_max_size);
|
||||||
Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay");
|
Texture? overlayTexture = Skin.GetTexture("fruit-bananas-overlay")?.WithMaximumSize(banana_max_size);
|
||||||
|
|
||||||
SetTexture(texture, overlayTexture);
|
SetTexture(texture, overlayTexture);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
|
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class LegacyCatchComboCounter : CompositeDrawable, ICatchComboCounter
|
public partial class LegacyCatchComboCounter : UprightAspectMaintainingContainer, ICatchComboCounter
|
||||||
{
|
{
|
||||||
private readonly LegacyRollingCounter counter;
|
private readonly LegacyRollingCounter counter;
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
lastDisplayedCombo = combo;
|
lastDisplayedCombo = combo;
|
||||||
|
|
||||||
if (Time.Elapsed < 0)
|
if ((Clock as IGameplayClock)?.IsRewinding == true)
|
||||||
{
|
{
|
||||||
// needs more work to make rewind somehow look good.
|
// 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).
|
// basically we want the previous increment to play... or turning off RemoveCompletedTransforms (not feasible from a performance angle).
|
||||||
|
33
osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcher.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,14 +7,12 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public partial class LegacyCatcherNew : CompositeDrawable
|
public partial class LegacyCatcherNew : LegacyCatcher
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<CatcherAnimationState> currentState { get; set; } = null!;
|
private Bindable<CatcherAnimationState> currentState { get; set; } = null!;
|
||||||
@ -23,25 +21,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
private Drawable currentDrawable = null!;
|
private Drawable currentDrawable = null!;
|
||||||
|
|
||||||
public LegacyCatcherNew()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
foreach (var state in Enum.GetValues<CatcherAnimationState>())
|
foreach (var state in Enum.GetValues<CatcherAnimationState>())
|
||||||
{
|
{
|
||||||
AddInternal(drawables[state] = getDrawableFor(state).With(d =>
|
AddInternal(drawables[state] = getDrawableFor(state).With(d => d.Alpha = 0));
|
||||||
{
|
|
||||||
d.Anchor = Anchor.TopCentre;
|
|
||||||
d.Origin = Anchor.TopCentre;
|
|
||||||
d.RelativeSizeAxes = Axes.Both;
|
|
||||||
d.Size = Vector2.One;
|
|
||||||
d.FillMode = FillMode.Fit;
|
|
||||||
d.Alpha = 0;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDrawable = drawables[CatcherAnimationState.Idle];
|
currentDrawable = drawables[CatcherAnimationState.Idle];
|
||||||
|
@ -3,30 +3,21 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public partial class LegacyCatcherOld : CompositeDrawable
|
public partial class LegacyCatcherOld : LegacyCatcher
|
||||||
{
|
{
|
||||||
public LegacyCatcherOld()
|
public LegacyCatcherOld()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
InternalChild = (skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? Empty()).With(d =>
|
InternalChild = skin.GetAnimation(@"fruit-ryuuta", true, true, true) ?? Empty();
|
||||||
{
|
|
||||||
d.Anchor = Anchor.TopCentre;
|
|
||||||
d.Origin = Anchor.TopCentre;
|
|
||||||
d.RelativeSizeAxes = Axes.Both;
|
|
||||||
d.Size = Vector2.One;
|
|
||||||
d.FillMode = FillMode.Fit;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
public partial class LegacyDropletPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 droplet_max_size = new Vector2(160);
|
||||||
|
|
||||||
public LegacyDropletPiece()
|
public LegacyDropletPiece()
|
||||||
{
|
{
|
||||||
Scale = new Vector2(0.8f);
|
Scale = new Vector2(0.8f);
|
||||||
@ -17,8 +20,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Texture? texture = Skin.GetTexture("fruit-drop");
|
Texture? texture = Skin.GetTexture("fruit-drop")?.WithMaximumSize(droplet_max_size);
|
||||||
Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay");
|
Texture? overlayTexture = Skin.GetTexture("fruit-drop-overlay")?.WithMaximumSize(droplet_max_size);
|
||||||
|
|
||||||
SetTexture(texture, overlayTexture);
|
SetTexture(texture, overlayTexture);
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
{
|
{
|
||||||
internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
internal partial class LegacyFruitPiece : LegacyCatchHitObjectPiece
|
||||||
{
|
{
|
||||||
|
private static readonly Vector2 fruit_max_size = new Vector2(160);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -22,21 +26,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
switch (visualRepresentation)
|
switch (visualRepresentation)
|
||||||
{
|
{
|
||||||
case FruitVisualRepresentation.Pear:
|
case FruitVisualRepresentation.Pear:
|
||||||
SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay"));
|
setTextures("pear");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FruitVisualRepresentation.Grape:
|
case FruitVisualRepresentation.Grape:
|
||||||
SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay"));
|
setTextures("grapes");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FruitVisualRepresentation.Pineapple:
|
case FruitVisualRepresentation.Pineapple:
|
||||||
SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay"));
|
setTextures("apple");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FruitVisualRepresentation.Raspberry:
|
case FruitVisualRepresentation.Raspberry:
|
||||||
SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay"));
|
setTextures("orange");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setTextures(string fruitName) => SetTexture(
|
||||||
|
Skin.GetTexture($"fruit-{fruitName}")?.WithMaximumSize(fruit_max_size),
|
||||||
|
Skin.GetTexture($"fruit-{fruitName}-overlay")?.WithMaximumSize(fruit_max_size)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfieldAdjustmentContainer()
|
public CatchPlayfieldAdjustmentContainer()
|
||||||
{
|
{
|
||||||
// because we are using centre anchor/origin, we will need to limit visibility in the future
|
Anchor = Anchor.TopCentre;
|
||||||
// to ensure tall windows do not get a readability advantage.
|
Origin = Anchor.TopCentre;
|
||||||
// it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values
|
|
||||||
// which are compatible with TopCentre alignment.
|
// playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
|
||||||
Anchor = Anchor.Centre;
|
// 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.
|
||||||
Origin = Anchor.Centre;
|
RelativePositionAxes = Axes.Y;
|
||||||
|
Y = (1 - playfield_size_adjust) / 4 * 3;
|
||||||
|
|
||||||
Size = new Vector2(playfield_size_adjust);
|
Size = new Vector2(playfield_size_adjust);
|
||||||
|
|
||||||
@ -42,18 +43,28 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private partial class ScalingContainer : Container
|
private partial class ScalingContainer : Container
|
||||||
{
|
{
|
||||||
|
public ScalingContainer()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
// in stable, fruit fall vertically from -100 to 340.
|
// in stable, fruit fall vertically from 100 pixels above the playfield top down to the catcher's Y position (i.e. -100 to 340),
|
||||||
// to emulate this, we want to make our playfield 440 gameplay pixels high.
|
// see: https://github.com/peppy/osu-stable-reference/blob/1531237b63392e82c003c712faa028406073aa8f/osu!/GameplayElements/HitObjects/Fruits/HitCircleFruits.cs#L65
|
||||||
// we then offset it -100 vertically in the position set below.
|
// we already have the playfield positioned similar to stable (see CatchPlayfieldAdjustmentContainer constructor),
|
||||||
const float stable_v_offset_ratio = 440 / 384f;
|
// 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);
|
Scale = new Vector2(Parent!.ChildSize.X / CatchPlayfield.WIDTH);
|
||||||
Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X);
|
Position = new Vector2(0f, playfield_v_catcher_offset * Scale.Y);
|
||||||
Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale);
|
Size = Vector2.Divide(new Vector2(1, playfield_v_size_adjustment), Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -29,6 +30,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The size of the catcher at 1x scale.
|
/// The size of the catcher at 1x scale.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="CatchPlayfield.WIDTH"/>
|
||||||
|
/// <seealso cref="CatchPlayfield.HEIGHT"/>
|
||||||
|
/// <seealso cref="IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY"/>
|
||||||
public const float BASE_SIZE = 106.75f;
|
public const float BASE_SIZE = 106.75f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -175,11 +183,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
|
public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
|
||||||
/// </summary>
|
|
||||||
private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -464,6 +467,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
d.Expire();
|
d.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
|
/// </summary>
|
||||||
|
private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize) * 2);
|
||||||
|
|
||||||
private enum DroppedObjectAnimation
|
private enum DroppedObjectAnimation
|
||||||
{
|
{
|
||||||
Drop,
|
Drop,
|
||||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
comboDisplay.X = Catcher.X;
|
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.
|
// 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).
|
// Setting `true` will prevent generation of false-positive after-images (with more false-negatives).
|
||||||
|
@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public partial class DrawableCatchRuleset : DrawableScrollingRuleset<CatchHitObject>
|
public partial class DrawableCatchRuleset : DrawableScrollingRuleset<CatchHitObject>
|
||||||
{
|
{
|
||||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant;
|
|
||||||
|
|
||||||
protected override bool UserScrollSpeedAdjustment => false;
|
protected override bool UserScrollSpeedAdjustment => false;
|
||||||
|
|
||||||
public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||||
@ -30,6 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
Direction.Value = ScrollingDirection.Down;
|
Direction.Value = ScrollingDirection.Down;
|
||||||
TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
|
TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
|
||||||
|
VisualisationMethod = ScrollVisualisationMethod.Constant;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
@ -26,8 +25,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
: base(new CatchSkinComponentLookup(CatchSkinComponents.Catcher), _ => new DefaultCatcher())
|
: base(new CatchSkinComponentLookup(CatchSkinComponents.Catcher), _ => new DefaultCatcher())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
|
Origin = Anchor.TopCentre;
|
||||||
OriginPosition = new Vector2(0.5f, 0.06f) * Catcher.BASE_SIZE;
|
CentreComponent = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Mania.Tests.Android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Mania.Tests.Android" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!mania Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!mania Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using osu.Framework.Android;
|
using osu.Framework.Android;
|
||||||
using osu.Game.Tests;
|
using osu.Game.Tests;
|
||||||
|