diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md
deleted file mode 100644
index 7026179259..0000000000
--- a/.github/ISSUE_TEMPLATE/01-bug-issues.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-name: Bug Report
-about: Report a bug or crash to desktop
----
-
-
-
-
-**Describe the bug:**
-
-**Screenshots or videos showing encountered issue:**
-
-**osu!lazer version:**
-
-**Logs:**
-
-
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index c62231e8e0..47a6a4c3d3 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,12 +1,12 @@
blank_issues_enabled: false
contact_links:
- - name: Suggestions or feature request
- url: https://github.com/ppy/osu/discussions/categories/ideas
- about: Got something you think should change or be added? Search for or start a new discussion!
- name: Help
url: https://github.com/ppy/osu/discussions/categories/q-a
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
+ - name: Suggestions or feature request
+ url: https://github.com/ppy/osu/discussions/categories/ideas
+ about: Got something you think should change or be added? Search for or start a new discussion!
- name: osu!stable issues
url: https://github.com/ppy/osu-stable-issues
- about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
+ about: For osu!(stable) - ie. the current "live" game version, check out the dedicated repository. Note that this is for serious bug reports only, not tech support.
diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml
new file mode 100644
index 0000000000..842522ae87
--- /dev/null
+++ b/.github/workflows/diffcalc.yml
@@ -0,0 +1,199 @@
+# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master.
+# Usage:
+# !pp check 0 | Runs only the osu! ruleset.
+# !pp check 0 2 | Runs only the osu! and catch rulesets.
+#
+
+name: Difficulty Calculation
+on:
+ issue_comment:
+ types: [ created ]
+
+env:
+ CONCURRENCY: 4
+ ALLOW_DOWNLOAD: 1
+ SAVE_DOWNLOADED: 1
+ SKIP_INSERT_ATTRIBUTES: 1
+
+jobs:
+ metadata:
+ name: Check for requests
+ runs-on: self-hosted
+ if: github.event.issue.pull_request && contains(github.event.comment.body, '!pp check') && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
+ outputs:
+ matrix: ${{ steps.generate-matrix.outputs.matrix }}
+ continue: ${{ steps.generate-matrix.outputs.continue }}
+ steps:
+ - name: Construct build matrix
+ id: generate-matrix
+ run: |
+ if [[ "${{ github.event.comment.body }}" =~ "osu" ]] ; then
+ MATRIX_PROJECTS_JSON+='{ "name": "osu", "id": 0 },'
+ fi
+ if [[ "${{ github.event.comment.body }}" =~ "taiko" ]] ; then
+ MATRIX_PROJECTS_JSON+='{ "name": "taiko", "id": 1 },'
+ fi
+ if [[ "${{ github.event.comment.body }}" =~ "catch" ]] ; then
+ MATRIX_PROJECTS_JSON+='{ "name": "catch", "id": 2 },'
+ fi
+ if [[ "${{ github.event.comment.body }}" =~ "mania" ]] ; then
+ MATRIX_PROJECTS_JSON+='{ "name": "mania", "id": 3 },'
+ fi
+
+ if [[ "${MATRIX_PROJECTS_JSON}" != "" ]]; then
+ MATRIX_JSON="{ \"ruleset\": [ ${MATRIX_PROJECTS_JSON} ] }"
+ echo "${MATRIX_JSON}"
+ CONTINUE="yes"
+ else
+ CONTINUE="no"
+ fi
+
+ echo "::set-output name=continue::${CONTINUE}"
+ echo "::set-output name=matrix::${MATRIX_JSON}"
+ diffcalc:
+ name: Run
+ runs-on: self-hosted
+ if: needs.metadata.outputs.continue == 'yes'
+ needs: metadata
+ strategy:
+ matrix: ${{ fromJson(needs.metadata.outputs.matrix) }}
+ steps:
+ - name: Verify MySQL connection from host
+ run: |
+ mysql -e "SHOW DATABASES"
+
+ - name: Drop previous databases
+ run: |
+ for db in osu_master osu_pr
+ do
+ mysql -e "DROP DATABASE IF EXISTS $db"
+ done
+
+ - name: Create directory structure
+ run: |
+ mkdir -p $GITHUB_WORKSPACE/master/
+ mkdir -p $GITHUB_WORKSPACE/pr/
+
+ # Checkout osu
+ - name: Checkout osu (master)
+ uses: actions/checkout@v2
+ with:
+ repository: peppy/osu
+ ref: 'diffcalc-optimisations'
+ path: 'master/osu'
+ - name: Checkout osu (pr)
+ uses: actions/checkout@v2
+ with:
+ path: 'pr/osu'
+
+ - name: Checkout osu-difficulty-calculator (master)
+ uses: actions/checkout@v2
+ with:
+ repository: peppy/osu-difficulty-calculator
+ ref: 'bypass-attrib-row-insert'
+ path: 'master/osu-difficulty-calculator'
+ - name: Checkout osu-difficulty-calculator (pr)
+ uses: actions/checkout@v2
+ with:
+ repository: peppy/osu-difficulty-calculator
+ ref: 'bypass-attrib-row-insert'
+ path: 'pr/osu-difficulty-calculator'
+
+ - name: Install .NET 5.0.x
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: "5.0.x"
+
+ # Sanity checks to make sure diffcalc is not run when incompatible.
+ - name: Build diffcalc (master)
+ run: |
+ cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator
+ ./UseLocalOsu.sh
+ dotnet build
+ - name: Build diffcalc (pr)
+ run: |
+ cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator
+ ./UseLocalOsu.sh
+ dotnet build
+
+ - name: Download + import data
+ run: |
+ PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top_1000 | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
+ BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
+
+ # Set env variable for further steps.
+ echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV
+
+ cd $GITHUB_WORKSPACE
+
+ echo "Downloading database dump $PERFORMANCE_DATA_NAME.."
+ wget -q -nc https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2
+ echo "Extracting.."
+ tar -xf $PERFORMANCE_DATA_NAME.tar.bz2
+
+ echo "Downloading beatmap dump $BEATMAPS_DATA_NAME.."
+ wget -q -nc https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2
+ echo "Extracting.."
+ tar -xf $BEATMAPS_DATA_NAME.tar.bz2
+
+ cd $PERFORMANCE_DATA_NAME
+
+ for db in osu_master osu_pr
+ do
+ echo "Setting up database $db.."
+
+ mysql -e "CREATE DATABASE $db"
+
+ echo "Importing beatmaps.."
+ cat osu_beatmaps.sql | mysql $db
+ echo "Importing beatmapsets.."
+ cat osu_beatmapsets.sql | mysql $db
+
+ echo "Creating table structure.."
+ mysql $db -e 'CREATE TABLE `osu_beatmap_difficulty` (
+ `beatmap_id` int unsigned NOT NULL,
+ `mode` tinyint NOT NULL DEFAULT 0,
+ `mods` int unsigned NOT NULL,
+ `diff_unified` float NOT NULL,
+ `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`beatmap_id`,`mode`,`mods`),
+ KEY `diff_sort` (`mode`,`mods`,`diff_unified`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;'
+ done
+
+ - name: Run diffcalc (master)
+ env:
+ DB_NAME: osu_master
+ run: |
+ cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator
+ dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
+ - name: Run diffcalc (pr)
+ env:
+ DB_NAME: osu_pr
+ run: |
+ cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator
+ dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
+
+ - name: Print diffs
+ run: |
+ mysql -e "
+ SELECT
+ m.beatmap_id,
+ m.mods,
+ b.filename,
+ m.diff_unified as 'sr_master',
+ p.diff_unified as 'sr_pr',
+ (p.diff_unified - m.diff_unified) as 'diff'
+ FROM osu_master.osu_beatmap_difficulty m
+ JOIN osu_pr.osu_beatmap_difficulty p
+ ON m.beatmap_id = p.beatmap_id
+ AND m.mode = p.mode
+ AND m.mods = p.mods
+ JOIN osu_pr.osu_beatmaps b
+ ON b.beatmap_id = p.beatmap_id
+ WHERE abs(m.diff_unified - p.diff_unified) > 0.1
+ ORDER BY abs(m.diff_unified - p.diff_unified)
+ DESC
+ LIMIT 10000;"
+
+ # Todo: Run ppcalc
diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml
deleted file mode 100644
index 4274d01bab..0000000000
--- a/.github/workflows/test-diffcalc.yml
+++ /dev/null
@@ -1,163 +0,0 @@
-# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master.
-# Usage:
-# !pp check 0 | Runs only the osu! ruleset.
-# !pp check 0 2 | Runs only the osu! and catch rulesets.
-#
-
-name: Diffcalc Consistency Checks
-on:
- issue_comment:
- types: [ created ]
-
-env:
- DB_USER: root
- DB_HOST: 127.0.0.1
- CONCURRENCY: 4
- ALLOW_DOWNLOAD: 1
- SAVE_DOWNLOADED: 1
-
-jobs:
- diffcalc:
- name: Diffcalc
- runs-on: ubuntu-latest
- continue-on-error: true
-
- if: |
- github.event.issue.pull_request &&
- contains(github.event.comment.body, '!pp check') &&
- (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
-
- strategy:
- fail-fast: false
- matrix:
- ruleset:
- - { name: osu, id: 0 }
- - { name: taiko, id: 1 }
- - { name: catch, id: 2 }
- - { name: mania, id: 3 }
-
- services:
- mysql:
- image: mysql:8.0
- env:
- MYSQL_ALLOW_EMPTY_PASSWORD: yes
- ports:
- - 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
-
- steps:
- - name: Verify ruleset
- if: contains(github.event.comment.body, matrix.ruleset.id) == false
- run: |
- echo "${{ github.event.comment.body }} doesn't contain ${{ matrix.ruleset.id }}"
- exit 1
-
- - name: Verify MySQL connection from host
- run: |
- sudo apt-get install -y mysql-client
- mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "SHOW DATABASES"
-
- - name: Create directory structure
- run: |
- mkdir -p $GITHUB_WORKSPACE/master/
- mkdir -p $GITHUB_WORKSPACE/pr/
-
- # Checkout osu
- - name: Checkout osu (master)
- uses: actions/checkout@v2
- with:
- repository: ppy/osu
- path: 'master/osu'
- - name: Checkout osu (pr)
- uses: actions/checkout@v2
- with:
- path: 'pr/osu'
-
- # Checkout osu-difficulty-calculator
- - name: Checkout osu-difficulty-calculator (master)
- uses: actions/checkout@v2
- with:
- repository: ppy/osu-difficulty-calculator
- path: 'master/osu-difficulty-calculator'
- - name: Checkout osu-difficulty-calculator (pr)
- uses: actions/checkout@v2
- with:
- repository: ppy/osu-difficulty-calculator
- path: 'pr/osu-difficulty-calculator'
-
- - name: Install .NET 5.0.x
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "5.0.x"
-
- # Sanity checks to make sure diffcalc is not run when incompatible.
- - name: Build diffcalc (master)
- run: |
- cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator
- ./UseLocalOsu.sh
- dotnet build
- - name: Build diffcalc (pr)
- run: |
- cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator
- ./UseLocalOsu.sh
- dotnet build
-
- # Initial data imports
- - name: Download + import data
- run: |
- PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
- BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
-
- # Set env variable for further steps.
- echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV
-
- cd $GITHUB_WORKSPACE
-
- wget https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2
- wget https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2
- tar -xf $PERFORMANCE_DATA_NAME.tar.bz2
- tar -xf $BEATMAPS_DATA_NAME.tar.bz2
-
- cd $GITHUB_WORKSPACE/$PERFORMANCE_DATA_NAME
-
- mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_master"
- mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_pr"
-
- cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_master
- cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_pr
-
- # Run diffcalc
- - name: Run diffcalc (master)
- env:
- DB_NAME: osu_master
- run: |
- cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator
- dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
- - name: Run diffcalc (pr)
- env:
- DB_NAME: osu_pr
- run: |
- cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator
- dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
-
- # Print diffs
- - name: Print diffs
- run: |
- mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "
- SELECT
- m.beatmap_id,
- m.mods,
- m.diff_unified as 'sr_master',
- p.diff_unified as 'sr_pr',
- (p.diff_unified - m.diff_unified) as 'diff'
- FROM osu_master.osu_beatmap_difficulty m
- JOIN osu_pr.osu_beatmap_difficulty p
- ON m.beatmap_id = p.beatmap_id
- AND m.mode = p.mode
- AND m.mods = p.mods
- WHERE abs(m.diff_unified - p.diff_unified) > 0.1
- ORDER BY abs(m.diff_unified - p.diff_unified)
- DESC
- LIMIT 10000;"
-
- # Todo: Run ppcalc
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/indexLayout.xml b/.idea/.idea.osu/.idea/indexLayout.xml
index 27ba142e96..7b08163ceb 100644
--- a/.idea/.idea.osu/.idea/indexLayout.xml
+++ b/.idea/.idea.osu/.idea/indexLayout.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
index 9c512a01ea..536fdfc6df 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
- OsuGame game = new OsuGame();
- game.SetHost(host);
-
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
- game
};
+
+ AddGame(new OsuGame());
}
}
}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
index 270d906b01..3cdf44e6f1 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
- OsuGame game = new OsuGame();
- game.SetHost(host);
-
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
- game
};
+
+ AddGame(new OsuGame());
}
}
}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
index aed6abb6bf..4d3f5086d9 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
- OsuGame game = new OsuGame();
- game.SetHost(host);
-
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
- game
};
+
+ AddGame(new OsuGame());
}
}
}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
index 270d906b01..3cdf44e6f1 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
@@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase)
{
- OsuGame game = new OsuGame();
- game.SetHost(host);
-
Children = new Drawable[]
{
new Box
@@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
- game
};
+
+ AddGame(new OsuGame());
}
}
}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs
index dd0a20f1b4..98dba622d0 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osuTK;
@@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Pippidon.UI
}
}
- public bool OnPressed(PippidonAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PippidonAction.MoveUp:
changeLane(-1);
@@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Pippidon.UI
}
}
- public void OnReleased(PippidonAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Android.props b/osu.Android.props
index d4331a5e65..4859510e6c 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 73b60f51a4..d0a94767d1 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -44,9 +44,9 @@ namespace osu.Game.Rulesets.Catch.Mods
}
// disable keyboard controls
- public bool OnPressed(CatchAction action) => true;
+ public bool OnPressed(KeyBindingPressEvent e) => true;
- public void OnReleased(CatchAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index b30c3d82a4..604e878782 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
@@ -144,9 +145,9 @@ namespace osu.Game.Rulesets.Catch.UI
Catcher.VisualDirection = Direction.Left;
}
- public bool OnPressed(CatchAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case CatchAction.MoveLeft:
currentDirection--;
@@ -164,9 +165,9 @@ namespace osu.Game.Rulesets.Catch.UI
return false;
}
- public void OnReleased(CatchAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- switch (action)
+ switch (e.Action)
{
case CatchAction.MoveLeft:
currentDirection++;
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-0@2x.png
new file mode 100644
index 0000000000..2db5d76e78
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-1@2x.png
new file mode 100644
index 0000000000..6e7aded39f
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1-1@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-0@2x.png
new file mode 100644
index 0000000000..f11fb4f853
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-1@2x.png
new file mode 100644
index 0000000000..4eac5f6f2a
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note1H-1@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-0@2x.png
new file mode 100644
index 0000000000..456cee5382
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-1@2x.png
new file mode 100644
index 0000000000..71a09cb4bb
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2-1@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2H@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2H@2x.png
new file mode 100644
index 0000000000..e6da7a1055
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-note2H@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteS@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteS@2x.png
new file mode 100644
index 0000000000..c9bc23e8d9
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteS@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteSH@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteSH@2x.png
new file mode 100644
index 0000000000..c9bc23e8d9
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/metrics-skin/mania-noteSH@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs
index 4a6c59e297..92c95b8fde 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs
@@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("Hold key", () =>
{
clock.CurrentTime = 0;
- note.OnPressed(ManiaAction.Key1);
+ note.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, ManiaAction.Key1));
});
AddStep("progress time", () => clock.CurrentTime = 500);
AddAssert("head is visible", () => note.Head.Alpha == 1);
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 2923a2af2f..4e9781f336 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -253,12 +254,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldBrokenTime = Time.Current;
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (AllJudged)
return false;
- if (action != Action.Value)
+ if (e.Action != Action.Value)
return false;
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
@@ -288,12 +289,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
isHitting.Value = true;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
if (AllJudged)
return;
- if (action != Action.Value)
+ if (e.Action != Action.Value)
return;
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
index 8458345998..6722ad8ab8 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
@@ -43,9 +44,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// it will be hidden along with its parenting hold note when required.
}
- public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+ public override bool OnPressed(KeyBindingPressEvent e) => false; // Handled by the hold note
- public override void OnReleased(ManiaAction action)
+ public override void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
index 18aa3f66d4..803685363c 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
@@ -68,9 +69,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
- public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+ public override bool OnPressed(KeyBindingPressEvent e) => false; // Handled by the hold note
- public override void OnReleased(ManiaAction action)
+ public override void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index d53c28868d..51727908c9 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Configuration;
@@ -97,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
ApplyResult(r => r.Type = result);
}
- public virtual bool OnPressed(ManiaAction action)
+ public virtual bool OnPressed(KeyBindingPressEvent e)
{
- if (action != Action.Value)
+ if (e.Action != Action.Value)
return false;
if (CheckHittable?.Invoke(this, Time.Current) == false)
@@ -108,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return UpdateResult(true);
}
- public virtual void OnReleased(ManiaAction action)
+ public virtual void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs
index 661e7f66f4..54ddcbd5fe 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyColumnBackground.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
@@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
}
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == Column.Action.Value)
+ if (e.Action == Column.Action.Value)
{
light.FadeIn();
light.ScaleTo(Vector2.One);
@@ -87,12 +88,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return false;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
// Todo: Should be 400 * 100 / CurrentBPM
const double animation_length = 250;
- if (action == Column.Action.Value)
+ if (e.Action == Column.Action.Value)
{
light.FadeTo(0, animation_length);
light.ScaleTo(new Vector2(1, 0), animation_length);
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs
index 21e5bdd5d6..1e75533442 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteHeadPiece.cs
@@ -1,18 +1,18 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyHoldNoteHeadPiece : LegacyNotePiece
{
- protected override Texture GetTexture(ISkinSource skin)
+ protected override Drawable GetAnimation(ISkinSource skin)
{
// TODO: Should fallback to the head from default legacy skin instead of note.
- return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
- ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
+ return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
+ ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs
index 232b47ae27..e6d4291d79 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHoldNoteTailPiece.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
-using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
@@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
: new ValueChangedEvent(ScrollingDirection.Up, ScrollingDirection.Up));
}
- protected override Texture GetTexture(ISkinSource skin)
+ protected override Drawable GetAnimation(ISkinSource skin)
{
// TODO: Should fallback to the head from default legacy skin instead of note.
- return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
- ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
- ?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
+ return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
+ ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
+ ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
index 10319a7d4d..9c339345c4 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
@@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
}
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == column.Action.Value)
+ if (e.Action == column.Action.Value)
{
upSprite.FadeTo(0);
downSprite.FadeTo(1);
@@ -97,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return false;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action == column.Action.Value)
+ if (e.Action == column.Action.Value)
{
upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1);
downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0);
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs
index 31279796ce..321a87f8b1 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyNotePiece.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
@@ -19,7 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private readonly IBindable direction = new Bindable();
private Container directionContainer;
- private Sprite noteSprite;
+
+ [CanBeNull]
+ private Drawable noteAnimation;
private float? minimumColumnWidth;
@@ -39,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Child = noteSprite = new Sprite { Texture = GetTexture(skin) }
+ Child = noteAnimation = GetAnimation(skin) ?? Empty()
};
direction.BindTo(scrollingInfo.Direction);
@@ -50,12 +54,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
base.Update();
- if (noteSprite.Texture != null)
+ Texture texture = null;
+
+ if (noteAnimation is Sprite sprite)
+ texture = sprite.Texture;
+ else if (noteAnimation is TextureAnimation textureAnimation && textureAnimation.FrameCount > 0)
+ texture = textureAnimation.CurrentFrame;
+
+ if (texture != null)
{
// The height is scaled to the minimum column width, if provided.
float minimumWidth = minimumColumnWidth ?? DrawWidth;
-
- noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth);
+ noteAnimation.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), texture.DisplayWidth);
}
}
@@ -73,9 +83,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
}
}
- protected virtual Texture GetTexture(ISkinSource skin) => GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
+ [CanBeNull]
+ protected virtual Drawable GetAnimation(ISkinSource skin) => GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
- protected Texture GetTextureFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
+ [CanBeNull]
+ protected Drawable GetAnimationFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
{
string suffix = string.Empty;
@@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
string noteImage = GetColumnSkinConfig(skin, lookup)?.Value
?? $"mania-note{FallbackColumnIndex}{suffix}";
- return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge);
+ return skin.GetAnimation(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index f5e30efd91..9d060944cd 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
@@ -122,16 +123,16 @@ namespace osu.Game.Rulesets.Mania.UI
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action != Action.Value)
+ if (e.Action != Action.Value)
return false;
sampleTriggerSource.Play();
return true;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index 75cc351310..77ddc6fbbf 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
@@ -91,16 +92,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == this.action.Value)
+ if (e.Action == action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action == this.action.Value)
+ if (e.Action == action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
index 4b4bc157d5..807f6a77d9 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
@@ -74,16 +75,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == column.Action.Value)
+ if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action == column.Action.Value)
+ if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
index 47cb9bd45a..267ed1f5f4 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
@@ -101,16 +102,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
}
- public bool OnPressed(ManiaAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == column.Action.Value)
+ if (e.Action == column.Action.Value)
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false;
}
- public void OnReleased(ManiaAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action == column.Action.Value)
+ if (e.Action == column.Action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs
new file mode 100644
index 0000000000..bd39dead34
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs
@@ -0,0 +1,175 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Skinning.Legacy;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneCursorParticles : TestSceneOsuPlayer
+ {
+ protected override bool Autoplay => autoplay;
+ protected override bool HasCustomSteps => true;
+
+ private bool autoplay;
+ private IBeatmap currentBeatmap;
+
+ [Resolved]
+ private SkinManager skinManager { get; set; }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentBeatmap ?? base.CreateBeatmap(ruleset);
+
+ [Test]
+ public void TestLegacyBreakParticles()
+ {
+ LegacyCursorParticles cursorParticles = null;
+
+ createLegacyTest(false, () => new Beatmap
+ {
+ Breaks =
+ {
+ new BreakPeriod(8500, 10000),
+ },
+ HitObjects =
+ {
+ new HitCircle
+ {
+ StartTime = 8000,
+ Position = OsuPlayfield.BASE_SIZE / 2,
+ },
+ new HitCircle
+ {
+ StartTime = 11000,
+ Position = OsuPlayfield.BASE_SIZE / 2,
+ },
+ }
+ });
+
+ AddUntilStep("fetch cursor particles", () =>
+ {
+ cursorParticles = this.ChildrenOfType().SingleOrDefault();
+ return cursorParticles != null;
+ });
+
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
+
+ AddAssert("particles are being spawned", () => cursorParticles.Active);
+
+ AddStep("press left mouse button", () => InputManager.PressButton(MouseButton.Left));
+ AddWaitStep("wait a bit", 5);
+ AddStep("press right mouse button", () => InputManager.PressButton(MouseButton.Right));
+ AddWaitStep("wait a bit", 5);
+ AddStep("release left mouse button", () => InputManager.ReleaseButton(MouseButton.Left));
+ AddWaitStep("wait a bit", 5);
+ AddStep("release right mouse button", () => InputManager.ReleaseButton(MouseButton.Right));
+
+ AddUntilStep("wait for beatmap start", () => !Player.IsBreakTime.Value);
+ AddAssert("particle spawning stopped", () => !cursorParticles.Active);
+
+ AddUntilStep("wait for break", () => Player.IsBreakTime.Value);
+ AddAssert("particles are being spawned", () => cursorParticles.Active);
+
+ AddUntilStep("wait for break end", () => !Player.IsBreakTime.Value);
+ AddAssert("particle spawning stopped", () => !cursorParticles.Active);
+ }
+
+ [Test]
+ public void TestLegacyKiaiParticles()
+ {
+ LegacyCursorParticles cursorParticles = null;
+ DrawableSpinner spinner = null;
+ DrawableSlider slider = null;
+
+ createLegacyTest(true, () =>
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
+ controlPointInfo.Add(5000, new EffectControlPoint { KiaiMode = false });
+
+ return new Beatmap
+ {
+ ControlPointInfo = controlPointInfo,
+ HitObjects =
+ {
+ new Spinner
+ {
+ StartTime = 0,
+ Duration = 1000,
+ Position = OsuPlayfield.BASE_SIZE / 2,
+ },
+ new Slider
+ {
+ StartTime = 2500,
+ RepeatCount = 0,
+ Position = OsuPlayfield.BASE_SIZE / 2,
+ Path = new SliderPath(new[]
+ {
+ new PathControlPoint(Vector2.Zero),
+ new PathControlPoint(new Vector2(100, 0)),
+ })
+ },
+ new HitCircle
+ {
+ StartTime = 4500,
+ Position = OsuPlayfield.BASE_SIZE / 2,
+ },
+ },
+ };
+ }
+ );
+
+ AddUntilStep("fetch cursor particles", () =>
+ {
+ cursorParticles = this.ChildrenOfType().SingleOrDefault();
+ return cursorParticles != null;
+ });
+
+ AddUntilStep("wait for spinner tracking", () =>
+ {
+ spinner = this.ChildrenOfType().SingleOrDefault();
+ return spinner?.RotationTracker.Tracking == true;
+ });
+ AddAssert("particles are being spawned", () => cursorParticles.Active);
+
+ AddUntilStep("spinner tracking stopped", () => !spinner.RotationTracker.Tracking);
+ AddAssert("particle spawning stopped", () => !cursorParticles.Active);
+
+ AddUntilStep("wait for slider tracking", () =>
+ {
+ slider = this.ChildrenOfType().SingleOrDefault();
+ return slider?.Tracking.Value == true;
+ });
+ AddAssert("particles are being spawned", () => cursorParticles.Active);
+
+ AddUntilStep("slider tracking stopped", () => !slider.Tracking.Value);
+ AddAssert("particle spawning stopped", () => !cursorParticles.Active);
+ }
+
+ private void createLegacyTest(bool autoplay, Func beatmap) => CreateTest(() =>
+ {
+ AddStep("set beatmap", () =>
+ {
+ this.autoplay = autoplay;
+ currentBeatmap = beatmap();
+ });
+ AddStep("setup default legacy skin", () =>
+ {
+ skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
+ });
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 2326a0c391..f9dc9abd75 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
+using osu.Framework.Input.Events;
using osu.Framework.Testing.Input;
using osu.Framework.Utils;
using osu.Game.Audio;
@@ -143,9 +144,9 @@ namespace osu.Game.Rulesets.Osu.Tests
pressed = value;
if (value)
- OnPressed(OsuAction.LeftButton);
+ OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
else
- OnReleased(OsuAction.LeftButton);
+ OnReleased(new KeyBindingReleaseEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
index 1fdcd73dde..575523b168 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
@@ -4,6 +4,7 @@
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
@@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void scheduleHit() => AddStep("schedule action", () =>
{
var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
- Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(OsuAction.LeftButton), delay);
+ Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay);
});
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index 662cbaee68..0f362851a9 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -20,6 +20,7 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Tests.Visual;
@@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Osu.Tests
if (firstObject == null)
return false;
- var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable;
+ var skinnable = firstObject.ApproachCircle;
- if (skin == null && skinnable?.Drawable is Sprite)
+ if (skin == null && skinnable?.Drawable is DefaultApproachCircle)
// check for default skin provider
return true;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 141138c125..ac77a93239 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public double AimStrain { get; set; }
public double SpeedStrain { get; set; }
+ public double FlashlightRating { get; set; }
public double ApproachRate { get; set; }
public double OverallDifficulty { get; set; }
public int HitCircleCount { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index ff55873d69..4c8d0b2ce6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -35,10 +35,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
+ double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
- double basePerformance = Math.Pow(Math.Pow(baseAimPerformance, 1.1) + Math.Pow(baseSpeedPerformance, 1.1), 1 / 1.1);
+ double baseFlashlightPerformance = 0.0;
+
+ if (mods.Any(h => h is OsuModFlashlight))
+ baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0;
+
+ double basePerformance =
+ Math.Pow(
+ Math.Pow(baseAimPerformance, 1.1) +
+ Math.Pow(baseSpeedPerformance, 1.1) +
+ Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
+ );
+
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
@@ -56,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Mods = mods,
AimStrain = aimRating,
SpeedStrain = speedRating,
+ FlashlightRating = flashlightRating,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo,
@@ -91,6 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
new Aim(mods),
new Speed(mods, hitWindowGreat),
+ new Flashlight(mods)
};
}
@@ -100,6 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
new OsuModHalfTime(),
new OsuModEasy(),
new OsuModHardRock(),
+ new OsuModFlashlight(),
};
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index e6ab978dfb..bf4d92652c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
// Custom multipliers for NoFail and SpunOut.
- double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+ double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
if (mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
@@ -52,11 +52,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = computeAimValue();
double speedValue = computeSpeedValue();
double accuracyValue = computeAccuracyValue();
+ double flashlightValue = computeFlashlightValue();
double totalValue =
Math.Pow(
Math.Pow(aimValue, 1.1) +
Math.Pow(speedValue, 1.1) +
- Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
+ Math.Pow(accuracyValue, 1.1) +
+ Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryRatings != null)
@@ -64,6 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
+ categoryRatings.Add("Flashlight", flashlightValue);
categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", Attributes.MaxCombo);
@@ -81,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
- // Longer maps are worth more
+ // Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
@@ -91,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (countMiss > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
- // Combo scaling
+ // Combo scaling.
if (Attributes.MaxCombo > 0)
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
@@ -109,23 +112,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModHidden))
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
- double flashlightBonus = 1.0;
+ aimValue *= approachRateBonus;
- if (mods.Any(h => h is OsuModFlashlight))
- {
- // Apply object-based bonus for flashlight.
- flashlightBonus = 1.0 + 0.35 * Math.Min(1.0, totalHits / 200.0) +
- (totalHits > 200
- ? 0.3 * Math.Min(1.0, (totalHits - 200) / 300.0) +
- (totalHits > 500 ? (totalHits - 500) / 1200.0 : 0.0)
- : 0.0);
- }
-
- aimValue *= Math.Max(flashlightBonus, approachRateBonus);
-
- // Scale the aim value with accuracy _slightly_
+ // Scale the aim value with accuracy _slightly_.
aimValue *= 0.5 + accuracy / 2.0;
- // It is important to also consider accuracy difficulty when doing that
+ // It is important to also consider accuracy difficulty when doing that.
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
@@ -135,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
- // Longer maps are worth more
+ // Longer maps are worth more.
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
@@ -144,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (countMiss > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
- // Combo scaling
+ // Combo scaling.
if (Attributes.MaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
@@ -159,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => m is OsuModHidden))
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
- // Scale the speed value with accuracy and OD
+ // Scale the speed value with accuracy and OD.
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
// Scale the speed value with # of 50s to punish doubletapping.
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
@@ -169,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAccuracyValue()
{
- // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
+ // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
@@ -178,15 +169,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
else
betterAccuracyPercentage = 0;
- // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points
+ // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points.
if (betterAccuracyPercentage < 0)
betterAccuracyPercentage = 0;
// Lots of arbitrary values from testing.
- // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
+ // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution.
double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
- // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+ // Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
if (mods.Any(m => m is OsuModHidden))
@@ -197,6 +188,42 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return accuracyValue;
}
+ private double computeFlashlightValue()
+ {
+ if (!mods.Any(h => h is OsuModFlashlight))
+ return 0.0;
+
+ double rawFlashlight = Attributes.FlashlightRating;
+
+ if (mods.Any(m => m is OsuModTouchDevice))
+ rawFlashlight = Math.Pow(rawFlashlight, 0.8);
+
+ double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
+
+ // Add an additional bonus for HDFL.
+ if (mods.Any(h => h is OsuModHidden))
+ flashlightValue *= 1.3;
+
+ // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
+ if (countMiss > 0)
+ flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
+
+ // Combo scaling.
+ if (Attributes.MaxCombo > 0)
+ flashlightValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
+
+ // Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
+ flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) +
+ (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0);
+
+ // Scale the flashlight value with accuracy _slightly_.
+ flashlightValue *= 0.5 + accuracy / 2.0;
+ // It is important to also consider accuracy difficulty when doing that.
+ flashlightValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
+
+ return flashlightValue;
+ }
+
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
new file mode 100644
index 0000000000..abd900a80d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
@@ -0,0 +1,66 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
+{
+ ///
+ /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled.
+ ///
+ public class Flashlight : OsuStrainSkill
+ {
+ public Flashlight(Mod[] mods)
+ : base(mods)
+ {
+ }
+
+ protected override double SkillMultiplier => 0.15;
+ protected override double StrainDecayBase => 0.15;
+ protected override double DecayWeight => 1.0;
+ protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
+
+ protected override double StrainValueOf(DifficultyHitObject current)
+ {
+ if (current.BaseObject is Spinner)
+ return 0;
+
+ var osuCurrent = (OsuDifficultyHitObject)current;
+ var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject);
+
+ double scalingFactor = 52.0 / osuHitObject.Radius;
+ double smallDistNerf = 1.0;
+ double cumulativeStrainTime = 0.0;
+
+ double result = 0.0;
+
+ for (int i = 0; i < Previous.Count; i++)
+ {
+ var osuPrevious = (OsuDifficultyHitObject)Previous[i];
+ var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject);
+
+ if (!(osuPrevious.BaseObject is Spinner))
+ {
+ double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length;
+
+ cumulativeStrainTime += osuPrevious.StrainTime;
+
+ // We want to nerf objects that can be easily seen within the Flashlight circle radius.
+ if (i == 0)
+ smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
+
+ // We also want to nerf stacks so that only the first object of the stack is accounted for.
+ double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0);
+
+ result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
+ }
+ }
+
+ return Math.Pow(smallDistNerf * result, 2.0);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 6269a41350..1be9b5bf2e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -127,9 +127,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false;
}
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PlatformAction.Delete:
return DeleteSelected();
@@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 46fc8f99b2..f05aea0df4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
@@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public OsuAction? HitAction => HitArea.HitAction;
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
- public ApproachCircle ApproachCircle { get; private set; }
+ public SkinnableDrawable ApproachCircle { get; private set; }
public HitReceptor HitArea { get; private set; }
public SkinnableDrawable CirclePiece { get; private set; }
@@ -74,8 +75,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- ApproachCircle = new ApproachCircle
+ ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle())
{
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
Alpha = 0,
Scale = new Vector2(4),
}
@@ -88,7 +92,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
- AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue);
}
protected override void LoadComplete()
@@ -228,15 +231,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
CornerExponent = 2;
}
- public bool OnPressed(OsuAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
if (IsHovered && (Hit?.Invoke() ?? false))
{
- HitAction = action;
+ HitAction = e.Action;
return true;
}
@@ -246,7 +249,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return false;
}
- public void OnReleased(OsuAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
+ }
+
+ private class ProxyableSkinnableDrawable : SkinnableDrawable
+ {
+ public override bool RemoveWhenNotAlive => false;
+
+ public ProxyableSkinnableDrawable(ISkinComponent component, Func defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
+ : base(component, defaultImplementation, confineMode)
{
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 0bec33bf77..3acec4498d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -32,6 +32,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public SliderBall Ball { get; private set; }
public SkinnableDrawable Body { get; private set; }
+ ///
+ /// A target container which can be used to add top level elements to the slider's display.
+ /// Intended to be used for proxy purposes only.
+ ///
+ public Container OverlayElementContainer { get; private set; }
+
public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects;
[CanBeNull]
@@ -65,6 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tailContainer = new Container { RelativeSizeAxes = Axes.Both },
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
+ headContainer = new Container { RelativeSizeAxes = Axes.Both },
+ OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
Ball = new SliderBall(this)
{
GetInitialHitAction = () => HeadCircle.HitAction,
@@ -72,7 +80,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true,
Alpha = 0
},
- headContainer = new Container { RelativeSizeAxes = Axes.Both },
slidingSample = new PausableSkinnableSound { Looping = true }
};
@@ -179,6 +186,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tailContainer.Clear(false);
repeatContainer.Clear(false);
tickContainer.Clear(false);
+
+ OverlayElementContainer.Clear(false);
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index 01c0d988ee..2b026e6840 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[CanBeNull]
public Slider Slider => DrawableSlider?.HitObject;
- protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
+ public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index 4a2a18ffd6..673211ac6c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[CanBeNull]
public Slider Slider => DrawableSlider?.HitObject;
- protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
+ public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
private double animDuration;
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 46e501758b..71657ed532 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu
FollowPoint,
Cursor,
CursorTrail,
+ CursorParticles,
SliderScorePoint,
ReverseArrow,
HitCircleText,
@@ -18,5 +19,6 @@ namespace osu.Game.Rulesets.Osu
SliderBall,
SliderBody,
SpinnerBody,
+ ApproachCircle,
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ApproachCircle.cs
deleted file mode 100644
index 62f00a2b49..0000000000
--- a/osu.Game.Rulesets.Osu/Skinning/Default/ApproachCircle.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Skinning;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Skinning.Default
-{
- public class ApproachCircle : Container
- {
- public override bool RemoveWhenNotAlive => false;
-
- public ApproachCircle()
- {
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
-
- RelativeSizeAxes = Axes.Both;
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- Child = new SkinnableApproachCircle();
- }
-
- private class SkinnableApproachCircle : SkinnableSprite
- {
- public SkinnableApproachCircle()
- : base("Gameplay/osu/approachcircle")
- {
- }
-
- protected override Drawable CreateDefault(ISkinComponent component)
- {
- var drawable = base.CreateDefault(component);
-
- // account for the sprite being used for the default approach circle being taken from stable,
- // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
- drawable.Scale = new Vector2(128 / 118f);
-
- return drawable;
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs
new file mode 100644
index 0000000000..a522367fe6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultApproachCircle.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public class DefaultApproachCircle : SkinnableSprite
+ {
+ private readonly IBindable accentColour = new Bindable();
+
+ [Resolved]
+ private DrawableHitObject drawableObject { get; set; }
+
+ public DefaultApproachCircle()
+ : base("Gameplay/osu/approachcircle")
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ accentColour.BindTo(drawableObject.AccentColour);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ accentColour.BindValueChanged(colour => Colour = colour.NewValue, true);
+ }
+
+ protected override Drawable CreateDefault(ISkinComponent component)
+ {
+ var drawable = base.CreateDefault(component);
+
+ // Although this is a non-legacy component, osu-resources currently stores approach circle as a legacy-like texture.
+ // See LegacyApproachCircle for documentation as to why this is required.
+ drawable.Scale = new Vector2(128 / 118f);
+
+ return drawable;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs
new file mode 100644
index 0000000000..6a2cb871b1
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyApproachCircle.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Legacy
+{
+ public class LegacyApproachCircle : SkinnableSprite
+ {
+ private readonly IBindable accentColour = new Bindable();
+
+ [Resolved]
+ private DrawableHitObject drawableObject { get; set; }
+
+ public LegacyApproachCircle()
+ : base("Gameplay/osu/approachcircle")
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ accentColour.BindTo(drawableObject.AccentColour);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ accentColour.BindValueChanged(colour => Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
+ }
+
+ protected override Drawable CreateDefault(ISkinComponent component)
+ {
+ var drawable = base.CreateDefault(component);
+
+ // account for the sprite being used for the default approach circle being taken from stable,
+ // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
+ drawable.Scale = new Vector2(128 / 118f);
+
+ return drawable;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
new file mode 100644
index 0000000000..c2db5f3f82
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
@@ -0,0 +1,253 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Legacy
+{
+ public class LegacyCursorParticles : CompositeDrawable, IKeyBindingHandler
+ {
+ public bool Active => breakSpewer?.Active.Value == true || kiaiSpewer?.Active.Value == true;
+
+ private LegacyCursorParticleSpewer breakSpewer;
+ private LegacyCursorParticleSpewer kiaiSpewer;
+
+ [Resolved(canBeNull: true)]
+ private Player player { get; set; }
+
+ [Resolved(canBeNull: true)]
+ private OsuPlayfield playfield { get; set; }
+
+ [Resolved(canBeNull: true)]
+ private GameplayBeatmap gameplayBeatmap { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, OsuColour colours)
+ {
+ var texture = skin.GetTexture("star2");
+ var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255);
+
+ if (texture != null)
+ {
+ // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
+ texture.ScaleAdjust *= 1.6f;
+ }
+
+ InternalChildren = new[]
+ {
+ breakSpewer = new LegacyCursorParticleSpewer(texture, 20)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = starBreakAdditive,
+ Direction = SpewDirection.None,
+ },
+ kiaiSpewer = new LegacyCursorParticleSpewer(texture, 60)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = starBreakAdditive,
+ Direction = SpewDirection.None,
+ },
+ };
+
+ if (player != null)
+ ((IBindable)breakSpewer.Active).BindTo(player.IsBreakTime);
+ }
+
+ protected override void Update()
+ {
+ if (playfield == null || gameplayBeatmap == null) return;
+
+ DrawableHitObject kiaiHitObject = null;
+
+ // Check whether currently in a kiai section first. This is only done as an optimisation to avoid enumerating AliveObjects when not necessary.
+ if (gameplayBeatmap.ControlPointInfo.EffectPointAt(Time.Current).KiaiMode)
+ kiaiHitObject = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(isTracking);
+
+ kiaiSpewer.Active.Value = kiaiHitObject != null;
+ }
+
+ private bool isTracking(DrawableHitObject h)
+ {
+ if (!h.HitObject.Kiai)
+ return false;
+
+ switch (h)
+ {
+ case DrawableSlider slider:
+ return slider.Tracking.Value;
+
+ case DrawableSpinner spinner:
+ return spinner.RotationTracker.Tracking;
+ }
+
+ return false;
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ handleInput(e.Action, true);
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ handleInput(e.Action, false);
+ }
+
+ private bool leftPressed;
+ private bool rightPressed;
+
+ private void handleInput(OsuAction action, bool pressed)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ leftPressed = pressed;
+ break;
+
+ case OsuAction.RightButton:
+ rightPressed = pressed;
+ break;
+ }
+
+ if (leftPressed && rightPressed)
+ breakSpewer.Direction = SpewDirection.Omni;
+ else if (leftPressed)
+ breakSpewer.Direction = SpewDirection.Left;
+ else if (rightPressed)
+ breakSpewer.Direction = SpewDirection.Right;
+ else
+ breakSpewer.Direction = SpewDirection.None;
+ }
+
+ private class LegacyCursorParticleSpewer : ParticleSpewer, IRequireHighFrequencyMousePosition
+ {
+ private const int particle_duration_min = 300;
+ private const int particle_duration_max = 1000;
+
+ public SpewDirection Direction { get; set; }
+
+ protected override bool CanSpawnParticles => base.CanSpawnParticles && cursorScreenPosition.HasValue;
+ protected override float ParticleGravity => 240;
+
+ public LegacyCursorParticleSpewer(Texture texture, int perSecond)
+ : base(texture, perSecond, particle_duration_max)
+ {
+ Active.BindValueChanged(_ => resetVelocityCalculation());
+ }
+
+ private Vector2? cursorScreenPosition;
+ private Vector2 cursorVelocity;
+
+ private const double max_velocity_frame_length = 15;
+ private double velocityFrameLength;
+ private Vector2 totalPosDifference;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ if (cursorScreenPosition == null)
+ {
+ cursorScreenPosition = e.ScreenSpaceMousePosition;
+ return base.OnMouseMove(e);
+ }
+
+ // calculate cursor velocity.
+ totalPosDifference += e.ScreenSpaceMousePosition - cursorScreenPosition.Value;
+ cursorScreenPosition = e.ScreenSpaceMousePosition;
+
+ velocityFrameLength += Math.Abs(Clock.ElapsedFrameTime);
+
+ if (velocityFrameLength > max_velocity_frame_length)
+ {
+ cursorVelocity = totalPosDifference / (float)velocityFrameLength;
+
+ totalPosDifference = Vector2.Zero;
+ velocityFrameLength = 0;
+ }
+
+ return base.OnMouseMove(e);
+ }
+
+ private void resetVelocityCalculation()
+ {
+ cursorScreenPosition = null;
+ totalPosDifference = Vector2.Zero;
+ velocityFrameLength = 0;
+ }
+
+ protected override FallingParticle CreateParticle() =>
+ new FallingParticle
+ {
+ StartPosition = ToLocalSpace(cursorScreenPosition ?? Vector2.Zero),
+ Duration = RNG.NextSingle(particle_duration_min, particle_duration_max),
+ StartAngle = (float)(RNG.NextDouble() * 4 - 2),
+ EndAngle = RNG.NextSingle(-2f, 2f),
+ EndScale = RNG.NextSingle(2f),
+ Velocity = getVelocity(),
+ };
+
+ private Vector2 getVelocity()
+ {
+ Vector2 velocity = Vector2.Zero;
+
+ switch (Direction)
+ {
+ case SpewDirection.Left:
+ velocity = new Vector2(
+ RNG.NextSingle(-460f, 0),
+ RNG.NextSingle(-40f, 40f)
+ );
+ break;
+
+ case SpewDirection.Right:
+ velocity = new Vector2(
+ RNG.NextSingle(0, 460f),
+ RNG.NextSingle(-40f, 40f)
+ );
+ break;
+
+ case SpewDirection.Omni:
+ velocity = new Vector2(
+ RNG.NextSingle(-460f, 460f),
+ RNG.NextSingle(-160f, 160f)
+ );
+ break;
+ }
+
+ velocity += cursorVelocity * 40;
+
+ return velocity;
+ }
+ }
+
+ private enum SpewDirection
+ {
+ None,
+ Left,
+ Right,
+ Omni,
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index 7a210324d7..3afd814174 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
}
- private Container circleSprites;
private Drawable hitCircleSprite;
- private Drawable hitCircleOverlay;
+
+ protected Drawable HitCircleOverlay { get; private set; }
private SkinnableSpriteText hitCircleText;
@@ -70,28 +70,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
Texture overlayTexture = getTextureWithFallback("overlay");
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
- circleSprites = new Container
+ hitCircleSprite = new KiaiFlashingSprite
{
+ Texture = baseTexture,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ HitCircleOverlay = new KiaiFlashingSprite
+ {
+ Texture = overlayTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Children = new[]
- {
- hitCircleSprite = new KiaiFlashingSprite
- {
- Texture = baseTexture,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- hitCircleOverlay = new KiaiFlashingSprite
- {
- Texture = overlayTexture,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }
- }
},
};
@@ -111,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
if (overlayAboveNumber)
- AddInternal(hitCircleOverlay.CreateProxy());
+ ChangeInternalChildDepth(HitCircleOverlay, float.MinValue);
accentColour.BindTo(drawableObject.AccentColour);
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
@@ -153,8 +144,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
switch (state)
{
case ArmedState.Hit:
- circleSprites.FadeOut(legacy_fade_duration, Easing.Out);
- circleSprites.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
+ hitCircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
+ hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
+
+ HitCircleOverlay.FadeOut(legacy_fade_duration, Easing.Out);
+ HitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
if (hasNumber)
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs
new file mode 100644
index 0000000000..fd7bfe7e60
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs
@@ -0,0 +1,67 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Legacy
+{
+ public class LegacyReverseArrow : CompositeDrawable
+ {
+ private ISkin skin { get; }
+
+ [Resolved(canBeNull: true)]
+ private DrawableHitObject drawableHitObject { get; set; }
+
+ private Drawable proxy;
+
+ public LegacyReverseArrow(ISkin skin)
+ {
+ this.skin = skin;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ string lookupName = new OsuSkinComponent(OsuSkinComponents.ReverseArrow).LookupName;
+
+ InternalChild = skin.GetAnimation(lookupName, true, true) ?? Empty();
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ proxy = CreateProxy();
+
+ if (drawableHitObject != null)
+ {
+ drawableHitObject.HitObjectApplied += onHitObjectApplied;
+ onHitObjectApplied(drawableHitObject);
+ }
+ }
+
+ private void onHitObjectApplied(DrawableHitObject drawableObject)
+ {
+ Debug.Assert(proxy.Parent == null);
+
+ // see logic in LegacySliderHeadHitCircle.
+ (drawableObject as DrawableSliderRepeat)?.DrawableSlider
+ .OverlayElementContainer.Add(proxy);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ if (drawableHitObject != null)
+ drawableHitObject.HitObjectApplied -= onHitObjectApplied;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs
new file mode 100644
index 0000000000..13ba42ba50
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs
@@ -0,0 +1,53 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Legacy
+{
+ public class LegacySliderHeadHitCircle : LegacyMainCirclePiece
+ {
+ [Resolved(canBeNull: true)]
+ private DrawableHitObject drawableHitObject { get; set; }
+
+ private Drawable proxiedHitCircleOverlay;
+
+ public LegacySliderHeadHitCircle()
+ : base("sliderstartcircle")
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ proxiedHitCircleOverlay = HitCircleOverlay.CreateProxy();
+
+ if (drawableHitObject != null)
+ {
+ drawableHitObject.HitObjectApplied += onHitObjectApplied;
+ onHitObjectApplied(drawableHitObject);
+ }
+ }
+
+ private void onHitObjectApplied(DrawableHitObject drawableObject)
+ {
+ Debug.Assert(proxiedHitCircleOverlay.Parent == null);
+
+ // see logic in LegacyReverseArrow.
+ (drawableObject as DrawableSliderHead)?.DrawableSlider
+ .OverlayElementContainer.Add(proxiedHitCircleOverlay.With(d => d.Depth = float.MinValue));
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (drawableHitObject != null)
+ drawableHitObject.HitObjectApplied -= onHitObjectApplied;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 41b0a88f11..8a24e36420 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -67,7 +67,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
case OsuSkinComponents.SliderHeadHitCircle:
if (hasHitCircle.Value)
- return new LegacyMainCirclePiece("sliderstartcircle");
+ return new LegacySliderHeadHitCircle();
+
+ return null;
+
+ case OsuSkinComponents.ReverseArrow:
+ if (hasHitCircle.Value)
+ return new LegacyReverseArrow(this);
return null;
@@ -89,6 +95,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
+ case OsuSkinComponents.CursorParticles:
+ if (GetTexture("star2") != null)
+ return new LegacyCursorParticles();
+
+ return null;
+
case OsuSkinComponents.HitCircleText:
if (!this.HasFont(LegacyFont.HitCircle))
return null;
@@ -108,6 +120,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyOldStyleSpinner();
return null;
+
+ case OsuSkinComponents.ApproachCircle:
+ return new LegacyApproachCircle();
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
index f7ba8b9fc4..24f9217a5f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinColour.cs
@@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderBorder,
SliderBall,
SpinnerBackground,
+ StarBreakAdditive,
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 5812e8cf75..83bcc88e5f 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Configuration;
@@ -42,7 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
InternalChild = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling)
+ Children = new[]
+ {
+ cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
+ new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
+ }
};
}
@@ -115,9 +120,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
(ActiveCursor as OsuCursor)?.Contract();
}
- public bool OnPressed(OsuAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
@@ -129,9 +134,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
return false;
}
- public void OnReleased(OsuAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- switch (action)
+ switch (e.Action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index c1c2ea2299..2233a547b9 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -24,6 +24,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.UI
{
+ [Cached]
public class OsuPlayfield : Playfield
{
private readonly PlayfieldBorder playfieldBorder;
diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
index 27d48d1296..4d4340936d 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
@@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.UI
base.OnHoverLost(e);
}
- public bool OnPressed(OsuAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
@@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.UI
return false;
}
- public void OnReleased(OsuAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs
index f048cad18c..6d4cac0ebe 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Scoring;
@@ -37,6 +38,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
Result.Type = Type;
}
- public override bool OnPressed(TaikoAction action) => false;
+ public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs
index 829bcf34a1..ea877c9e17 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
@@ -30,6 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss;
}
- public override bool OnPressed(TaikoAction action) => false;
+ public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index 1253b7c8ae..0a325f174e 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -36,24 +36,27 @@ namespace osu.Game.Rulesets.Taiko.Mods
public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
{
this.taikoPlayfield = taikoPlayfield;
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ FlashlightSize = getSizeFor(0);
AddLayout(flashlightProperties);
}
- private float getSizeFor(int combo)
+ private Vector2 getSizeFor(int combo)
{
+ float size = default_flashlight_size;
+
if (combo > 200)
- return default_flashlight_size * 0.8f;
+ size *= 0.8f;
else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
+ size *= 0.9f;
+
+ // Preserve flashlight size through the playfield's aspect adjustment.
+ return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), getSizeFor(e.NewValue), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
@@ -64,7 +67,11 @@ namespace osu.Game.Rulesets.Taiko.Mods
if (!flashlightProperties.IsValid)
{
- FlashlightPosition = taikoPlayfield.HitTarget.ToSpaceOfOtherDrawable(taikoPlayfield.HitTarget.OriginPosition, this);
+ FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
+
+ ClearTransforms(targetMember: nameof(FlashlightSize));
+ FlashlightSize = getSizeFor(Combo.Value);
+
flashlightProperties.Validate();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
index a6b3fe1cd9..7f565cb82d 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
@@ -2,45 +2,55 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.UI;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Mods
{
- public class TaikoModHidden : ModHidden
+ public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset
{
public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06;
- private ControlPointInfo controlPointInfo;
+ ///
+ /// How far away from the hit target should hitobjects start to fade out.
+ /// Range: [0, 1]
+ ///
+ private const float fade_out_start_time = 1f;
+
+ ///
+ /// How long hitobjects take to fade out, in terms of the scrolling length.
+ /// Range: [0, 1]
+ ///
+ private const float fade_out_duration = 0.375f;
+
+ private DrawableTaikoRuleset drawableRuleset;
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ this.drawableRuleset = (DrawableTaikoRuleset)drawableRuleset;
+ }
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
ApplyNormalVisibilityState(hitObject, state);
}
- protected double MultiplierAt(double position)
- {
- double beatLength = controlPointInfo.TimingPointAt(position).BeatLength;
- double speedMultiplier = controlPointInfo.DifficultyPointAt(position).SpeedMultiplier;
-
- return speedMultiplier * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength;
- }
-
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
switch (hitObject)
{
case DrawableDrumRollTick _:
case DrawableHit _:
- double preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime);
- double start = hitObject.HitObject.StartTime - preempt * 0.6;
- double duration = preempt * 0.3;
+ double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier;
+ double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time;
+ double duration = preempt * fade_out_duration;
using (hitObject.BeginAbsoluteSequence(start))
{
@@ -56,10 +66,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
break;
}
}
-
- public override void ApplyToBeatmap(IBeatmap beatmap)
- {
- controlPointInfo = beatmap.ControlPointInfo;
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index d066abf767..521189d36c 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody),
_ => new ElongatedCirclePiece());
- public override bool OnPressed(TaikoAction action) => false;
+ public override bool OnPressed(KeyBindingPressEvent e) => false;
private void onNewResult(DrawableHitObject obj, JudgementResult result)
{
@@ -196,7 +197,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
- public override bool OnPressed(TaikoAction action) => false;
+ public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 0df45c424d..dc2ed200a1 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -4,6 +4,7 @@
using System;
using JetBrains.Annotations;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning;
@@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- public override bool OnPressed(TaikoAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
- JudgementType = action == TaikoAction.LeftRim || action == TaikoAction.RightRim ? HitType.Rim : HitType.Centre;
+ JudgementType = e.Action == TaikoAction.LeftRim || e.Action == TaikoAction.RightRim ? HitType.Rim : HitType.Centre;
return UpdateResult(true);
}
@@ -91,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
- public override bool OnPressed(TaikoAction action) => false;
+ public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 1e9fc187eb..97adc5f197 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -8,6 +8,7 @@ using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -145,19 +146,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = result);
}
- public override bool OnPressed(TaikoAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
if (pressHandledThisFrame)
return true;
if (Judged)
return false;
- validActionPressed = HitActions.Contains(action);
+ validActionPressed = HitActions.Contains(e.Action);
// Only count this as handled if the new judgement is a hit
var result = UpdateResult(true);
if (IsHit)
- HitAction = action;
+ HitAction = e.Action;
// Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded
// E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note
@@ -165,11 +166,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return result;
}
- public override void OnReleased(TaikoAction action)
+ public override void OnReleased(KeyBindingReleaseEvent e)
{
- if (action == HitAction)
+ if (e.Action == HitAction)
HitAction = null;
- base.OnReleased(action);
+ base.OnReleased(e);
}
protected override void Update()
@@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
- public override bool OnPressed(TaikoAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
// Don't process actions until the main hitobject is hit
if (!ParentHitObject.IsHit)
@@ -276,7 +277,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return false;
// Don't handle invalid hit action presses
- if (!ParentHitObject.HitActions.Contains(action))
+ if (!ParentHitObject.HitActions.Contains(e.Action))
return false;
return UpdateResult(true);
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index 888f47d341..2d19296d06 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -12,6 +12,7 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Skinning.Default;
@@ -266,13 +267,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private bool? lastWasCentre;
- public override bool OnPressed(TaikoAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
// Don't handle keys before the swell starts
if (Time.Current < HitObject.StartTime)
return false;
- var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre;
+ var isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre;
// Ensure alternating centre and rim hits
if (lastWasCentre == isCentre)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
index 47fc7e5ab3..d4ea9ed29f 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs
@@ -3,6 +3,7 @@
using JetBrains.Annotations;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning;
@@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
- public override bool OnPressed(TaikoAction action) => false;
+ public override bool OnPressed(KeyBindingPressEvent e) => false;
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
_ => new TickPiece());
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 6a8d8a611c..eb64ba72f2 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
@@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
///
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
- public abstract bool OnPressed(TaikoAction action);
+ public abstract bool OnPressed(KeyBindingPressEvent e);
- public virtual void OnReleased(TaikoAction action)
+ public virtual void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
index 9d35093591..86be40dea8 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
@@ -141,16 +142,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
Centre.Texture = skin.GetTexture(@"taiko-drum-inner");
}
- public bool OnPressed(TaikoAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
Drawable target = null;
- if (action == CentreAction)
+ if (e.Action == CentreAction)
{
target = Centre;
sampleTriggerSource.Play(HitType.Centre);
}
- else if (action == RimAction)
+ else if (e.Action == RimAction)
{
target = Rim;
sampleTriggerSource.Play(HitType.Rim);
@@ -173,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
return false;
}
- public void OnReleased(TaikoAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index 6ddbf3c16b..824b95639b 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -16,6 +17,7 @@ using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Scoring;
using osu.Game.Skinning;
@@ -60,6 +62,14 @@ namespace osu.Game.Rulesets.Taiko.UI
scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y;
}
+ public MultiplierControlPoint ControlPointAt(double time)
+ {
+ int result = ControlPoints.BinarySearch(new MultiplierControlPoint(time));
+ if (result < 0)
+ result = Math.Clamp(~result - 1, 0, ControlPoints.Count);
+ return ControlPoints[result];
+ }
+
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index ddfaf64549..861b800038 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
@@ -151,19 +152,19 @@ namespace osu.Game.Rulesets.Taiko.UI
[Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
- public bool OnPressed(TaikoAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
Drawable target = null;
Drawable back = null;
- if (action == CentreAction)
+ if (e.Action == CentreAction)
{
target = centreHit;
back = centre;
sampleTriggerSource.Play(HitType.Centre);
}
- else if (action == RimAction)
+ else if (e.Action == RimAction)
{
target = rimHit;
back = rim;
@@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Taiko.UI
return false;
}
- public void OnReleased(TaikoAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 5dc25d6643..b2bd60d342 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -424,14 +424,14 @@ namespace osu.Game.Tests.Beatmaps.IO
checkBeatmapCount(osu, 12);
checkSingleReferencedFileCount(osu, 18);
- var breakTemp = TestResources.GetTestBeatmapForImport();
+ var brokenTempFilename = TestResources.GetTestBeatmapForImport();
MemoryStream brokenOsu = new MemoryStream();
- MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(breakTemp));
+ MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename));
- File.Delete(breakTemp);
+ File.Delete(brokenTempFilename);
- using (var outStream = File.Open(breakTemp, FileMode.CreateNew))
+ using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
using (var zip = ZipArchive.Open(brokenOsz))
{
zip.AddEntry("broken.osu", brokenOsu, false);
@@ -441,7 +441,7 @@ namespace osu.Game.Tests.Beatmaps.IO
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
try
{
- await manager.Import(new ImportTask(breakTemp));
+ await manager.Import(new ImportTask(brokenTempFilename));
}
catch
{
@@ -456,6 +456,8 @@ namespace osu.Game.Tests.Beatmaps.IO
checkSingleReferencedFileCount(osu, 18);
Assert.AreEqual(1, loggedExceptionCount);
+
+ File.Delete(brokenTempFilename);
}
finally
{
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 7170a76b8b..839366d98e 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.IO;
using NUnit.Framework;
using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
namespace osu.Game.Tests.Resources
{
@@ -11,6 +13,8 @@ namespace osu.Game.Tests.Resources
{
public const double QUICK_BEATMAP_LENGTH = 10000;
+ private static readonly TemporaryNativeStorage temp_storage = new TemporaryNativeStorage("TestResources");
+
public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
@@ -25,7 +29,7 @@ namespace osu.Game.Tests.Resources
/// A path to a copy of a beatmap archive (osz). Should be deleted after use.
public static string GetQuickTestBeatmapForImport()
{
- var tempPath = Path.GetTempFileName() + ".osz";
+ var tempPath = getTempFilename();
using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz"))
using (var newFile = File.Create(tempPath))
stream.CopyTo(newFile);
@@ -41,7 +45,7 @@ namespace osu.Game.Tests.Resources
/// A path to a copy of a beatmap archive (osz). Should be deleted after use.
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
- var tempPath = Path.GetTempFileName() + ".osz";
+ var tempPath = getTempFilename();
using (var stream = GetTestBeatmapStream(virtualTrack))
using (var newFile = File.Create(tempPath))
@@ -50,5 +54,7 @@ namespace osu.Game.Tests.Resources
Assert.IsTrue(File.Exists(tempPath));
return tempPath;
}
+
+ private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz");
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
index 7accaef818..1ba0965ceb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
@@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
@@ -137,6 +138,23 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("no circle added", () => !this.ChildrenOfType().Any());
}
+ [Test]
+ public void TestClear()
+ {
+ AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
+
+ AddStep("hit", () => newJudgement(0.2D));
+ AddAssert("bar added", () => this.ChildrenOfType().All(
+ meter => meter.ChildrenOfType().Count() == 1));
+ AddAssert("circle added", () => this.ChildrenOfType().All(
+ meter => meter.ChildrenOfType().Count() == 1));
+
+ AddStep("clear", () => this.ChildrenOfType().ForEach(meter => meter.Clear()));
+
+ AddAssert("bar cleared", () => !this.ChildrenOfType().Any());
+ AddAssert("colour cleared", () => !this.ChildrenOfType().Any());
+ }
+
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
{
hitWindows?.SetDifficulty(overallDifficulty);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
index 6de85499c5..0a39d94027 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
@@ -80,13 +81,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public bool ReceivedAction;
- public bool OnPressed(TestAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- ReceivedAction = action == TestAction.Down;
+ ReceivedAction = e.Action == TestAction.Down;
return true;
}
- public void OnReleased(TestAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs
new file mode 100644
index 0000000000..ce5cd629e0
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneParticleSpewer.cs
@@ -0,0 +1,128 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [TestFixture]
+ public class TestSceneParticleSpewer : OsuTestScene
+ {
+ private TestParticleSpewer spewer;
+
+ [Resolved]
+ private SkinManager skinManager { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = spewer = createSpewer();
+
+ AddToggleStep("toggle spawning", value => spewer.Active.Value = value);
+ AddSliderStep("particle gravity", 0f, 1f, 0f, value => spewer.Gravity = value);
+ AddSliderStep("particle velocity", 0f, 1f, 0.5f, value => spewer.MaxVelocity = value);
+ AddStep("move to new location", () =>
+ {
+ spewer.TransformTo(nameof(spewer.SpawnPosition), new Vector2(RNG.NextSingle(), RNG.NextSingle()), 1000, Easing.Out);
+ });
+ }
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create spewer", () => Child = spewer = createSpewer());
+ }
+
+ [Test]
+ public void TestPresence()
+ {
+ AddStep("start spewer", () => spewer.Active.Value = true);
+ AddAssert("is present", () => spewer.IsPresent);
+
+ AddWaitStep("wait for some particles", 3);
+ AddStep("stop spewer", () => spewer.Active.Value = false);
+
+ AddWaitStep("wait for clean screen", 8);
+ AddAssert("is not present", () => !spewer.IsPresent);
+ }
+
+ [Test]
+ public void TestTimeJumps()
+ {
+ ManualClock testClock = new ManualClock();
+
+ AddStep("prepare clock", () =>
+ {
+ testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * -3;
+ spewer.Clock = new FramedClock(testClock);
+ });
+ AddStep("start spewer", () => spewer.Active.Value = true);
+ AddAssert("spawned first particle", () => spewer.TotalCreatedParticles == 1);
+
+ AddStep("move clock forward", () => testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * 3);
+ AddAssert("spawned second particle", () => spewer.TotalCreatedParticles == 2);
+
+ AddStep("move clock backwards", () => testClock.CurrentTime = TestParticleSpewer.MAX_DURATION * -1);
+ AddAssert("spawned third particle", () => spewer.TotalCreatedParticles == 3);
+ }
+
+ private TestParticleSpewer createSpewer() =>
+ new TestParticleSpewer(skinManager.DefaultLegacySkin.GetTexture("star2"))
+ {
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.Both,
+ RelativeSizeAxes = Axes.Both,
+ Position = new Vector2(0.5f),
+ Size = new Vector2(0.5f),
+ };
+
+ private class TestParticleSpewer : ParticleSpewer
+ {
+ public const int MAX_DURATION = 1500;
+ private const int rate = 250;
+
+ public int TotalCreatedParticles { get; private set; }
+
+ public float Gravity;
+
+ public float MaxVelocity = 0.25f;
+
+ public Vector2 SpawnPosition { get; set; } = new Vector2(0.5f);
+
+ protected override float ParticleGravity => Gravity;
+
+ public TestParticleSpewer(Texture texture)
+ : base(texture, rate, MAX_DURATION)
+ {
+ }
+
+ protected override FallingParticle CreateParticle()
+ {
+ TotalCreatedParticles++;
+
+ return new FallingParticle
+ {
+ Velocity = new Vector2(
+ RNG.NextSingle(-MaxVelocity, MaxVelocity),
+ RNG.NextSingle(-MaxVelocity, MaxVelocity)
+ ),
+ StartPosition = SpawnPosition,
+ Duration = RNG.NextSingle(MAX_DURATION),
+ StartAngle = RNG.NextSingle(MathF.PI * 2),
+ EndAngle = RNG.NextSingle(MathF.PI * 2),
+ EndScale = RNG.NextSingle(0.5f, 1.5f)
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 2ce0213ea2..0a3fedaf8e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -226,13 +226,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return base.OnMouseMove(e);
}
- public bool OnPressed(TestAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
box.Colour = Color4.White;
return true;
}
- public void OnReleased(TestAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
box.Colour = Color4.Black;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index 85a2870bf9..dfd5e2dc58 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -159,13 +159,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return base.OnMouseMove(e);
}
- public bool OnPressed(TestAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
box.Colour = Color4.White;
return true;
}
- public void OnReleased(TestAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
box.Colour = Color4.Black;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index d9d0dc6c58..6f5f774758 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -279,13 +279,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return base.OnMouseMove(e);
}
- public bool OnPressed(TestAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
box.Colour = Color4.White;
return true;
}
- public void OnReleased(TestAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
box.Colour = Color4.Black;
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
new file mode 100644
index 0000000000..5fdadfc2fb
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Login;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneLoginPanel : OsuManualInputManagerTestScene
+ {
+ private LoginPanel loginPanel;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create login dialog", () =>
+ {
+ Add(loginPanel = new LoginPanel
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 0.5f,
+ });
+ });
+ }
+
+ [Test]
+ public void TestBasicLogin()
+ {
+ AddStep("logout", () => API.Logout());
+
+ AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password");
+ AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick());
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
index 4e2a91af78..b7da31a2b5 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- public void TestJoinRoomWithIncorrectPassword()
+ public void TestJoinRoomWithIncorrectPasswordViaButton()
{
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
@@ -96,6 +96,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible);
+ AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
+ }
+
+ [Test]
+ public void TestJoinRoomWithIncorrectPasswordViaEnter()
+ {
+ DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;
+
+ AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
+ AddStep("select room", () => InputManager.Key(Key.Down));
+ AddStep("attempt join room", () => InputManager.Key(Key.Enter));
+ AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null);
+ AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong");
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+
+ AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
+ AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible);
+ AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
index 43459408d5..e2baa82ba0 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
@@ -96,9 +96,6 @@ namespace osu.Game.Tests.Visual.Navigation
{
AddStep("create game", () =>
{
- game = new OsuGame();
- game.SetHost(host);
-
Children = new Drawable[]
{
new Box
@@ -106,8 +103,9 @@ namespace osu.Game.Tests.Visual.Navigation
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
- game
};
+
+ AddGame(game = new OsuGame());
});
AddUntilStep("wait for load", () => game.IsLoaded);
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index cc64d37116..aeb800f58a 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -77,7 +77,13 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("press enter", () => InputManager.Key(Key.Enter));
- AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
+ AddUntilStep("wait for player", () =>
+ {
+ // dismiss any notifications that may appear (ie. muted notification).
+ clickMouseInCentre();
+ return (player = Game.ScreenStack.CurrentScreen as Player) != null;
+ });
+
AddAssert("retry count is 0", () => player.RestartCount == 0);
AddStep("attempt to retry", () => player.ChildrenOfType().First().Action());
@@ -104,7 +110,14 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } });
AddStep("press enter", () => InputManager.Key(Key.Enter));
- AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
+
+ AddUntilStep("wait for player", () =>
+ {
+ // dismiss any notifications that may appear (ie. muted notification).
+ clickMouseInCentre();
+ return (player = Game.ScreenStack.CurrentScreen as Player) != null;
+ });
+
AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning);
AddStep("seek to near end", () => player.ChildrenOfType().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000));
AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded);
@@ -131,7 +144,13 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("press enter", () => InputManager.Key(Key.Enter));
- AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
+ AddUntilStep("wait for player", () =>
+ {
+ // dismiss any notifications that may appear (ie. muted notification).
+ clickMouseInCentre();
+ return (player = Game.ScreenStack.CurrentScreen as Player) != null;
+ });
+
AddUntilStep("wait for fail", () => player.HasFailed);
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
@@ -399,7 +418,15 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
+ AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape));
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
+ AddStep("test dispose doesn't crash", () => Game.Dispose());
+ }
+
+ private void clickMouseInCentre()
+ {
+ InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre);
+ InputManager.Click(MouseButton.Left);
}
private void pushEscape() =>
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index 57ba051214..168d9fafcf 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Settings
{
AddAssert($"Check {name} is bound to {keyName}", () =>
{
- var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name));
+ var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == name));
var firstButton = firstRow.ChildrenOfType().First();
return firstButton.Text.Text == keyName;
@@ -247,7 +247,7 @@ namespace osu.Game.Tests.Visual.Settings
AddStep($"Scroll to {name}", () =>
{
- var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text == name));
+ var firstRow = panel.ChildrenOfType().First(r => r.ChildrenOfType().Any(s => s.Text.ToString() == name));
firstButton = firstRow.ChildrenOfType().First();
panel.ChildrenOfType().First().ScrollTo(firstButton);
diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs
index c55988d1bb..7729ad0ff3 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs
+++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs
@@ -5,8 +5,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
-using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osuTK.Graphics;
@@ -18,10 +18,18 @@ namespace osu.Game.Tests.Visual
{
private TestOsuScreenStack stack;
- [SetUpSteps]
- public void SetUpSteps()
+ [Cached]
+ private MusicController musicController = new MusicController();
+
+ [BackgroundDependencyLoader]
+ private void load()
{
- AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; });
+ stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both };
+
+ Add(musicController);
+ Add(stack);
+
+ LoadComponent(stack);
}
[Test]
@@ -42,6 +50,44 @@ namespace osu.Game.Tests.Visual
AddAssert("Parallax is off", () => stack.ParallaxAmount == 0);
}
+ [Test]
+ public void AllowTrackAdjustmentsTest()
+ {
+ AddStep("push allowing screen", () => stack.Push(loadNewScreen()));
+ AddAssert("allows adjustments 1", () => musicController.AllowTrackAdjustments);
+
+ AddStep("push inheriting screen", () => stack.Push(loadNewScreen()));
+ AddAssert("allows adjustments 2", () => musicController.AllowTrackAdjustments);
+
+ AddStep("push disallowing screen", () => stack.Push(loadNewScreen()));
+ AddAssert("disallows adjustments 3", () => !musicController.AllowTrackAdjustments);
+
+ AddStep("push inheriting screen", () => stack.Push(loadNewScreen()));
+ AddAssert("disallows adjustments 4", () => !musicController.AllowTrackAdjustments);
+
+ AddStep("push inheriting screen", () => stack.Push(loadNewScreen()));
+ AddAssert("disallows adjustments 5", () => !musicController.AllowTrackAdjustments);
+
+ AddStep("push allowing screen", () => stack.Push(loadNewScreen()));
+ AddAssert("allows adjustments 6", () => musicController.AllowTrackAdjustments);
+
+ // Now start exiting from screens
+ AddStep("exit screen", () => stack.Exit());
+ AddAssert("disallows adjustments 7", () => !musicController.AllowTrackAdjustments);
+
+ AddStep("exit screen", () => stack.Exit());
+ AddAssert("disallows adjustments 8", () => !musicController.AllowTrackAdjustments);
+
+ AddStep("exit screen", () => stack.Exit());
+ AddAssert("disallows adjustments 9", () => !musicController.AllowTrackAdjustments);
+
+ AddStep("exit screen", () => stack.Exit());
+ AddAssert("allows adjustments 10", () => musicController.AllowTrackAdjustments);
+
+ AddStep("exit screen", () => stack.Exit());
+ AddAssert("allows adjustments 11", () => musicController.AllowTrackAdjustments);
+ }
+
public class TestScreen : ScreenWithBeatmapBackground
{
private readonly string screenText;
@@ -78,5 +124,26 @@ namespace osu.Game.Tests.Visual
{
public new float ParallaxAmount => base.ParallaxAmount;
}
+
+ private class AllowScreen : OsuScreen
+ {
+ public override bool? AllowTrackAdjustments => true;
+ }
+
+ public class DisallowScreen : OsuScreen
+ {
+ public override bool? AllowTrackAdjustments => false;
+ }
+
+ private class InheritScreen : OsuScreen
+ {
+ }
+
+ private OsuScreen loadNewScreen() where T : OsuScreen, new()
+ {
+ OsuScreen screen = new T();
+ LoadComponent(screen);
+ return screen;
+ }
}
}
diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs
index 1cceb59b11..d402195f29 100644
--- a/osu.Game/Database/DatabaseContextFactory.cs
+++ b/osu.Game/Database/DatabaseContextFactory.cs
@@ -163,8 +163,11 @@ namespace osu.Game.Database
public void FlushConnections()
{
- foreach (var context in threadContexts.Values)
- context.Dispose();
+ if (threadContexts != null)
+ {
+ foreach (var context in threadContexts.Values)
+ context.Dispose();
+ }
recycleThreadContexts();
}
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 35c48a50d0..ab8763e576 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Allocation;
using System.Collections.Generic;
using osu.Framework.Graphics.Batches;
+using osu.Framework.Graphics.OpenGL.Buffers;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Lists;
@@ -181,7 +182,10 @@ namespace osu.Game.Graphics.Backgrounds
private void addTriangles(bool randomY)
{
- AimCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
+ // limited by the maximum size of QuadVertexBuffer for safety.
+ const int max_triangles = QuadVertexBuffer.MAX_QUADS;
+
+ AimCount = (int)Math.Min(max_triangles, (DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio));
for (int i = 0; i < AimCount - parts.Count; i++)
parts.Add(createTriangle(randomY));
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index b9b098df80..16ec7ab838 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -88,9 +88,9 @@ namespace osu.Game.Graphics.Containers
base.OnMouseUp(e);
}
- public virtual bool OnPressed(GlobalAction action)
+ public virtual bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
Hide();
@@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Graphics/ParticleExplosion.cs b/osu.Game/Graphics/ParticleExplosion.cs
index e0d2b50c55..094cc87bbe 100644
--- a/osu.Game/Graphics/ParticleExplosion.cs
+++ b/osu.Game/Graphics/ParticleExplosion.cs
@@ -115,6 +115,8 @@ namespace osu.Game.Graphics
null, TextureCoords);
}
}
+
+ protected override bool CanDrawOpaqueInterior => false;
}
private readonly struct ParticlePart
diff --git a/osu.Game/Graphics/ParticleSpewer.cs b/osu.Game/Graphics/ParticleSpewer.cs
new file mode 100644
index 0000000000..54a2b1e890
--- /dev/null
+++ b/osu.Game/Graphics/ParticleSpewer.cs
@@ -0,0 +1,200 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.EnumExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Vertices;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Utils;
+using osuTK;
+
+namespace osu.Game.Graphics
+{
+ public abstract class ParticleSpewer : Sprite
+ {
+ private readonly FallingParticle[] particles;
+ private int currentIndex;
+ private double lastParticleAdded;
+
+ private readonly double cooldown;
+ private readonly double maxDuration;
+
+ ///
+ /// Determines whether particles are being spawned.
+ ///
+ public readonly BindableBool Active = new BindableBool();
+
+ public override bool IsPresent => base.IsPresent && hasActiveParticles;
+
+ protected virtual bool CanSpawnParticles => true;
+ protected virtual float ParticleGravity => 0;
+
+ private bool hasActiveParticles => Active.Value || (lastParticleAdded + maxDuration) > Time.Current;
+
+ protected ParticleSpewer(Texture texture, int perSecond, double maxDuration)
+ {
+ Texture = texture;
+ Blending = BlendingParameters.Additive;
+
+ particles = new FallingParticle[perSecond * (int)Math.Ceiling(maxDuration / 1000)];
+
+ cooldown = 1000f / perSecond;
+ this.maxDuration = maxDuration;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (Active.Value && CanSpawnParticles && Math.Abs(Time.Current - lastParticleAdded) > cooldown)
+ {
+ var newParticle = CreateParticle();
+ newParticle.StartTime = (float)Time.Current;
+
+ particles[currentIndex] = newParticle;
+
+ currentIndex = (currentIndex + 1) % particles.Length;
+ lastParticleAdded = Time.Current;
+ }
+
+ Invalidate(Invalidation.DrawNode);
+ }
+
+ ///
+ /// Called each time a new particle should be spawned.
+ ///
+ protected virtual FallingParticle CreateParticle() => new FallingParticle();
+
+ protected override DrawNode CreateDrawNode() => new ParticleSpewerDrawNode(this);
+
+ # region DrawNode
+
+ private class ParticleSpewerDrawNode : SpriteDrawNode
+ {
+ private readonly FallingParticle[] particles;
+
+ protected new ParticleSpewer Source => (ParticleSpewer)base.Source;
+
+ private readonly float maxDuration;
+
+ private float currentTime;
+ private float gravity;
+ private Axes relativePositionAxes;
+ private Vector2 sourceSize;
+
+ public ParticleSpewerDrawNode(ParticleSpewer source)
+ : base(source)
+ {
+ particles = new FallingParticle[Source.particles.Length];
+ maxDuration = (float)Source.maxDuration;
+ }
+
+ public override void ApplyState()
+ {
+ base.ApplyState();
+
+ Source.particles.CopyTo(particles, 0);
+
+ currentTime = (float)Source.Time.Current;
+ gravity = Source.ParticleGravity;
+ relativePositionAxes = Source.RelativePositionAxes;
+ sourceSize = Source.DrawSize;
+ }
+
+ protected override void Blit(Action vertexAction)
+ {
+ foreach (var p in particles)
+ {
+ var timeSinceStart = currentTime - p.StartTime;
+
+ // ignore particles from the future.
+ // these can appear when seeking in replays.
+ if (timeSinceStart < 0) continue;
+
+ var alpha = p.AlphaAtTime(timeSinceStart);
+ if (alpha <= 0) continue;
+
+ var pos = p.PositionAtTime(timeSinceStart, gravity, maxDuration);
+ var scale = p.ScaleAtTime(timeSinceStart);
+ var angle = p.AngleAtTime(timeSinceStart);
+
+ var rect = createDrawRect(pos, scale);
+
+ var quad = new Quad(
+ transformPosition(rect.TopLeft, rect.Centre, angle),
+ transformPosition(rect.TopRight, rect.Centre, angle),
+ transformPosition(rect.BottomLeft, rect.Centre, angle),
+ transformPosition(rect.BottomRight, rect.Centre, angle)
+ );
+
+ DrawQuad(Texture, quad, DrawColourInfo.Colour.MultiplyAlpha(alpha), null, vertexAction,
+ new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
+ null, TextureCoords);
+ }
+ }
+
+ private RectangleF createDrawRect(Vector2 position, float scale)
+ {
+ var width = Texture.DisplayWidth * scale;
+ var height = Texture.DisplayHeight * scale;
+
+ if (relativePositionAxes.HasFlagFast(Axes.X))
+ position.X *= sourceSize.X;
+ if (relativePositionAxes.HasFlagFast(Axes.Y))
+ position.Y *= sourceSize.Y;
+
+ return new RectangleF(
+ position.X - width / 2,
+ position.Y - height / 2,
+ width,
+ height);
+ }
+
+ private Vector2 transformPosition(Vector2 pos, Vector2 centre, float angle)
+ {
+ float cos = MathF.Cos(angle);
+ float sin = MathF.Sin(angle);
+
+ float x = centre.X + (pos.X - centre.X) * cos + (pos.Y - centre.Y) * sin;
+ float y = centre.Y + (pos.Y - centre.Y) * cos - (pos.X - centre.X) * sin;
+
+ return Vector2Extensions.Transform(new Vector2(x, y), DrawInfo.Matrix);
+ }
+
+ protected override bool CanDrawOpaqueInterior => false;
+ }
+
+ #endregion
+
+ protected struct FallingParticle
+ {
+ public float StartTime;
+ public Vector2 StartPosition;
+ public Vector2 Velocity;
+ public float Duration;
+ public float StartAngle;
+ public float EndAngle;
+ public float EndScale;
+
+ public float AlphaAtTime(float timeSinceStart) => 1 - progressAtTime(timeSinceStart);
+
+ public float ScaleAtTime(float timeSinceStart) => (float)Interpolation.Lerp(1, EndScale, progressAtTime(timeSinceStart));
+
+ public float AngleAtTime(float timeSinceStart) => (float)Interpolation.Lerp(StartAngle, EndAngle, progressAtTime(timeSinceStart));
+
+ public Vector2 PositionAtTime(float timeSinceStart, float gravity, float maxDuration)
+ {
+ var progress = progressAtTime(timeSinceStart);
+ var currentGravity = new Vector2(0, gravity * Duration / maxDuration * progress);
+
+ return StartPosition + (Velocity + currentGravity) * timeSinceStart / maxDuration;
+ }
+
+ private float progressAtTime(float timeSinceStart) => Math.Clamp(timeSinceStart / Duration, 0, 1);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index fb7fe4947b..9cd403f409 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -12,6 +12,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Configuration;
@@ -57,9 +58,9 @@ namespace osu.Game.Graphics
shutter = audio.Samples.Get("UI/shutter");
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.TakeScreenshot:
shutter.Play();
@@ -70,7 +71,7 @@ namespace osu.Game.Graphics
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs
index 1607762908..c965fbcf45 100644
--- a/osu.Game/Graphics/UserInterface/BackButton.cs
+++ b/osu.Game/Graphics/UserInterface/BackButton.cs
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface
@@ -61,9 +62,9 @@ namespace osu.Game.Graphics.UserInterface
{
public Action OnBackPressed;
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
OnBackPressed?.Invoke();
@@ -73,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
index 6c8238a1b8..ceea9620c8 100644
--- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs
@@ -70,11 +70,11 @@ namespace osu.Game.Graphics.UserInterface
return base.OnKeyDown(e);
}
- public virtual bool OnPressed(GlobalAction action)
+ public virtual bool OnPressed(KeyBindingPressEvent e)
{
if (!HasFocus) return false;
- if (action == GlobalAction.Back)
+ if (e.Action == GlobalAction.Back)
{
if (Text.Length > 0)
{
@@ -86,7 +86,7 @@ namespace osu.Game.Graphics.UserInterface
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
index 4a91741ce6..6937782be6 100644
--- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
@@ -30,9 +30,9 @@ namespace osu.Game.Graphics.UserInterface
PlaceholderText = "type to search";
}
- public override bool OnPressed(PlatformAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PlatformAction.MoveBackwardLine:
case PlatformAction.MoveForwardLine:
@@ -43,7 +43,7 @@ namespace osu.Game.Graphics.UserInterface
return false;
}
- return base.OnPressed(action);
+ return base.OnPressed(e);
}
protected override bool OnKeyDown(KeyDownEvent e)
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
index 2cb696be0a..226c39c030 100644
--- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osuTK;
@@ -55,12 +56,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
this.FadeOut(fade_duration, Easing.OutQuint);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (State.Value == Visibility.Hidden)
return false;
- if (action == GlobalAction.Back)
+ if (e.Action == GlobalAction.Back)
{
Hide();
return true;
@@ -69,7 +70,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 0176a00e9d..f62131e2d7 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -2,11 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
namespace osu.Game.Input.Bindings
{
@@ -137,152 +138,152 @@ namespace osu.Game.Input.Bindings
public enum GlobalAction
{
- [Description("Toggle chat overlay")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChat))]
ToggleChat,
- [Description("Toggle social overlay")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleSocial))]
ToggleSocial,
- [Description("Reset input settings")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ResetInputSettings))]
ResetInputSettings,
- [Description("Toggle toolbar")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleToolbar))]
ToggleToolbar,
- [Description("Toggle settings")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleSettings))]
ToggleSettings,
- [Description("Toggle beatmap listing")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleBeatmapListing))]
ToggleBeatmapListing,
- [Description("Increase volume")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseVolume))]
IncreaseVolume,
- [Description("Decrease volume")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseVolume))]
DecreaseVolume,
- [Description("Toggle mute")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleMute))]
ToggleMute,
// In-Game Keybindings
- [Description("Skip cutscene")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SkipCutscene))]
SkipCutscene,
- [Description("Quick retry (hold)")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickRetry))]
QuickRetry,
- [Description("Take screenshot")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.TakeScreenshot))]
TakeScreenshot,
- [Description("Toggle gameplay mouse buttons")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons))]
ToggleGameplayMouseButtons,
- [Description("Back")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Back))]
Back,
- [Description("Increase scroll speed")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseScrollSpeed))]
IncreaseScrollSpeed,
- [Description("Decrease scroll speed")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseScrollSpeed))]
DecreaseScrollSpeed,
- [Description("Select")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Select))]
Select,
- [Description("Quick exit (hold)")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.QuickExit))]
QuickExit,
// Game-wide beatmap music controller keybindings
- [Description("Next track")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicNext))]
MusicNext,
- [Description("Previous track")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicPrev))]
MusicPrev,
- [Description("Play / pause")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.MusicPlay))]
MusicPlay,
- [Description("Toggle now playing overlay")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleNowPlaying))]
ToggleNowPlaying,
- [Description("Previous selection")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectPrevious))]
SelectPrevious,
- [Description("Next selection")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectNext))]
SelectNext,
- [Description("Home")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.Home))]
Home,
- [Description("Toggle notifications")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleNotifications))]
ToggleNotifications,
- [Description("Pause gameplay")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PauseGameplay))]
PauseGameplay,
// Editor
- [Description("Setup mode")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSetupMode))]
EditorSetupMode,
- [Description("Compose mode")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorComposeMode))]
EditorComposeMode,
- [Description("Design mode")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorDesignMode))]
EditorDesignMode,
- [Description("Timing mode")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTimingMode))]
EditorTimingMode,
- [Description("Hold for HUD")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.HoldForHUD))]
HoldForHUD,
- [Description("Random skin")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.RandomSkin))]
RandomSkin,
- [Description("Pause / resume replay")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.TogglePauseReplay))]
TogglePauseReplay,
- [Description("Toggle in-game interface")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))]
ToggleInGameInterface,
// Song select keybindings
- [Description("Toggle Mod Select")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))]
ToggleModSelection,
- [Description("Random")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectNextRandom))]
SelectNextRandom,
- [Description("Rewind")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectPreviousRandom))]
SelectPreviousRandom,
- [Description("Beatmap Options")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleBeatmapOptions))]
ToggleBeatmapOptions,
- [Description("Verify mode")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorVerifyMode))]
EditorVerifyMode,
- [Description("Nudge selection left")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorNudgeLeft))]
EditorNudgeLeft,
- [Description("Nudge selection right")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorNudgeRight))]
EditorNudgeRight,
- [Description("Toggle skin editor")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleSkinEditor))]
ToggleSkinEditor,
- [Description("Previous volume meter")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.PreviousVolumeMeter))]
PreviousVolumeMeter,
- [Description("Next volume meter")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.NextVolumeMeter))]
NextVolumeMeter,
- [Description("Seek replay forward")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SeekReplayForward))]
SeekReplayForward,
- [Description("Seek replay backward")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SeekReplayBackward))]
SeekReplayBackward,
- [Description("Toggle chat focus")]
+ [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))]
ToggleChatFocus
}
}
diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs
index f3d531cf6c..bfe21f650a 100644
--- a/osu.Game/Input/IdleTracker.cs
+++ b/osu.Game/Input/IdleTracker.cs
@@ -55,13 +55,13 @@ namespace osu.Game.Input
isIdle.Value = TimeSpentIdle > timeToIdle && AllowIdle;
}
- public bool OnPressed(PlatformAction action) => updateLastInteractionTime();
+ public bool OnPressed(KeyBindingPressEvent e) => updateLastInteractionTime();
- public void OnReleased(PlatformAction action) => updateLastInteractionTime();
+ public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime();
- public bool OnPressed(GlobalAction action) => updateLastInteractionTime();
+ public bool OnPressed(KeyBindingPressEvent e) => updateLastInteractionTime();
- public void OnReleased(GlobalAction action) => updateLastInteractionTime();
+ public void OnReleased(KeyBindingReleaseEvent e) => updateLastInteractionTime();
protected override bool Handle(UIEvent e)
{
diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
new file mode 100644
index 0000000000..14159f0d34
--- /dev/null
+++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs
@@ -0,0 +1,254 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GlobalActionKeyBindingStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GlobalActionKeyBinding";
+
+ ///
+ /// "Toggle chat overlay"
+ ///
+ public static LocalisableString ToggleChat => new TranslatableString(getKey(@"toggle_chat"), @"Toggle chat overlay");
+
+ ///
+ /// "Toggle social overlay"
+ ///
+ public static LocalisableString ToggleSocial => new TranslatableString(getKey(@"toggle_social"), @"Toggle social overlay");
+
+ ///
+ /// "Reset input settings"
+ ///
+ public static LocalisableString ResetInputSettings => new TranslatableString(getKey(@"reset_input_settings"), @"Reset input settings");
+
+ ///
+ /// "Toggle toolbar"
+ ///
+ public static LocalisableString ToggleToolbar => new TranslatableString(getKey(@"toggle_toolbar"), @"Toggle toolbar");
+
+ ///
+ /// "Toggle settings"
+ ///
+ public static LocalisableString ToggleSettings => new TranslatableString(getKey(@"toggle_settings"), @"Toggle settings");
+
+ ///
+ /// "Toggle beatmap listing"
+ ///
+ public static LocalisableString ToggleBeatmapListing => new TranslatableString(getKey(@"toggle_beatmap_listing"), @"Toggle beatmap listing");
+
+ ///
+ /// "Increase volume"
+ ///
+ public static LocalisableString IncreaseVolume => new TranslatableString(getKey(@"increase_volume"), @"Increase volume");
+
+ ///
+ /// "Decrease volume"
+ ///
+ public static LocalisableString DecreaseVolume => new TranslatableString(getKey(@"decrease_volume"), @"Decrease volume");
+
+ ///
+ /// "Toggle mute"
+ ///
+ public static LocalisableString ToggleMute => new TranslatableString(getKey(@"toggle_mute"), @"Toggle mute");
+
+ ///
+ /// "Skip cutscene"
+ ///
+ public static LocalisableString SkipCutscene => new TranslatableString(getKey(@"skip_cutscene"), @"Skip cutscene");
+
+ ///
+ /// "Quick retry (hold)"
+ ///
+ public static LocalisableString QuickRetry => new TranslatableString(getKey(@"quick_retry"), @"Quick retry (hold)");
+
+ ///
+ /// "Take screenshot"
+ ///
+ public static LocalisableString TakeScreenshot => new TranslatableString(getKey(@"take_screenshot"), @"Take screenshot");
+
+ ///
+ /// "Toggle gameplay mouse buttons"
+ ///
+ public static LocalisableString ToggleGameplayMouseButtons => new TranslatableString(getKey(@"toggle_gameplay_mouse_buttons"), @"Toggle gameplay mouse buttons");
+
+ ///
+ /// "Back"
+ ///
+ public static LocalisableString Back => new TranslatableString(getKey(@"back"), @"Back");
+
+ ///
+ /// "Increase scroll speed"
+ ///
+ public static LocalisableString IncreaseScrollSpeed => new TranslatableString(getKey(@"increase_scroll_speed"), @"Increase scroll speed");
+
+ ///
+ /// "Decrease scroll speed"
+ ///
+ public static LocalisableString DecreaseScrollSpeed => new TranslatableString(getKey(@"decrease_scroll_speed"), @"Decrease scroll speed");
+
+ ///
+ /// "Select"
+ ///
+ public static LocalisableString Select => new TranslatableString(getKey(@"select"), @"Select");
+
+ ///
+ /// "Quick exit (hold)"
+ ///
+ public static LocalisableString QuickExit => new TranslatableString(getKey(@"quick_exit"), @"Quick exit (hold)");
+
+ ///
+ /// "Next track"
+ ///
+ public static LocalisableString MusicNext => new TranslatableString(getKey(@"music_next"), @"Next track");
+
+ ///
+ /// "Previous track"
+ ///
+ public static LocalisableString MusicPrev => new TranslatableString(getKey(@"music_prev"), @"Previous track");
+
+ ///
+ /// "Play / pause"
+ ///
+ public static LocalisableString MusicPlay => new TranslatableString(getKey(@"music_play"), @"Play / pause");
+
+ ///
+ /// "Toggle now playing overlay"
+ ///
+ public static LocalisableString ToggleNowPlaying => new TranslatableString(getKey(@"toggle_now_playing"), @"Toggle now playing overlay");
+
+ ///
+ /// "Previous selection"
+ ///
+ public static LocalisableString SelectPrevious => new TranslatableString(getKey(@"select_previous"), @"Previous selection");
+
+ ///
+ /// "Next selection"
+ ///
+ public static LocalisableString SelectNext => new TranslatableString(getKey(@"select_next"), @"Next selection");
+
+ ///
+ /// "Home"
+ ///
+ public static LocalisableString Home => new TranslatableString(getKey(@"home"), @"Home");
+
+ ///
+ /// "Toggle notifications"
+ ///
+ public static LocalisableString ToggleNotifications => new TranslatableString(getKey(@"toggle_notifications"), @"Toggle notifications");
+
+ ///
+ /// "Pause gameplay"
+ ///
+ public static LocalisableString PauseGameplay => new TranslatableString(getKey(@"pause_gameplay"), @"Pause gameplay");
+
+ ///
+ /// "Setup mode"
+ ///
+ public static LocalisableString EditorSetupMode => new TranslatableString(getKey(@"editor_setup_mode"), @"Setup mode");
+
+ ///
+ /// "Compose mode"
+ ///
+ public static LocalisableString EditorComposeMode => new TranslatableString(getKey(@"editor_compose_mode"), @"Compose mode");
+
+ ///
+ /// "Design mode"
+ ///
+ public static LocalisableString EditorDesignMode => new TranslatableString(getKey(@"editor_design_mode"), @"Design mode");
+
+ ///
+ /// "Timing mode"
+ ///
+ public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
+
+ ///
+ /// "Hold for HUD"
+ ///
+ public static LocalisableString HoldForHUD => new TranslatableString(getKey(@"hold_for_hud"), @"Hold for HUD");
+
+ ///
+ /// "Random skin"
+ ///
+ public static LocalisableString RandomSkin => new TranslatableString(getKey(@"random_skin"), @"Random skin");
+
+ ///
+ /// "Pause / resume replay"
+ ///
+ public static LocalisableString TogglePauseReplay => new TranslatableString(getKey(@"toggle_pause_replay"), @"Pause / resume replay");
+
+ ///
+ /// "Toggle in-game interface"
+ ///
+ public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
+
+ ///
+ /// "Toggle Mod Select"
+ ///
+ public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle Mod Select");
+
+ ///
+ /// "Random"
+ ///
+ public static LocalisableString SelectNextRandom => new TranslatableString(getKey(@"select_next_random"), @"Random");
+
+ ///
+ /// "Rewind"
+ ///
+ public static LocalisableString SelectPreviousRandom => new TranslatableString(getKey(@"select_previous_random"), @"Rewind");
+
+ ///
+ /// "Beatmap Options"
+ ///
+ public static LocalisableString ToggleBeatmapOptions => new TranslatableString(getKey(@"toggle_beatmap_options"), @"Beatmap Options");
+
+ ///
+ /// "Verify mode"
+ ///
+ public static LocalisableString EditorVerifyMode => new TranslatableString(getKey(@"editor_verify_mode"), @"Verify mode");
+
+ ///
+ /// "Nudge selection left"
+ ///
+ public static LocalisableString EditorNudgeLeft => new TranslatableString(getKey(@"editor_nudge_left"), @"Nudge selection left");
+
+ ///
+ /// "Nudge selection right"
+ ///
+ public static LocalisableString EditorNudgeRight => new TranslatableString(getKey(@"editor_nudge_right"), @"Nudge selection right");
+
+ ///
+ /// "Toggle skin editor"
+ ///
+ public static LocalisableString ToggleSkinEditor => new TranslatableString(getKey(@"toggle_skin_editor"), @"Toggle skin editor");
+
+ ///
+ /// "Previous volume meter"
+ ///
+ public static LocalisableString PreviousVolumeMeter => new TranslatableString(getKey(@"previous_volume_meter"), @"Previous volume meter");
+
+ ///
+ /// "Next volume meter"
+ ///
+ public static LocalisableString NextVolumeMeter => new TranslatableString(getKey(@"next_volume_meter"), @"Next volume meter");
+
+ ///
+ /// "Seek replay forward"
+ ///
+ public static LocalisableString SeekReplayForward => new TranslatableString(getKey(@"seek_replay_forward"), @"Seek replay forward");
+
+ ///
+ /// "Seek replay backward"
+ ///
+ public static LocalisableString SeekReplayBackward => new TranslatableString(getKey(@"seek_replay_backward"), @"Seek replay backward");
+
+ ///
+ /// "Toggle chat focus"
+ ///
+ public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 2107b3a0e9..0e55a65aec 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -27,6 +27,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Collections;
@@ -968,11 +969,11 @@ namespace osu.Game
return component;
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (introScreen == null) return false;
- switch (action)
+ switch (e.Action)
{
case GlobalAction.ResetInputSettings:
Host.ResetInputHandlers();
@@ -1006,7 +1007,7 @@ namespace osu.Game
#endregion
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
@@ -1075,8 +1076,6 @@ namespace osu.Game
OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode);
API.Activity.BindTo(newOsuScreen.Activity);
- MusicController.AllowTrackAdjustments = newOsuScreen.AllowTrackAdjustments;
-
if (newOsuScreen.HideOverlaysOnEnter)
CloseAllOverlays();
else
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 59a05aec4f..7aa460981a 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -527,7 +527,7 @@ namespace osu.Game
BeatmapManager?.Dispose();
LocalConfig?.Dispose();
- contextFactory.FlushConnections();
+ contextFactory?.FlushConnections();
}
}
}
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
index 07a0cfb57f..bbde03aa10 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
@@ -186,9 +186,9 @@ namespace osu.Game.Overlays.BeatmapListing
return true;
}
- public override bool OnPressed(GlobalAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
- if (!base.OnPressed(action))
+ if (!base.OnPressed(e))
return false;
TextChanged?.Invoke();
diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs
index a8f2e654d7..ce12e9554d 100644
--- a/osu.Game/Overlays/ChangelogOverlay.cs
+++ b/osu.Game/Overlays/ChangelogOverlay.cs
@@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@@ -91,9 +92,9 @@ namespace osu.Game.Overlays
Show();
}
- public override bool OnPressed(GlobalAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
if (Current.Value == null)
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 0445c63eb4..25c5154d4a 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -372,9 +372,9 @@ namespace osu.Game.Overlays
return base.OnKeyDown(e);
}
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PlatformAction.TabNew:
ChannelTabControl.SelectChannelSelectorTab();
@@ -392,7 +392,7 @@ namespace osu.Game.Overlays
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index 43ef42a809..d5d31343f2 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -7,6 +7,7 @@ using osu.Game.Overlays.Dialog;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using System.Linq;
+using osu.Framework.Input.Events;
namespace osu.Game.Overlays
{
@@ -83,16 +84,16 @@ namespace osu.Game.Overlays
this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine);
}
- public override bool OnPressed(GlobalAction action)
+ public override bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Select:
CurrentDialog?.Buttons.OfType().FirstOrDefault()?.TriggerClick();
return true;
}
- return base.OnPressed(action);
+ return base.OnPressed(e);
}
}
}
diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs
new file mode 100644
index 0000000000..e43b84d52a
--- /dev/null
+++ b/osu.Game/Overlays/Login/LoginForm.cs
@@ -0,0 +1,111 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
+using osu.Game.Configuration;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Overlays.Settings;
+using osuTK;
+
+namespace osu.Game.Overlays.Login
+{
+ public class LoginForm : FillFlowContainer
+ {
+ private TextBox username;
+ private TextBox password;
+ private ShakeContainer shakeSignIn;
+
+ [Resolved(CanBeNull = true)]
+ private IAPIProvider api { get; set; }
+
+ public Action RequestHide;
+
+ private void performLogin()
+ {
+ if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text))
+ api?.Login(username.Text, password.Text);
+ else
+ shakeSignIn.Shake();
+ }
+
+ [BackgroundDependencyLoader(permitNulls: true)]
+ private void load(OsuConfigManager config, AccountCreationOverlay accountCreation)
+ {
+ Direction = FillDirection.Vertical;
+ Spacing = new Vector2(0, 5);
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
+ Children = new Drawable[]
+ {
+ username = new OsuTextBox
+ {
+ PlaceholderText = "username",
+ RelativeSizeAxes = Axes.X,
+ Text = api?.ProvidedUsername ?? string.Empty,
+ TabbableContentContainer = this
+ },
+ password = new OsuPasswordTextBox
+ {
+ PlaceholderText = "password",
+ RelativeSizeAxes = Axes.X,
+ TabbableContentContainer = this,
+ },
+ new SettingsCheckbox
+ {
+ LabelText = "Remember username",
+ Current = config.GetBindable(OsuSetting.SaveUsername),
+ },
+ new SettingsCheckbox
+ {
+ LabelText = "Stay signed in",
+ Current = config.GetBindable(OsuSetting.SavePassword),
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ shakeSignIn = new ShakeContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = new SettingsButton
+ {
+ Text = "Sign in",
+ Action = performLogin
+ },
+ }
+ }
+ },
+ new SettingsButton
+ {
+ Text = "Register",
+ Action = () =>
+ {
+ RequestHide();
+ accountCreation.Show();
+ }
+ }
+ };
+
+ password.OnCommit += (sender, newText) => performLogin();
+ }
+
+ public override bool AcceptsFocus => true;
+
+ protected override bool OnClick(ClickEvent e) => true;
+
+ protected override void OnFocus(FocusEvent e)
+ {
+ Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); });
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs
new file mode 100644
index 0000000000..d1e5bfe809
--- /dev/null
+++ b/osu.Game/Overlays/Login/LoginPanel.cs
@@ -0,0 +1,199 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Users;
+using osuTK;
+using RectangleF = osu.Framework.Graphics.Primitives.RectangleF;
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays.Login
+{
+ public class LoginPanel : FillFlowContainer
+ {
+ private bool bounding = true;
+ private LoginForm form;
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ private UserGridPanel panel;
+ private UserDropdown dropdown;
+
+ ///
+ /// Called to request a hide of a parent displaying this container.
+ ///
+ public Action RequestHide;
+
+ private readonly IBindable apiState = new Bindable();
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
+
+ public bool Bounding
+ {
+ get => bounding;
+ set
+ {
+ bounding = value;
+ Invalidate(Invalidation.MiscGeometry);
+ }
+ }
+
+ public LoginPanel()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ Direction = FillDirection.Vertical;
+ Spacing = new Vector2(0f, 5f);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ apiState.BindTo(api.State);
+ apiState.BindValueChanged(onlineStateChanged, true);
+ }
+
+ private void onlineStateChanged(ValueChangedEvent state) => Schedule(() =>
+ {
+ form = null;
+
+ switch (state.NewValue)
+ {
+ case APIState.Offline:
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = "ACCOUNT",
+ Margin = new MarginPadding { Bottom = 5 },
+ Font = OsuFont.GetFont(weight: FontWeight.Bold),
+ },
+ form = new LoginForm
+ {
+ RequestHide = RequestHide
+ }
+ };
+ break;
+
+ case APIState.Failing:
+ case APIState.Connecting:
+ LinkFlowContainer linkFlow;
+
+ Children = new Drawable[]
+ {
+ new LoadingSpinner
+ {
+ State = { Value = Visibility.Visible },
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ },
+ linkFlow = new LinkFlowContainer
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ TextAnchor = Anchor.TopCentre,
+ AutoSizeAxes = Axes.Both,
+ Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
+ Margin = new MarginPadding { Top = 10, Bottom = 10 },
+ },
+ };
+
+ linkFlow.AddLink("cancel", api.Logout, string.Empty);
+ break;
+
+ case APIState.Online:
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Left = 20, Right = 20 },
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0f, 10f),
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Signed in",
+ Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold),
+ Margin = new MarginPadding { Top = 5, Bottom = 5 },
+ },
+ },
+ },
+ panel = new UserGridPanel(api.LocalUser.Value)
+ {
+ RelativeSizeAxes = Axes.X,
+ Action = RequestHide
+ },
+ dropdown = new UserDropdown { RelativeSizeAxes = Axes.X },
+ },
+ },
+ };
+
+ panel.Status.BindTo(api.LocalUser.Value.Status);
+ panel.Activity.BindTo(api.LocalUser.Value.Activity);
+
+ dropdown.Current.BindValueChanged(action =>
+ {
+ switch (action.NewValue)
+ {
+ case UserAction.Online:
+ api.LocalUser.Value.Status.Value = new UserStatusOnline();
+ dropdown.StatusColour = colours.Green;
+ break;
+
+ case UserAction.DoNotDisturb:
+ api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
+ dropdown.StatusColour = colours.Red;
+ break;
+
+ case UserAction.AppearOffline:
+ api.LocalUser.Value.Status.Value = new UserStatusOffline();
+ dropdown.StatusColour = colours.Gray7;
+ break;
+
+ case UserAction.SignOut:
+ api.Logout();
+ break;
+ }
+ }, true);
+ break;
+ }
+
+ if (form != null) GetContainingInputManager()?.ChangeFocus(form);
+ });
+
+ public override bool AcceptsFocus => true;
+
+ protected override bool OnClick(ClickEvent e) => true;
+
+ protected override void OnFocus(FocusEvent e)
+ {
+ if (form != null) GetContainingInputManager().ChangeFocus(form);
+ base.OnFocus(e);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Login/UserAction.cs b/osu.Game/Overlays/Login/UserAction.cs
new file mode 100644
index 0000000000..07b6b4bf7e
--- /dev/null
+++ b/osu.Game/Overlays/Login/UserAction.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+
+namespace osu.Game.Overlays.Login
+{
+ public enum UserAction
+ {
+ Online,
+
+ [Description(@"Do not disturb")]
+ DoNotDisturb,
+
+ [Description(@"Appear offline")]
+ AppearOffline,
+
+ [Description(@"Sign out")]
+ SignOut,
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Overlays/Login/UserDropdown.cs b/osu.Game/Overlays/Login/UserDropdown.cs
new file mode 100644
index 0000000000..ac4e7f8eda
--- /dev/null
+++ b/osu.Game/Overlays/Login/UserDropdown.cs
@@ -0,0 +1,124 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.Login
+{
+ public class UserDropdown : OsuEnumDropdown
+ {
+ protected override DropdownHeader CreateHeader() => new UserDropdownHeader();
+
+ protected override DropdownMenu CreateMenu() => new UserDropdownMenu();
+
+ public Color4 StatusColour
+ {
+ set
+ {
+ if (Header is UserDropdownHeader h)
+ h.StatusColour = value;
+ }
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ AccentColour = colours.Gray5;
+ }
+
+ protected class UserDropdownMenu : OsuDropdownMenu
+ {
+ public UserDropdownMenu()
+ {
+ Masking = true;
+ CornerRadius = 5;
+
+ Margin = new MarginPadding { Bottom = 5 };
+
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(0.25f),
+ Radius = 4,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ BackgroundColour = colours.Gray3;
+ }
+
+ protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item);
+
+ private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem
+ {
+ public DrawableUserDropdownMenuItem(MenuItem item)
+ : base(item)
+ {
+ Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
+ CornerRadius = 5;
+ }
+
+ protected override Drawable CreateContent() => new Content
+ {
+ Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } }
+ };
+ }
+ }
+
+ private class UserDropdownHeader : OsuDropdownHeader
+ {
+ public const float LABEL_LEFT_MARGIN = 20;
+
+ private readonly SpriteIcon statusIcon;
+
+ public Color4 StatusColour
+ {
+ set => statusIcon.FadeColour(value, 500, Easing.OutQuint);
+ }
+
+ public UserDropdownHeader()
+ {
+ Foreground.Padding = new MarginPadding { Left = 10, Right = 10 };
+ Margin = new MarginPadding { Bottom = 5 };
+ Masking = true;
+ CornerRadius = 5;
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(0.25f),
+ Radius = 4,
+ };
+
+ Icon.Size = new Vector2(14);
+ Icon.Margin = new MarginPadding(0);
+
+ Foreground.Add(statusIcon = new SpriteIcon
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Icon = FontAwesome.Regular.Circle,
+ Size = new Vector2(14),
+ });
+
+ Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ BackgroundColour = colours.Gray3;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs
index e7caaa3aca..f3562aa6d9 100644
--- a/osu.Game/Overlays/LoginOverlay.cs
+++ b/osu.Game/Overlays/LoginOverlay.cs
@@ -5,17 +5,17 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
-using osu.Game.Overlays.Settings.Sections.General;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
+using osu.Game.Overlays.Login;
namespace osu.Game.Overlays
{
public class LoginOverlay : OsuFocusedOverlayContainer
{
- private LoginSettings settingsSection;
+ private LoginPanel panel;
private const float transition_time = 400;
@@ -50,7 +50,7 @@ namespace osu.Game.Overlays
AutoSizeEasing = Easing.OutQuint,
Children = new Drawable[]
{
- settingsSection = new LoginSettings
+ panel = new LoginPanel
{
Padding = new MarginPadding(10),
RequestHide = Hide,
@@ -75,17 +75,17 @@ namespace osu.Game.Overlays
{
base.PopIn();
- settingsSection.Bounding = true;
+ panel.Bounding = true;
this.FadeIn(transition_time, Easing.OutQuint);
- GetContainingInputManager().ChangeFocus(settingsSection);
+ GetContainingInputManager().ChangeFocus(panel);
}
protected override void PopOut()
{
base.PopOut();
- settingsSection.Bounding = false;
+ panel.Bounding = false;
this.FadeOut(transition_time);
}
}
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index fdef48d556..ec7e49920c 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -416,7 +416,7 @@ namespace osu.Game.Overlays.Mods
return base.OnKeyDown(e);
}
- public override bool OnPressed(GlobalAction action) => false; // handled by back button
+ public override bool OnPressed(KeyBindingPressEvent e) => false; // handled by back button
private void updateAvailableMods()
{
diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs
index f06e02e5e1..dba4bf926f 100644
--- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs
+++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
@@ -26,23 +27,23 @@ namespace osu.Game.Overlays.Music
[Resolved(canBeNull: true)]
private OnScreenDisplay onScreenDisplay { get; set; }
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (beatmap.Disabled)
return false;
- switch (action)
+ switch (e.Action)
{
case GlobalAction.MusicPlay:
// use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842)
bool wasPlaying = musicController.IsPlaying;
if (musicController.TogglePause())
- onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", action));
+ onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track", e.Action));
return true;
case GlobalAction.MusicNext:
- musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", action)));
+ musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track", e.Action)));
return true;
@@ -52,11 +53,11 @@ namespace osu.Game.Overlays.Music
switch (res)
{
case PreviousTrackResult.Restart:
- onScreenDisplay?.Display(new MusicActionToast("Restart track", action));
+ onScreenDisplay?.Display(new MusicActionToast("Restart track", e.Action));
break;
case PreviousTrackResult.Previous:
- onScreenDisplay?.Display(new MusicActionToast("Previous track", action));
+ onScreenDisplay?.Display(new MusicActionToast("Previous track", e.Action));
break;
}
});
@@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Music
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
deleted file mode 100644
index 8f757f7a36..0000000000
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.UserInterface;
-using osu.Game.Configuration;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Online.API;
-using osuTK;
-using osu.Game.Users;
-using System.ComponentModel;
-using osu.Framework.Bindables;
-using osu.Game.Graphics;
-using osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Containers;
-using RectangleF = osu.Framework.Graphics.Primitives.RectangleF;
-using Container = osu.Framework.Graphics.Containers.Container;
-
-namespace osu.Game.Overlays.Settings.Sections.General
-{
- public class LoginSettings : FillFlowContainer
- {
- private bool bounding = true;
- private LoginForm form;
-
- [Resolved]
- private OsuColour colours { get; set; }
-
- private UserGridPanel panel;
- private UserDropdown dropdown;
-
- ///
- /// Called to request a hide of a parent displaying this container.
- ///
- public Action RequestHide;
-
- private readonly IBindable apiState = new Bindable();
-
- [Resolved]
- private IAPIProvider api { get; set; }
-
- public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
-
- public bool Bounding
- {
- get => bounding;
- set
- {
- bounding = value;
- Invalidate(Invalidation.MiscGeometry);
- }
- }
-
- public LoginSettings()
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
- Direction = FillDirection.Vertical;
- Spacing = new Vector2(0f, 5f);
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- apiState.BindTo(api.State);
- apiState.BindValueChanged(onlineStateChanged, true);
- }
-
- private void onlineStateChanged(ValueChangedEvent state) => Schedule(() =>
- {
- form = null;
-
- switch (state.NewValue)
- {
- case APIState.Offline:
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Text = "ACCOUNT",
- Margin = new MarginPadding { Bottom = 5 },
- Font = OsuFont.GetFont(weight: FontWeight.Bold),
- },
- form = new LoginForm
- {
- RequestHide = RequestHide
- }
- };
- break;
-
- case APIState.Failing:
- case APIState.Connecting:
- LinkFlowContainer linkFlow;
-
- Children = new Drawable[]
- {
- new LoadingSpinner
- {
- State = { Value = Visibility.Visible },
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- },
- linkFlow = new LinkFlowContainer
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- TextAnchor = Anchor.TopCentre,
- AutoSizeAxes = Axes.Both,
- Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
- Margin = new MarginPadding { Top = 10, Bottom = 10 },
- },
- };
-
- linkFlow.AddLink("cancel", api.Logout, string.Empty);
- break;
-
- case APIState.Online:
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Left = 20, Right = 20 },
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0f, 10f),
- Children = new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new[]
- {
- new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = "Signed in",
- Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold),
- Margin = new MarginPadding { Top = 5, Bottom = 5 },
- },
- },
- },
- panel = new UserGridPanel(api.LocalUser.Value)
- {
- RelativeSizeAxes = Axes.X,
- Action = RequestHide
- },
- dropdown = new UserDropdown { RelativeSizeAxes = Axes.X },
- },
- },
- };
-
- panel.Status.BindTo(api.LocalUser.Value.Status);
- panel.Activity.BindTo(api.LocalUser.Value.Activity);
-
- dropdown.Current.BindValueChanged(action =>
- {
- switch (action.NewValue)
- {
- case UserAction.Online:
- api.LocalUser.Value.Status.Value = new UserStatusOnline();
- dropdown.StatusColour = colours.Green;
- break;
-
- case UserAction.DoNotDisturb:
- api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
- dropdown.StatusColour = colours.Red;
- break;
-
- case UserAction.AppearOffline:
- api.LocalUser.Value.Status.Value = new UserStatusOffline();
- dropdown.StatusColour = colours.Gray7;
- break;
-
- case UserAction.SignOut:
- api.Logout();
- break;
- }
- }, true);
- break;
- }
-
- if (form != null) GetContainingInputManager()?.ChangeFocus(form);
- });
-
- public override bool AcceptsFocus => true;
-
- protected override bool OnClick(ClickEvent e) => true;
-
- protected override void OnFocus(FocusEvent e)
- {
- if (form != null) GetContainingInputManager().ChangeFocus(form);
- base.OnFocus(e);
- }
-
- private class LoginForm : FillFlowContainer
- {
- private TextBox username;
- private TextBox password;
- private ShakeContainer shakeSignIn;
-
- [Resolved(CanBeNull = true)]
- private IAPIProvider api { get; set; }
-
- public Action RequestHide;
-
- private void performLogin()
- {
- if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text))
- api?.Login(username.Text, password.Text);
- else
- shakeSignIn.Shake();
- }
-
- [BackgroundDependencyLoader(permitNulls: true)]
- private void load(OsuConfigManager config, AccountCreationOverlay accountCreation)
- {
- Direction = FillDirection.Vertical;
- Spacing = new Vector2(0, 5);
- AutoSizeAxes = Axes.Y;
- RelativeSizeAxes = Axes.X;
- Children = new Drawable[]
- {
- username = new OsuTextBox
- {
- PlaceholderText = "username",
- RelativeSizeAxes = Axes.X,
- Text = api?.ProvidedUsername ?? string.Empty,
- TabbableContentContainer = this
- },
- password = new OsuPasswordTextBox
- {
- PlaceholderText = "password",
- RelativeSizeAxes = Axes.X,
- TabbableContentContainer = this,
- },
- new SettingsCheckbox
- {
- LabelText = "Remember username",
- Current = config.GetBindable(OsuSetting.SaveUsername),
- },
- new SettingsCheckbox
- {
- LabelText = "Stay signed in",
- Current = config.GetBindable(OsuSetting.SavePassword),
- },
- new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- shakeSignIn = new ShakeContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Child = new SettingsButton
- {
- Text = "Sign in",
- Action = performLogin
- },
- }
- }
- },
- new SettingsButton
- {
- Text = "Register",
- Action = () =>
- {
- RequestHide();
- accountCreation.Show();
- }
- }
- };
-
- password.OnCommit += (sender, newText) => performLogin();
- }
-
- public override bool AcceptsFocus => true;
-
- protected override bool OnClick(ClickEvent e) => true;
-
- protected override void OnFocus(FocusEvent e)
- {
- Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); });
- }
- }
-
- private class UserDropdown : OsuEnumDropdown
- {
- protected override DropdownHeader CreateHeader() => new UserDropdownHeader();
-
- protected override DropdownMenu CreateMenu() => new UserDropdownMenu();
-
- public Color4 StatusColour
- {
- set
- {
- if (Header is UserDropdownHeader h)
- h.StatusColour = value;
- }
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- AccentColour = colours.Gray5;
- }
-
- private class UserDropdownMenu : OsuDropdownMenu
- {
- public UserDropdownMenu()
- {
- Masking = true;
- CornerRadius = 5;
-
- Margin = new MarginPadding { Bottom = 5 };
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Black.Opacity(0.25f),
- Radius = 4,
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- BackgroundColour = colours.Gray3;
- }
-
- protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item);
-
- private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem
- {
- public DrawableUserDropdownMenuItem(MenuItem item)
- : base(item)
- {
- Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
- CornerRadius = 5;
- }
-
- protected override Drawable CreateContent() => new Content
- {
- Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } }
- };
- }
- }
-
- private class UserDropdownHeader : OsuDropdownHeader
- {
- public const float LABEL_LEFT_MARGIN = 20;
-
- private readonly SpriteIcon statusIcon;
-
- public Color4 StatusColour
- {
- set => statusIcon.FadeColour(value, 500, Easing.OutQuint);
- }
-
- public UserDropdownHeader()
- {
- Foreground.Padding = new MarginPadding { Left = 10, Right = 10 };
- Margin = new MarginPadding { Bottom = 5 };
- Masking = true;
- CornerRadius = 5;
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Black.Opacity(0.25f),
- Radius = 4,
- };
-
- Icon.Size = new Vector2(14);
- Icon.Margin = new MarginPadding(0);
-
- Foreground.Add(statusIcon = new SpriteIcon
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Icon = FontAwesome.Regular.Circle,
- Size = new Vector2(14),
- });
-
- Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- BackgroundColour = colours.Gray3;
- }
- }
- }
-
- private enum UserAction
- {
- Online,
-
- [Description(@"Do not disturb")]
- DoNotDisturb,
-
- [Description(@"Appear offline")]
- AppearOffline,
-
- [Description(@"Sign out")]
- SignOut,
- }
- }
-}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
index c38c516f21..85d88c96f8 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
},
text = new OsuSpriteText
{
- Text = action.GetDescription(),
+ Text = action.GetLocalisableDescription(),
Margin = new MarginPadding(padding),
},
buttons = new FillFlowContainer
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
index 1d67968ab1..e509cac2f1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
@@ -24,8 +24,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private OsuDirectorySelector directorySelector;
- public override bool AllowTrackAdjustments => false;
-
///
/// Text to display in the header to inform the user of what they are selecting.
///
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 7481cfdbf5..dc0b06b255 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -178,12 +178,12 @@ namespace osu.Game.Overlays.Toolbar
this.FadeOut(transition_time, Easing.InQuint);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (OverlayActivationMode.Value == OverlayActivation.Disabled)
return false;
- switch (action)
+ switch (e.Action)
{
case GlobalAction.ToggleToolbar:
hiddenByUser = State.Value == Visibility.Visible; // set before toggling to allow the operation to always succeed.
@@ -194,7 +194,7 @@ namespace osu.Game.Overlays.Toolbar
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index 6da41b2b5f..dd554200ca 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -184,9 +184,9 @@ namespace osu.Game.Overlays.Toolbar
tooltipContainer.FadeOut(100);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == Hotkey)
+ if (e.Action == Hotkey)
{
TriggerClick();
return true;
@@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Toolbar
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
index b24214ff3d..4129b46ce3 100644
--- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
+++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs
@@ -19,27 +19,27 @@ namespace osu.Game.Overlays.Volume
private ScheduledDelegate keyRepeat;
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.DecreaseVolume:
case GlobalAction.IncreaseVolume:
keyRepeat?.Cancel();
- keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(action), 150);
+ keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(e.Action), 150);
return true;
case GlobalAction.ToggleMute:
case GlobalAction.NextVolumeMeter:
case GlobalAction.PreviousVolumeMeter:
- ActionRequested?.Invoke(action);
+ ActionRequested?.Invoke(e.Action);
return true;
}
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
keyRepeat?.Cancel();
}
@@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Volume
return true;
}
- public bool OnScroll(GlobalAction action, float amount, bool isPrecise) =>
- ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
+ public bool OnScroll(KeyBindingScrollEvent e) =>
+ ScrollActionRequested?.Invoke(e.Action, e.ScrollAmount, e.IsPrecise) ?? false;
}
}
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 7249dd77e5..ff28b45ebb 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -365,12 +365,12 @@ namespace osu.Game.Overlays.Volume
{
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (!IsHovered)
return false;
- switch (action)
+ switch (e.Action)
{
case GlobalAction.SelectPrevious:
State = SelectionState.Selected;
@@ -386,7 +386,7 @@ namespace osu.Game.Overlays.Volume
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Overlays/Wiki/WikiMainPage.cs b/osu.Game/Overlays/Wiki/WikiMainPage.cs
index 3fb0aa450e..c9ee2cbfd5 100644
--- a/osu.Game/Overlays/Wiki/WikiMainPage.cs
+++ b/osu.Game/Overlays/Wiki/WikiMainPage.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers;
using HtmlAgilityPack;
using osu.Framework.Allocation;
using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Wiki
{
@@ -56,12 +56,12 @@ namespace osu.Game.Overlays.Wiki
{
Vertical = 30,
},
- Child = new OsuSpriteText
+ Child = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(Typeface.Inter, size: 12, weight: FontWeight.Light))
{
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
Text = blurbNode.InnerText,
- Font = OsuFont.GetFont(Typeface.Inter, size: 12, weight: FontWeight.Light),
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
+ TextAnchor = Anchor.TopCentre,
}
};
}
diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs
index 7abae71273..a77a83b36c 100644
--- a/osu.Game/Rulesets/Mods/ModFlashlight.cs
+++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs
@@ -69,9 +69,11 @@ namespace osu.Game.Rulesets.Mods
public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
var flashlight = CreateFlashlight();
- flashlight.Combo = Combo;
+
flashlight.RelativeSizeAxes = Axes.Both;
flashlight.Colour = Color4.Black;
+
+ flashlight.Combo.BindTo(Combo);
drawableRuleset.KeyBindingInputManager.Add(flashlight);
flashlight.Breaks = drawableRuleset.Beatmap.Breaks;
@@ -81,7 +83,8 @@ namespace osu.Game.Rulesets.Mods
public abstract class Flashlight : Drawable
{
- internal BindableInt Combo;
+ public readonly BindableInt Combo = new BindableInt();
+
private IShader shader;
protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this);
diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs
index d18e0f9541..b57c224059 100644
--- a/osu.Game/Rulesets/UI/ReplayRecorder.cs
+++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs
@@ -70,16 +70,16 @@ namespace osu.Game.Rulesets.UI
return base.OnMouseMove(e);
}
- public bool OnPressed(T action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- pressedActions.Add(action);
+ pressedActions.Add(e.Action);
recordFrame(true);
return false;
}
- public void OnReleased(T action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- pressedActions.Remove(action);
+ pressedActions.Remove(e.Action);
recordFrame(true);
}
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index e6cd2aa3dc..a3f311c7a6 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -152,12 +152,12 @@ namespace osu.Game.Rulesets.UI
{
}
- public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.Rate >= 0));
+ public bool OnPressed(KeyBindingPressEvent e) => Target.Children.OfType>().Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
- public void OnReleased(T action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
foreach (var c in Target.Children.OfType>())
- c.OnReleased(action, Clock.Rate >= 0);
+ c.OnReleased(e.Action, Clock.Rate >= 0);
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
index f8d5a6c5a9..7b30bb9574 100644
--- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Framework.Lists;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
@@ -188,12 +189,12 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// The amount to adjust by. Greater than 0 if the scroll speed should be increased, less than 0 if it should be decreased.
protected virtual void AdjustScrollSpeed(int amount) => this.TransformBindableTo(TimeRange, TimeRange.Value - amount * time_span_step, 200, Easing.OutQuint);
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (!UserScrollSpeedAdjustment)
return false;
- switch (action)
+ switch (e.Action)
{
case GlobalAction.IncreaseScrollSpeed:
scheduleScrollSpeedAdjustment(1);
@@ -209,7 +210,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
private ScheduledDelegate scheduledScrollSpeedAdjustment;
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
scheduledScrollSpeedAdjustment?.Cancel();
scheduledScrollSpeedAdjustment = null;
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index b99dacbd4a..75d4d13f94 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -228,9 +228,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false;
}
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PlatformAction.SelectAll:
SelectAll();
@@ -240,7 +240,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index 1d1d95890f..44eb062db8 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -137,9 +137,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Whether any items could be reversed.
public virtual bool HandleReverse() => false;
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PlatformAction.Delete:
DeleteSelected();
@@ -149,7 +149,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs
index 354013a5fd..c43e554b85 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs
@@ -4,6 +4,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects;
using osuTK;
@@ -20,9 +21,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
// for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation
public override bool HandleMovement(MoveSelectionEvent moveEvent) => true;
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.EditorNudgeLeft:
nudgeSelection(-1);
@@ -36,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index e324f1edeb..926a2ad4e0 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
@@ -77,15 +78,15 @@ namespace osu.Game.Screens.Edit.Compose
#region Input Handling
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == PlatformAction.Copy)
+ if (e.Action == PlatformAction.Copy)
host.GetClipboard()?.SetText(formatSelectionAsString());
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 5bb47e1c11..2ff0101dc0 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit
public override bool DisallowExternalBeatmapRulesetChanges => true;
- public override bool AllowTrackAdjustments => false;
+ public override bool? AllowTrackAdjustments => false;
protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash;
@@ -347,9 +347,9 @@ namespace osu.Game.Screens.Edit
clock.ProcessFrame();
}
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case PlatformAction.Cut:
Cut();
@@ -379,7 +379,7 @@ namespace osu.Game.Screens.Edit
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
@@ -434,9 +434,9 @@ namespace osu.Game.Screens.Edit
return true;
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
// as we don't want to display the back button, manual handling of exit action is required.
@@ -468,7 +468,7 @@ namespace osu.Game.Screens.Edit
}
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs
index 05a8fdd26d..b38a62d838 100644
--- a/osu.Game/Screens/Edit/Verify/IssueTable.cs
+++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
@@ -52,7 +53,7 @@ namespace osu.Game.Screens.Edit.Verify
if (issue.Time != null)
{
clock.Seek(issue.Time.Value);
- editor.OnPressed(GlobalAction.EditorComposeMode);
+ editor.OnPressed(new KeyBindingPressEvent(GetContainingInputManager().CurrentState, GlobalAction.EditorComposeMode));
}
if (!issue.HitObjects.Any())
diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs
index 17384c161c..910a0c7d61 100644
--- a/osu.Game/Screens/IOsuScreen.cs
+++ b/osu.Game/Screens/IOsuScreen.cs
@@ -59,9 +59,10 @@ namespace osu.Game.Screens
Bindable Ruleset { get; }
///
- /// Whether mod track adjustments are allowed to be applied.
+ /// Whether mod track adjustments should be applied on entering this screen.
+ /// A value means that the parent screen's value of this setting will be used.
///
- bool AllowTrackAdjustments { get; }
+ bool? AllowTrackAdjustments { get; }
///
/// Invoked when the back button has been pressed to close any overlays before exiting this .
diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs
index 606174193d..7e1d55b3e2 100644
--- a/osu.Game/Screens/Import/FileImportScreen.cs
+++ b/osu.Game/Screens/Import/FileImportScreen.cs
@@ -23,8 +23,6 @@ namespace osu.Game.Screens.Import
{
public override bool HideOverlaysOnEnter => true;
- public override bool AllowTrackAdjustments => false;
-
private OsuFileSelector fileSelector;
private Container contentContainer;
private TextFlowContainer currentFileText;
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 6c712e9d5b..5f76176aab 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -218,9 +218,9 @@ namespace osu.Game.Screens.Menu
return base.OnKeyDown(e);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
return goBack();
@@ -234,7 +234,7 @@ namespace osu.Game.Screens.Menu
}
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
index a491283e5f..364da2f887 100644
--- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
+++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@@ -18,9 +19,9 @@ namespace osu.Game.Screens.Menu
{
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action == GlobalAction.Back)
+ if (e.Action == GlobalAction.Back)
{
BeginConfirm();
return true;
@@ -29,9 +30,9 @@ namespace osu.Game.Screens.Menu
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action == GlobalAction.Back)
+ if (e.Action == GlobalAction.Back)
{
if (!Fired)
AbortConfirm();
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 8b2ec43e3e..221b31f855 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Screens.Menu
public override bool AllowExternalScreenChange => true;
- public override bool AllowTrackAdjustments => false;
-
private Screen songSelect;
private MenuSideFlashes sideFlashes;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 76cb02199b..907b7e308a 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -141,29 +141,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
#region Key selection logic (shared with BeatmapCarousel)
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.SelectNext:
- beginRepeatSelection(() => selectNext(1), action);
+ beginRepeatSelection(() => selectNext(1), e.Action);
return true;
case GlobalAction.SelectPrevious:
- beginRepeatSelection(() => selectNext(-1), action);
+ beginRepeatSelection(() => selectNext(-1), e.Action);
return true;
}
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.SelectNext:
case GlobalAction.SelectPrevious:
- endRepeatSelection(action);
+ endRepeatSelection(e.Action);
break;
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
index 95fa34ab43..fe7c7cc364 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
@@ -132,12 +132,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
})
};
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (SelectedRoom.Value != Room)
return false;
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Select:
TriggerClick();
@@ -147,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
@@ -189,9 +189,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private OsuPasswordTextBox passwordTextbox;
private TriangleButton joinButton;
private OsuSpriteText errorText;
+ private Sample sampleJoinFail;
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, AudioManager audio)
{
Child = new FillFlowContainer
{
@@ -227,6 +228,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
}
};
+ sampleJoinFail = audio.Samples.Get(@"UI/password-fail");
+
joinButton.Action = () => lounge?.Join(room, passwordTextbox.Text, null, joinFailed);
}
@@ -234,6 +237,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
passwordTextbox.Text = string.Empty;
+ GetContainingInputManager().ChangeFocus(passwordTextbox);
+
errorText.Text = error;
errorText
.FadeIn()
@@ -242,6 +247,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
.FadeOutFromOne(1000, Easing.In);
Body.Shake();
+
+ sampleJoinFail?.Play();
}
protected override void LoadComplete()
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
index 3801463095..53131ab90e 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
namespace osu.Game.Screens.OnlinePlay.Match.Components
{
@@ -16,12 +17,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
Triangles.TriangleScale = 1.5f;
}
- public bool OnPressed(PlatformAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
if (!Enabled.Value)
return false;
- switch (action)
+ switch (e.Action)
{
case PlatformAction.DocumentNew:
// might as well also handle new tab. it's a bit of an undefined flow on this screen.
@@ -33,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
return false;
}
- public void OnReleased(PlatformAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
index 7a8839cdad..a6cdde14f6 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@@ -63,9 +64,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
Settings.Delay(TRANSITION_DURATION / 2).FadeOut(TRANSITION_DURATION / 2);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Select:
if (IsLoading)
@@ -86,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index 9095b78eb4..bcb793062b 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -29,6 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
[Cached(typeof(IBindable))]
protected readonly Bindable SelectedItem = new Bindable();
+ public override bool? AllowTrackAdjustments => true;
+
protected override BackgroundScreen CreateBackground() => new RoomBackgroundScreen(Room.Playlist.FirstOrDefault())
{
SelectedItem = { BindTarget = SelectedItem }
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
index 3af72fa25a..af0c50a848 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs
@@ -71,9 +71,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}, true);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.ToggleChatFocus:
if (Textbox.HasFocus)
@@ -94,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index bf7c738882..c45e3a79da 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public override bool DisallowExternalBeatmapRulesetChanges => true;
// We are managing our own adjustments. For now, this happens inside the Player instances themselves.
- public override bool AllowTrackAdjustments => false;
+ public override bool? AllowTrackAdjustments => false;
///
/// Whether all spectating players have finished loading.
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index 62bfd2cfed..fc20b21b60 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -24,8 +24,6 @@ namespace osu.Game.Screens.OnlinePlay
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
- public override bool AllowTrackAdjustments => false;
-
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
// this is required due to PlayerLoader eventually being pushed to the main stack
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 9aec2a5c19..ccc891d3bf 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -11,11 +11,11 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets;
-using osu.Game.Screens.Menu;
using osu.Game.Overlays;
-using osu.Game.Users;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Menu;
+using osu.Game.Users;
namespace osu.Game.Screens
{
@@ -81,7 +81,10 @@ namespace osu.Game.Screens
public virtual float BackgroundParallaxAmount => 1;
- public virtual bool AllowTrackAdjustments => true;
+ [Resolved]
+ private MusicController musicController { get; set; }
+
+ public virtual bool? AllowTrackAdjustments => null;
public Bindable Beatmap { get; private set; }
@@ -91,6 +94,8 @@ namespace osu.Game.Screens
private OsuScreenDependencies screenDependencies;
+ private bool? trackAdjustmentStateAtSuspend;
+
internal void CreateLeasedDependencies(IReadOnlyDependencyContainer dependencies) => createDependencies(dependencies);
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@@ -170,8 +175,14 @@ namespace osu.Game.Screens
{
if (PlayResumeSound)
sampleExit?.Play();
+
applyArrivingDefaults(true);
+ // it's feasible to resume to a screen if the target screen never loaded successfully.
+ // in such a case there's no need to restore this value.
+ if (trackAdjustmentStateAtSuspend != null)
+ musicController.AllowTrackAdjustments = trackAdjustmentStateAtSuspend.Value;
+
base.OnResuming(last);
}
@@ -179,6 +190,8 @@ namespace osu.Game.Screens
{
base.OnSuspending(next);
+ trackAdjustmentStateAtSuspend = musicController.AllowTrackAdjustments;
+
onSuspendingLogo();
}
@@ -186,6 +199,9 @@ namespace osu.Game.Screens
{
applyArrivingDefaults(false);
+ if (AllowTrackAdjustments != null)
+ musicController.AllowTrackAdjustments = AllowTrackAdjustments.Value;
+
if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true)
{
// If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily.
diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs
index e2a0414df7..ebbcbd7650 100644
--- a/osu.Game/Screens/OsuScreenStack.cs
+++ b/osu.Game/Screens/OsuScreenStack.cs
@@ -51,6 +51,6 @@ namespace osu.Game.Screens
}
private void setParallax(IScreen next) =>
- parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f;
+ parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * (((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f);
}
}
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index f791da80c8..0c9b827a41 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -13,6 +13,7 @@ namespace osu.Game.Screens.Play
///
/// Encapsulates gameplay timing logic and provides a via DI for gameplay components to use.
///
+ [Cached]
public abstract class GameplayClockContainer : Container, IAdjustableClock
{
///
@@ -35,6 +36,11 @@ namespace osu.Game.Screens.Play
///
protected IClock SourceClock { get; private set; }
+ ///
+ /// Invoked when a seek has been performed via
+ ///
+ public event Action OnSeek;
+
///
/// Creates a new .
///
@@ -88,6 +94,8 @@ namespace osu.Game.Screens.Play
// Manually process to make sure the gameplay clock is correctly updated after a seek.
GameplayClock.UnderlyingClock.ProcessFrame();
+
+ OnSeek?.Invoke();
}
///
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index 0efa66bac0..9e3400b09d 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -187,9 +187,9 @@ namespace osu.Game.Screens.Play
InternalButtons.Add(button);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.SelectPrevious:
InternalButtons.SelectPrevious();
@@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
}
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index 7562df5a3b..fb49dedce7 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -286,5 +286,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration).Expire();
}
}
+
+ public override void Clear() => judgementsContainer.Clear();
}
}
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
index dda2a6da95..5012be7249 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
@@ -33,6 +33,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
judgementsFlow.Push(GetColourForHitResult(judgement.Type));
}
+ public override void Clear() => judgementsFlow.Clear();
+
private class JudgementFlow : FillFlowContainer
{
private const int max_available_judgements = 20;
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
index 788ba5be8c..c7b06a3a2c 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
@@ -22,6 +22,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
[Resolved]
private OsuColour colours { get; set; }
+ [Resolved(canBeNull: true)]
+ private GameplayClockContainer gameplayClockContainer { get; set; }
+
public bool UsesFixedAnchor { get; set; }
[BackgroundDependencyLoader(true)]
@@ -34,6 +37,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{
base.LoadComplete();
+ if (gameplayClockContainer != null)
+ gameplayClockContainer.OnSeek += Clear;
+
processor.NewJudgement += OnNewJudgement;
}
@@ -67,12 +73,21 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
}
}
+ ///
+ /// Invoked by .
+ /// Any inheritors of should have this method clear their container that displays the hit error results.
+ ///
+ public abstract void Clear();
+
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (processor != null)
processor.NewJudgement -= OnNewJudgement;
+
+ if (gameplayClockContainer != null)
+ gameplayClockContainer.OnSeek -= Clear;
}
}
}
diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs
index 284ac899ed..850543136c 100644
--- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs
+++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs
@@ -206,9 +206,9 @@ namespace osu.Game.Screens.Play.HUD
base.OnHoverLost(e);
}
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
case GlobalAction.PauseGameplay: // in the future this behaviour will differ for replays etc.
@@ -220,9 +220,9 @@ namespace osu.Game.Screens.Play.HUD
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.Back:
case GlobalAction.PauseGameplay:
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 13df9fefa7..54c74a7177 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@@ -280,9 +281,9 @@ namespace osu.Game.Screens.Play
protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.HoldForHUD:
holdingForHUD = true;
@@ -311,9 +312,9 @@ namespace osu.Game.Screens.Play
return false;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- switch (action)
+ switch (e.Action)
{
case GlobalAction.HoldForHUD:
holdingForHUD = false;
diff --git a/osu.Game/Screens/Play/HotkeyExitOverlay.cs b/osu.Game/Screens/Play/HotkeyExitOverlay.cs
index 8d7e2481bf..13b72ffaf6 100644
--- a/osu.Game/Screens/Play/HotkeyExitOverlay.cs
+++ b/osu.Game/Screens/Play/HotkeyExitOverlay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@@ -9,17 +10,17 @@ namespace osu.Game.Screens.Play
{
public class HotkeyExitOverlay : HoldToConfirmOverlay, IKeyBindingHandler
{
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action != GlobalAction.QuickExit) return false;
+ if (e.Action != GlobalAction.QuickExit) return false;
BeginConfirm();
return true;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action != GlobalAction.QuickExit) return;
+ if (e.Action != GlobalAction.QuickExit) return;
AbortConfirm();
}
diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
index 58fd941f36..308befe372 100644
--- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
+++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@@ -9,17 +10,17 @@ namespace osu.Game.Screens.Play
{
public class HotkeyRetryOverlay : HoldToConfirmOverlay, IKeyBindingHandler
{
- public bool OnPressed(GlobalAction action)
+ public bool OnPressed(KeyBindingPressEvent e)
{
- if (action != GlobalAction.QuickRetry) return false;
+ if (e.Action != GlobalAction.QuickRetry) return false;
BeginConfirm();
return true;
}
- public void OnReleased(GlobalAction action)
+ public void OnReleased(KeyBindingReleaseEvent e)
{
- if (action != GlobalAction.QuickRetry) return;
+ if (e.Action != GlobalAction.QuickRetry) return;
AbortConfirm();
}
diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs
index 2ed4afafd3..66a44e5314 100644
--- a/osu.Game/Screens/Play/KeyCounterDisplay.cs
+++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs
@@ -33,8 +33,6 @@ namespace osu.Game.Screens.Play
public KeyCounterDisplay()
{
- AutoSizeAxes = Axes.Both;
-
InternalChild = KeyFlow = new FillFlowContainer
{
Direction = FillDirection.Horizontal,
@@ -42,6 +40,15 @@ namespace osu.Game.Screens.Play
};
}
+ protected override void Update()
+ {
+ base.Update();
+
+ // Don't use autosize as it will shrink to zero when KeyFlow is hidden.
+ // In turn this can cause the display to be masked off screen and never become visible again.
+ Size = KeyFlow.Size;
+ }
+
public override void Add(KeyCounter key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index e8a2790c94..9927467bd6 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
// We are managing our own adjustments (see OnEntering/OnExiting).
- public override bool AllowTrackAdjustments => false;
+ public override bool? AllowTrackAdjustments => false;
private readonly IBindable gameActive = new Bindable