1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 01:27:29 +08:00

merged in strainTime change and augmented rhythm calc

This commit is contained in:
Xexxar 2021-09-25 02:52:10 +00:00
commit 0129762104
225 changed files with 4614 additions and 1692 deletions

View File

@ -1,30 +0,0 @@
---
name: Bug Report
about: Report a bug or crash to desktop
---
<!--
IMPORTANT: Your issue may already be reported.
Please check:
- Pinned issues, at the top of https://github.com/ppy/osu/issues
- Current priority 0 issues at https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0
- Search for your issue. If you find that it already exists, please respond with a reaction or add any further information that may be helpful.
-->
**Describe the bug:**
**Screenshots or videos showing encountered issue:**
**osu!lazer version:**
**Logs:**
<!--
*please attach logs here, which are located at:*
- `%AppData%/osu/logs` *(on Windows),*
- `~/.local/share/osu/logs` *(on Linux & macOS).*
- `Android/data/sh.ppy.osulazer/files/logs` *(on Android)*,
- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
-->

View File

@ -1,12 +1,12 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: 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 - name: Help
url: https://github.com/ppy/osu/discussions/categories/q-a 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! 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 - name: osu!stable issues
url: https://github.com/ppy/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.

205
.github/workflows/diffcalc.yml vendored Normal file
View File

