mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 02:02:53 +08:00
Merge branch 'master' into bubble_mod_implementation_clean
This commit is contained in:
commit
b2b9f2a036
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,17 +13,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
||||||
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
||||||
- name: Install .NET 3.1.x LTS
|
- name: Install .NET 3.1.x LTS
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "3.1.x"
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -77,10 +77,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ jobs:
|
|||||||
# Attempt to upload results even if test fails.
|
# Attempt to upload results even if test fails.
|
||||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
@ -106,10 +106,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -125,10 +125,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
|
18
.github/workflows/diffcalc.yml
vendored
18
.github/workflows/diffcalc.yml
vendored
@ -48,8 +48,8 @@ jobs:
|
|||||||
CONTINUE="no"
|
CONTINUE="no"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "::set-output name=continue::${CONTINUE}"
|
echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=matrix::${MATRIX_JSON}"
|
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
|
||||||
diffcalc:
|
diffcalc:
|
||||||
name: Run
|
name: Run
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
@ -80,34 +80,34 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
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 "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||||
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')"
|
echo "repo=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Checkout osu
|
# Checkout osu
|
||||||
- name: Checkout osu (master)
|
- name: Checkout osu (master)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'master/osu'
|
path: 'master/osu'
|
||||||
- name: Checkout osu (pr)
|
- name: Checkout osu (pr)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'pr/osu'
|
path: 'pr/osu'
|
||||||
repository: ${{ steps.upstreambranch.outputs.repo }}
|
repository: ${{ steps.upstreambranch.outputs.repo }}
|
||||||
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
||||||
|
|
||||||
- name: Checkout osu-difficulty-calculator (master)
|
- name: Checkout osu-difficulty-calculator (master)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-difficulty-calculator
|
repository: ppy/osu-difficulty-calculator
|
||||||
path: 'master/osu-difficulty-calculator'
|
path: 'master/osu-difficulty-calculator'
|
||||||
- name: Checkout osu-difficulty-calculator (pr)
|
- name: Checkout osu-difficulty-calculator (pr)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-difficulty-calculator
|
repository: ppy/osu-difficulty-calculator
|
||||||
path: 'pr/osu-difficulty-calculator'
|
path: 'pr/osu-difficulty-calculator'
|
||||||
|
|
||||||
- name: Install .NET 5.0.x
|
- name: Install .NET 5.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "5.0.x"
|
dotnet-version: "5.0.x"
|
||||||
|
|
||||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ When it comes to contributing to the project, the two main things you can do to
|
|||||||
|
|
||||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||||
|
|
||||||
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
|
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.314.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.418.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -139,7 +138,17 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f =>
|
||||||
|
{
|
||||||
|
// on macOS, URL associations are handled via SDL_DROPFILE events.
|
||||||
|
if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
HandleLink(f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDrop(new[] { f });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||||
@ -151,10 +160,6 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
lock (importableFiles)
|
lock (importableFiles)
|
||||||
{
|
{
|
||||||
string firstExtension = Path.GetExtension(filePaths.First());
|
|
||||||
|
|
||||||
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
|
||||||
|
|
||||||
importableFiles.AddRange(filePaths);
|
importableFiles.AddRange(filePaths);
|
||||||
|
|
||||||
Logger.Log($"Adding {filePaths.Length} files for import");
|
Logger.Log($"Adding {filePaths.Length} files for import");
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
||||||
RightSideToolboxContainer.Alpha = 0;
|
|
||||||
DistanceSpacingMultiplier.Disabled = true;
|
DistanceSpacingMultiplier.Disabled = true;
|
||||||
|
|
||||||
LayerBelowRuleset.Add(new PlayfieldBorder
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModDaycore : ModDaycore
|
public class CatchModDaycore : ModDaycore
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
ExtendedMaxValue = 11,
|
ExtendedMaxValue = 11,
|
||||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||||
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
ExtendedMaxValue = 11,
|
ExtendedMaxValue = 11,
|
||||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModDoubleTime : ModDoubleTime
|
public class CatchModDoubleTime : ModDoubleTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModHalfTime : ModHalfTime
|
public class CatchModHalfTime : ModHalfTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
Size = new Vector2(BASE_SIZE);
|
Size = new Vector2(BASE_SIZE);
|
||||||
|
|
||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
@ -333,8 +334,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
||||||
|
|
||||||
body.Scale = scaleFromDirection;
|
body.Scale = scaleFromDirection;
|
||||||
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
// Inverse of catcher scale is applied here, as catcher gets scaled by circle size and so do the incoming fruit.
|
||||||
|
caughtObjectContainer.Scale = (1 / Scale.X) * (flipCatcherPlate ? scaleFromDirection : Vector2.One);
|
||||||
|
hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||||
|
|
||||||
// Correct overshooting.
|
// Correct overshooting.
|
||||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||||
@ -414,10 +418,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private void clearPlate(DroppedObjectAnimation animation)
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
|
var caughtObjects = caughtObjectContainer.Children.ToArray();
|
||||||
|
|
||||||
caughtObjectContainer.Clear(false);
|
caughtObjectContainer.Clear(false);
|
||||||
|
|
||||||
|
// Use the already returned PoolableDrawables for new objects
|
||||||
|
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
|
||||||
|
|
||||||
droppedObjectTarget.AddRange(droppedObjects);
|
droppedObjectTarget.AddRange(droppedObjects);
|
||||||
|
|
||||||
foreach (var droppedObject in droppedObjects)
|
foreach (var droppedObject in droppedObjects)
|
||||||
@ -426,10 +433,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var droppedObject = getDroppedObject(caughtObject);
|
|
||||||
|
|
||||||
caughtObjectContainer.Remove(caughtObject, false);
|
caughtObjectContainer.Remove(caughtObject, false);
|
||||||
|
|
||||||
|
var droppedObject = getDroppedObject(caughtObject);
|
||||||
|
|
||||||
droppedObjectTarget.Add(droppedObject);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
applyDropAnimation(droppedObject, animation);
|
applyDropAnimation(droppedObject, animation);
|
||||||
@ -452,6 +459,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define lifetime start for dropped objects to be disposed correctly when rewinding replay
|
||||||
|
d.LifetimeStart = Clock.CurrentTime;
|
||||||
d.Expire();
|
d.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -10,5 +13,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public partial class TestSceneManiaPlayer : PlayerTestScene
|
public partial class TestSceneManiaPlayer : PlayerTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("change direction to down", () => changeDirectionTo(ManiaScrollingDirection.Down));
|
||||||
|
AddStep("change direction to up", () => changeDirectionTo(ManiaScrollingDirection.Up));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeDirectionTo(ManiaScrollingDirection direction)
|
||||||
|
{
|
||||||
|
var rulesetConfig = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(new ManiaRuleset()).AsNonNull();
|
||||||
|
rulesetConfig.SetValue(ManiaRulesetSetting.ScrollDirection, direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModDaycore : ModDaycore
|
public class ManiaModDaycore : ModDaycore
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModDoubleTime : ModDoubleTime
|
public class ManiaModDoubleTime : ModDoubleTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModHalfTime : ModHalfTime
|
public class ManiaModHalfTime : ModHalfTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
|
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||||
|
// The rationale for this is account for heads/tails with corner radius.
|
||||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail()
|
public DrawableHoldNoteTail()
|
||||||
: this(null)
|
: this(null)
|
||||||
|
@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
largeFaint = new Container
|
largeFaint = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
@ -80,11 +79,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
if (direction.NewValue == ScrollingDirection.Up)
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
|
largeFaint.Anchor = Anchor.TopCentre;
|
||||||
|
largeFaint.Origin = Anchor.TopCentre;
|
||||||
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
|
largeFaint.Anchor = Anchor.BottomCentre;
|
||||||
|
largeFaint.Origin = Anchor.BottomCentre;
|
||||||
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||||
{
|
{
|
||||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
|
||||||
|
|
||||||
private Drawable background = null!;
|
private Drawable background = null!;
|
||||||
private Box foreground = null!;
|
private ArgonHoldNoteHittingLayer hittingLayer = null!;
|
||||||
|
|
||||||
public ArgonHoldBodyPiece()
|
public ArgonHoldBodyPiece()
|
||||||
{
|
{
|
||||||
@ -32,7 +31,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
// Without this, the width of the body will be slightly larger than the head/tail.
|
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -41,12 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
foreground = new Box
|
hittingLayer = new ArgonHoldNoteHittingLayer()
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (drawableObject != null)
|
if (drawableObject != null)
|
||||||
@ -54,44 +47,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
var holdNote = (DrawableHoldNote)drawableObject;
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
AccentColour.BindTo(holdNote.AccentColour);
|
AccentColour.BindTo(holdNote.AccentColour);
|
||||||
IsHitting.BindTo(holdNote.IsHitting);
|
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
background.Colour = colour.NewValue.Darken(1.2f);
|
background.Colour = colour.NewValue.Darken(0.6f);
|
||||||
foreground.Colour = colour.NewValue.Opacity(0.2f);
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
IsHitting.BindValueChanged(hitting =>
|
|
||||||
{
|
|
||||||
const float animation_length = 50;
|
|
||||||
|
|
||||||
foreground.ClearTransforms();
|
|
||||||
|
|
||||||
if (hitting.NewValue)
|
|
||||||
{
|
|
||||||
// wait for the next sync point
|
|
||||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
|
||||||
|
|
||||||
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
|
||||||
{
|
|
||||||
foreground.FadeTo(1, animation_length).Then()
|
|
||||||
.FadeTo(0.5f, animation_length)
|
|
||||||
.Loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreground.FadeOut(animation_length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Recycle()
|
public void Recycle()
|
||||||
{
|
{
|
||||||
foreground.ClearTransforms();
|
hittingLayer.Recycle();
|
||||||
foreground.Alpha = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
internal partial class ArgonHoldNoteHeadPiece : ArgonNotePiece
|
||||||
|
{
|
||||||
|
protected override Drawable CreateIcon() => new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 2,
|
||||||
|
Size = new Vector2(20, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using Box = osu.Framework.Graphics.Shapes.Box;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public partial class ArgonHoldNoteHittingLayer : Box
|
||||||
|
{
|
||||||
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
public ArgonHoldNoteHittingLayer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AccentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
Colour = colour.NewValue.Lighten(0.2f).Opacity(0.3f);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
IsHitting.BindValueChanged(hitting =>
|
||||||
|
{
|
||||||
|
const float animation_length = 80;
|
||||||
|
|
||||||
|
ClearTransforms();
|
||||||
|
|
||||||
|
if (hitting.NewValue)
|
||||||
|
{
|
||||||
|
// wait for the next sync point
|
||||||
|
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(synchronisedOffset))
|
||||||
|
{
|
||||||
|
this.FadeTo(1, animation_length, Easing.OutSine).Then()
|
||||||
|
.FadeTo(0.5f, animation_length, Easing.InSine)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeOut(animation_length);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -16,47 +18,68 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject? drawableObject { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box shadeBackground;
|
private readonly Box foreground;
|
||||||
private readonly Box shadeForeground;
|
private readonly ArgonHoldNoteHittingLayer hittingLayer;
|
||||||
|
private readonly Box foregroundAdditive;
|
||||||
|
|
||||||
public ArgonHoldNoteTailPiece()
|
public ArgonHoldNoteTailPiece()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
Height = ArgonNotePiece.NOTE_HEIGHT,
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeForeground = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black),
|
||||||
|
// Avoid ugly single pixel overlap.
|
||||||
|
Height = 0.9f,
|
||||||
},
|
},
|
||||||
},
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
foreground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
hittingLayer = new ArgonHoldNoteHittingLayer(),
|
||||||
|
foregroundAdditive = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Height = 0.5f,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
@ -65,9 +88,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
accentColour.BindValueChanged(onAccentChanged, true);
|
accentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
|
||||||
|
drawableObject.HitObjectApplied += hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hitObjectApplied(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
var holdNoteTail = (DrawableHoldNoteTail)drawableHitObject;
|
||||||
|
|
||||||
|
hittingLayer.Recycle();
|
||||||
|
|
||||||
|
hittingLayer.AccentColour.UnbindBindings();
|
||||||
|
hittingLayer.AccentColour.BindTo(holdNoteTail.HoldNote.AccentColour);
|
||||||
|
|
||||||
|
hittingLayer.IsHitting.UnbindBindings();
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
{
|
{
|
||||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||||
@ -75,8 +113,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
{
|
{
|
||||||
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
|
foreground.Colour = accent.NewValue.Darken(0.6f); // matches body
|
||||||
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
|
|
||||||
|
foregroundAdditive.Colour = ColourInfo.GradientVertical(
|
||||||
|
accent.NewValue.Opacity(0.4f),
|
||||||
|
accent.NewValue.Opacity(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
drawableObject.HitObjectApplied -= hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box colouredBox;
|
private readonly Box colouredBox;
|
||||||
private readonly Box shadow;
|
|
||||||
|
|
||||||
public ArgonNotePiece()
|
public ArgonNotePiece()
|
||||||
{
|
{
|
||||||
@ -36,11 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
CornerRadius = CORNER_RADIUS;
|
CornerRadius = CORNER_RADIUS;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
shadow = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black)
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -65,18 +65,22 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = CORNER_RADIUS * 2,
|
Height = CORNER_RADIUS * 2,
|
||||||
},
|
},
|
||||||
new SpriteIcon
|
CreateIcon(),
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Y = 4,
|
|
||||||
Icon = FontAwesome.Solid.AngleDown,
|
|
||||||
Size = new Vector2(20),
|
|
||||||
Scale = new Vector2(1, 0.7f)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual Drawable CreateIcon() => new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 4,
|
||||||
|
// TODO: replace with a non-squashed version.
|
||||||
|
// The 0.7f height scale should be removed.
|
||||||
|
Icon = FontAwesome.Solid.AngleDown,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
Scale = new Vector2(1, 0.7f)
|
||||||
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||||
{
|
{
|
||||||
@ -105,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
accent.NewValue.Lighten(0.1f),
|
accent.NewValue.Lighten(0.1f),
|
||||||
accent.NewValue
|
accent.NewValue
|
||||||
);
|
);
|
||||||
|
|
||||||
shadow.Colour = accent.NewValue.Darken(0.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return new ArgonHoldNoteTailPiece();
|
return new ArgonHoldNoteTailPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.HoldNoteHead:
|
case ManiaSkinComponents.HoldNoteHead:
|
||||||
|
return new ArgonHoldNoteHeadPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.Note:
|
case ManiaSkinComponents.Note:
|
||||||
return new ArgonNotePiece();
|
return new ArgonNotePiece();
|
||||||
|
|
||||||
@ -69,12 +71,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Color4 colour_special_column = new Color4(169, 106, 255, 255);
|
||||||
|
|
||||||
|
private const int total_colours = 6;
|
||||||
|
|
||||||
|
private static readonly Color4 colour_yellow = new Color4(255, 197, 40, 255);
|
||||||
|
private static readonly Color4 colour_orange = new Color4(252, 109, 1, 255);
|
||||||
|
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||||
|
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||||
|
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||||
|
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||||
|
|
||||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
{
|
{
|
||||||
int column = maniaLookup.ColumnIndex ?? 0;
|
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
||||||
var stage = beatmap.GetStageForColumnIndex(column);
|
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
||||||
|
|
||||||
switch (maniaLookup.Lookup)
|
switch (maniaLookup.Lookup)
|
||||||
{
|
{
|
||||||
@ -87,53 +100,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||||
return SkinUtils.As<TValue>(new Bindable<float>(
|
return SkinUtils.As<TValue>(new Bindable<float>(
|
||||||
stage.IsSpecialColumn(column) ? 120 : 60
|
stage.IsSpecialColumn(columnIndex) ? 120 : 60
|
||||||
));
|
));
|
||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
|
||||||
Color4 colour;
|
var colour = getColourForLayout(columnIndex, stage);
|
||||||
|
|
||||||
const int total_colours = 7;
|
|
||||||
|
|
||||||
if (stage.IsSpecialColumn(column))
|
|
||||||
colour = new Color4(159, 101, 255, 255);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (column % total_colours)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
colour = new Color4(240, 216, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
colour = new Color4(240, 101, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
colour = new Color4(240, 0, 130, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
colour = new Color4(192, 0, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
colour = new Color4(0, 96, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
colour = new Color4(0, 226, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
colour = new Color4(0, 240, 96, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||||
}
|
}
|
||||||
@ -141,5 +113,203 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
return base.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Color4 getColourForLayout(int columnIndex, StageDefinition stage)
|
||||||
|
{
|
||||||
|
// Account for cases like dual-stage (assume that all stages have the same column count for now).
|
||||||
|
columnIndex %= stage.Columns;
|
||||||
|
|
||||||
|
// For now, these are defined per column count as per https://user-images.githubusercontent.com/50823728/218038463-b450f46c-ef21-4551-b133-f866be59970c.png
|
||||||
|
// See https://github.com/ppy/osu/discussions/21996 for discussion.
|
||||||
|
switch (stage.Columns)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return colour_yellow;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_green;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_green;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_cyan;
|
||||||
|
|
||||||
|
case 4: return colour_purple;
|
||||||
|
|
||||||
|
case 5: return colour_pink;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_special_column;
|
||||||
|
|
||||||
|
case 4: return colour_green;
|
||||||
|
|
||||||
|
case 5: return colour_cyan;
|
||||||
|
|
||||||
|
case 6: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_yellow;
|
||||||
|
|
||||||
|
case 5: return colour_orange;
|
||||||
|
|
||||||
|
case 6: return colour_pink;
|
||||||
|
|
||||||
|
case 7: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_special_column;
|
||||||
|
|
||||||
|
case 5: return colour_yellow;
|
||||||
|
|
||||||
|
case 6: return colour_orange;
|
||||||
|
|
||||||
|
case 7: return colour_pink;
|
||||||
|
|
||||||
|
case 8: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
case 6: return colour_yellow;
|
||||||
|
|
||||||
|
case 7: return colour_orange;
|
||||||
|
|
||||||
|
case 8: return colour_pink;
|
||||||
|
|
||||||
|
case 9: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback for unhandled scenarios
|
||||||
|
|
||||||
|
if (stage.IsSpecialColumn(columnIndex))
|
||||||
|
return colour_special_column;
|
||||||
|
|
||||||
|
switch (columnIndex % total_colours)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -8,6 +8,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -18,6 +19,7 @@ using osu.Framework.Testing.Input;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable background;
|
private Drawable background;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> ripples = new Bindable<bool>();
|
||||||
|
|
||||||
public TestSceneGameplayCursor()
|
public TestSceneGameplayCursor()
|
||||||
{
|
{
|
||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
@ -57,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddToggleStep("ripples", v => ripples.Value = v);
|
||||||
|
|
||||||
AddSliderStep("circle size", 0f, 10f, 0f, val =>
|
AddSliderStep("circle size", 0f, 10f, 0f, val =>
|
||||||
{
|
{
|
||||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||||
@ -67,6 +73,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("test cursor container", () => loadContent(false));
|
AddStep("test cursor container", () => loadContent(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var rulesetConfig = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||||
|
rulesetConfig.BindWith(OsuRulesetSetting.ShowCursorRipples, ripples);
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(1, 1)]
|
[TestCase(1, 1)]
|
||||||
[TestCase(5, 1)]
|
[TestCase(5, 1)]
|
||||||
[TestCase(10, 1)]
|
[TestCase(10, 1)]
|
||||||
|
@ -21,7 +21,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; } = null!;
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
private TestActionKeyCounter leftKeyCounter = null!;
|
private DefaultKeyCounter leftKeyCounter = null!;
|
||||||
|
|
||||||
private TestActionKeyCounter rightKeyCounter = null!;
|
private DefaultKeyCounter rightKeyCounter = null!;
|
||||||
|
|
||||||
private OsuInputManager osuInputManager = null!;
|
private OsuInputManager osuInputManager = null!;
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
|
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Depth = float.MinValue,
|
Depth = float.MinValue,
|
||||||
X = -100,
|
X = -100,
|
||||||
},
|
},
|
||||||
rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
|
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
assertKeyCounter(1, 1);
|
assertKeyCounter(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPositionalTrackingAfterLongDistanceTravelled()
|
||||||
|
{
|
||||||
|
// When a single touch has already travelled enough distance on screen, it should remain as the positional
|
||||||
|
// tracking touch until released (unless a direct touch occurs).
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 0);
|
||||||
|
checkPressed(OsuAction.LeftButton);
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// cover some distance
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch2);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 1);
|
||||||
|
checkNotPressed(OsuAction.LeftButton);
|
||||||
|
checkPressed(OsuAction.RightButton);
|
||||||
|
// in this case, touch 2 should not become the positional tracking touch.
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// even if the second touch moves on the screen, the original tracking touch is retained.
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||||
{
|
{
|
||||||
@ -562,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private void assertKeyCounter(int left, int right)
|
private void assertKeyCounter(int left, int right)
|
||||||
{
|
{
|
||||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
|
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
|
||||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
|
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseAllTouches()
|
private void releaseAllTouches()
|
||||||
@ -579,11 +615,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
||||||
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
||||||
|
|
||||||
public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler<OsuAction>
|
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
|
||||||
{
|
{
|
||||||
public OsuAction Action { get; }
|
public OsuAction Action { get; }
|
||||||
|
|
||||||
public TestActionKeyCounter(OsuAction action)
|
public TestActionKeyCounterTrigger(OsuAction action)
|
||||||
: base(action.ToString())
|
: base(action.ToString())
|
||||||
{
|
{
|
||||||
Action = action;
|
Action = action;
|
||||||
@ -593,8 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
if (e.Action == Action)
|
if (e.Action == Action)
|
||||||
{
|
{
|
||||||
IsLit = true;
|
Activate();
|
||||||
Increment();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -602,7 +637,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
{
|
{
|
||||||
if (e.Action == Action) IsLit = false;
|
if (e.Action == Action)
|
||||||
|
Deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
|||||||
SetDefault(OsuRulesetSetting.SnakingInSliders, true);
|
SetDefault(OsuRulesetSetting.SnakingInSliders, true);
|
||||||
SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
|
SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
|
||||||
SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
|
SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
|
||||||
|
SetDefault(OsuRulesetSetting.ShowCursorRipples, false);
|
||||||
SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
|
SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
|||||||
SnakingInSliders,
|
SnakingInSliders,
|
||||||
SnakingOutSliders,
|
SnakingOutSliders,
|
||||||
ShowCursorTrail,
|
ShowCursorTrail,
|
||||||
|
ShowCursorRipples,
|
||||||
PlayfieldBorderStyle,
|
PlayfieldBorderStyle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|||||||
protected override bool AlwaysShowWhenSelected => true;
|
protected override bool AlwaysShowWhenSelected => true;
|
||||||
|
|
||||||
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
||||||
|| (ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
|| (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
||||||
|
|
||||||
protected OsuSelectionBlueprint(T hitObject)
|
protected OsuSelectionBlueprint(T hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModDaycore : ModDaycore
|
public class OsuModDaycore : ModDaycore
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModDoubleTime : ModDoubleTime
|
public class OsuModDoubleTime : ModDoubleTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModHalfTime : ModHalfTime
|
public class OsuModHalfTime : ModHalfTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
Cursor,
|
Cursor,
|
||||||
CursorTrail,
|
CursorTrail,
|
||||||
CursorParticles,
|
CursorParticles,
|
||||||
|
CursorRipple,
|
||||||
SliderScorePoint,
|
SliderScorePoint,
|
||||||
ReverseArrow,
|
ReverseArrow,
|
||||||
HitCircleText,
|
HitCircleText,
|
||||||
|
@ -100,6 +100,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorRipple:
|
||||||
|
if (GetTexture("cursor-ripple") != null)
|
||||||
|
{
|
||||||
|
var ripple = this.GetAnimation("cursor-ripple", false, false);
|
||||||
|
|
||||||
|
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
|
||||||
|
// If anyone complains about these not being applied, this can be uncommented.
|
||||||
|
//
|
||||||
|
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
|
||||||
|
// so we might be okay.
|
||||||
|
//
|
||||||
|
// if (ripple != null)
|
||||||
|
// {
|
||||||
|
// ripple.Scale = new Vector2(0.5f);
|
||||||
|
// ripple.Alpha = 0.2f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ripple;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.CursorParticles:
|
case OsuSkinComponents.CursorParticles:
|
||||||
if (GetTexture("star2") != null)
|
if (GetTexture("star2") != null)
|
||||||
return new LegacyCursorParticles();
|
return new LegacyCursorParticles();
|
||||||
|
@ -252,13 +252,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
renderer.SetBlend(BlendingParameters.Additive);
|
renderer.SetBlend(BlendingParameters.Additive);
|
||||||
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||||
|
|
||||||
TextureShader.Bind();
|
BindTextureShader(renderer);
|
||||||
|
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
for (int i = 0; i < points.Count; i++)
|
for (int i = 0; i < points.Count; i++)
|
||||||
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
||||||
|
|
||||||
TextureShader.Unbind();
|
UnbindTextureShader(renderer);
|
||||||
renderer.PopLocalMatrix();
|
renderer.PopLocalMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs
Normal file
105
osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||||
|
{
|
||||||
|
public partial class CursorRippleVisualiser : CompositeDrawable, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private readonly Bindable<bool> showRipples = new Bindable<bool>(true);
|
||||||
|
|
||||||
|
private readonly DrawablePool<CursorRipple> ripplePool = new DrawablePool<CursorRipple>(20);
|
||||||
|
|
||||||
|
public CursorRippleVisualiser()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 CursorScale { get; set; } = Vector2.One;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(OsuRulesetConfigManager? rulesetConfig)
|
||||||
|
{
|
||||||
|
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorRipples, showRipples);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
if (showRipples.Value)
|
||||||
|
{
|
||||||
|
AddInternal(ripplePool.Get(r =>
|
||||||
|
{
|
||||||
|
r.Position = e.MousePosition;
|
||||||
|
r.Scale = CursorScale;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class CursorRipple : PoolableDrawable
|
||||||
|
{
|
||||||
|
private Drawable ripple = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChild = ripple = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple())
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareForUse()
|
||||||
|
{
|
||||||
|
base.PrepareForUse();
|
||||||
|
|
||||||
|
ClearTransforms(true);
|
||||||
|
|
||||||
|
ripple.ScaleTo(0.1f)
|
||||||
|
.ScaleTo(1, 700, Easing.Out);
|
||||||
|
|
||||||
|
this
|
||||||
|
.FadeOutFromOne(700)
|
||||||
|
.Expire(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class DefaultCursorRipple : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new RingPiece(3)
|
||||||
|
{
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2),
|
||||||
|
Alpha = 0.1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private Bindable<float> userCursorScale;
|
private Bindable<float> userCursorScale;
|
||||||
private Bindable<bool> autoCursorScale;
|
private Bindable<bool> autoCursorScale;
|
||||||
|
|
||||||
|
private readonly CursorRippleVisualiser rippleVisualiser;
|
||||||
|
|
||||||
public OsuCursorContainer()
|
public OsuCursorContainer()
|
||||||
{
|
{
|
||||||
InternalChild = fadeContainer = new Container
|
InternalChild = fadeContainer = new Container
|
||||||
@ -48,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
|
cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
|
||||||
|
rippleVisualiser = new CursorRippleVisualiser(),
|
||||||
new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
|
new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -82,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
var newScale = new Vector2(e.NewValue);
|
var newScale = new Vector2(e.NewValue);
|
||||||
|
|
||||||
ActiveCursor.Scale = newScale;
|
ActiveCursor.Scale = newScale;
|
||||||
|
rippleVisualiser.CursorScale = newScale;
|
||||||
cursorTrail.Scale = newScale;
|
cursorTrail.Scale = newScale;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
LabelText = RulesetSettingsStrings.CursorTrail,
|
LabelText = RulesetSettingsStrings.CursorTrail,
|
||||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||||
},
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = RulesetSettingsStrings.CursorRipples,
|
||||||
|
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples)
|
||||||
|
},
|
||||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||||
{
|
{
|
||||||
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||||
|
@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance (in local pixels) that a touch must move before being considered a permanent tracking touch.
|
||||||
|
/// After this distance is covered, any extra touches on the screen will be considered as button inputs, unless
|
||||||
|
/// a new touch directly interacts with a hit circle.
|
||||||
|
/// </summary>
|
||||||
|
private const float distance_before_position_tracking_lock_in = 100;
|
||||||
|
|
||||||
private TrackedTouch? positionTrackingTouch;
|
private TrackedTouch? positionTrackingTouch;
|
||||||
|
|
||||||
private readonly OsuInputManager osuInputManager;
|
private readonly OsuInputManager osuInputManager;
|
||||||
@ -97,26 +104,32 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
|
// ..or if the current position tracking touch was not a direct touch (and didn't travel across the screen too far).
|
||||||
if (!positionTrackingTouch.DirectTouch)
|
if (!positionTrackingTouch.DirectTouch && positionTrackingTouch.DistanceTravelled < distance_before_position_tracking_lock_in)
|
||||||
{
|
{
|
||||||
positionTrackingTouch = newTouch;
|
positionTrackingTouch = newTouch;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
||||||
// If it was a direct touch and still has its action pressed, that action should be released.
|
// If it still has its action pressed, that action should be released.
|
||||||
//
|
//
|
||||||
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
||||||
if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
|
if (positionTrackingTouch.Action is OsuAction touchAction)
|
||||||
{
|
{
|
||||||
osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
|
osuInputManager.KeyBindingContainer.TriggerReleased(touchAction);
|
||||||
positionTrackingTouch.Action = null;
|
positionTrackingTouch.Action = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTouchMovement(TouchEvent touchEvent)
|
private void handleTouchMovement(TouchEvent touchEvent)
|
||||||
{
|
{
|
||||||
|
if (touchEvent is TouchMoveEvent moveEvent)
|
||||||
|
{
|
||||||
|
var trackedTouch = trackedTouches.Single(t => t.Source == touchEvent.Touch.Source);
|
||||||
|
trackedTouch.DistanceTravelled += moveEvent.Delta.Length;
|
||||||
|
}
|
||||||
|
|
||||||
// Movement should only be tracked for the most recent touch.
|
// Movement should only be tracked for the most recent touch.
|
||||||
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
||||||
return;
|
return;
|
||||||
@ -148,8 +161,16 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public OsuAction? Action;
|
public OsuAction? Action;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the touch was on a hit circle receptor.
|
||||||
|
/// </summary>
|
||||||
public readonly bool DirectTouch;
|
public readonly bool DirectTouch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total distance on screen travelled by this touch (in local pixels).
|
||||||
|
/// </summary>
|
||||||
|
public float DistanceTravelled;
|
||||||
|
|
||||||
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
|
@ -1,17 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -35,20 +33,11 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
switch (e.Button)
|
if (e.Button != MouseButton.Left)
|
||||||
{
|
return false;
|
||||||
case MouseButton.Left:
|
|
||||||
HitObject.Type = HitType.Centre;
|
|
||||||
EndPlacement(true);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MouseButton.Right:
|
EndPlacement(true);
|
||||||
HitObject.Type = HitType.Rim;
|
return true;
|
||||||
EndPlacement(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||||
drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false;
|
drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false;
|
||||||
|
|
||||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||||
playfield.ClassicHitTargetPosition.Value = true;
|
playfield.ClassicHitTargetPosition.Value = true;
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public class TaikoModDaycore : ModDaycore
|
public class TaikoModDaycore : ModDaycore
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public class TaikoModDoubleTime : ModDoubleTime
|
public class TaikoModDoubleTime : ModDoubleTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public class TaikoModHalfTime : ModHalfTime
|
public class TaikoModHalfTime : ModHalfTime
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public class TaikoModNightcore : ModNightcore<TaikoHitObject>
|
public class TaikoModNightcore : ModNightcore<TaikoHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
public new BindableDouble TimeRange => base.TimeRange;
|
public new BindableDouble TimeRange => base.TimeRange;
|
||||||
|
|
||||||
public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
|
public readonly BindableBool LockPlayfieldAspectRange = new BindableBool(true);
|
||||||
|
|
||||||
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
||||||
|
|
||||||
@ -69,7 +69,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
const float scroll_rate = 10;
|
const float scroll_rate = 10;
|
||||||
|
|
||||||
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
||||||
float ratio = DrawHeight / 480;
|
// Width is used because it defines how many notes fit on the playfield.
|
||||||
|
// We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default.
|
||||||
|
float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT);
|
||||||
|
|
||||||
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
||||||
}
|
}
|
||||||
@ -92,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
LockPlayfieldMaxAspect = { BindTarget = LockPlayfieldMaxAspect }
|
LockPlayfieldAspectRange = { BindTarget = LockPlayfieldAspectRange }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||||
|
@ -11,9 +11,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
||||||
private const float default_aspect = 16f / 9f;
|
|
||||||
|
|
||||||
public readonly IBindable<bool> LockPlayfieldMaxAspect = new BindableBool(true);
|
public const float MAXIMUM_ASPECT = 16f / 9f;
|
||||||
|
public const float MINIMUM_ASPECT = 5f / 4f;
|
||||||
|
|
||||||
|
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -26,12 +28,22 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
//
|
//
|
||||||
// As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
|
// As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
|
||||||
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
||||||
if (LockPlayfieldMaxAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect)
|
if (LockPlayfieldAspectRange.Value)
|
||||||
height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
|
{
|
||||||
|
float currentAspect = Parent.ChildSize.X / Parent.ChildSize.Y;
|
||||||
|
|
||||||
|
if (currentAspect > MAXIMUM_ASPECT)
|
||||||
|
height *= currentAspect / MAXIMUM_ASPECT;
|
||||||
|
else if (currentAspect < MINIMUM_ASPECT)
|
||||||
|
height *= currentAspect / MINIMUM_ASPECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
|
||||||
|
height = Math.Min(height, 1f / 3f);
|
||||||
Height = height;
|
Height = height;
|
||||||
|
|
||||||
// Position the taiko playfield exactly one playfield from the top of the screen.
|
// Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it.
|
||||||
|
// Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered.
|
||||||
RelativePositionAxes = Axes.Y;
|
RelativePositionAxes = Axes.Y;
|
||||||
Y = height;
|
Y = height;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.iOS
|
namespace osu.Game.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +9,7 @@ namespace osu.Game.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -161,6 +160,51 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithLowercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-lowercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.Decode(stream);
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithUppercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-uppercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.Decode(stream);
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeImageSpecifiedAsVideo()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("image-specified-as-video.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.Decode(stream);
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeBeatmapTimingPoints()
|
public void TestDecodeBeatmapTimingPoints()
|
||||||
{
|
{
|
||||||
@ -320,6 +364,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
var comboColors = decoder.Decode(stream).ComboColours;
|
var comboColors = decoder.Decode(stream).ComboColours;
|
||||||
|
|
||||||
|
Debug.Assert(comboColors != null);
|
||||||
|
|
||||||
Color4[] expectedColors =
|
Color4[] expectedColors =
|
||||||
{
|
{
|
||||||
new Color4(142, 199, 255, 255),
|
new Color4(142, 199, 255, 255),
|
||||||
@ -330,7 +376,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
new Color4(255, 177, 140, 255),
|
new Color4(255, 177, 140, 255),
|
||||||
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
|
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
|
||||||
};
|
};
|
||||||
Assert.AreEqual(expectedColors.Length, comboColors?.Count);
|
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||||
for (int i = 0; i < expectedColors.Length; i++)
|
for (int i = 0; i < expectedColors.Length; i++)
|
||||||
Assert.AreEqual(expectedColors[i], comboColors[i]);
|
Assert.AreEqual(expectedColors[i], comboColors[i]);
|
||||||
}
|
}
|
||||||
@ -415,14 +461,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
Assert.IsNotNull(positionData);
|
Assert.IsNotNull(positionData);
|
||||||
Assert.IsNotNull(curveData);
|
Assert.IsNotNull(curveData);
|
||||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
Assert.AreEqual(new Vector2(192, 168), positionData!.Position);
|
||||||
Assert.AreEqual(956, hitObjects[0].StartTime);
|
Assert.AreEqual(956, hitObjects[0].StartTime);
|
||||||
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||||
|
|
||||||
positionData = hitObjects[1] as IHasPosition;
|
positionData = hitObjects[1] as IHasPosition;
|
||||||
|
|
||||||
Assert.IsNotNull(positionData);
|
Assert.IsNotNull(positionData);
|
||||||
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
Assert.AreEqual(new Vector2(304, 56), positionData!.Position);
|
||||||
Assert.AreEqual(1285, hitObjects[1].StartTime);
|
Assert.AreEqual(1285, hitObjects[1].StartTime);
|
||||||
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||||
}
|
}
|
||||||
@ -578,8 +624,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFallbackDecoderForCorruptedHeader()
|
public void TestFallbackDecoderForCorruptedHeader()
|
||||||
{
|
{
|
||||||
Decoder<Beatmap> decoder = null;
|
Decoder<Beatmap> decoder = null!;
|
||||||
Beatmap beatmap = null;
|
Beatmap beatmap = null!;
|
||||||
|
|
||||||
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
@ -596,8 +642,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestFallbackDecoderForMissingHeader()
|
public void TestFallbackDecoderForMissingHeader()
|
||||||
{
|
{
|
||||||
Decoder<Beatmap> decoder = null;
|
Decoder<Beatmap> decoder = null!;
|
||||||
Beatmap beatmap = null;
|
Beatmap beatmap = null!;
|
||||||
|
|
||||||
using (var resStream = TestResources.OpenResource("missing-header.osu"))
|
using (var resStream = TestResources.OpenResource("missing-header.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
@ -614,8 +660,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeFileWithEmptyLinesAtStart()
|
public void TestDecodeFileWithEmptyLinesAtStart()
|
||||||
{
|
{
|
||||||
Decoder<Beatmap> decoder = null;
|
Decoder<Beatmap> decoder = null!;
|
||||||
Beatmap beatmap = null;
|
Beatmap beatmap = null!;
|
||||||
|
|
||||||
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
|
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
@ -632,8 +678,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeFileWithEmptyLinesAndNoHeader()
|
public void TestDecodeFileWithEmptyLinesAndNoHeader()
|
||||||
{
|
{
|
||||||
Decoder<Beatmap> decoder = null;
|
Decoder<Beatmap> decoder = null!;
|
||||||
Beatmap beatmap = null;
|
Beatmap beatmap = null!;
|
||||||
|
|
||||||
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
|
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
@ -650,8 +696,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeFileWithContentImmediatelyAfterHeader()
|
public void TestDecodeFileWithContentImmediatelyAfterHeader()
|
||||||
{
|
{
|
||||||
Decoder<Beatmap> decoder = null;
|
Decoder<Beatmap> decoder = null!;
|
||||||
Beatmap beatmap = null;
|
Beatmap beatmap = null!;
|
||||||
|
|
||||||
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
|
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
@ -678,7 +724,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestAllowFallbackDecoderOverwrite()
|
public void TestAllowFallbackDecoderOverwrite()
|
||||||
{
|
{
|
||||||
Decoder<Beatmap> decoder = null;
|
Decoder<Beatmap> decoder = null!;
|
||||||
|
|
||||||
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -30,35 +28,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.IsTrue(storyboard.HasDrawable);
|
Assert.IsTrue(storyboard.HasDrawable);
|
||||||
Assert.AreEqual(6, storyboard.Layers.Count());
|
Assert.AreEqual(6, storyboard.Layers.Count());
|
||||||
|
|
||||||
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
|
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||||
Assert.IsNotNull(background);
|
Assert.IsNotNull(background);
|
||||||
Assert.AreEqual(16, background.Elements.Count);
|
Assert.AreEqual(16, background.Elements.Count);
|
||||||
Assert.IsTrue(background.VisibleWhenFailing);
|
Assert.IsTrue(background.VisibleWhenFailing);
|
||||||
Assert.IsTrue(background.VisibleWhenPassing);
|
Assert.IsTrue(background.VisibleWhenPassing);
|
||||||
Assert.AreEqual("Background", background.Name);
|
Assert.AreEqual("Background", background.Name);
|
||||||
|
|
||||||
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
|
StoryboardLayer fail = storyboard.Layers.Single(l => l.Depth == 2);
|
||||||
Assert.IsNotNull(fail);
|
Assert.IsNotNull(fail);
|
||||||
Assert.AreEqual(0, fail.Elements.Count);
|
Assert.AreEqual(0, fail.Elements.Count);
|
||||||
Assert.IsTrue(fail.VisibleWhenFailing);
|
Assert.IsTrue(fail.VisibleWhenFailing);
|
||||||
Assert.IsFalse(fail.VisibleWhenPassing);
|
Assert.IsFalse(fail.VisibleWhenPassing);
|
||||||
Assert.AreEqual("Fail", fail.Name);
|
Assert.AreEqual("Fail", fail.Name);
|
||||||
|
|
||||||
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
|
StoryboardLayer pass = storyboard.Layers.Single(l => l.Depth == 1);
|
||||||
Assert.IsNotNull(pass);
|
Assert.IsNotNull(pass);
|
||||||
Assert.AreEqual(0, pass.Elements.Count);
|
Assert.AreEqual(0, pass.Elements.Count);
|
||||||
Assert.IsFalse(pass.VisibleWhenFailing);
|
Assert.IsFalse(pass.VisibleWhenFailing);
|
||||||
Assert.IsTrue(pass.VisibleWhenPassing);
|
Assert.IsTrue(pass.VisibleWhenPassing);
|
||||||
Assert.AreEqual("Pass", pass.Name);
|
Assert.AreEqual("Pass", pass.Name);
|
||||||
|
|
||||||
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
|
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0);
|
||||||
Assert.IsNotNull(foreground);
|
Assert.IsNotNull(foreground);
|
||||||
Assert.AreEqual(151, foreground.Elements.Count);
|
Assert.AreEqual(151, foreground.Elements.Count);
|
||||||
Assert.IsTrue(foreground.VisibleWhenFailing);
|
Assert.IsTrue(foreground.VisibleWhenFailing);
|
||||||
Assert.IsTrue(foreground.VisibleWhenPassing);
|
Assert.IsTrue(foreground.VisibleWhenPassing);
|
||||||
Assert.AreEqual("Foreground", foreground.Name);
|
Assert.AreEqual("Foreground", foreground.Name);
|
||||||
|
|
||||||
StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue);
|
StoryboardLayer overlay = storyboard.Layers.Single(l => l.Depth == int.MinValue);
|
||||||
Assert.IsNotNull(overlay);
|
Assert.IsNotNull(overlay);
|
||||||
Assert.IsEmpty(overlay.Elements);
|
Assert.IsEmpty(overlay.Elements);
|
||||||
Assert.IsTrue(overlay.VisibleWhenFailing);
|
Assert.IsTrue(overlay.VisibleWhenFailing);
|
||||||
@ -76,7 +74,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
|
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
|
||||||
Assert.NotNull(sprite);
|
Assert.NotNull(sprite);
|
||||||
Assert.IsTrue(sprite.HasCommands);
|
Assert.IsTrue(sprite!.HasCommands);
|
||||||
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
|
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
|
||||||
Assert.IsTrue(sprite.IsDrawable);
|
Assert.IsTrue(sprite.IsDrawable);
|
||||||
Assert.AreEqual(Anchor.Centre, sprite.Origin);
|
Assert.AreEqual(Anchor.Centre, sprite.Origin);
|
||||||
@ -97,6 +95,27 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLoopWithoutExplicitFadeOut()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("animation-loop-no-explicit-end-time.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||||
|
Assert.AreEqual(1, background.Elements.Count);
|
||||||
|
|
||||||
|
Assert.AreEqual(2000, background.Elements[0].StartTime);
|
||||||
|
Assert.AreEqual(2000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||||
|
|
||||||
|
Assert.AreEqual(3000, (background.Elements[0] as StoryboardAnimation)?.GetEndTime());
|
||||||
|
Assert.AreEqual(12000, (background.Elements[0] as StoryboardAnimation)?.EndTimeForDisplay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCorrectAnimationStartTime()
|
public void TestCorrectAnimationStartTime()
|
||||||
{
|
{
|
||||||
@ -171,6 +190,55 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithLowercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-lowercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
|
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
Assert.AreEqual("Video.avi", ((StoryboardVideo)video.Elements[0]).Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeVideoWithUppercaseExtension()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("video-with-uppercase-extension.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
|
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
Assert.AreEqual("Video.AVI", ((StoryboardVideo)video.Elements[0]).Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeImageSpecifiedAsVideo()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("image-specified-as-video.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||||
|
Assert.That(video.Elements.Count, Is.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeOutOfRangeLoopAnimationType()
|
public void TestDecodeOutOfRangeLoopAnimationType()
|
||||||
{
|
{
|
||||||
|
@ -176,6 +176,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
},
|
},
|
||||||
BeatmapInfo = beatmap,
|
BeatmapInfo = beatmap,
|
||||||
|
BeatmapHash = beatmap.Hash,
|
||||||
Ruleset = beatmap.Ruleset,
|
Ruleset = beatmap.Ruleset,
|
||||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
[Events]
|
||||||
|
//Storyboard Layer 0 (Background)
|
||||||
|
Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever
|
||||||
|
F,0,2000,,0,1
|
||||||
|
L,2000,10
|
||||||
|
F,18,0,1000,1,0
|
4
osu.Game.Tests/Resources/image-specified-as-video.osb
Normal file
4
osu.Game.Tests/Resources/image-specified-as-video.osb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Events]
|
||||||
|
Video,0,"BG.jpg",0,0
|
@ -0,0 +1,5 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Events]
|
||||||
|
0,0,"BG.jpg",0,0
|
||||||
|
Video,0,"Video.avi",0,0
|
@ -0,0 +1,5 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Events]
|
||||||
|
0,0,"BG.jpg",0,0
|
||||||
|
Video,0,"Video.AVI",0,0
|
@ -164,7 +164,7 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
this.parentManager = parentManager;
|
this.parentManager = parentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override byte[] LoadRaw(string name) => parentManager.LoadRaw(name);
|
public override byte[] GetRawData(string fileName) => parentManager.GetRawData(fileName);
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -51,9 +49,11 @@ namespace osu.Game.Tests.Testing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRetrieveShader()
|
public void TestRetrieveShader()
|
||||||
{
|
{
|
||||||
AddAssert("ruleset shaders retrieved", () =>
|
AddStep("ruleset shaders retrieved without error", () =>
|
||||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs") != null &&
|
{
|
||||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs") != null);
|
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestVertex.vs");
|
||||||
|
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestFragment.fs");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -76,12 +76,12 @@ namespace osu.Game.Tests.Testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
|
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
|
||||||
|
|
||||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null;
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestRulesetConfigManager : IRulesetConfigManager
|
private class TestRulesetConfigManager : IRulesetConfigManager
|
||||||
|
@ -311,6 +311,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
public bool IsDrawable => true;
|
public bool IsDrawable => true;
|
||||||
public double StartTime => double.MinValue;
|
public double StartTime => double.MinValue;
|
||||||
public double EndTime => double.MaxValue;
|
public double EndTime => double.MaxValue;
|
||||||
|
public double EndTimeForDisplay => double.MaxValue;
|
||||||
|
|
||||||
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
|
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
|
||||||
}
|
}
|
||||||
|
@ -100,8 +100,10 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
|
|
||||||
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
protected override void BindUniformResources(IShader shader, IRenderer renderer)
|
||||||
{
|
{
|
||||||
|
base.BindUniformResources(shader, renderer);
|
||||||
|
|
||||||
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
borderDataBuffer.Data = borderDataBuffer.Data with
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
{
|
{
|
||||||
@ -109,9 +111,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
TexelSize = texelSize
|
TexelSize = texelSize
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureShader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
shader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||||
|
|
||||||
base.Draw(renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public partial class TestSceneLocallyModifyingOnlineBeatmaps : EditorSavingTestScene
|
||||||
|
{
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
CreateInitialBeatmap = () =>
|
||||||
|
{
|
||||||
|
var importedSet = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely();
|
||||||
|
return Game.BeatmapManager.GetWorkingBeatmap(importedSet!.Value.Beatmaps.First());
|
||||||
|
};
|
||||||
|
|
||||||
|
base.SetUpSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocallyModifyingOnlineBeatmap()
|
||||||
|
{
|
||||||
|
AddAssert("editor beatmap has online ID", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.GreaterThan(0));
|
||||||
|
|
||||||
|
AddStep("delete first hitobject", () => EditorBeatmap.RemoveAt(0));
|
||||||
|
SaveEditor();
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
AddAssert("editor beatmap online ID reset", () => EditorBeatmap.BeatmapInfo.OnlineID, () => Is.EqualTo(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2));
|
||||||
|
|
||||||
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
||||||
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
|
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value);
|
||||||
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
||||||
|
|
||||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
||||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||||
|
|
||||||
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
||||||
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
||||||
|
@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
addSeekStep(3000);
|
addSeekStep(3000);
|
||||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15);
|
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15);
|
||||||
AddStep("clear results", () => Player.Results.Clear());
|
AddStep("clear results", () => Player.Results.Clear());
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||||
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
|
|
||||||
scoreProcessor.Combo.Value = 1;
|
scoreProcessor.Combo.Value = 1;
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -17,43 +19,63 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public TestSceneKeyCounter()
|
public TestSceneKeyCounter()
|
||||||
{
|
{
|
||||||
KeyCounterKeyboard testCounter;
|
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay
|
||||||
|
|
||||||
KeyCounterDisplay kc = new KeyCounterDisplay
|
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Children = new KeyCounter[]
|
Position = new Vector2(0, 72.7f)
|
||||||
{
|
|
||||||
testCounter = new KeyCounterKeyboard(Key.X),
|
|
||||||
new KeyCounterKeyboard(Key.X),
|
|
||||||
new KeyCounterMouse(MouseButton.Left),
|
|
||||||
new KeyCounterMouse(MouseButton.Right),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Position = new Vector2(0, -72.7f)
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultDisplay.AddRange(new InputTrigger[]
|
||||||
|
{
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||||
|
});
|
||||||
|
|
||||||
|
argonDisplay.AddRange(new InputTrigger[]
|
||||||
|
{
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterKeyboardTrigger(Key.X),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||||
|
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||||
|
});
|
||||||
|
|
||||||
|
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
|
||||||
|
|
||||||
AddStep("Add random", () =>
|
AddStep("Add random", () =>
|
||||||
{
|
{
|
||||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||||
kc.Add(new KeyCounterKeyboard(key));
|
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||||
|
argonDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||||
});
|
});
|
||||||
|
|
||||||
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
|
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key;
|
||||||
|
|
||||||
void addPressKeyStep()
|
addPressKeyStep();
|
||||||
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
||||||
|
addPressKeyStep();
|
||||||
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
||||||
|
AddStep("Disable counting", () =>
|
||||||
{
|
{
|
||||||
AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
argonDisplay.IsCounting.Value = false;
|
||||||
}
|
defaultDisplay.IsCounting.Value = false;
|
||||||
|
});
|
||||||
|
addPressKeyStep();
|
||||||
|
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
||||||
|
|
||||||
addPressKeyStep();
|
Add(defaultDisplay);
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
|
Add(argonDisplay);
|
||||||
addPressKeyStep();
|
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
|
|
||||||
AddStep("Disable counting", () => testCounter.IsCounting = false);
|
|
||||||
addPressKeyStep();
|
|
||||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
|
|
||||||
|
|
||||||
Add(kc);
|
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private SessionStatics sessionStatics { get; set; }
|
private SessionStatics sessionStatics { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
[Cached(typeof(INotificationOverlay))]
|
[Cached(typeof(INotificationOverlay))]
|
||||||
private readonly NotificationOverlay notificationOverlay;
|
private readonly NotificationOverlay notificationOverlay;
|
||||||
|
|
||||||
@ -317,6 +320,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
saveVolumes();
|
saveVolumes();
|
||||||
setFullVolume();
|
setFullVolume();
|
||||||
|
|
||||||
|
AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true));
|
||||||
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
||||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
@ -333,12 +337,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
restoreVolumes();
|
restoreVolumes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEpilepsyWarningWithDisabledStoryboard()
|
||||||
|
{
|
||||||
|
saveVolumes();
|
||||||
|
setFullVolume();
|
||||||
|
|
||||||
|
AddStep("disable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, false));
|
||||||
|
AddStep("change epilepsy warning", () => epilepsyWarning = true);
|
||||||
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddUntilStep("epilepsy warning absent", () => getWarning() == null);
|
||||||
|
|
||||||
|
restoreVolumes();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEpilepsyWarningEarlyExit()
|
public void TestEpilepsyWarningEarlyExit()
|
||||||
{
|
{
|
||||||
saveVolumes();
|
saveVolumes();
|
||||||
setFullVolume();
|
setFullVolume();
|
||||||
|
|
||||||
|
AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true));
|
||||||
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
||||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
@ -449,7 +471,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("click notification", () => notification.TriggerClick());
|
AddStep("click notification", () => notification.TriggerClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault();
|
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault(w => w.IsAlive);
|
||||||
|
|
||||||
private partial class TestPlayerLoader : PlayerLoader
|
private partial class TestPlayerLoader : PlayerLoader
|
||||||
{
|
{
|
||||||
|
@ -164,6 +164,36 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRevertNestedObjects()
|
||||||
|
{
|
||||||
|
ManualClock clock = null;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
beatmap.HitObjects.Add(new TestHitObjectWithNested { Duration = 40 });
|
||||||
|
|
||||||
|
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
||||||
|
|
||||||
|
AddStep("skip to middle of object", () => clock.CurrentTime = (beatmap.HitObjects[0].StartTime + beatmap.HitObjects[0].GetEndTime()) / 2);
|
||||||
|
AddAssert("2 objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(2));
|
||||||
|
|
||||||
|
AddStep("skip to before end of object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() - 1);
|
||||||
|
AddAssert("3 objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
DrawableHitObject drawableHitObject = null;
|
||||||
|
HashSet<HitObject> revertedHitObjects = new HashSet<HitObject>();
|
||||||
|
|
||||||
|
AddStep("retrieve drawable hit object", () => drawableHitObject = playfield.ChildrenOfType<DrawableTestHitObjectWithNested>().Single());
|
||||||
|
AddStep("set up revert tracking", () =>
|
||||||
|
{
|
||||||
|
revertedHitObjects.Clear();
|
||||||
|
drawableHitObject.OnRevertResult += (ho, _) => revertedHitObjects.Add(ho.HitObject);
|
||||||
|
});
|
||||||
|
AddStep("skip back to object start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime);
|
||||||
|
AddAssert("3 reverts fired", () => revertedHitObjects, () => Has.Count.EqualTo(3));
|
||||||
|
AddAssert("no objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestApplyHitResultOnKilled()
|
public void TestApplyHitResultOnKilled()
|
||||||
{
|
{
|
||||||
@ -258,6 +288,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
||||||
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
|
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
|
||||||
|
RegisterPool<TestHitObjectWithNested, DrawableTestHitObjectWithNested>(poolSize);
|
||||||
|
RegisterPool<NestedHitObject, DrawableNestedHitObject>(poolSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||||
@ -388,6 +420,120 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestHitObjectWithNested : TestHitObject
|
||||||
|
{
|
||||||
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
AddNested(new NestedHitObject { StartTime = (float)Duration * (i + 1) / 4 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NestedHitObject : ConvertHitObject
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class DrawableTestHitObjectWithNested : DrawableHitObject<TestHitObjectWithNested>
|
||||||
|
{
|
||||||
|
private Container nestedContainer;
|
||||||
|
|
||||||
|
public DrawableTestHitObjectWithNested()
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Colour4.Red
|
||||||
|
},
|
||||||
|
nestedContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
Size = new Vector2(200, 50);
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
base.AddNestedHitObject(hitObject);
|
||||||
|
nestedContainer.Add(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ClearNestedHitObjects()
|
||||||
|
{
|
||||||
|
base.ClearNestedHitObjects();
|
||||||
|
nestedContainer.Clear(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
{
|
||||||
|
base.CheckForResult(userTriggered, timeOffset);
|
||||||
|
if (timeOffset >= 0)
|
||||||
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class DrawableNestedHitObject : DrawableHitObject<NestedHitObject>
|
||||||
|
{
|
||||||
|
public DrawableNestedHitObject()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableNestedHitObject(NestedHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
Size = new Vector2(15);
|
||||||
|
Colour = Colour4.White;
|
||||||
|
RelativePositionAxes = Axes.Both;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
X = (float)((HitObject.StartTime - ParentHitObject!.HitObject.StartTime) / (ParentHitObject.HitObject.GetEndTime() - ParentHitObject.HitObject.StartTime));
|
||||||
|
Y = 0.5f;
|
||||||
|
|
||||||
|
LifetimeStart = ParentHitObject.LifetimeStart;
|
||||||
|
LifetimeEnd = ParentHitObject.LifetimeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
{
|
||||||
|
base.CheckForResult(userTriggered, timeOffset);
|
||||||
|
if (timeOffset >= 0)
|
||||||
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0));
|
||||||
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -45,6 +46,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDoesNotFailOnExit()
|
||||||
|
{
|
||||||
|
loadPlayerWithBeatmap();
|
||||||
|
|
||||||
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
|
AddAssert("ensure rank is not fail", () => Player.ScoreProcessor.Rank.Value, () => Is.Not.EqualTo(ScoreRank.F));
|
||||||
|
AddStep("exit player", () => Player.Exit());
|
||||||
|
AddUntilStep("wait for exit", () => Player.Parent == null);
|
||||||
|
AddAssert("ensure rank is not fail", () => Player.ScoreProcessor.Rank.Value, () => Is.Not.EqualTo(ScoreRank.F));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPauseViaSpaceWithSkip()
|
public void TestPauseViaSpaceWithSkip()
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,8 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Skinning.Components;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -52,6 +54,134 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragSelection()
|
||||||
|
{
|
||||||
|
BigBlackBox box1 = null!;
|
||||||
|
BigBlackBox box2 = null!;
|
||||||
|
BigBlackBox box3 = null!;
|
||||||
|
|
||||||
|
AddStep("Add big black boxes", () =>
|
||||||
|
{
|
||||||
|
var target = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
|
target.Add(box1 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Position = new Vector2(-90),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
target.Add(box2 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
target.Add(box3 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Position = new Vector2(90),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// This step is specifically added to reproduce an edge case which was found during cyclic selection development.
|
||||||
|
// If everything is working as expected it should not affect the subsequent drag selections.
|
||||||
|
AddRepeatStep("Select top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft + new Vector2(box1.ScreenSpaceDrawQuad.Width / 8));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
}, 2);
|
||||||
|
|
||||||
|
AddStep("Begin drag top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Drag to bottom right", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Release button", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("First two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box1, box2 }));
|
||||||
|
|
||||||
|
AddStep("Begin drag bottom right", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.BottomRight + new Vector2(box3.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Drag to top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Release button", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Last two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||||
|
|
||||||
|
// Test cyclic selection doesn't trigger in this state.
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Last two boxes still selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCyclicSelection()
|
||||||
|
{
|
||||||
|
SkinBlueprint[] blueprints = null!;
|
||||||
|
|
||||||
|
AddStep("Add big black boxes", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Three black boxes added", () => targetContainer.Components.OfType<BigBlackBox>().Count(), () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("Store black box blueprints", () =>
|
||||||
|
{
|
||||||
|
blueprints = skinEditor.ChildrenOfType<SkinBlueprint>().Where(b => b.Item is BigBlackBox).ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||||
|
|
||||||
|
AddStep("move cursor to black box", () =>
|
||||||
|
{
|
||||||
|
// Slightly offset from centre to avoid random failures (see https://github.com/ppy/osu-framework/issues/5669).
|
||||||
|
InputManager.MoveMouseTo(((Drawable)blueprints[0].Item).ScreenSpaceDrawQuad.Centre + new Vector2(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 2", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item));
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 3", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item));
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||||
|
|
||||||
|
AddStep("select all boxes", () =>
|
||||||
|
{
|
||||||
|
skinEditor.SelectedComponents.Clear();
|
||||||
|
skinEditor.SelectedComponents.AddRange(targetContainer.Components.OfType<BigBlackBox>().Skip(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all boxes selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestBringToFront(bool alterSelectionOrder)
|
public void TestBringToFront(bool alterSelectionOrder)
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
scoreProcessor.Combo.Value = 1;
|
scoreProcessor.Combo.Value = 1;
|
||||||
|
|
||||||
return new Container
|
return new Container
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Tests.Gameplay;
|
using osu.Game.Tests.Gameplay;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
// best way to check without exposing.
|
// best way to check without exposing.
|
||||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestComboCounterIncrementing()
|
public void TestComboCounterIncrementing()
|
||||||
@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||||
|
|
||||||
// Add any key just to display the key counter visually.
|
// Add any key just to display the key counter visually.
|
||||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
|
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||||
|
|
||||||
action?.Invoke(hudOverlay);
|
action?.Invoke(hudOverlay);
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -22,8 +20,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public partial class TestSceneSkinnableSound : OsuTestScene
|
public partial class TestSceneSkinnableSound : OsuTestScene
|
||||||
{
|
{
|
||||||
private TestSkinSourceContainer skinSource;
|
private TestSkinSourceContainer skinSource = null!;
|
||||||
private PausableSkinnableSound skinnableSound;
|
private PausableSkinnableSound skinnableSound = null!;
|
||||||
|
|
||||||
|
private const string sample_lookup = "Gameplay/normal-sliderslide";
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
};
|
};
|
||||||
|
|
||||||
// has to be added after the hierarchy above else the `ISkinSource` dependency won't be cached.
|
// has to be added after the hierarchy above else the `ISkinSource` dependency won't be cached.
|
||||||
skinSource.Add(skinnableSound = new PausableSkinnableSound(new SampleInfo("Gameplay/normal-sliderslide")));
|
skinSource.Add(skinnableSound = new PausableSkinnableSound(new SampleInfo(sample_lookup)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,10 +99,28 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("sample not playing", () => !skinnableSound.IsPlaying);
|
AddAssert("sample not playing", () => !skinnableSound.IsPlaying);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSampleUpdatedBeforePlaybackWhenNotPresent()
|
||||||
|
{
|
||||||
|
AddStep("make sample non-present", () => skinnableSound.Hide());
|
||||||
|
AddUntilStep("ensure not present", () => skinnableSound.IsPresent, () => Is.False);
|
||||||
|
|
||||||
|
AddUntilStep("ensure sample loaded", () => skinnableSound.ChildrenOfType<DrawableSample>().Single().Name, () => Is.EqualTo(sample_lookup));
|
||||||
|
|
||||||
|
AddStep("change source", () =>
|
||||||
|
{
|
||||||
|
skinSource.OverridingSample = new SampleVirtual("new skin");
|
||||||
|
skinSource.TriggerSourceChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start sample", () => skinnableSound.Play());
|
||||||
|
AddUntilStep("sample updated", () => skinnableSound.ChildrenOfType<DrawableSample>().Single().Name, () => Is.EqualTo("new skin"));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSkinChangeDoesntPlayOnPause()
|
public void TestSkinChangeDoesntPlayOnPause()
|
||||||
{
|
{
|
||||||
DrawableSample sample = null;
|
DrawableSample? sample = null;
|
||||||
AddStep("start sample", () =>
|
AddStep("start sample", () =>
|
||||||
{
|
{
|
||||||
skinnableSound.Play();
|
skinnableSound.Play();
|
||||||
@ -118,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddAssert("retrieve and ensure current sample is different", () =>
|
AddAssert("retrieve and ensure current sample is different", () =>
|
||||||
{
|
{
|
||||||
DrawableSample oldSample = sample;
|
DrawableSample? oldSample = sample;
|
||||||
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
|
sample = skinnableSound.ChildrenOfType<DrawableSample>().Single();
|
||||||
return sample != oldSample;
|
return sample != oldSample;
|
||||||
});
|
});
|
||||||
@ -134,20 +152,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private partial class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
|
private partial class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ISkinSource source { get; set; }
|
private ISkinSource source { get; set; } = null!;
|
||||||
|
|
||||||
public event Action SourceChanged;
|
public event Action? SourceChanged;
|
||||||
|
|
||||||
public Bindable<bool> SamplePlaybackDisabled { get; } = new Bindable<bool>();
|
public Bindable<bool> SamplePlaybackDisabled { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
|
public ISample? OverridingSample;
|
||||||
|
|
||||||
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => SamplePlaybackDisabled;
|
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => SamplePlaybackDisabled;
|
||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => source?.GetDrawableComponent(lookup);
|
public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => source.GetDrawableComponent(lookup);
|
||||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
|
public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
|
public ISample? GetSample(ISampleInfo sampleInfo) => OverridingSample ?? source.GetSample(sampleInfo);
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source?.GetConfig<TLookup, TValue>(lookup);
|
|
||||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction);
|
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
public IEnumerable<ISkin> AllSources => new[] { this }.Concat(source?.AllSources ?? Enumerable.Empty<ISkin>());
|
where TLookup : notnull
|
||||||
|
where TValue : notnull
|
||||||
|
{
|
||||||
|
return source.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISkin? FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : source.FindProvider(lookupFunction);
|
||||||
|
public IEnumerable<ISkin> AllSources => new[] { this }.Concat(source.AllSources);
|
||||||
|
|
||||||
public void TriggerSourceChanged()
|
public void TriggerSourceChanged()
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
@ -106,6 +107,26 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveFailedReplayWithStoryboardEndedDoesNotProgress()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("fail on first judgement", () => currentFailConditions = (_, _) => true);
|
||||||
|
AddStep("set storyboard duration to 0s", () => currentStoryboardDuration = 0);
|
||||||
|
});
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
|
|
||||||
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddUntilStep("wait for button clickable", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().Enabled.Value);
|
||||||
|
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
|
||||||
|
|
||||||
|
// Test a regression where importing the fail replay would cause progression to results screen in a failed state.
|
||||||
|
AddWaitStep("wait some", 10);
|
||||||
|
AddAssert("player is still current screen", () => Player.IsCurrentScreen());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestShowResultsFalse()
|
public void TestShowResultsFalse()
|
||||||
{
|
{
|
||||||
|
@ -114,6 +114,19 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(OverlayActivation.All)]
|
||||||
|
[TestCase(OverlayActivation.Disabled)]
|
||||||
|
public void TestButtonKeyboardInputRespectsOverlayActivation(OverlayActivation mode)
|
||||||
|
{
|
||||||
|
AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode);
|
||||||
|
AddStep("hide toolbar", () => toolbar.Hide());
|
||||||
|
|
||||||
|
if (mode == OverlayActivation.Disabled)
|
||||||
|
AddAssert("check buttons not accepting input", () => InputManager.NonPositionalInputQueue.OfType<ToolbarButton>().Count(), () => Is.Zero);
|
||||||
|
else
|
||||||
|
AddAssert("check buttons accepting input", () => InputManager.NonPositionalInputQueue.OfType<ToolbarButton>().Count(), () => Is.Not.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(OverlayActivation.All)]
|
[TestCase(OverlayActivation.All)]
|
||||||
[TestCase(OverlayActivation.Disabled)]
|
[TestCase(OverlayActivation.Disabled)]
|
||||||
public void TestRespectsOverlayActivation(OverlayActivation mode)
|
public void TestRespectsOverlayActivation(OverlayActivation mode)
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
// 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.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
|
{
|
||||||
|
public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When entering the editor, a new beatmap is created as part of the asynchronous load process.
|
||||||
|
/// This test ensures that in the case of an early exit from the editor (ie. while it's still loading)
|
||||||
|
/// doesn't leave a dangling beatmap behind.
|
||||||
|
///
|
||||||
|
/// This may not fail 100% due to timing, but has a pretty high chance of hitting a failure so works well enough
|
||||||
|
/// as a test.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCancelNavigationToEditor()
|
||||||
|
{
|
||||||
|
BeatmapSetInfo[] beatmapSets = null!;
|
||||||
|
|
||||||
|
AddStep("Fetch initial beatmaps", () => beatmapSets = allBeatmapSets());
|
||||||
|
|
||||||
|
AddStep("Set current beatmap to default", () => Game.Beatmap.SetDefault());
|
||||||
|
|
||||||
|
AddStep("Push editor loader", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
|
AddUntilStep("Wait for loader current", () => Game.ScreenStack.CurrentScreen is EditorLoader);
|
||||||
|
AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit());
|
||||||
|
|
||||||
|
AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
|
AddAssert("Check no new beatmaps were made", () => allBeatmapSets().SequenceEqual(beatmapSets));
|
||||||
|
|
||||||
|
BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All<BeatmapSetInfo>().Where(x => !x.DeletePending).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Overlays.Settings.Sections.Input;
|
using osu.Game.Overlays.Settings.Sections.Input;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -69,10 +70,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
|
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
|
||||||
|
|
||||||
AddStep("press 'z'", () => InputManager.Key(Key.Z));
|
AddStep("press 'z'", () => InputManager.Key(Key.Z));
|
||||||
AddAssert("key counter didn't increase", () => keyCounter.CountPresses == 0);
|
AddAssert("key counter didn't increase", () => keyCounter.CountPresses.Value == 0);
|
||||||
|
|
||||||
AddStep("press 's'", () => InputManager.Key(Key.S));
|
AddStep("press 's'", () => InputManager.Key(Key.S));
|
||||||
AddAssert("key counter did increase", () => keyCounter.CountPresses == 1);
|
AddAssert("key counter did increase", () => keyCounter.CountPresses.Value == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel
|
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel
|
||||||
|
@ -14,6 +14,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -26,7 +27,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapSetOverlay : OsuTestScene
|
public partial class TestSceneBeatmapSetOverlay : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly TestBeatmapSetOverlay overlay;
|
private readonly TestBeatmapSetOverlay overlay;
|
||||||
|
|
||||||
@ -281,6 +282,22 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert(@"type is correct", () => type == lookupType.ToString());
|
AddAssert(@"type is correct", () => type == lookupType.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapSetWithGuestDifficulty()
|
||||||
|
{
|
||||||
|
AddStep("show map", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty()));
|
||||||
|
AddStep("move mouse to host difficulty", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(0));
|
||||||
|
});
|
||||||
|
AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType<BeatmapPicker>().Single().ChildrenOfType<OsuSpriteText>().All(s => s.Text != "BanchoBot"));
|
||||||
|
AddStep("move mouse to guest difficulty", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(overlay.ChildrenOfType<DifficultyIcon>().ElementAt(1));
|
||||||
|
});
|
||||||
|
AddAssert("guest mapper information shown", () => overlay.ChildrenOfType<BeatmapPicker>().Single().ChildrenOfType<OsuSpriteText>().Any(s => s.Text == "BanchoBot"));
|
||||||
|
}
|
||||||
|
|
||||||
private APIBeatmapSet createManyDifficultiesBeatmapSet()
|
private APIBeatmapSet createManyDifficultiesBeatmapSet()
|
||||||
{
|
{
|
||||||
var set = getBeatmapSet();
|
var set = getBeatmapSet();
|
||||||
@ -320,6 +337,60 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
return beatmapSet;
|
return beatmapSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private APIBeatmapSet createBeatmapSetWithGuestDifficulty()
|
||||||
|
{
|
||||||
|
var set = getBeatmapSet();
|
||||||
|
|
||||||
|
var beatmaps = new List<APIBeatmap>();
|
||||||
|
|
||||||
|
var guestUser = new APIUser
|
||||||
|
{
|
||||||
|
Username = @"BanchoBot",
|
||||||
|
Id = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
set.RelatedUsers = new[]
|
||||||
|
{
|
||||||
|
set.Author, guestUser
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmaps.Add(new APIBeatmap
|
||||||
|
{
|
||||||
|
OnlineID = 1145,
|
||||||
|
DifficultyName = "Host Diff",
|
||||||
|
RulesetID = Ruleset.Value.OnlineID,
|
||||||
|
StarRating = 1.4,
|
||||||
|
OverallDifficulty = 3.5f,
|
||||||
|
AuthorID = set.AuthorID,
|
||||||
|
FailTimes = new APIFailTimes
|
||||||
|
{
|
||||||
|
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
|
||||||
|
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
|
||||||
|
},
|
||||||
|
Status = BeatmapOnlineStatus.Graveyard
|
||||||
|
});
|
||||||
|
|
||||||
|
beatmaps.Add(new APIBeatmap
|
||||||
|
{
|
||||||
|
OnlineID = 1919,
|
||||||
|
DifficultyName = "Guest Diff",
|
||||||
|
RulesetID = Ruleset.Value.OnlineID,
|
||||||
|
StarRating = 8.1,
|
||||||
|
OverallDifficulty = 3.5f,
|
||||||
|
AuthorID = 3,
|
||||||
|
FailTimes = new APIFailTimes
|
||||||
|
{
|
||||||
|
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
|
||||||
|
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
|
||||||
|
},
|
||||||
|
Status = BeatmapOnlineStatus.Graveyard
|
||||||
|
});
|
||||||
|
|
||||||
|
set.Beatmaps = beatmaps.ToArray();
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
private void downloadAssert(bool shown)
|
private void downloadAssert(bool shown)
|
||||||
{
|
{
|
||||||
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
|
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
|
||||||
|
@ -126,6 +126,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Id = 13926,
|
Id = 13926,
|
||||||
TournamentId = 35,
|
TournamentId = 35,
|
||||||
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
||||||
|
Image = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US@2x.jpg",
|
||||||
},
|
},
|
||||||
Badges = new[]
|
Badges = new[]
|
||||||
{
|
{
|
||||||
|
@ -84,16 +84,80 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(10);
|
checkDisplayedCount(10);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(20);
|
checkDisplayedCount(20);
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalScoresDisplayOnBeatmapEdit()
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmapInfo = null!;
|
||||||
|
string originalHash = string.Empty;
|
||||||
|
|
||||||
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||||
|
|
||||||
|
AddStep(@"Import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
|
|
||||||
|
leaderboard.BeatmapInfo = beatmapInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
|
AddStep(@"Perform initial save to guarantee stable hash", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
|
||||||
|
originalHash = beatmapInfo.Hash;
|
||||||
|
});
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
AddStep(@"Save with changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 12;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash changed", () => beatmapInfo.Hash, () => Is.Not.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
checkDisplayedCount(20);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
AddStep(@"Revert changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 8;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash restored", () => beatmapInfo.Hash, () => Is.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -162,9 +226,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMoreScores(Func<BeatmapInfo> beatmapInfo)
|
private void importMoreScores(Func<BeatmapInfo> beatmapInfo)
|
||||||
{
|
{
|
||||||
AddStep(@"Load new scores via manager", () =>
|
AddStep(@"Import new scores", () =>
|
||||||
{
|
{
|
||||||
foreach (var score in generateSampleScores(beatmapInfo()))
|
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||||
scoreManager.Import(score);
|
scoreManager.Import(score);
|
||||||
@ -176,8 +240,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("Clear all scores", () => scoreManager.Delete());
|
AddStep("Clear all scores", () => scoreManager.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCount(int expected) =>
|
private void checkDisplayedCount(int expected) =>
|
||||||
AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count() == expected);
|
AddUntilStep($"{expected} scores displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count(), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
|
private void checkStoredCount(int expected) =>
|
||||||
|
AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All<ScoreInfo>().Count(s => !s.DeletePending)), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -210,6 +277,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
},
|
},
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 6602580,
|
Id = 6602580,
|
||||||
@ -226,6 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-30),
|
Date = DateTime.Now.AddSeconds(-30),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
@ -243,6 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-70),
|
Date = DateTime.Now.AddSeconds(-70),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -261,6 +331,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMinutes(-40),
|
Date = DateTime.Now.AddMinutes(-40),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -279,6 +350,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-2),
|
Date = DateTime.Now.AddHours(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -297,6 +369,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-25),
|
Date = DateTime.Now.AddHours(-25),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -315,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-50),
|
Date = DateTime.Now.AddHours(-50),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -333,6 +407,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-72),
|
Date = DateTime.Now.AddHours(-72),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -351,6 +426,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMonths(-3),
|
Date = DateTime.Now.AddMonths(-3),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -369,6 +445,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddYears(-2),
|
Date = DateTime.Now.AddYears(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
|
@ -1068,6 +1068,21 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("options disabled", () => !songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
|
AddAssert("options disabled", () => !songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTextBoxBeatmapDifficultyCount()
|
||||||
|
{
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddAssert("0 matching shown", () => songSelect.ChildrenOfType<FilterControl>().Single().InformationalText == "0 matches");
|
||||||
|
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
|
AddAssert("3 matching shown", () => songSelect.ChildrenOfType<FilterControl>().Single().InformationalText == "3 matches");
|
||||||
|
AddStep("delete all beatmaps", () => manager.Delete());
|
||||||
|
AddUntilStep("wait for no beatmap", () => Beatmap.IsDefault);
|
||||||
|
AddAssert("0 matching shown", () => songSelect.ChildrenOfType<FilterControl>().Single().InformationalText == "0 matches");
|
||||||
|
}
|
||||||
|
|
||||||
private void waitForInitialSelection()
|
private void waitForInitialSelection()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
|
||||||
|
@ -14,10 +14,11 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapListingSortTabControl : OsuTestScene
|
public partial class TestSceneBeatmapListingSortTabControl : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly BeatmapListingSortTabControl control;
|
private readonly BeatmapListingSortTabControl control;
|
||||||
|
|
||||||
@ -111,6 +112,29 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
|
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSortDirectionOnCriteriaChange()
|
||||||
|
{
|
||||||
|
AddStep("set category to leaderboard", () => control.Reset(SearchCategory.Leaderboard, false));
|
||||||
|
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||||
|
|
||||||
|
AddStep("click ranked sort button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().Single(s => s.Active.Value));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sort direction is ascending", () => control.SortDirection.Value == SortDirection.Ascending);
|
||||||
|
|
||||||
|
AddStep("click first inactive sort button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().First(s => !s.Active.Value));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||||
|
}
|
||||||
|
|
||||||
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
||||||
{
|
{
|
||||||
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
||||||
|
@ -94,6 +94,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
OnlineID = i,
|
OnlineID = i,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Accuracy = RNG.NextDouble(),
|
Accuracy = RNG.NextDouble(),
|
||||||
TotalScore = RNG.Next(1, 1000000),
|
TotalScore = RNG.Next(1, 1000000),
|
||||||
MaxCombo = RNG.Next(1, 1000),
|
MaxCombo = RNG.Next(1, 1000),
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -67,6 +68,19 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
r.Add(new ModPreset
|
||||||
|
{
|
||||||
|
Name = "Half Time 0.5x",
|
||||||
|
Description = "Very slow",
|
||||||
|
Ruleset = r.Find<RulesetInfo>(OsuRuleset.SHORT_NAME),
|
||||||
|
Mods = new[]
|
||||||
|
{
|
||||||
|
new OsuModHalfTime
|
||||||
|
{
|
||||||
|
SpeedChange = { Value = 0.5 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -566,6 +580,28 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("5 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 5);
|
AddAssert("5 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModMultiplierUpdates()
|
||||||
|
{
|
||||||
|
createScreen();
|
||||||
|
|
||||||
|
AddStep("select mod preset with half time", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ModPresetPanel>().Single(preset => preset.Preset.Value.Name == "Half Time 0.5x"));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType<DifficultyMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.5));
|
||||||
|
|
||||||
|
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
|
||||||
|
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
|
||||||
|
AddStep("force collection", GC.Collect);
|
||||||
|
|
||||||
|
AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick());
|
||||||
|
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModSettingsArea>().Single()
|
||||||
|
.ChildrenOfType<RestoreDefaultValueButton<double>>().Single().TriggerClick());
|
||||||
|
AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType<DifficultyMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.7));
|
||||||
|
}
|
||||||
|
|
||||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
private void waitForColumnLoad() => AddUntilStep("all column content loaded",
|
||||||
() => modSelectOverlay.ChildrenOfType<ModColumn>().Any() && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
() => modSelectOverlay.ChildrenOfType<ModColumn>().Any() && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||||
|
|
||||||
|
@ -332,13 +332,6 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
private void saveChanges()
|
private void saveChanges()
|
||||||
{
|
{
|
||||||
foreach (var r in ladder.Rounds)
|
|
||||||
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
|
||||||
|
|
||||||
ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat(
|
|
||||||
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||||
string serialisedLadder = GetSerialisedLadder();
|
string serialisedLadder = GetSerialisedLadder();
|
||||||
|
|
||||||
@ -349,6 +342,13 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
public string GetSerialisedLadder()
|
public string GetSerialisedLadder()
|
||||||
{
|
{
|
||||||
|
foreach (var r in ladder.Rounds)
|
||||||
|
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
||||||
|
|
||||||
|
ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat(
|
||||||
|
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(ladder,
|
return JsonConvert.SerializeObject(ladder,
|
||||||
new JsonSerializerSettings
|
new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osu" };
|
protected override string[] HashableFileTypes => new[] { ".osu" };
|
||||||
|
|
||||||
public Action<(BeatmapSetInfo beatmapSet, bool isBatch)>? ProcessBeatmap { private get; set; }
|
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
|
||||||
|
|
||||||
public BeatmapImporter(Storage storage, RealmAccess realm)
|
public BeatmapImporter(Storage storage, RealmAccess realm)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Beatmaps
|
|||||||
first.PerformRead(s =>
|
first.PerformRead(s =>
|
||||||
{
|
{
|
||||||
// Re-run processing even in this case. We might have outdated metadata.
|
// Re-run processing even in this case. We might have outdated metadata.
|
||||||
ProcessBeatmap?.Invoke((s, false));
|
ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst);
|
||||||
});
|
});
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
|
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
|
||||||
{
|
{
|
||||||
base.PostImport(model, realm, parameters);
|
base.PostImport(model, realm, parameters);
|
||||||
ProcessBeatmap?.Invoke((model, parameters.Batch));
|
ProcessBeatmap?.Invoke(model, parameters.Batch ? MetadataLookupScope.LocalCacheFirst : MetadataLookupScope.OnlineFirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm)
|
private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm)
|
||||||
|
@ -167,7 +167,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public double DistanceSpacing { get; set; } = 1.0;
|
public double DistanceSpacing { get; set; } = 1.0;
|
||||||
|
|
||||||
public int BeatDivisor { get; set; }
|
public int BeatDivisor { get; set; } = 4;
|
||||||
|
|
||||||
public int GridSize { get; set; }
|
public int GridSize { get; set; }
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user