Merge branch 'no-more-difficulty-control-points-info' into slider-timeline-velcotiy-adjust-v2
30
.github/ISSUE_TEMPLATE/01-bug-issues.md
vendored
@ -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)
|
||||
-->
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,12 +1,12 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: Help
|
||||
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: osu!stable issues
|
||||
url: https://github.com/ppy/osu-stable-issues
|
||||
about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
|
||||
about: For osu!(stable) - ie. the current "live" game version, check out the dedicated repository. Note that this is for serious bug reports only, not tech support.
|
||||
|
||||
|
205
.github/workflows/diffcalc.yml
vendored
Normal 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
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--filter *" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -14,7 +14,7 @@
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net5.0" />
|
||||
<method v="2">
|
||||
<option name="Build" enabled="true" />
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ContentModelUserStore">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
|
@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
|
||||
|
||||
**Latest build:**
|
||||
|
||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
|
||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||
|
||||
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
|
||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, OsuGameBase gameBase)
|
||||
{
|
||||
OsuGame game = new OsuGame();
|
||||
game.SetHost(host);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
game
|
||||
};
|
||||
|
||||
AddGame(new OsuGame());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, OsuGameBase gameBase)
|
||||
{
|
||||
OsuGame game = new OsuGame();
|
||||
game.SetHost(host);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
game
|
||||
};
|
||||
|
||||
AddGame(new OsuGame());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, OsuGameBase gameBase)
|
||||
{
|
||||
OsuGame game = new OsuGame();
|
||||
game.SetHost(host);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
game
|
||||
};
|
||||
|
||||
AddGame(new OsuGame());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,6 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, OsuGameBase gameBase)
|
||||
{
|
||||
OsuGame game = new OsuGame();
|
||||
game.SetHost(host);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -25,8 +22,9 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
game
|
||||
};
|
||||
|
||||
AddGame(new OsuGame());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK;
|
||||
@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Pippidon.UI
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnPressed(PippidonAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<PippidonAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case PippidonAction.MoveUp:
|
||||
changeLane(-1);
|
||||
@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Pippidon.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReleased(PippidonAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<PippidonAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -51,11 +51,11 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.830.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.918.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.927.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
<PackageReference Include="Realm" Version="10.3.0" />
|
||||
<PackageReference Include="Realm" Version="10.5.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Benchmarks
|
||||
[Params(1, 10, 100)]
|
||||
public int Times { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
mod = new OsuModDoubleTime();
|
||||
}
|
||||
|
||||
|
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
@ -0,0 +1,62 @@
|
||||
// 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 BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkRuleset : BenchmarkTest
|
||||
{
|
||||
private OsuRuleset ruleset;
|
||||
private APIMod apiModDoubleTime;
|
||||
private APIMod apiModDifficultyAdjust;
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
ruleset = new OsuRuleset();
|
||||
apiModDoubleTime = new APIMod { Acronym = "DT" };
|
||||
apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkToModDoubleTime()
|
||||
{
|
||||
apiModDoubleTime.ToMod(ruleset);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkToModDifficultyAdjust()
|
||||
{
|
||||
apiModDifficultyAdjust.ToMod(ruleset);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetAllMods()
|
||||
{
|
||||
ruleset.CreateAllMods().Consume(new Consumer());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetAllModsForReference()
|
||||
{
|
||||
ruleset.AllMods.Consume(new Consumer());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetForAcronym()
|
||||
{
|
||||
ruleset.CreateModFromAcronym("DT");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchmarkGetForType()
|
||||
{
|
||||
ruleset.CreateMod<ModDoubleTime>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
|
||||
{
|
||||
BenchmarkSwitcher
|
||||
.FromAssembly(typeof(Program).Assembly)
|
||||
.Run(args);
|
||||
.Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
||||
foreach (var hitObject in beatmap.HitObjects
|
||||
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj })
|
||||
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj })
|
||||
.Cast<CatchHitObject>()
|
||||
.OrderBy(x => x.StartTime))
|
||||
{
|
||||
|
@ -44,9 +44,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
@ -144,9 +145,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Catcher.VisualDirection = Direction.Left;
|
||||
}
|
||||
|
||||
public bool OnPressed(CatchAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<CatchAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection--;
|
||||
@ -164,9 +165,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(CatchAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<CatchAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection++;
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.9 KiB |
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
c.Add(CreateHitObject().With(h =>
|
||||
{
|
||||
h.HitObject.StartTime = START_TIME;
|
||||
h.HitObject.StartTime = Time.Current + 5000;
|
||||
h.AccentColour.Value = Color4.Orange;
|
||||
}));
|
||||
})
|
||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
c.Add(CreateHitObject().With(h =>
|
||||
{
|
||||
h.HitObject.StartTime = START_TIME;
|
||||
h.HitObject.StartTime = Time.Current + 5000;
|
||||
h.AccentColour.Value = Color4.Orange;
|
||||
}));
|
||||
})
|
||||
|
@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
/// </summary>
|
||||
public abstract class ManiaSkinnableTestScene : SkinnableTestScene
|
||||
{
|
||||
protected const double START_TIME = 1000000000;
|
||||
|
||||
[Cached(Type = typeof(IScrollingInfo))]
|
||||
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>();
|
||||
|
||||
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
|
||||
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000);
|
||||
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ZeroScrollAlgorithm();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(5000);
|
||||
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddStep("Hold key", () =>
|
||||
{
|
||||
clock.CurrentTime = 0;
|
||||
note.OnPressed(ManiaAction.Key1);
|
||||
note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager().CurrentState, ManiaAction.Key1));
|
||||
});
|
||||
AddStep("progress time", () => clock.CurrentTime = 500);
|
||||
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
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.Configuration;
|
||||
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
|
||||
{
|
||||
@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[Resolved]
|
||||
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;
|
||||
|
||||
var ruleset = new ManiaRuleset();
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
|
||||
{
|
||||
HitObjects =
|
||||
@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new Note { StartTime = beat_length }
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo(),
|
||||
BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
|
||||
BeatmapInfo = { Ruleset = Ruleset.Value },
|
||||
};
|
||||
|
||||
foreach (var note in beatmap.HitObjects)
|
||||
@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
BeatLength = beat_length
|
||||
});
|
||||
|
||||
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);
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
|
||||
@ -84,37 +82,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
||||
{
|
||||
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
|
||||
|
||||
if (IsForCurrentRuleset && original.ControlPointInfo is LegacyControlPointInfo originalLegacyControlPoints)
|
||||
{
|
||||
// original is cloned so we're safe to replace control point storage at this point.
|
||||
original.ControlPointInfo = new LegacyControlPointInfo();
|
||||
|
||||
// convert all slider velocity adjustments to scroll speed adjustments.
|
||||
foreach (var controlPoint in originalLegacyControlPoints.AllControlPoints)
|
||||
{
|
||||
double time = controlPoint.Time;
|
||||
|
||||
switch (controlPoint)
|
||||
{
|
||||
default:
|
||||
original.ControlPointInfo.Add(time, controlPoint);
|
||||
break;
|
||||
|
||||
case DifficultyControlPoint difficultyPoint:
|
||||
var reference = originalLegacyControlPoints.EffectPointAt(time);
|
||||
|
||||
var scrollControlPoint = new EffectControlPoint();
|
||||
|
||||
scrollControlPoint.CopyFrom(reference);
|
||||
scrollControlPoint.ScrollSpeed = difficultyPoint.SliderVelocity;
|
||||
|
||||
original.ControlPointInfo.Add(time, scrollControlPoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
|
||||
Random = new FastRandom(seed);
|
||||
|
||||
|
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
lowerBound ??= RandomStart;
|
||||
upperBound ??= TotalColumns;
|
||||
nextColumn ??= (_ => GetRandomColumn(lowerBound, upperBound));
|
||||
nextColumn ??= _ => GetRandomColumn(lowerBound, upperBound);
|
||||
|
||||
// Check for the initial column
|
||||
if (isValid(initialColumn))
|
||||
@ -176,7 +176,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
return initialColumn;
|
||||
|
||||
bool isValid(int column) => validation?.Invoke(column) != false && !patterns.Any(p => p.ColumnHasObject(column));
|
||||
bool isValid(int column)
|
||||
{
|
||||
if (validation?.Invoke(column) == false)
|
||||
return false;
|
||||
|
||||
foreach (var p in patterns)
|
||||
{
|
||||
if (p.ColumnHasObject(column))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -12,46 +12,68 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
/// </summary>
|
||||
internal class Pattern
|
||||
{
|
||||
private readonly List<ManiaHitObject> hitObjects = new List<ManiaHitObject>();
|
||||
private List<ManiaHitObject> hitObjects;
|
||||
private HashSet<int> containedColumns;
|
||||
|
||||
/// <summary>
|
||||
/// All the hit objects contained in this pattern.
|
||||
/// </summary>
|
||||
public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
|
||||
public IEnumerable<ManiaHitObject> HitObjects => hitObjects ?? Enumerable.Empty<ManiaHitObject>();
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a column of this patterns contains a hit object.
|
||||
/// </summary>
|
||||
/// <param name="column">The column index.</param>
|
||||
/// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns>
|
||||
public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column);
|
||||
public bool ColumnHasObject(int column) => containedColumns?.Contains(column) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of columns taken up by hit objects in this pattern.
|
||||
/// </summary>
|
||||
public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count();
|
||||
public int ColumnWithObjects => containedColumns?.Count ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a hit object to this pattern.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The hit object to add.</param>
|
||||
public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject);
|
||||
public void Add(ManiaHitObject hitObject)
|
||||
{
|
||||
prepareStorage();
|
||||
|
||||
hitObjects.Add(hitObject);
|
||||
containedColumns.Add(hitObject.Column);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies hit object from another pattern to this one.
|
||||
/// </summary>
|
||||
/// <param name="other">The other pattern.</param>
|
||||
public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects);
|
||||
public void Add(Pattern other)
|
||||
{
|
||||
prepareStorage();
|
||||
|
||||
if (other.hitObjects != null)
|
||||
{
|
||||
hitObjects.AddRange(other.hitObjects);
|
||||
|
||||
foreach (var h in other.hitObjects)
|
||||
containedColumns.Add(h.Column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears this pattern, removing all hit objects.
|
||||
/// </summary>
|
||||
public void Clear() => hitObjects.Clear();
|
||||
public void Clear()
|
||||
{
|
||||
hitObjects?.Clear();
|
||||
containedColumns?.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a hit object from this pattern.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The hit object to remove.</param>
|
||||
public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject);
|
||||
private void prepareStorage()
|
||||
{
|
||||
hitObjects ??= new List<ManiaHitObject>();
|
||||
containedColumns ??= new HashSet<int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -253,12 +254,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
HoldBrokenTime = Time.Current;
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
{
|
||||
if (AllJudged)
|
||||
return false;
|
||||
|
||||
if (action != Action.Value)
|
||||
if (e.Action != Action.Value)
|
||||
return false;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
@ -288,12 +289,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
isHitting.Value = true;
|
||||
}
|
||||
|
||||
public void OnReleased(ManiaAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
if (AllJudged)
|
||||
return;
|
||||
|
||||
if (action != Action.Value)
|
||||
if (e.Action != Action.Value)
|
||||
return;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
@ -43,9 +44,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
// it will be hidden along with its parenting hold note when required.
|
||||
}
|
||||
|
||||
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
|
||||
public override bool OnPressed(KeyBindingPressEvent<ManiaAction> e) => false; // Handled by the hold note
|
||||
|
||||
public override void OnReleased(ManiaAction action)
|
||||
public override void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
@ -68,9 +69,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
});
|
||||
}
|
||||
|
||||
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
|
||||
public override bool OnPressed(KeyBindingPressEvent<ManiaAction> e) => false; // Handled by the hold note
|
||||
|
||||
public override void OnReleased(ManiaAction action)
|
||||
public override void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
@ -66,6 +67,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
updateSnapColour();
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
base.OnDirectionChanged(e);
|
||||
@ -91,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
ApplyResult(r => r.Type = result);
|
||||
}
|
||||
|
||||
public virtual bool OnPressed(ManiaAction action)
|
||||
public virtual bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
{
|
||||
if (action != Action.Value)
|
||||
if (e.Action != Action.Value)
|
||||
return false;
|
||||
|
||||
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||
@ -102,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
return UpdateResult(true);
|
||||
}
|
||||
|
||||
public virtual void OnReleased(ManiaAction action)
|
||||
public virtual void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
{
|
||||
if (action == Column.Action.Value)
|
||||
if (e.Action == Column.Action.Value)
|
||||
{
|
||||
light.FadeIn();
|
||||
light.ScaleTo(Vector2.One);
|
||||
@ -87,12 +88,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(ManiaAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
// Todo: Should be 400 * 100 / CurrentBPM
|
||||
const double animation_length = 250;
|
||||
|
||||
if (action == Column.Action.Value)
|
||||
if (e.Action == Column.Action.Value)
|
||||
{
|
||||
light.FadeTo(0, animation_length);
|
||||
light.ScaleTo(new Vector2(1, 0), animation_length);
|
||||
|
@ -1,18 +1,18 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
public class LegacyHoldNoteHeadPiece : LegacyNotePiece
|
||||
{
|
||||
protected override Texture GetTexture(ISkinSource skin)
|
||||
protected override Drawable GetAnimation(ISkinSource skin)
|
||||
{
|
||||
// TODO: Should fallback to the head from default legacy skin instead of note.
|
||||
return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
|
||||
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
|
||||
return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
|
||||
?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
: new ValueChangedEvent<ScrollingDirection>(ScrollingDirection.Up, ScrollingDirection.Up));
|
||||
}
|
||||
|
||||
protected override Texture GetTexture(ISkinSource skin)
|
||||
protected override Drawable GetAnimation(ISkinSource skin)
|
||||
{
|
||||
// TODO: Should fallback to the head from default legacy skin instead of note.
|
||||
return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
|
||||
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
|
||||
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
|
||||
return GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
|
||||
?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
|
||||
?? GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
{
|
||||
if (action == column.Action.Value)
|
||||
if (e.Action == column.Action.Value)
|
||||
{
|
||||
upSprite.FadeTo(0);
|
||||
downSprite.FadeTo(1);
|
||||
@ -97,9 +98,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(ManiaAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
if (action == column.Action.Value)
|
||||
if (e.Action == column.Action.Value)
|
||||
{
|
||||
upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1);
|
||||
downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0);
|
||||
|
@ -1,9 +1,11 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@ -19,7 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
private Container directionContainer;
|
||||
private Sprite noteSprite;
|
||||
|
||||
[CanBeNull]
|
||||
private Drawable noteAnimation;
|
||||
|
||||
private float? minimumColumnWidth;
|
||||
|
||||
@ -39,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = noteSprite = new Sprite { Texture = GetTexture(skin) }
|
||||
Child = noteAnimation = GetAnimation(skin) ?? Empty()
|
||||
};
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
@ -50,12 +54,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (noteSprite.Texture != null)
|
||||
Texture texture = null;
|
||||
|
||||
if (noteAnimation is Sprite sprite)
|
||||
texture = sprite.Texture;
|
||||
else if (noteAnimation is TextureAnimation textureAnimation && textureAnimation.FrameCount > 0)
|
||||
texture = textureAnimation.CurrentFrame;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
// The height is scaled to the minimum column width, if provided.
|
||||
float minimumWidth = minimumColumnWidth ?? DrawWidth;
|
||||
|
||||
noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth);
|
||||
noteAnimation.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), texture.DisplayWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,9 +83,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Texture GetTexture(ISkinSource skin) => GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
|
||||
[CanBeNull]
|
||||
protected virtual Drawable GetAnimation(ISkinSource skin) => GetAnimationFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
|
||||
|
||||
protected Texture GetTextureFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
|
||||
[CanBeNull]
|
||||
protected Drawable GetAnimationFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
|
||||
{
|
||||
string suffix = string.Empty;
|
||||
|
||||
@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value
|
||||
?? $"mania-note{FallbackColumnIndex}{suffix}";
|
||||
|
||||
return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge);
|
||||
return skin.GetAnimation(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -122,16 +123,16 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||
{
|
||||
if (action != Action.Value)
|
||||
if (e.Action != Action.Value)
|
||||
return false;
|
||||
|
||||
sampleTriggerSource.Play();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnReleased(ManiaAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK.Graphics;
|
||||
@ -91,16 +92,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -74,16 +75,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -101,16 +102,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.7568168283591499d, "diffcalc-test")]
|
||||
[TestCase(1.0348244046058293d, "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.4783236764532557d, "diffcalc-test")]
|
||||
[TestCase(1.2708532136987165d, "zero-length-sliders")]
|
||||
[TestCase(8.3858089051603368d, "diffcalc-test")]
|
||||
[TestCase(1.2723279173428435d, "zero-length-sliders")]
|
||||
public void TestClockRateAdjusted(double expected, string name)
|
||||
=> Test(expected, name, new OsuModDoubleTime());
|
||||
|
||||
|
175
osu.Game.Rulesets.Osu.Tests/TestSceneCursorParticles.cs
Normal 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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Testing.Input;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
@ -143,9 +144,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
pressed = value;
|
||||
if (value)
|
||||
OnPressed(OsuAction.LeftButton);
|
||||
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
||||
else
|
||||
OnReleased(OsuAction.LeftButton);
|
||||
OnReleased(new KeyBindingReleaseEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private void scheduleHit() => AddStep("schedule action", () =>
|
||||
{
|
||||
var delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
|
||||
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(OsuAction.LeftButton), delay);
|
||||
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Visual;
|
||||
@ -86,9 +87,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
if (firstObject == null)
|
||||
return false;
|
||||
|
||||
var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable;
|
||||
var skinnable = firstObject.ApproachCircle;
|
||||
|
||||
if (skin == null && skinnable?.Drawable is Sprite)
|
||||
if (skin == null && skinnable?.Drawable is DefaultApproachCircle)
|
||||
// check for default skin provider
|
||||
return true;
|
||||
|
||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public double AimStrain { get; set; }
|
||||
public double SpeedStrain { get; set; }
|
||||
public double FlashlightRating { get; set; }
|
||||
public double ApproachRate { get; set; }
|
||||
public double OverallDifficulty { get; set; }
|
||||
public int HitCircleCount { get; set; }
|
||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
private double hitWindowGreat;
|
||||
|
||||
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
@ -34,13 +35,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||
double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
|
||||
|
||||
HitWindows hitWindows = new OsuHitWindows();
|
||||
hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
|
||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||
double 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;
|
||||
|
||||
int maxCombo = beatmap.HitObjects.Count;
|
||||
@ -56,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
Mods = mods,
|
||||
AimStrain = aimRating,
|
||||
SpeedStrain = speedRating,
|
||||
FlashlightRating = flashlightRating,
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||
MaxCombo = maxCombo,
|
||||
@ -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),
|
||||
new Speed(mods)
|
||||
};
|
||||
HitWindows hitWindows = new OsuHitWindows();
|
||||
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[]
|
||||
{
|
||||
@ -91,6 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
new OsuModHalfTime(),
|
||||
new OsuModEasy(),
|
||||
new OsuModHardRock(),
|
||||
new OsuModFlashlight(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
|
||||
if (mods.Any(m => m is OsuModNoFail))
|
||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
|
||||
@ -52,11 +52,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double aimValue = computeAimValue();
|
||||
double speedValue = computeSpeedValue();
|
||||
double accuracyValue = computeAccuracyValue();
|
||||
double flashlightValue = computeFlashlightValue();
|
||||
double totalValue =
|
||||
Math.Pow(
|
||||
Math.Pow(aimValue, 1.1) +
|
||||
Math.Pow(speedValue, 1.1) +
|
||||
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
|
||||
Math.Pow(accuracyValue, 1.1) +
|
||||
Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
|
||||
) * multiplier;
|
||||
|
||||
if (categoryRatings != null)
|
||||
@ -64,6 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
categoryRatings.Add("Aim", aimValue);
|
||||
categoryRatings.Add("Speed", speedValue);
|
||||
categoryRatings.Add("Accuracy", accuracyValue);
|
||||
categoryRatings.Add("Flashlight", flashlightValue);
|
||||
categoryRatings.Add("OD", Attributes.OverallDifficulty);
|
||||
categoryRatings.Add("AR", Attributes.ApproachRate);
|
||||
categoryRatings.Add("Max Combo", Attributes.MaxCombo);
|
||||
@ -81,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
|
||||
// Longer maps are worth more
|
||||
// Longer maps are worth more.
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
|
||||
@ -91,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (countMiss > 0)
|
||||
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
|
||||
|
||||
// Combo scaling
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
@ -109,23 +112,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||
|
||||
double flashlightBonus = 1.0;
|
||||
aimValue *= approachRateBonus;
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
{
|
||||
// Apply object-based bonus for flashlight.
|
||||
flashlightBonus = 1.0 + 0.35 * Math.Min(1.0, totalHits / 200.0) +
|
||||
(totalHits > 200
|
||||
? 0.3 * Math.Min(1.0, (totalHits - 200) / 300.0) +
|
||||
(totalHits > 500 ? (totalHits - 500) / 1200.0 : 0.0)
|
||||
: 0.0);
|
||||
}
|
||||
|
||||
aimValue *= Math.Max(flashlightBonus, approachRateBonus);
|
||||
|
||||
// Scale the aim value with accuracy _slightly_
|
||||
// Scale the aim value with accuracy _slightly_.
|
||||
aimValue *= 0.5 + accuracy / 2.0;
|
||||
// It is important to also consider accuracy difficulty when doing that
|
||||
// It is important to also consider accuracy difficulty when doing that.
|
||||
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return aimValue;
|
||||
@ -135,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
|
||||
// Longer maps are worth more
|
||||
// Longer maps are worth more.
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
speedValue *= lengthBonus;
|
||||
@ -144,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (countMiss > 0)
|
||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
|
||||
|
||||
// Combo scaling
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
@ -159,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (mods.Any(m => m is OsuModHidden))
|
||||
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||
|
||||
// Scale the speed value with accuracy and OD
|
||||
// Scale the speed value with accuracy and OD.
|
||||
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
|
||||
// Scale the speed value with # of 50s to punish doubletapping.
|
||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||
@ -169,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
private double computeAccuracyValue()
|
||||
{
|
||||
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
|
||||
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window.
|
||||
double betterAccuracyPercentage;
|
||||
int amountHitObjectsWithAccuracy = Attributes.HitCircleCount;
|
||||
|
||||
@ -178,15 +169,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
else
|
||||
betterAccuracyPercentage = 0;
|
||||
|
||||
// It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points
|
||||
// It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points.
|
||||
if (betterAccuracyPercentage < 0)
|
||||
betterAccuracyPercentage = 0;
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution.
|
||||
double accuracyValue = Math.Pow(1.52163, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
|
||||
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
|
||||
|
||||
if (mods.Any(m => m is OsuModHidden))
|
||||
@ -197,6 +188,42 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return accuracyValue;
|
||||
}
|
||||
|
||||
private double computeFlashlightValue()
|
||||
{
|
||||
if (!mods.Any(h => h is OsuModFlashlight))
|
||||
return 0.0;
|
||||
|
||||
double rawFlashlight = Attributes.FlashlightRating;
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
|
||||
|
||||
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
|
||||
|
||||
// Add an additional bonus for HDFL.
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
flashlightValue *= 1.3;
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (countMiss > 0)
|
||||
flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
|
||||
|
||||
// Combo scaling.
|
||||
if (Attributes.MaxCombo > 0)
|
||||
flashlightValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
// Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
|
||||
flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) +
|
||||
(totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0);
|
||||
|
||||
// Scale the flashlight value with accuracy _slightly_.
|
||||
flashlightValue *= 0.5 + accuracy / 2.0;
|
||||
// It is important to also consider accuracy difficulty when doing that.
|
||||
flashlightValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return flashlightValue;
|
||||
}
|
||||
|
||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||
}
|
||||
|
@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
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>
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
@ -32,11 +37,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
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 lastObject;
|
||||
|
||||
@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
setDistances();
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(50, DeltaTime);
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects.
|
||||
StrainTime = Math.Max(DeltaTime, 25);
|
||||
}
|
||||
|
||||
private void setDistances()
|
||||
|
66
osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
Normal file
@ -0,0 +1,66 @@
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
|
||||
protected override double SkillMultiplier => 0.15;
|
||||
protected override double StrainDecayBase => 0.15;
|
||||
protected override double DecayWeight => 1.0;
|
||||
protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations.
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
var osuHitObject = (OsuHitObject)(osuCurrent.BaseObject);
|
||||
|
||||
double scalingFactor = 52.0 / osuHitObject.Radius;
|
||||
double smallDistNerf = 1.0;
|
||||
double cumulativeStrainTime = 0.0;
|
||||
|
||||
double result = 0.0;
|
||||
|
||||
for (int i = 0; i < Previous.Count; i++)
|
||||
{
|
||||
var osuPrevious = (OsuDifficultyHitObject)Previous[i];
|
||||
var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject);
|
||||
|
||||
if (!(osuPrevious.BaseObject is Spinner))
|
||||
{
|
||||
double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length;
|
||||
|
||||
cumulativeStrainTime += osuPrevious.StrainTime;
|
||||
|
||||
// We want to nerf objects that can be easily seen within the Flashlight circle radius.
|
||||
if (i == 0)
|
||||
smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
|
||||
|
||||
// We also want to nerf stacks so that only the first object of the stack is accounted for.
|
||||
double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0);
|
||||
|
||||
result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Pow(smallDistNerf * result, 2.0);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
@ -26,12 +27,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
protected override double DifficultyMultiplier => 1.04;
|
||||
|
||||
private const double min_speed_bonus = 75; // ~200BPM
|
||||
private const double max_speed_bonus = 45; // ~330BPM
|
||||
private const double speed_balancing_factor = 40;
|
||||
|
||||
public Speed(Mod[] mods)
|
||||
private readonly double greatWindow;
|
||||
|
||||
public Speed(Mod[] mods, double hitWindowGreat)
|
||||
: base(mods)
|
||||
{
|
||||
greatWindow = hitWindowGreat;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
@ -40,13 +43,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
var osuPrevious = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null;
|
||||
|
||||
double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
|
||||
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
|
||||
double strainTime = osuCurrent.StrainTime;
|
||||
|
||||
double greatWindowFull = greatWindow * 2;
|
||||
double speedWindowRatio = strainTime / greatWindowFull;
|
||||
|
||||
// Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
|
||||
if (osuPrevious != null && strainTime < greatWindowFull && osuPrevious.StrainTime > strainTime)
|
||||
strainTime = Interpolation.Lerp(osuPrevious.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);
|
||||
|
||||
double speedBonus = 1.0;
|
||||
if (deltaTime < min_speed_bonus)
|
||||
speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
|
||||
if (strainTime < min_speed_bonus)
|
||||
speedBonus = 1 + Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
||||
|
||||
double angleBonus = 1.0;
|
||||
|
||||
@ -64,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
}
|
||||
}
|
||||
|
||||
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime;
|
||||
return (1 + (speedBonus - 1) * 0.75)
|
||||
* angleBonus
|
||||
* (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5))
|
||||
/ strainTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,9 +127,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnPressed(PlatformAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case PlatformAction.Delete:
|
||||
return DeleteSelected();
|
||||
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(PlatformAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<PlatformAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -42,10 +42,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
};
|
||||
|
||||
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[]
|
||||
{
|
||||
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;
|
||||
@ -63,6 +65,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||
},
|
||||
distanceSnapGridContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
@ -73,7 +79,19 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||
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
|
||||
updateDistanceSnapGrid();
|
||||
@ -91,6 +109,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private readonly Cached distanceSnapGridCache = new Cached();
|
||||
private double? lastDistanceSnapGridTime;
|
||||
|
||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -122,13 +142,19 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
if (positionSnap.ScreenSpacePosition != screenSpacePosition)
|
||||
return positionSnap;
|
||||
|
||||
// will be null if distance snap is disabled or not feasible for the current time value.
|
||||
if (distanceSnapGrid == null)
|
||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||
if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(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)
|
||||
|
69
osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public OsuAction? HitAction => HitArea.HitAction;
|
||||
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
||||
|
||||
public ApproachCircle ApproachCircle { get; private set; }
|
||||
public SkinnableDrawable ApproachCircle { get; private set; }
|
||||
public HitReceptor HitArea { get; private set; }
|
||||
public SkinnableDrawable CirclePiece { get; private set; }
|
||||
|
||||
@ -74,8 +75,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
ApproachCircle = new ApproachCircle
|
||||
ApproachCircle = new ProxyableSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ApproachCircle), _ => new DefaultApproachCircle())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Scale = new Vector2(4),
|
||||
}
|
||||
@ -88,7 +92,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||
AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -228,15 +231,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
CornerExponent = 2;
|
||||
}
|
||||
|
||||
public bool OnPressed(OsuAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
if (IsHovered && (Hit?.Invoke() ?? false))
|
||||
{
|
||||
HitAction = action;
|
||||
HitAction = e.Action;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -246,7 +249,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(OsuAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public SliderBall Ball { 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;
|
||||
|
||||
[CanBeNull]
|
||||
@ -65,6 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
||||
tickContainer = new Container<DrawableSliderTick> { 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)
|
||||
{
|
||||
GetInitialHitAction = () => HeadCircle.HitAction,
|
||||
@ -72,7 +80,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0
|
||||
},
|
||||
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||
};
|
||||
|
||||
@ -179,6 +186,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
tailContainer.Clear(false);
|
||||
repeatContainer.Clear(false);
|
||||
tickContainer.Clear(false);
|
||||
|
||||
OverlayElementContainer.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
[CanBeNull]
|
||||
public Slider Slider => DrawableSlider?.HitObject;
|
||||
|
||||
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
|
||||
public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult;
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
[CanBeNull]
|
||||
public Slider Slider => DrawableSlider?.HitObject;
|
||||
|
||||
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
|
||||
private double animDuration;
|
||||
|
||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
FollowPoint,
|
||||
Cursor,
|
||||
CursorTrail,
|
||||
CursorParticles,
|
||||
SliderScorePoint,
|
||||
ReverseArrow,
|
||||
HitCircleText,
|
||||
@ -18,5 +19,6 @@ namespace osu.Game.Rulesets.Osu
|
||||
SliderBall,
|
||||
SliderBody,
|
||||
SpinnerBody,
|
||||
ApproachCircle,
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
253
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||
}
|
||||
|
||||
private Container circleSprites;
|
||||
private Drawable hitCircleSprite;
|
||||
private Drawable hitCircleOverlay;
|
||||
|
||||
protected Drawable HitCircleOverlay { get; private set; }
|
||||
|
||||
private SkinnableSpriteText hitCircleText;
|
||||
|
||||
@ -70,28 +70,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
|
||||
Texture overlayTexture = getTextureWithFallback("overlay");
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
circleSprites = new Container
|
||||
hitCircleSprite = new KiaiFlashingSprite
|
||||
{
|
||||
Texture = baseTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
HitCircleOverlay = new KiaiFlashingSprite
|
||||
{
|
||||
Texture = overlayTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
hitCircleSprite = new KiaiFlashingSprite
|
||||
{
|
||||
Texture = baseTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
hitCircleOverlay = new KiaiFlashingSprite
|
||||
{
|
||||
Texture = overlayTexture,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -111,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
bool overlayAboveNumber = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
|
||||
|
||||
if (overlayAboveNumber)
|
||||
AddInternal(hitCircleOverlay.CreateProxy());
|
||||
ChangeInternalChildDepth(HitCircleOverlay, float.MinValue);
|
||||
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
@ -153,8 +144,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
circleSprites.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
circleSprites.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||
hitCircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||
|
||||
HitCircleOverlay.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
HitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||
|
||||
if (hasNumber)
|
||||
{
|
||||
|
61
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// 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
|
||||
{
|
||||
[Resolved(canBeNull: true)]
|
||||
private DrawableHitObject drawableHitObject { get; set; }
|
||||
|
||||
private Drawable proxy;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skinSource)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
string lookupName = new OsuSkinComponent(OsuSkinComponents.ReverseArrow).LookupName;
|
||||
|
||||
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -67,7 +67,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
case OsuSkinComponents.SliderHeadHitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece("sliderstartcircle");
|
||||
return new LegacySliderHeadHitCircle();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.ReverseArrow:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyReverseArrow();
|
||||
|
||||
return null;
|
||||
|
||||
@ -89,6 +95,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorParticles:
|
||||
if (GetTexture("star2") != null)
|
||||
return new LegacyCursorParticles();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircleText:
|
||||
if (!this.HasFont(LegacyFont.HitCircle))
|
||||
return null;
|
||||
@ -108,6 +120,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
return new LegacyOldStyleSpinner();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.ApproachCircle:
|
||||
return new LegacyApproachCircle();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
SliderBorder,
|
||||
SliderBall,
|
||||
SpinnerBackground,
|
||||
StarBreakAdditive,
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
@ -42,7 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
InternalChild = fadeContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling)
|
||||
Children = new[]
|
||||
{
|
||||
cursorTrail = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
|
||||
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -115,9 +120,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
(ActiveCursor as OsuCursor)?.Contract();
|
||||
}
|
||||
|
||||
public bool OnPressed(OsuAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
@ -129,9 +134,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(OsuAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
|
@ -24,6 +24,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
[Cached]
|
||||
public class OsuPlayfield : Playfield
|
||||
{
|
||||
private readonly PlayfieldBorder playfieldBorder;
|
||||
|
@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(OsuAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
switch (action)
|
||||
switch (e.Action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(OsuAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -37,6 +38,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
Result.Type = Type;
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
@ -30,6 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss;
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
}
|
||||
}
|
||||
|
@ -36,24 +36,27 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
|
||||
{
|
||||
this.taikoPlayfield = taikoPlayfield;
|
||||
FlashlightSize = new Vector2(0, getSizeFor(0));
|
||||
FlashlightSize = getSizeFor(0);
|
||||
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
private float getSizeFor(int combo)
|
||||
private Vector2 getSizeFor(int combo)
|
||||
{
|
||||
float size = default_flashlight_size;
|
||||
|
||||
if (combo > 200)
|
||||
return default_flashlight_size * 0.8f;
|
||||
size *= 0.8f;
|
||||
else if (combo > 100)
|
||||
return default_flashlight_size * 0.9f;
|
||||
else
|
||||
return default_flashlight_size;
|
||||
size *= 0.9f;
|
||||
|
||||
// Preserve flashlight size through the playfield's aspect adjustment.
|
||||
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
protected override void OnComboChange(ValueChangedEvent<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";
|
||||
@ -64,7 +67,11 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
if (!flashlightProperties.IsValid)
|
||||
{
|
||||
FlashlightPosition = taikoPlayfield.HitTarget.ToSpaceOfOtherDrawable(taikoPlayfield.HitTarget.OriginPosition, this);
|
||||
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
ClearTransforms(targetMember: nameof(FlashlightSize));
|
||||
FlashlightSize = getSizeFor(Combo.Value);
|
||||
|
||||
flashlightProperties.Validate();
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,40 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModHidden : ModHidden
|
||||
public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset<TaikoHitObject>
|
||||
{
|
||||
public override string Description => @"Beats fade out before you hit them!";
|
||||
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)
|
||||
{
|
||||
@ -30,9 +48,9 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
case DrawableDrumRollTick _:
|
||||
case DrawableHit _:
|
||||
double preempt = 10000 / MultiplierAt(hitObject.HitObject);
|
||||
double start = hitObject.HitObject.StartTime - preempt * 0.6;
|
||||
double duration = preempt * 0.3;
|
||||
double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier;
|
||||
double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time;
|
||||
double duration = preempt * fade_out_duration;
|
||||
|
||||
using (hitObject.BeginAbsoluteSequence(start))
|
||||
{
|
||||
@ -48,17 +66,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected double MultiplierAt(HitObject obj)
|
||||
{
|
||||
double beatLength = controlPointInfo.TimingPointAt(obj.StartTime).BeatLength;
|
||||
|
||||
return obj.DifficultyControlPoint.SliderVelocity * TimingControlPoint.DEFAULT_BEAT_LENGTH / beatLength;
|
||||
}
|
||||
|
||||
public override void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
controlPointInfo = beatmap.ControlPointInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody),
|
||||
_ => new ElongatedCirclePiece());
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
|
||||
private void onNewResult(DrawableHitObject obj, JudgementResult result)
|
||||
{
|
||||
@ -196,7 +197,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
@ -61,9 +62,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action)
|
||||
public override bool OnPressed(KeyBindingPressEvent<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);
|
||||
}
|
||||
|
||||
@ -91,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -145,19 +146,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
ApplyResult(r => r.Type = result);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action)
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
if (pressHandledThisFrame)
|
||||
return true;
|
||||
if (Judged)
|
||||
return false;
|
||||
|
||||
validActionPressed = HitActions.Contains(action);
|
||||
validActionPressed = HitActions.Contains(e.Action);
|
||||
|
||||
// Only count this as handled if the new judgement is a hit
|
||||
var result = UpdateResult(true);
|
||||
if (IsHit)
|
||||
HitAction = action;
|
||||
HitAction = e.Action;
|
||||
|
||||
// Regardless of whether we've hit or not, any secondary key presses in the same frame should be discarded
|
||||
// E.g. hitting a non-strong centre as a strong should not fall through and perform a hit on the next note
|
||||
@ -165,11 +166,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void OnReleased(TaikoAction action)
|
||||
public override void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
if (action == HitAction)
|
||||
if (e.Action == HitAction)
|
||||
HitAction = null;
|
||||
base.OnReleased(action);
|
||||
base.OnReleased(e);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action)
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
// Don't process actions until the main hitobject is hit
|
||||
if (!ParentHitObject.IsHit)
|
||||
@ -276,7 +277,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return false;
|
||||
|
||||
// Don't handle invalid hit action presses
|
||||
if (!ParentHitObject.HitActions.Contains(action))
|
||||
if (!ParentHitObject.HitActions.Contains(e.Action))
|
||||
return false;
|
||||
|
||||
return UpdateResult(true);
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||
@ -266,13 +267,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
private bool? lastWasCentre;
|
||||
|
||||
public override bool OnPressed(TaikoAction action)
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
// Don't handle keys before the swell starts
|
||||
if (Time.Current < HitObject.StartTime)
|
||||
return false;
|
||||
|
||||
var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre;
|
||||
var isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre;
|
||||
|
||||
// Ensure alternating centre and rim hits
|
||||
if (lastWasCentre == isCentre)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
|
||||
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
|
||||
_ => new TickPiece());
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Skinning;
|
||||
@ -141,16 +142,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
Centre.Texture = skin.GetTexture(@"taiko-drum-inner");
|
||||
}
|
||||
|
||||
public bool OnPressed(TaikoAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
Drawable target = null;
|
||||
|
||||
if (action == CentreAction)
|
||||
if (e.Action == CentreAction)
|
||||
{
|
||||
target = Centre;
|
||||
sampleTriggerSource.Play(HitType.Centre);
|
||||
}
|
||||
else if (action == RimAction)
|
||||
else if (e.Action == RimAction)
|
||||
{
|
||||
target = Rim;
|
||||
sampleTriggerSource.Play(HitType.Rim);
|
||||
@ -173,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(TaikoAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -16,6 +17,7 @@ using osu.Game.Input.Handlers;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Timing;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
@ -60,6 +62,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y;
|
||||
}
|
||||
|
||||
public MultiplierControlPoint ControlPointAt(double time)
|
||||
{
|
||||
int result = ControlPoints.BinarySearch(new MultiplierControlPoint(time));
|
||||
if (result < 0)
|
||||
result = Math.Clamp(~result - 1, 0, ControlPoints.Count);
|
||||
return ControlPoints[result];
|
||||
}
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -151,19 +152,19 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
[Resolved(canBeNull: true)]
|
||||
private GameplayClock gameplayClock { get; set; }
|
||||
|
||||
public bool OnPressed(TaikoAction action)
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
Drawable target = null;
|
||||
Drawable back = null;
|
||||
|
||||
if (action == CentreAction)
|
||||
if (e.Action == CentreAction)
|
||||
{
|
||||
target = centreHit;
|
||||
back = centre;
|
||||
|
||||
sampleTriggerSource.Play(HitType.Centre);
|
||||
}
|
||||
else if (action == RimAction)
|
||||
else if (e.Action == RimAction)
|
||||
{
|
||||
target = rimHit;
|
||||
back = rim;
|
||||
@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(TaikoAction action)
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
|
||||
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||
}
|
||||
|
@ -424,14 +424,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
checkBeatmapCount(osu, 12);
|
||||
checkSingleReferencedFileCount(osu, 18);
|
||||
|
||||
var breakTemp = TestResources.GetTestBeatmapForImport();
|
||||
var brokenTempFilename = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
MemoryStream brokenOsu = new MemoryStream();
|
||||
MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(breakTemp));
|
||||
MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename));
|
||||
|
||||
File.Delete(breakTemp);
|
||||
File.Delete(brokenTempFilename);
|
||||
|
||||
using (var outStream = File.Open(breakTemp, FileMode.CreateNew))
|
||||
using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
|
||||
using (var zip = ZipArchive.Open(brokenOsz))
|
||||
{
|
||||
zip.AddEntry("broken.osu", brokenOsu, false);
|
||||
@ -441,7 +441,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
||||
try
|
||||
{
|
||||
await manager.Import(new ImportTask(breakTemp));
|
||||
await manager.Import(new ImportTask(brokenTempFilename));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -456,6 +456,8 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
checkSingleReferencedFileCount(osu, 18);
|
||||
|
||||
Assert.AreEqual(1, loggedExceptionCount);
|
||||
|
||||
File.Delete(brokenTempFilename);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Tests.Database
|
||||
@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
keyBindingStore.Register(testContainer);
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
Assert.That(queryCount(), Is.EqualTo(3));
|
||||
|
||||
@ -66,7 +67,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
keyBindingStore.Register(testContainer);
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
using (var primaryUsage = realmContextFactory.GetForRead())
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Mods
|
||||
@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods
|
||||
public class ModSettingsEqualityComparison
|
||||
{
|
||||
[Test]
|
||||
public void Test()
|
||||
public void TestAPIMod()
|
||||
{
|
||||
var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } });
|
||||
var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
|
||||
var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
|
||||
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMod()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
|
||||
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
|
||||
var apiMod1 = new APIMod(mod1);
|
||||
var apiMod2 = new APIMod(mod2);
|
||||
var apiMod3 = new APIMod(mod3);
|
||||
|
||||
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
|
||||
var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
|
||||
var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
|
||||
|
||||
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
|
||||
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod2));
|
||||
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod3));
|
||||
Assert.That(apiMod2, Is.EqualTo(apiMod3));
|
||||
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
|
||||
|
||||
Assert.That(mod3, Is.EqualTo(mod2));
|
||||
Assert.That(apiMod3, Is.EqualTo(apiMod2));
|
||||
Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
||||
assertPlaybackPosition(0);
|
||||
|
||||
AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000);
|
||||
assertPlaybackPosition(-1000);
|
||||
assertPlaybackPosition(0);
|
||||
|
||||
AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500);
|
||||
assertPlaybackPosition(-500);
|
||||
assertPlaybackPosition(0);
|
||||
}
|
||||
|
||||
private void assertPlaybackPosition(double expectedPosition)
|
||||
|
@ -1,9 +1,11 @@
|
||||
// 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.IO;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
|
||||
namespace osu.Game.Tests.Resources
|
||||
{
|
||||
@ -11,6 +13,8 @@ namespace osu.Game.Tests.Resources
|
||||
{
|
||||
public const double QUICK_BEATMAP_LENGTH = 10000;
|
||||
|
||||
private static readonly TemporaryNativeStorage temp_storage = new TemporaryNativeStorage("TestResources");
|
||||
|
||||
public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
|
||||
|
||||
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
|
||||
@ -25,7 +29,7 @@ namespace osu.Game.Tests.Resources
|
||||
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
|
||||
public static string GetQuickTestBeatmapForImport()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName() + ".osz";
|
||||
var tempPath = getTempFilename();
|
||||
using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz"))
|
||||
using (var newFile = File.Create(tempPath))
|
||||
stream.CopyTo(newFile);
|
||||
@ -41,7 +45,7 @@ namespace osu.Game.Tests.Resources
|
||||
/// <returns>A path to a copy of a beatmap archive (osz). Should be deleted after use.</returns>
|
||||
public static string GetTestBeatmapForImport(bool virtualTrack = false)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName() + ".osz";
|
||||
var tempPath = getTempFilename();
|
||||
|
||||
using (var stream = GetTestBeatmapStream(virtualTrack))
|
||||
using (var newFile = File.Create(tempPath))
|
||||
@ -50,5 +54,7 @@ namespace osu.Game.Tests.Resources
|
||||
Assert.IsTrue(File.Exists(tempPath));
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz");
|
||||
}
|
||||
}
|
||||
|
132
osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs
Normal file
@ -0,0 +1,132 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneBlueprintOrdering : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
private EditorBlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<EditorBlueprintContainer>().First();
|
||||
|
||||
[Test]
|
||||
public void TestSelectedObjectHasPriorityWhenOverlapping()
|
||||
{
|
||||
var firstSlider = new Slider
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2()),
|
||||
new PathControlPoint(new Vector2(150, -50)),
|
||||
new PathControlPoint(new Vector2(300, 0))
|
||||
}),
|
||||
Position = new Vector2(0, 100)
|
||||
};
|
||||
var secondSlider = new Slider
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2()),
|
||||
new PathControlPoint(new Vector2(-50, 50)),
|
||||
new PathControlPoint(new Vector2(-100, 100))
|
||||
}),
|
||||
Position = new Vector2(200, 0)
|
||||
};
|
||||
|
||||
AddStep("add overlapping sliders", () =>
|
||||
{
|
||||
EditorBeatmap.Add(firstSlider);
|
||||
EditorBeatmap.Add(secondSlider);
|
||||
});
|
||||
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(firstSlider));
|
||||
|
||||
AddStep("move mouse to common point", () =>
|
||||
{
|
||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||
InputManager.MoveMouseTo(pos);
|
||||
});
|
||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOverlappingObjectsWithSameStartTime()
|
||||
{
|
||||
AddStep("add overlapping circles", () =>
|
||||
{
|
||||
EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2));
|
||||
EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2 + new Vector2(-10, -20)));
|
||||
EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2 + new Vector2(10, -20)));
|
||||
});
|
||||
|
||||
AddStep("click at centre of playfield", () =>
|
||||
{
|
||||
var hitObjectContainer = Editor.ChildrenOfType<HitObjectContainer>().Single();
|
||||
var centre = hitObjectContainer.ToScreenSpace(OsuPlayfield.BASE_SIZE / 2);
|
||||
InputManager.MoveMouseTo(centre);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("frontmost object selected", () =>
|
||||
{
|
||||
var hasCombo = Editor.ChildrenOfType<HitCircleSelectionBlueprint>().Single(b => b.IsSelected).Item as IHasComboInformation;
|
||||
return hasCombo?.IndexInCurrentCombo == 0;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlacementOfConcurrentObjectWithDuration()
|
||||
{
|
||||
AddStep("seek to timing point", () => EditorClock.Seek(2170));
|
||||
AddStep("add hit circle", () => EditorBeatmap.Add(createHitCircle(2170, Vector2.Zero)));
|
||||
|
||||
AddStep("choose spinner placement tool", () =>
|
||||
{
|
||||
InputManager.Key(Key.Number4);
|
||||
var hitObjectContainer = Editor.ChildrenOfType<HitObjectContainer>().Single();
|
||||
InputManager.MoveMouseTo(hitObjectContainer.ScreenSpaceDrawQuad.Centre);
|
||||
});
|
||||
|
||||
AddStep("begin placing spinner", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddStep("end placing spinner", () =>
|
||||
{
|
||||
EditorClock.Seek(2500);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("two timeline blueprints present", () => Editor.ChildrenOfType<TimelineHitObjectBlueprint>().Count() == 2);
|
||||
}
|
||||
|
||||
private HitCircle createHitCircle(double startTime, Vector2 position) => new HitCircle
|
||||
{
|
||||
StartTime = startTime,
|
||||
Position = position,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,70 +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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneBlueprintSelection : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
private EditorBlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<EditorBlueprintContainer>().First();
|
||||
|
||||
[Test]
|
||||
public void TestSelectedObjectHasPriorityWhenOverlapping()
|
||||
{
|
||||
var firstSlider = new Slider
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2()),
|
||||
new PathControlPoint(new Vector2(150, -50)),
|
||||
new PathControlPoint(new Vector2(300, 0))
|
||||
}),
|
||||
Position = new Vector2(0, 100)
|
||||
};
|
||||
var secondSlider = new Slider
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(new Vector2()),
|
||||
new PathControlPoint(new Vector2(-50, 50)),
|
||||
new PathControlPoint(new Vector2(-100, 100))
|
||||
}),
|
||||
Position = new Vector2(200, 0)
|
||||
};
|
||||
|
||||
AddStep("add overlapping sliders", () =>
|
||||
{
|
||||
EditorBeatmap.Add(firstSlider);
|
||||
EditorBeatmap.Add(secondSlider);
|
||||
});
|
||||
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(firstSlider));
|
||||
|
||||
AddStep("move mouse to common point", () =>
|
||||
{
|
||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||
InputManager.MoveMouseTo(pos);
|
||||
});
|
||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider);
|
||||
}
|
||||
}
|
||||
}
|