@ -0,0 +1,205 @@
# 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/
- name: Get upstream branch # https://akaimo.hatenablog.jp/entry/2020/05/16/101251
id: upstreambranch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"
echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')"
# Checkout osu
- name: Checkout osu (master)
uses: actions/checkout@v2
with:
path: 'master/osu'
- name: Checkout osu (pr)
uses: actions/checkout@v2
with:
path: 'pr/osu'
repository: ${{ steps.upstreambranch.outputs.repo }}
ref: ${{ steps.upstreambranch.outputs.branchname }}
- name: Checkout osu-difficulty-calculator (master)
uses: actions/checkout@v2
with:
repository: ppy/osu-difficulty-calculator
path: 'master/osu-difficulty-calculator'
- name: Checkout osu-difficulty-calculator (pr)
uses: actions/checkout@v2
with:
repository: ppy/osu-difficulty-calculator
path: 'pr/osu-difficulty-calculator'
- name: Install .NET 5.0.x
uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
# Sanity checks to make sure diffcalc is not run when incompatible.
- name: Build diffcalc (master)
run: |
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator
./UseLocalOsu.sh
dotnet build
- name: Build diffcalc (pr)
run: |
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator
./UseLocalOsu.sh
dotnet build
- name: Download + import data
run: |
PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top_1000 | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
# Set env variable for further steps.
echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV
cd $GITHUB_WORKSPACE
echo "Downloading database dump $PERFORMANCE_DATA_NAME.."
wget -q -nc https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2
echo "Extracting.."
tar -xf $PERFORMANCE_DATA_NAME.tar.bz2
echo "Downloading beatmap dump $BEATMAPS_DATA_NAME.."
wget -q -nc https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2
echo "Extracting.."
tar -xf $BEATMAPS_DATA_NAME.tar.bz2
cd $PERFORMANCE_DATA_NAME
for db in osu_master osu_pr
do
echo "Setting up database $db.."
mysql -e "CREATE DATABASE $db"
echo "Importing beatmaps.."
cat osu_beatmaps.sql | mysql $db
echo "Importing beatmapsets.."
cat osu_beatmapsets.sql | mysql $db
echo "Creating table structure.."
mysql $db -e 'CREATE TABLE `osu_beatmap_difficulty` (
`beatmap_id` int unsigned NOT NULL,
`mode` tinyint NOT NULL DEFAULT 0,
`mods` int unsigned NOT NULL,
`diff_unified` float NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`beatmap_id`,`mode`,`mods`),
KEY `diff_sort` (`mode`,`mods`,`diff_unified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;'
done
- name: Run diffcalc (master)
env:
DB_NAME: osu_master
run: |
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
- name: Run diffcalc (pr)
env:
DB_NAME: osu_pr
run: |
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
- name: Print diffs
run: |
mysql -e "
SELECT
m.beatmap_id,
m.mods,
b.filename,
m.diff_unified as 'sr_master',
p.diff_unified as 'sr_pr',
(p.diff_unified - m.diff_unified) as 'diff'
FROM osu_master.osu_beatmap_difficulty m
JOIN osu_pr.osu_beatmap_difficulty p
ON m.beatmap_id = p.beatmap_id
AND m.mode = p.mode
AND m.mods = p.mods
JOIN osu_pr.osu_beatmaps b
ON b.beatmap_id = p.beatmap_id
WHERE abs(m.diff_unified - p.diff_unified) > 0.1
ORDER BY abs(m.diff_unified - p.diff_unified)
DESC
LIMIT 10000;"
# Todo: Run ppcalc

View File

@ -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

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ContentModelUserStore"> <component name="UserContentModel">
<attachedFolders /> <attachedFolders />
<explicitIncludes /> <explicitIncludes />
<explicitExcludes /> <explicitExcludes />

View File

@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase) private void load(GameHost host, OsuGameBase gameBase)
{ {
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
game
}; };
AddGame(new OsuGame());
} }
} }
} }

View File

@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase) private void load(GameHost host, OsuGameBase gameBase)
{ {
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
game
}; };
AddGame(new OsuGame());
} }
} }
} }

View File

@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase) private void load(GameHost host, OsuGameBase gameBase)
{ {
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
game
}; };
AddGame(new OsuGame());
} }
} }
} }

View File

@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host, OsuGameBase gameBase) private void load(GameHost host, OsuGameBase gameBase)
{ {
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
game
}; };
AddGame(new OsuGame());
} }
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osuTK; using osuTK;
@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Pippidon.UI
} }
} }
public bool OnPressed(PippidonAction action) public bool OnPressed(KeyBindingPressEvent<PippidonAction> e)
{ {
switch (action) switch (e.Action)
{ {
case PippidonAction.MoveUp: case PippidonAction.MoveUp:
changeLane(-1); changeLane(-1);
@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Pippidon.UI
} }
} }
public void OnReleased(PippidonAction action) public void OnReleased(KeyBindingReleaseEvent<PippidonAction> e)
{ {
} }

View File

@ -51,11 +51,11 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.907.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.918.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.907.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.924.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.5.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -44,9 +44,9 @@ namespace osu.Game.Rulesets.Catch.Mods
} }
// disable keyboard controls // disable keyboard controls
public bool OnPressed(CatchAction action) => true; public bool OnPressed(KeyBindingPressEvent<CatchAction> e) => true;
public void OnReleased(CatchAction action) public void OnReleased(KeyBindingReleaseEvent<CatchAction> e)
{ {
} }

View File

@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
@ -144,9 +145,9 @@ namespace osu.Game.Rulesets.Catch.UI
Catcher.VisualDirection = Direction.Left; Catcher.VisualDirection = Direction.Left;
} }
public bool OnPressed(CatchAction action) public bool OnPressed(KeyBindingPressEvent<CatchAction> e)
{ {
switch (action) switch (e.Action)
{ {
case CatchAction.MoveLeft: case CatchAction.MoveLeft:
currentDirection--; currentDirection--;
@ -164,9 +165,9 @@ namespace osu.Game.Rulesets.Catch.UI
return false; return false;
} }
public void OnReleased(CatchAction action) public void OnReleased(KeyBindingReleaseEvent<CatchAction> e)
{ {
switch (action) switch (e.Action)
{ {
case CatchAction.MoveLeft: case CatchAction.MoveLeft:
currentDirection++; currentDirection++;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
c.Add(CreateHitObject().With(h => c.Add(CreateHitObject().With(h =>
{ {
h.HitObject.StartTime = START_TIME; h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange; h.AccentColour.Value = Color4.Orange;
})); }));
}) })
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
c.Add(CreateHitObject().With(h => c.Add(CreateHitObject().With(h =>
{ {
h.HitObject.StartTime = START_TIME; h.HitObject.StartTime = Time.Current + 5000;
h.AccentColour.Value = Color4.Orange; h.AccentColour.Value = Color4.Orange;
})); }));
}) })

View File

@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
/// </summary> /// </summary>
public abstract class ManiaSkinnableTestScene : SkinnableTestScene public abstract class ManiaSkinnableTestScene : SkinnableTestScene
{ {
protected const double START_TIME = 1000000000;
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
@ -55,27 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>(); public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction; IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000); IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(5000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm(); IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
}
private class ZeroScrollAlgorithm : IScrollAlgorithm
{
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
=> double.MinValue;
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
=> scrollLength;
public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
=> (float)((time - START_TIME) / timeRange) * scrollLength;
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
=> 0;
public void Reset()
{
}
} }
} }
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("Hold key", () => AddStep("Hold key", () =>
{ {
clock.CurrentTime = 0; clock.CurrentTime = 0;
note.OnPressed(ManiaAction.Key1); note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager().CurrentState, ManiaAction.Key1));
}); });
AddStep("progress time", () => clock.CurrentTime = 500); AddStep("progress time", () => clock.CurrentTime = 500);
AddAssert("head is visible", () => note.Head.Alpha == 1); AddAssert("head is visible", () => note.Head.Alpha == 1);

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
[Resolved] [Resolved]
private RulesetConfigCache configCache { get; set; } private RulesetConfigCache configCache { get; set; }
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>(); private Bindable<bool> configTimingBasedNoteColouring;
protected override void LoadComplete() private ManualClock clock;
private DrawableManiaRuleset drawableRuleset;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup hierarchy", () => Child = new Container
{
Clock = new FramedClock(clock = new ManualClock()),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
}
});
AddStep("retrieve config bindable", () =>
{
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
configTimingBasedNoteColouring = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring);
});
}
[Test]
public void TestSimple()
{
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
}
[Test]
public void TestToggleOffScreen()
{
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
seekTo(10000);
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
seekTo(0);
AddAssert("all notes not coloured", () => this.ChildrenOfType<DrawableNote>().All(note => note.Colour == Colour4.White));
seekTo(10000);
AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
seekTo(0);
AddAssert("some notes coloured", () => this.ChildrenOfType<DrawableNote>().Any(note => note.Colour != Colour4.White));
}
private void seekTo(double time)
{
AddStep($"seek to {time}", () => clock.CurrentTime = time);
AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
}
private ManiaBeatmap createTestBeatmap()
{ {
const double beat_length = 500; const double beat_length = 500;
var ruleset = new ManiaRuleset();
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }) var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
{ {
HitObjects = HitObjects =
@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
new Note { StartTime = beat_length } new Note { StartTime = beat_length }
}, },
ControlPointInfo = new ControlPointInfo(), ControlPointInfo = new ControlPointInfo(),
BeatmapInfo = { Ruleset = ruleset.RulesetInfo }, BeatmapInfo = { Ruleset = Ruleset.Value },
}; };
foreach (var note in beatmap.HitObjects) foreach (var note in beatmap.HitObjects)
@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{ {
BeatLength = beat_length BeatLength = beat_length
}); });
return beatmap;
Child = new Container
{
Clock = new FramedClock(new ManualClock()),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
ruleset.CreateDrawableRulesetWith(beatmap)
}
};
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
} }
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -253,12 +254,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldBrokenTime = Time.Current; HoldBrokenTime = Time.Current;
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{ {
if (AllJudged) if (AllJudged)
return false; return false;
if (action != Action.Value) if (e.Action != Action.Value)
return false; return false;
// do not run any of this logic when rewinding, as it inverts order of presses/releases. // 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; isHitting.Value = true;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
if (AllJudged) if (AllJudged)
return; return;
if (action != Action.Value) if (e.Action != Action.Value)
return; return;
// do not run any of this logic when rewinding, as it inverts order of presses/releases. // do not run any of this logic when rewinding, as it inverts order of presses/releases.

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.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. // 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<ManiaAction> e) => false; // Handled by the hold note
public override void OnReleased(ManiaAction action) public override void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
} }
} }

View File

@ -3,6 +3,7 @@
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables 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<ManiaAction> e) => false; // Handled by the hold note
public override void OnReleased(ManiaAction action) public override void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
} }
} }

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
@ -66,6 +67,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true); StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
} }
protected override void OnApply()
{
base.OnApply();
updateSnapColour();
}
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e) protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{ {
base.OnDirectionChanged(e); base.OnDirectionChanged(e);
@ -91,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
ApplyResult(r => r.Type = result); ApplyResult(r => r.Type = result);
} }
public virtual bool OnPressed(ManiaAction action) public virtual bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{ {
if (action != Action.Value) if (e.Action != Action.Value)
return false; return false;
if (CheckHittable?.Invoke(this, Time.Current) == false) if (CheckHittable?.Invoke(this, Time.Current) == false)
@ -102,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return UpdateResult(true); return UpdateResult(true);
} }
public virtual void OnReleased(ManiaAction action) public virtual void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
} }
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{ {
if (action == Column.Action.Value) if (e.Action == Column.Action.Value)
{ {
light.FadeIn(); light.FadeIn();
light.ScaleTo(Vector2.One); light.ScaleTo(Vector2.One);
@ -87,12 +88,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return false; return false;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
// Todo: Should be 400 * 100 / CurrentBPM // Todo: Should be 400 * 100 / CurrentBPM
const double animation_length = 250; const double animation_length = 250;
if (action == Column.Action.Value) if (e.Action == Column.Action.Value)
{ {
light.FadeTo(0, animation_length); light.FadeTo(0, animation_length);
light.ScaleTo(new Vector2(1, 0), animation_length); light.ScaleTo(new Vector2(1, 0), animation_length);

View File

@ -1,18 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning.Legacy namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{ {
public class LegacyHoldNoteHeadPiece : LegacyNotePiece 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. // TODO: Should fallback to the head from default legacy skin instead of note.
return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
} }
} }
} }

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
: new ValueChangedEvent<ScrollingDirection>(ScrollingDirection.Up, ScrollingDirection.Up)); : new ValueChangedEvent<ScrollingDirection>(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. // TODO: Should fallback to the head from default legacy skin instead of note.
return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage) return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage) ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage); ?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
} }
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
} }
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{ {
if (action == column.Action.Value) if (e.Action == column.Action.Value)
{ {
upSprite.FadeTo(0); upSprite.FadeTo(0);
downSprite.FadeTo(1); downSprite.FadeTo(1);
@ -97,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return false; return false;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
if (action == column.Action.Value) if (e.Action == column.Action.Value)
{ {
upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1); upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1);
downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0); downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0);

View File

@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -19,7 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container directionContainer; private Container directionContainer;
private Sprite noteSprite;
[CanBeNull]
private Drawable noteAnimation;
private float? minimumColumnWidth; private float? minimumColumnWidth;
@ -39,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Child = noteSprite = new Sprite { Texture = GetTexture(skin) } Child = noteAnimation = GetAnimation(skin) ?? Empty()
}; };
direction.BindTo(scrollingInfo.Direction); direction.BindTo(scrollingInfo.Direction);
@ -50,12 +54,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{ {
base.Update(); 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. // The height is scaled to the minimum column width, if provided.
float minimumWidth = minimumColumnWidth ?? DrawWidth; float minimumWidth = minimumColumnWidth ?? DrawWidth;
noteAnimation.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), texture.DisplayWidth);
noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.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; string suffix = string.Empty;
@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value
?? $"mania-note{FallbackColumnIndex}{suffix}"; ?? $"mania-note{FallbackColumnIndex}{suffix}";
return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge); return skin.GetAnimation(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true);
} }
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling; 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))); HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{ {
if (action != Action.Value) if (e.Action != Action.Value)
return false; return false;
sampleTriggerSource.Play(); sampleTriggerSource.Play();
return true; return true;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics; using osuTK.Graphics;
@ -91,16 +92,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint); direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
{ {
if (action == this.action.Value) if (e.Action == action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false; return false;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
if (action == this.action.Value) if (e.Action == action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics; using osuTK.Graphics;
@ -74,16 +75,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
} }
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> 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); backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false; return false;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
if (action == column.Action.Value) if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -101,16 +102,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
} }
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(KeyBindingPressEvent<ManiaAction> 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); keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false; return false;
} }
public void OnReleased(ManiaAction action) public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
{ {
if (action == column.Action.Value) if (e.Action == column.Action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint); keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
} }
} }

View File

@ -0,0 +1,78 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public class TestSceneOsuEditorGrids : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Test]
public void TestGridExclusivity()
{
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
rectangularGridActive(false);
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
rectangularGridActive(true);
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
rectangularGridActive(false);
}
private void rectangularGridActive(bool active)
{
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
AddStep("move cursor to (1, 1)", () =>
{
var composer = Editor.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single();
InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1)));
});
if (active)
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
else
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
AddStep("choose selection tool", () => InputManager.Key(Key.Number1));
}
[Test]
public void TestGridSizeToggling()
{
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Any());
gridSizeIs(4);
nextGridSizeIs(8);
nextGridSizeIs(16);
nextGridSizeIs(32);
nextGridSizeIs(4);
}
private void nextGridSizeIs(int size)
{
AddStep("toggle to next grid size", () => InputManager.Key(Key.G));
gridSizeIs(size);
}
private void gridSizeIs(int size)
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single().Spacing == new Vector2(size)
&& EditorBeatmap.BeatmapInfo.GridSize == size);
}
}

View File

@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
<<<<<<< HEAD
[TestCase(6.6915334809485199d, "diffcalc-test")] [TestCase(6.6915334809485199d, "diffcalc-test")]
[TestCase(1.0366129190339499d, "zero-length-sliders")] [TestCase(1.0366129190339499d, "zero-length-sliders")]
public void Test(double expected, string name) public void Test(double expected, string name)
@ -22,6 +23,15 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase(8.3966904501824473d, "diffcalc-test")] [TestCase(8.3966904501824473d, "diffcalc-test")]
[TestCase(1.2732783892964523d, "zero-length-sliders")] [TestCase(1.2732783892964523d, "zero-length-sliders")]
=======
[TestCase(6.6634445062299665d, "diffcalc-test")]
[TestCase(1.0414203870195022d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
[TestCase(8.3858089051603368d, "diffcalc-test")]
[TestCase(1.2723279173428435d, "zero-length-sliders")]
>>>>>>> ppy/master
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime()); => Test(expected, name, new OsuModDoubleTime());

View File

@ -0,0 +1,175 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.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<LegacyCursorParticles>().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<LegacyCursorParticles>().SingleOrDefault();
return cursorParticles != null;
});
AddUntilStep("wait for spinner tracking", () =>
{
spinner = this.ChildrenOfType<DrawableSpinner>().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<DrawableSlider>().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<IBeatmap> beatmap) => CreateTest(() =>
{
AddStep("set beatmap", () =>
{
this.autoplay = autoplay;
currentBeatmap = beatmap();
});
AddStep("setup default legacy skin", () =>
{
skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
});
});
}
}

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Testing.Input; using osu.Framework.Testing.Input;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
@ -143,9 +144,9 @@ namespace osu.Game.Rulesets.Osu.Tests
pressed = value; pressed = value;
if (value) if (value)
OnPressed(OsuAction.LeftButton); OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
else else
OnReleased(OsuAction.LeftButton); OnReleased(new KeyBindingReleaseEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void scheduleHit() => AddStep("schedule action", () => private void scheduleHit() => AddStep("schedule action", () =>
{ {
var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current; var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(OsuAction.LeftButton), delay); Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay);
}); });
} }
} }

View File

@ -20,6 +20,7 @@ using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Storyboards; using osu.Game.Storyboards;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Osu.Tests
if (firstObject == null) if (firstObject == null)
return false; 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 // check for default skin provider
return true; return true;

View File

@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public double AimStrain { get; set; } public double AimStrain { get; set; }
public double SpeedStrain { get; set; } public double SpeedStrain { get; set; }
public double FlashlightRating { get; set; }
public double ApproachRate { get; set; } public double ApproachRate { get; set; }
public double OverallDifficulty { get; set; } public double OverallDifficulty { get; set; }
public int HitCircleCount { get; set; } public int HitCircleCount { get; set; }

View File

@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public class OsuDifficultyCalculator : DifficultyCalculator public class OsuDifficultyCalculator : DifficultyCalculator
{ {
private const double difficulty_multiplier = 0.0675; private const double difficulty_multiplier = 0.0675;
private double hitWindowGreat;
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
@ -34,13 +35,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
HitWindows hitWindows = new OsuHitWindows(); double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
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;
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count; int maxCombo = beatmap.HitObjects.Count;
@ -56,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
Mods = mods, Mods = mods,
AimStrain = aimRating, AimStrain = aimRating,
SpeedStrain = speedRating, SpeedStrain = speedRating,
FlashlightRating = flashlightRating,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6, OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo, MaxCombo = maxCombo,
@ -79,11 +92,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
} }
} }
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
{ {
new Aim(mods), HitWindows hitWindows = new OsuHitWindows();
new Speed(mods) hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
};
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
return new Skill[]
{
new Aim(mods),
new Speed(mods, hitWindowGreat),
new Flashlight(mods)
};
}
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{ {
@ -91,6 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
new OsuModHalfTime(), new OsuModHalfTime(),
new OsuModEasy(), new OsuModEasy(),
new OsuModHardRock(), new OsuModHardRock(),
new OsuModFlashlight(),
}; };
} }
} }

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
// Custom multipliers for NoFail and SpunOut. // 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)) if (mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss); multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
@ -52,11 +52,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = computeAimValue(); double aimValue = computeAimValue();
double speedValue = computeSpeedValue(); double speedValue = computeSpeedValue();
double accuracyValue = computeAccuracyValue(); double accuracyValue = computeAccuracyValue();
double flashlightValue = computeFlashlightValue();
double totalValue = double totalValue =
Math.Pow( Math.Pow(
Math.Pow(aimValue, 1.1) + Math.Pow(aimValue, 1.1) +
Math.Pow(speedValue, 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; ) * multiplier;
if (categoryRatings != null) if (categoryRatings != null)
@ -64,6 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Aim", aimValue); categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue); categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue); categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("Flashlight", flashlightValue);
categoryRatings.Add("OD", Attributes.OverallDifficulty); categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate); categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", Attributes.MaxCombo); 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; 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) + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.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) if (countMiss > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss); aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
// Combo scaling // Combo scaling.
if (Attributes.MaxCombo > 0) if (Attributes.MaxCombo > 0)
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.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)) if (mods.Any(h => h is OsuModHidden))
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
double flashlightBonus = 1.0; aimValue *= approachRateBonus;
if (mods.Any(h => h is OsuModFlashlight)) // Scale the aim value with accuracy _slightly_.
{
// 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_
aimValue *= 0.5 + accuracy / 2.0; 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; aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue; 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; 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) + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus; speedValue *= lengthBonus;
@ -144,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (countMiss > 0) if (countMiss > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); 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) if (Attributes.MaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
@ -159,11 +150,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => m is OsuModHidden)) if (mods.Any(m => m is OsuModHidden))
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); 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); speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
// Punish high speed values with low OD to prevent OD abuse on rhythmically complex songs.
if (speedValue > 100 && Attributes.OverallDifficulty < 8)
speedValue = 100 + (speedValue - 100) * Math.Max(0.5, (Attributes.OverallDifficulty - 4) / 4);
// Scale the speed value with # of 50s to punish doubletapping. // Scale the speed value with # of 50s to punish doubletapping.
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
@ -174,9 +162,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
if (amountHitObjectsWithAccuracy == 0)
return 0;
// This section should be documented by Tr3, but effectively we're calculating the exact same way as before, but // This section should be documented by Tr3, but effectively we're calculating the exact same way as before, but
// we calculate a variance based on the object count and # of 50s, 100s, etc. This prevents us from having cases // we calculate a variance based on the object count and # of 50s, 100s, etc. This prevents us from having cases
// where an SS on lower OD is actually worth more than a 95% on OD11, even though the OD11 requires a greater // where an SS on lower OD is actually worth more than a 95% on OD11, even though the OD11 requires a greater
@ -206,6 +191,42 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return accuracyValue; 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 totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh; private int totalSuccessfulHits => countGreat + countOk + countMeh;
} }

View File

@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
/// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms to account for simultaneous <see cref="OsuDifficultyHitObject"/>s.
/// </summary>
public double StrainTime { get; private set; }
/// <summary> /// <summary>
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>. /// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
/// </summary> /// </summary>
@ -32,11 +37,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
/// </summary> /// </summary>
public double? Angle { get; private set; } public double? Angle { get; private set; }
/// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
/// </summary>
public readonly double StrainTime;
private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastLastObject;
private readonly OsuHitObject lastObject; private readonly OsuHitObject lastObject;
@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
setDistances(); setDistances();
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects.
StrainTime = Math.Max(50, DeltaTime); StrainTime = Math.Max(DeltaTime, 25);
} }
private void setDistances() private void setDistances()

View File

@ -0,0 +1,81 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Rulesets.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
{
/// <summary>
/// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled.
/// </summary>
public class Flashlight : OsuStrainSkill
{
public Flashlight(Mod[] mods)
: base(mods)
{
}
private double skillMultiplier => 0.15;
private 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.
private double currentStrain = 1;
private 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);
}
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
protected override double CalculateInitialStrain(double time) => currentStrain * strainDecay(time - Previous[0].StartTime);
protected override double StrainValueAt(DifficultyHitObject current)
{
double flashlightStrain = strainValueOf(current);
currentStrain *= strainDecay(current.DeltaTime);
currentStrain += flashlightStrain * skillMultiplier;
return currentStrain;
}
}
}

View File

@ -21,8 +21,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double pi_over_4 = Math.PI / 4; private const double pi_over_4 = Math.PI / 4;
private const double pi_over_2 = Math.PI / 2; private const double pi_over_2 = Math.PI / 2;
private const double rhythm_multiplier = 2.5; private const double rhythm_multiplier = 0.75;
private const int history_time_max = 3000; // 3 seconds of calculatingRhythmBonus max. private const int history_time_max = 5000; // 5 seconds of calculatingRhythmBonus max.
private double skillMultiplier => 1375; private double skillMultiplier => 1375;
private double strainDecayBase => 0.3; private double strainDecayBase => 0.3;
@ -35,20 +35,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double DifficultyMultiplier => 1.04; protected override double DifficultyMultiplier => 1.04;
private const double min_speed_bonus = 75; // ~200BPM private const double min_speed_bonus = 75; // ~200BPM
private const double max_speed_bonus = 45; // ~330BPM private const double max_speed_bonus = 45;
private const double speed_balancing_factor = 40; private const double speed_balancing_factor = 40;
protected override int HistoryLength => 32; protected override int HistoryLength => 32;
public Speed(Mod[] mods) private readonly double greatWindow;
public Speed(Mod[] mods, double hitWindowGreat)
: base(mods) : base(mods)
{ {
greatWindow = hitWindowGreat;
} }
/// <summary> /// <summary>
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>. /// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
/// </summary> /// </summary>
private double calculateRhythmBonus(DifficultyHitObject current) private double calculateRhythmBonus(DifficultyHitObject current, double greatWindowFull)
{ {
if (current.BaseObject is Spinner) if (current.BaseObject is Spinner)
return 0; return 0;
@ -77,7 +80,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double effectiveRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta); double effectiveRatio = Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta);
if (effectiveRatio > 0.5) if (effectiveRatio > 0.5)
effectiveRatio = 0.5 + (effectiveRatio - 0.5) * 5; // large buff for 1/3 -> 1/4 type transitions. effectiveRatio = 0.5 + (effectiveRatio - 0.5) * 10; // large buff for 1/3 -> 1/4 type transitions.
effectiveRatio *= Math.Max(prevDelta, currDelta) / greatWindowFull; // Increase scaling for when hitwindow is large but accuracy range is small.
effectiveRatio *= currHistoricalDecay; // scale with time effectiveRatio *= currHistoricalDecay; // scale with time
@ -89,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
} }
else else
{ {
if (islandSize > 6) if (islandSize > 6)
islandSize = 6; islandSize = 6;
@ -123,20 +130,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
} }
} }
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)
} }
private double tapStrainOf(DifficultyHitObject current, double speedBonus) private double tapStrainOf(DifficultyHitObject current, double speedBonus, double strainTime)
{ {
if (current.BaseObject is Spinner) if (current.BaseObject is Spinner)
return 0; return 0;
var osuCurrObj = (OsuDifficultyHitObject)current; return speedBonus / strainTime;
return speedBonus / osuCurrObj.StrainTime;
} }
private double movementStrainOf(DifficultyHitObject current, double speedBonus) private double movementStrainOf(DifficultyHitObject current, double speedBonus, double strainTime)
{ {
if (current.BaseObject is Spinner) if (current.BaseObject is Spinner)
return 0; return 0;
@ -144,7 +149,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
var osuCurrObj = (OsuDifficultyHitObject)current; var osuCurrObj = (OsuDifficultyHitObject)current;
double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance); double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance);
double angleBonus = 1.0; double angleBonus = 1.0;
if (osuCurrObj.Angle != null) if (osuCurrObj.Angle != null)
@ -157,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - angle)), 2) / 4; angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - angle)), 2) / 4;
} }
return (angleBonus * speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrObj.StrainTime; return (angleBonus * speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
} }
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
@ -166,21 +170,37 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double StrainValueAt(DifficultyHitObject current) protected override double StrainValueAt(DifficultyHitObject current)
{ {
// derive strainTime for calculation
var osuCurrObj = (OsuDifficultyHitObject)current;
var osuPrevObj = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null;
double strainTime = osuCurrObj.StrainTime;
double greatWindowFull = greatWindow * 2;
double speedWindowRatio = strainTime / greatWindowFull;
// Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
if (osuPrevObj != null && strainTime < greatWindowFull && osuPrevObj.StrainTime > strainTime)
strainTime = Interpolation.Lerp(osuPrevObj.StrainTime, strainTime, speedWindowRatio);
// Cap deltatime to the OD 300 hitwindow.
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1);
// derive speedBonus for calculation
double speedBonus = 1.0; double speedBonus = 1.0;
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
if (deltaTime < min_speed_bonus) if (strainTime < min_speed_bonus)
speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
currentRhythm = calculateRhythmBonus(current); currentRhythm = calculateRhythmBonus(current, greatWindowFull);
double decay = strainDecay(current.DeltaTime); double decay = strainDecay(current.DeltaTime);
currentTapStrain *= decay; currentTapStrain *= decay;
currentTapStrain += tapStrainOf(current, speedBonus) * skillMultiplier; currentTapStrain += tapStrainOf(current, speedBonus, strainTime) * skillMultiplier;
currentMovementStrain *= decay; currentMovementStrain *= decay;
currentMovementStrain += movementStrainOf(current, speedBonus) * skillMultiplier; currentMovementStrain += movementStrainOf(current, speedBonus, strainTime) * skillMultiplier;
return currentMovementStrain + currentTapStrain * currentRhythm; return currentMovementStrain + currentTapStrain * currentRhythm;
} }

View File

@ -127,9 +127,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false; return false;
} }
public bool OnPressed(PlatformAction action) public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
{ {
switch (action) switch (e.Action)
{ {
case PlatformAction.Delete: case PlatformAction.Delete:
return DeleteSelected(); return DeleteSelected();
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false; return false;
} }
public void OnReleased(PlatformAction action) public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
{ {
} }

View File

@ -42,10 +42,12 @@ namespace osu.Game.Rulesets.Osu.Edit
}; };
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>(); private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[] protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
{ {
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }),
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
}); });
private BindableList<HitObject> selectedHitObjects; private BindableList<HitObject> selectedHitObjects;
@ -63,6 +65,10 @@ namespace osu.Game.Rulesets.Osu.Edit
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
}, },
distanceSnapGridContainer = new Container distanceSnapGridContainer = new Container
{
RelativeSizeAxes = Axes.Both
},
rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }
@ -73,7 +79,19 @@ namespace osu.Game.Rulesets.Osu.Edit
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy(); placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
placementObject.ValueChanged += _ => updateDistanceSnapGrid(); placementObject.ValueChanged += _ => updateDistanceSnapGrid();
distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid(); distanceSnapToggle.ValueChanged += _ =>
{
updateDistanceSnapGrid();
if (distanceSnapToggle.Value == TernaryState.True)
rectangularGridSnapToggle.Value = TernaryState.False;
};
rectangularGridSnapToggle.ValueChanged += _ =>
{
if (rectangularGridSnapToggle.Value == TernaryState.True)
distanceSnapToggle.Value = TernaryState.False;
};
// we may be entering the screen with a selection already active // we may be entering the screen with a selection already active
updateDistanceSnapGrid(); updateDistanceSnapGrid();
@ -91,6 +109,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly Cached distanceSnapGridCache = new Cached(); private readonly Cached distanceSnapGridCache = new Cached();
private double? lastDistanceSnapGridTime; private double? lastDistanceSnapGridTime;
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -122,13 +142,19 @@ namespace osu.Game.Rulesets.Osu.Edit
if (positionSnap.ScreenSpacePosition != screenSpacePosition) if (positionSnap.ScreenSpacePosition != screenSpacePosition)
return positionSnap; return positionSnap;
// will be null if distance snap is disabled or not feasible for the current time value. if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
if (distanceSnapGrid == null) {
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
}
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); if (rectangularGridSnapToggle.Value == TernaryState.True)
{
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition));
return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
}
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
} }
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult) private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)

View File

@ -0,0 +1,69 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler<GlobalAction>
{
private static readonly int[] grid_sizes = { 4, 8, 16, 32 };
private int currentGridSizeIndex = grid_sizes.Length - 1;
[Resolved]
private EditorBeatmap editorBeatmap { get; set; }
public OsuRectangularPositionSnapGrid()
: base(OsuPlayfield.BASE_SIZE / 2)
{
}
[BackgroundDependencyLoader]
private void load()
{
var gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize);
if (gridSizeIndex >= 0)
currentGridSizeIndex = gridSizeIndex;
updateSpacing();
}
private void nextGridSize()
{
currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length;
updateSpacing();
}
private void updateSpacing()
{
int gridSize = grid_sizes[currentGridSizeIndex];
editorBeatmap.BeatmapInfo.GridSize = gridSize;
Spacing = new Vector2(gridSize);
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
{
case GlobalAction.EditorCycleGridDisplayMode:
nextGridSize();
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public OsuAction? HitAction => HitArea.HitAction; public OsuAction? HitAction => HitArea.HitAction;
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; 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 HitReceptor HitArea { get; private set; }
public SkinnableDrawable CirclePiece { get; private set; } public SkinnableDrawable CirclePiece { get; private set; }
@ -74,8 +75,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = 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, Alpha = 0,
Scale = new Vector2(4), Scale = new Vector2(4),
} }
@ -88,7 +92,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue)); ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -228,15 +231,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
CornerExponent = 2; CornerExponent = 2;
} }
public bool OnPressed(OsuAction action) public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
{ {
switch (action) switch (e.Action)
{ {
case OsuAction.LeftButton: case OsuAction.LeftButton:
case OsuAction.RightButton: case OsuAction.RightButton:
if (IsHovered && (Hit?.Invoke() ?? false)) if (IsHovered && (Hit?.Invoke() ?? false))
{ {
HitAction = action; HitAction = e.Action;
return true; return true;
} }
@ -246,7 +249,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return false; return false;
} }
public void OnReleased(OsuAction action) public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
{
}
}
private class ProxyableSkinnableDrawable : SkinnableDrawable
{
public override bool RemoveWhenNotAlive => false;
public ProxyableSkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable> defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(component, defaultImplementation, confineMode)
{ {
} }
} }

View File

@ -32,6 +32,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public SliderBall Ball { get; private set; } public SliderBall Ball { get; private set; }
public SkinnableDrawable Body { get; private set; } public SkinnableDrawable Body { get; private set; }
/// <summary>
/// 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.
/// </summary>
public Container OverlayElementContainer { get; private set; }
public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects; public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects;
[CanBeNull] [CanBeNull]
@ -65,6 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both }, tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both }, tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
Ball = new SliderBall(this) Ball = new SliderBall(this)
{ {
GetInitialHitAction = () => HeadCircle.HitAction, GetInitialHitAction = () => HeadCircle.HitAction,
@ -72,7 +80,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
slidingSample = new PausableSkinnableSound { Looping = true } slidingSample = new PausableSkinnableSound { Looping = true }
}; };
@ -179,6 +186,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tailContainer.Clear(false); tailContainer.Clear(false);
repeatContainer.Clear(false); repeatContainer.Clear(false);
tickContainer.Clear(false); tickContainer.Clear(false);
OverlayElementContainer.Clear(false);
} }
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[CanBeNull] [CanBeNull]
public Slider Slider => DrawableSlider?.HitObject; public Slider Slider => DrawableSlider?.HitObject;
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult;

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[CanBeNull] [CanBeNull]
public Slider Slider => DrawableSlider?.HitObject; public Slider Slider => DrawableSlider?.HitObject;
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
private double animDuration; private double animDuration;

View File

@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu
FollowPoint, FollowPoint,
Cursor, Cursor,
CursorTrail, CursorTrail,
CursorParticles,
SliderScorePoint, SliderScorePoint,
ReverseArrow, ReverseArrow,
HitCircleText, HitCircleText,
@ -18,5 +19,6 @@ namespace osu.Game.Rulesets.Osu
SliderBall, SliderBall,
SliderBody, SliderBody,
SpinnerBody, SpinnerBody,
ApproachCircle,
} }
} }

View File

@ -1,50 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.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;
}
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.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<Color4> accentColour = new Bindable<Color4>();
[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;
}
}
}

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.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<Color4> accentColour = new Bindable<Color4>();
[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;
}
}
}

View File

@ -0,0 +1,253 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using 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<OsuAction>
{
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, Color4>(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<bool>)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<OsuAction> e)
{
handleInput(e.Action, true);
return false;
}
public void OnReleased(KeyBindingReleaseEvent<OsuAction> 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,
}
}
}

View File

@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
} }
private Container circleSprites;
private Drawable hitCircleSprite; private Drawable hitCircleSprite;
private Drawable hitCircleOverlay;
protected Drawable HitCircleOverlay { get; private set; }
private SkinnableSpriteText hitCircleText; 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). // 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"); 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, Anchor = Anchor.Centre,
Origin = 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, bool>(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; bool overlayAboveNumber = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
if (overlayAboveNumber) if (overlayAboveNumber)
AddInternal(hitCircleOverlay.CreateProxy()); ChangeInternalChildDepth(HitCircleOverlay, float.MinValue);
accentColour.BindTo(drawableObject.AccentColour); accentColour.BindTo(drawableObject.AccentColour);
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
@ -153,8 +144,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
switch (state) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:
circleSprites.FadeOut(legacy_fade_duration, Easing.Out); hitCircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
circleSprites.ScaleTo(1.4f, 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) if (hasNumber)
{ {

View File

@ -0,0 +1,67 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.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;
}
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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;
}
}
}

View File

@ -67,7 +67,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
case OsuSkinComponents.SliderHeadHitCircle: case OsuSkinComponents.SliderHeadHitCircle:
if (hasHitCircle.Value) if (hasHitCircle.Value)
return new LegacyMainCirclePiece("sliderstartcircle"); return new LegacySliderHeadHitCircle();
return null;
case OsuSkinComponents.ReverseArrow:
if (hasHitCircle.Value)
return new LegacyReverseArrow(this);
return null; return null;
@ -89,6 +95,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null; return null;
case OsuSkinComponents.CursorParticles:
if (GetTexture("star2") != null)
return new LegacyCursorParticles();
return null;
case OsuSkinComponents.HitCircleText: case OsuSkinComponents.HitCircleText:
if (!this.HasFont(LegacyFont.HitCircle)) if (!this.HasFont(LegacyFont.HitCircle))
return null; return null;
@ -108,6 +120,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return new LegacyOldStyleSpinner(); return new LegacyOldStyleSpinner();
return null; return null;
case OsuSkinComponents.ApproachCircle:
return new LegacyApproachCircle();
} }
} }

View File

@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderBorder, SliderBorder,
SliderBall, SliderBall,
SpinnerBackground, SpinnerBackground,
StarBreakAdditive,
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Configuration;
@ -42,7 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
InternalChild = fadeContainer = new Container InternalChild = fadeContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, 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(); (ActiveCursor as OsuCursor)?.Contract();
} }
public bool OnPressed(OsuAction action) public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
{ {
switch (action) switch (e.Action)
{ {
case OsuAction.LeftButton: case OsuAction.LeftButton:
case OsuAction.RightButton: case OsuAction.RightButton:
@ -129,9 +134,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
return false; return false;
} }
public void OnReleased(OsuAction action) public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
{ {
switch (action) switch (e.Action)
{ {
case OsuAction.LeftButton: case OsuAction.LeftButton:
case OsuAction.RightButton: case OsuAction.RightButton:

View File

@ -24,6 +24,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
[Cached]
public class OsuPlayfield : Playfield public class OsuPlayfield : Playfield
{ {
private readonly PlayfieldBorder playfieldBorder; private readonly PlayfieldBorder playfieldBorder;

View File

@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.UI
base.OnHoverLost(e); base.OnHoverLost(e);
} }
public bool OnPressed(OsuAction action) public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
{ {
switch (action) switch (e.Action)
{ {
case OsuAction.LeftButton: case OsuAction.LeftButton:
case OsuAction.RightButton: case OsuAction.RightButton:
@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.UI
return false; return false;
} }
public void OnReleased(OsuAction action) public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
{ {
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -37,6 +38,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
Result.Type = Type; Result.Type = Type;
} }
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
@ -30,6 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss; nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss;
} }
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
} }
} }

View File

@ -36,24 +36,27 @@ namespace osu.Game.Rulesets.Taiko.Mods
public TaikoFlashlight(TaikoPlayfield taikoPlayfield) public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
{ {
this.taikoPlayfield = taikoPlayfield; this.taikoPlayfield = taikoPlayfield;
FlashlightSize = new Vector2(0, getSizeFor(0)); FlashlightSize = getSizeFor(0);
AddLayout(flashlightProperties); AddLayout(flashlightProperties);
} }
private float getSizeFor(int combo) private Vector2 getSizeFor(int combo)
{ {
float size = default_flashlight_size;
if (combo > 200) if (combo > 200)
return default_flashlight_size * 0.8f; size *= 0.8f;
else if (combo > 100) else if (combo > 100)
return default_flashlight_size * 0.9f; size *= 0.9f;
else
return default_flashlight_size; // Preserve flashlight size through the playfield's aspect adjustment.
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
} }
protected override void OnComboChange(ValueChangedEvent<int> e) protected override void OnComboChange(ValueChangedEvent<int> 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"; protected override string FragmentShader => "CircularFlashlight";
@ -64,7 +67,11 @@ namespace osu.Game.Rulesets.Taiko.Mods
if (!flashlightProperties.IsValid) 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(); flashlightProperties.Validate();
} }
} }

View File

@ -2,45 +2,55 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Mods namespace osu.Game.Rulesets.Taiko.Mods
{ {
public class TaikoModHidden : ModHidden public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset<TaikoHitObject>
{ {
public override string Description => @"Beats fade out before you hit them!"; public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
private ControlPointInfo controlPointInfo; /// <summary>
/// How far away from the hit target should hitobjects start to fade out.
/// Range: [0, 1]
/// </summary>
private const float fade_out_start_time = 1f;
/// <summary>
/// How long hitobjects take to fade out, in terms of the scrolling length.
/// Range: [0, 1]
/// </summary>
private const float fade_out_duration = 0.375f;
private DrawableTaikoRuleset drawableRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
{
this.drawableRuleset = (DrawableTaikoRuleset)drawableRuleset;
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ {
ApplyNormalVisibilityState(hitObject, 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) protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ {
switch (hitObject) switch (hitObject)
{ {
case DrawableDrumRollTick _: case DrawableDrumRollTick _:
case DrawableHit _: case DrawableHit _:
double preempt = 10000 / MultiplierAt(hitObject.HitObject.StartTime); double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier;
double start = hitObject.HitObject.StartTime - preempt * 0.6; double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time;
double duration = preempt * 0.3; double duration = preempt * fade_out_duration;
using (hitObject.BeginAbsoluteSequence(start)) using (hitObject.BeginAbsoluteSequence(start))
{ {
@ -56,10 +66,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
break; break;
} }
} }
public override void ApplyToBeatmap(IBeatmap beatmap)
{
controlPointInfo = beatmap.ControlPointInfo;
}
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; 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), protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody),
_ => new ElongatedCirclePiece()); _ => new ElongatedCirclePiece());
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
private void onNewResult(DrawableHitObject obj, JudgementResult result) 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); ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
} }
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning; 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<TaikoAction> 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); 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); ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
} }
} }
} }

View File

@ -8,6 +8,7 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -145,19 +146,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = result); ApplyResult(r => r.Type = result);
} }
public override bool OnPressed(TaikoAction action) public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
{ {
if (pressHandledThisFrame) if (pressHandledThisFrame)
return true; return true;
if (Judged) if (Judged)
return false; return false;
validActionPressed = HitActions.Contains(action); validActionPressed = HitActions.Contains(e.Action);
// Only count this as handled if the new judgement is a hit // Only count this as handled if the new judgement is a hit
var result = UpdateResult(true); var result = UpdateResult(true);
if (IsHit) 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 // 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 // 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; return result;
} }
public override void OnReleased(TaikoAction action) public override void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
{ {
if (action == HitAction) if (e.Action == HitAction)
HitAction = null; HitAction = null;
base.OnReleased(action); base.OnReleased(e);
} }
protected override void Update() protected override void Update()
@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(r => r.Type = r.Judgement.MaxResult);
} }
public override bool OnPressed(TaikoAction action) public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
{ {
// Don't process actions until the main hitobject is hit // Don't process actions until the main hitobject is hit
if (!ParentHitObject.IsHit) if (!ParentHitObject.IsHit)
@ -276,7 +277,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return false; return false;
// Don't handle invalid hit action presses // Don't handle invalid hit action presses
if (!ParentHitObject.HitActions.Contains(action)) if (!ParentHitObject.HitActions.Contains(e.Action))
return false; return false;
return UpdateResult(true); return UpdateResult(true);

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Rulesets.Taiko.Skinning.Default;
@ -266,13 +267,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private bool? lastWasCentre; private bool? lastWasCentre;
public override bool OnPressed(TaikoAction action) public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
{ {
// Don't handle keys before the swell starts // Don't handle keys before the swell starts
if (Time.Current < HitObject.StartTime) if (Time.Current < HitObject.StartTime)
return false; 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 // Ensure alternating centre and rim hits
if (lastWasCentre == isCentre) if (lastWasCentre == isCentre)

View File

@ -3,6 +3,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Taiko.Skinning.Default; using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning; 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<TaikoAction> e) => false;
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick), protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
_ => new TickPiece()); _ => new TickPiece());

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary> /// </summary>
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy(); public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
public abstract bool OnPressed(TaikoAction action); public abstract bool OnPressed(KeyBindingPressEvent<TaikoAction> e);
public virtual void OnReleased(TaikoAction action) public virtual void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
{ {
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -141,16 +142,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
Centre.Texture = skin.GetTexture(@"taiko-drum-inner"); Centre.Texture = skin.GetTexture(@"taiko-drum-inner");
} }
public bool OnPressed(TaikoAction action) public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
{ {
Drawable target = null; Drawable target = null;
if (action == CentreAction) if (e.Action == CentreAction)
{ {
target = Centre; target = Centre;
sampleTriggerSource.Play(HitType.Centre); sampleTriggerSource.Play(HitType.Centre);
} }
else if (action == RimAction) else if (e.Action == RimAction)
{ {
target = Rim; target = Rim;
sampleTriggerSource.Play(HitType.Rim); sampleTriggerSource.Play(HitType.Rim);
@ -173,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
return false; return false;
} }
public void OnReleased(TaikoAction action) public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
{ {
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -16,6 +17,7 @@ using osu.Game.Input.Handlers;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; 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; 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(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -151,19 +152,19 @@ namespace osu.Game.Rulesets.Taiko.UI
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; } private GameplayClock gameplayClock { get; set; }
public bool OnPressed(TaikoAction action) public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
{ {
Drawable target = null; Drawable target = null;
Drawable back = null; Drawable back = null;
if (action == CentreAction) if (e.Action == CentreAction)
{ {
target = centreHit; target = centreHit;
back = centre; back = centre;
sampleTriggerSource.Play(HitType.Centre); sampleTriggerSource.Play(HitType.Centre);
} }
else if (action == RimAction) else if (e.Action == RimAction)
{ {
target = rimHit; target = rimHit;
back = rim; back = rim;
@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Taiko.UI
return false; return false;
} }
public void OnReleased(TaikoAction action) public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
{ {
} }
} }

View File

@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle); Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
Assert.AreEqual(0, beatmapInfo.CountdownOffset); Assert.AreEqual(0, beatmapInfo.CountdownOffset);
} }

View File

@ -424,14 +424,14 @@ namespace osu.Game.Tests.Beatmaps.IO
checkBeatmapCount(osu, 12); checkBeatmapCount(osu, 12);
checkSingleReferencedFileCount(osu, 18); checkSingleReferencedFileCount(osu, 18);
var breakTemp = TestResources.GetTestBeatmapForImport(); var brokenTempFilename = TestResources.GetTestBeatmapForImport();
MemoryStream brokenOsu = new MemoryStream(); 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)) using (var zip = ZipArchive.Open(brokenOsz))
{ {
zip.AddEntry("broken.osu", brokenOsu, false); 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. // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
try try
{ {
await manager.Import(new ImportTask(breakTemp)); await manager.Import(new ImportTask(brokenTempFilename));
} }
catch catch
{ {
@ -456,6 +456,8 @@ namespace osu.Game.Tests.Beatmaps.IO
checkSingleReferencedFileCount(osu, 18); checkSingleReferencedFileCount(osu, 18);
Assert.AreEqual(1, loggedExceptionCount); Assert.AreEqual(1, loggedExceptionCount);
File.Delete(brokenTempFilename);
} }
finally finally
{ {

View File

@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.IO; using System.IO;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing;
namespace osu.Game.Tests.Resources namespace osu.Game.Tests.Resources
{ {
@ -11,6 +13,8 @@ namespace osu.Game.Tests.Resources
{ {
public const double QUICK_BEATMAP_LENGTH = 10000; 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 DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
@ -25,7 +29,7 @@ namespace osu.Game.Tests.Resources
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns> /// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
public static string GetQuickTestBeatmapForImport() 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 stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz"))
using (var newFile = File.Create(tempPath)) using (var newFile = File.Create(tempPath))
stream.CopyTo(newFile); stream.CopyTo(newFile);
@ -41,7 +45,7 @@ namespace osu.Game.Tests.Resources
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns> /// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
public static string GetTestBeatmapForImport(bool virtualTrack = false) public static string GetTestBeatmapForImport(bool virtualTrack = false)
{ {
var tempPath = Path.GetTempFileName() + ".osz"; var tempPath = getTempFilename();
using (var stream = GetTestBeatmapStream(virtualTrack)) using (var stream = GetTestBeatmapStream(virtualTrack))
using (var newFile = File.Create(tempPath)) using (var newFile = File.Create(tempPath))
@ -50,5 +54,7 @@ namespace osu.Game.Tests.Resources
Assert.IsTrue(File.Exists(tempPath)); Assert.IsTrue(File.Exists(tempPath));
return tempPath; return tempPath;
} }
private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz");
} }
} }

View File

@ -7,28 +7,19 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Menu;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
{ {
public class TestSceneDifficultySwitching : ScreenTestScene public class TestSceneDifficultySwitching : EditorTestScene
{ {
private BeatmapSetInfo importedBeatmapSet; protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
private Editor editor;
// required for screen transitions to work properly protected override bool IsolateSavingFromDatabase => false;
// (see comment in EditorLoader.LogoArriving).
[Cached]
private OsuLogo logo = new OsuLogo
{
Alpha = 0
};
[Resolved] [Resolved]
private OsuGameBase game { get; set; } private OsuGameBase game { get; set; }
@ -36,20 +27,18 @@ namespace osu.Game.Tests.Visual.Editing
[Resolved] [Resolved]
private BeatmapManager beatmaps { get; set; } private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader] private BeatmapSetInfo importedBeatmapSet;
private void load() => Add(logo);
[SetUpSteps] public override void SetUpSteps()
public void SetUp()
{ {
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result);
base.SetUpSteps();
}
AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); protected override void LoadEditor()
AddStep("push loader", () => Stack.Push(new EditorLoader())); {
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
AddUntilStep("wait for editor push", () => Stack.CurrentScreen is Editor); base.LoadEditor();
AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen);
AddUntilStep("wait for editor to load", () => editor.IsLoaded);
} }
[Test] [Test]
@ -66,17 +55,66 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("stack empty", () => Stack.CurrentScreen == null); AddAssert("stack empty", () => Stack.CurrentScreen == null);
} }
[Test]
public void TestClockPositionPreservedBetweenSwitches()
{
BeatmapInfo targetDifficulty = null;
AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000));
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000);
AddStep("exit editor", () => Stack.Exit());
// ensure editor loader didn't resume.
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test]
public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset)
{
BeatmapInfo targetDifficulty = null;
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
AddStep("copy object", () => Editor.Copy());
AddStep("set target difficulty", () =>
{
targetDifficulty = sameRuleset
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
});
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
AddStep("paste object", () => Editor.Paste());
if (sameRuleset)
AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any());
else
AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any());
AddStep("exit editor", () => Stack.Exit());
if (sameRuleset)
{
AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction());
}
// ensure editor loader didn't resume.
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test] [Test]
public void TestPreventSwitchDueToUnsavedChanges() public void TestPreventSwitchDueToUnsavedChanges()
{ {
BeatmapInfo targetDifficulty = null; BeatmapInfo targetDifficulty = null;
PromptForSaveDialog saveDialog = null; PromptForSaveDialog saveDialog = null;
AddStep("remove first hitobject", () => AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
{
var editorBeatmap = editor.ChildrenOfType<EditorBeatmap>().Single();
editorBeatmap.RemoveAt(0);
});
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty); switchToDifficulty(() => targetDifficulty);
@ -105,11 +143,7 @@ namespace osu.Game.Tests.Visual.Editing
BeatmapInfo targetDifficulty = null; BeatmapInfo targetDifficulty = null;
PromptForSaveDialog saveDialog = null; PromptForSaveDialog saveDialog = null;
AddStep("remove first hitobject", () => AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
{
var editorBeatmap = editor.ChildrenOfType<EditorBeatmap>().Single();
editorBeatmap.RemoveAt(0);
});
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty); switchToDifficulty(() => targetDifficulty);
@ -132,39 +166,12 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("stack empty", () => Stack.CurrentScreen == null); AddAssert("stack empty", () => Stack.CurrentScreen == null);
} }
private void switchToDifficulty(Func<BeatmapInfo> difficulty) private void switchToDifficulty(Func<BeatmapInfo> difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
{
AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType<EditorMenuBar>().Any());
AddStep("open file menu", () =>
{
var menuBar = editor.ChildrenOfType<EditorMenuBar>().Single();
var fileMenu = menuBar.ChildrenOfType<DrawableOsuMenuItem>().First();
InputManager.MoveMouseTo(fileMenu);
InputManager.Click(MouseButton.Left);
});
AddStep("open difficulty menu", () =>
{
var difficultySelector =
editor.ChildrenOfType<DrawableOsuMenuItem>().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty"));
InputManager.MoveMouseTo(difficultySelector);
});
AddWaitStep("wait for open", 3);
AddStep("switch to target difficulty", () =>
{
var difficultyMenuItem =
editor.ChildrenOfType<DrawableOsuMenuItem>()
.Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke()));
InputManager.MoveMouseTo(difficultyMenuItem);
InputManager.Click(MouseButton.Left);
});
}
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty) private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
{ {
AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke()));
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true);
} }
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Setup;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using SharpCompress.Archives; using SharpCompress.Archives;
@ -55,6 +56,9 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestExitWithoutSave() public void TestExitWithoutSave()
{ {
EditorBeatmap editorBeatmap = null;
AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
AddStep("exit without save", () => AddStep("exit without save", () =>
{ {
Editor.Exit(); Editor.Exit();
@ -62,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
}); });
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
} }
[Test] [Test]

View File

@ -0,0 +1,104 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene
{
private Container content;
protected override Container<Drawable> Content => content;
[BackgroundDependencyLoader]
private void load()
{
base.Content.AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.Gray
},
content = new Container
{
RelativeSizeAxes = Axes.Both
}
});
}
private static readonly object[][] test_cases =
{
new object[] { new Vector2(0, 0), new Vector2(10, 10) },
new object[] { new Vector2(240, 180), new Vector2(10, 15) },
new object[] { new Vector2(160, 120), new Vector2(30, 20) },
new object[] { new Vector2(480, 360), new Vector2(100, 100) },
};
[TestCaseSource(nameof(test_cases))]
public void TestRectangularGrid(Vector2 position, Vector2 spacing)
{
RectangularPositionSnapGrid grid = null;
AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position)
{
RelativeSizeAxes = Axes.Both,
Spacing = spacing
});
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
{
RelativeSizeAxes = Axes.Both,
GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos))
}));
}
private class SnappingCursorContainer : CompositeDrawable
{
public Func<Vector2, Vector2> GetSnapPosition;
private readonly Drawable cursor;
public SnappingCursorContainer()
{
RelativeSizeAxes = Axes.Both;
InternalChild = cursor = new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(50),
Colour = Color4.Red
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
base.OnMouseMove(e);
updatePosition(e.ScreenSpaceMousePosition);
return true;
}
private void updatePosition(Vector2 screenSpacePosition)
{
cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
}
}
}
}

View File

@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -137,6 +138,23 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any()); AddAssert("no circle added", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
} }
[Test]
public void TestClear()
{
AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
AddStep("hit", () => newJudgement(0.2D));
AddAssert("bar added", () => this.ChildrenOfType<BarHitErrorMeter>().All(
meter => meter.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Count() == 1));
AddAssert("circle added", () => this.ChildrenOfType<ColourHitErrorMeter>().All(
meter => meter.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Count() == 1));
AddStep("clear", () => this.ChildrenOfType<HitErrorMeter>().ForEach(meter => meter.Clear()));
AddAssert("bar cleared", () => !this.ChildrenOfType<BarHitErrorMeter.JudgementLine>().Any());
AddAssert("colour cleared", () => !this.ChildrenOfType<ColourHitErrorMeter.HitErrorCircle>().Any());
}
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
{ {
hitWindows?.SetDifficulty(overallDifficulty); hitWindows?.SetDifficulty(overallDifficulty);

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
@ -80,13 +81,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public bool ReceivedAction; public bool ReceivedAction;
public bool OnPressed(TestAction action) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
ReceivedAction = action == TestAction.Down; ReceivedAction = e.Action == TestAction.Down;
return true; return true;
} }
public void OnReleased(TestAction action) public void OnReleased(KeyBindingReleaseEvent<TestAction> e)
{ {
} }
} }

View File

@ -0,0 +1,128 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Framework.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)
};
}
}
}
}

View File

@ -54,7 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{ {
Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay }) Recorder = recorder = new TestReplayRecorder(new Score
{
Replay = replay,
ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
})
{ {
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
}, },
@ -222,13 +226,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
public bool OnPressed(TestAction action) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
box.Colour = Color4.White; box.Colour = Color4.White;
return true; return true;
} }
public void OnReleased(TestAction action) public void OnReleased(KeyBindingReleaseEvent<TestAction> e)
{ {
box.Colour = Color4.Black; box.Colour = Color4.Black;
} }

View File

@ -45,7 +45,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{ {
Recorder = new TestReplayRecorder(new Score { Replay = replay }) Recorder = new TestReplayRecorder(new Score
{
Replay = replay,
ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
})
{ {
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
}, },
@ -155,13 +159,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
public bool OnPressed(TestAction action) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
box.Colour = Color4.White; box.Colour = Color4.White;
return true; return true;
} }
public void OnReleased(TestAction action) public void OnReleased(KeyBindingReleaseEvent<TestAction> e)
{ {
box.Colour = Color4.Black; box.Colour = Color4.Black;
} }

View File

@ -279,13 +279,13 @@ namespace osu.Game.Tests.Visual.Gameplay
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
public bool OnPressed(TestAction action) public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{ {
box.Colour = Color4.White; box.Colour = Color4.White;
return true; return true;
} }
public void OnReleased(TestAction action) public void OnReleased(KeyBindingReleaseEvent<TestAction> e)
{ {
box.Colour = Color4.Black; box.Colour = Color4.Black;
} }
@ -354,7 +354,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestReplayRecorder : ReplayRecorder<TestAction> internal class TestReplayRecorder : ReplayRecorder<TestAction>
{ {
public TestReplayRecorder() public TestReplayRecorder()
: base(new Score()) : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } })
{ {
} }

View File

@ -0,0 +1,41 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.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<OsuPasswordTextBox>().First().Text = "password");
AddStep("submit", () => loginPanel.ChildrenOfType<OsuButton>().First(b => b.Text.ToString() == "Sign in").TriggerClick());
}
}
}

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -82,7 +83,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
[Test] [Test]
public void TestJoinRoomWithPassword() public void TestJoinRoomWithIncorrectPasswordViaButton()
{
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<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
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<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().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]
public void TestJoinRoomWithCorrectPassword()
{ {
DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null;

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@ -15,11 +16,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
SelectedRoom.Value = new Room(); SelectedRoom.Value = new Room();
Child = new MultiplayerMatchFooter Child = new Container
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Height = 50 RelativeSizeAxes = Axes.X,
Height = 50,
Child = new MultiplayerMatchFooter()
}; };
}); });
} }

View File

@ -75,7 +75,6 @@ namespace osu.Game.Tests.Visual.Navigation
typeof(FileStore), typeof(FileStore),
typeof(ScoreManager), typeof(ScoreManager),
typeof(BeatmapManager), typeof(BeatmapManager),
typeof(SettingsStore),
typeof(RulesetConfigCache), typeof(RulesetConfigCache),
typeof(OsuColour), typeof(OsuColour),
typeof(IBindable<WorkingBeatmap>), typeof(IBindable<WorkingBeatmap>),
@ -97,9 +96,6 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
AddStep("create game", () => AddStep("create game", () =>
{ {
game = new OsuGame();
game.SetHost(host);
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -107,8 +103,9 @@ namespace osu.Game.Tests.Visual.Navigation
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
game
}; };
AddGame(game = new OsuGame());
}); });
AddUntilStep("wait for load", () => game.IsLoaded); AddUntilStep("wait for load", () => game.IsLoaded);

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