diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5c11f91994..e60e0a39ae 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,17 +13,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- 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.
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
- name: Install .NET 3.1.x LTS
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: "3.1.x"
- name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
@@ -77,10 +77,10 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
@@ -94,7 +94,7 @@ jobs:
# Attempt to upload results even if test fails.
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
- name: Upload Test Results
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
@@ -106,10 +106,10 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
@@ -125,10 +125,10 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install .NET 6.0.x
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml
index 9e11ab6663..2c6ec17e18 100644
--- a/.github/workflows/diffcalc.yml
+++ b/.github/workflows/diffcalc.yml
@@ -48,8 +48,8 @@ jobs:
CONTINUE="no"
fi
- echo "::set-output name=continue::${CONTINUE}"
- echo "::set-output name=matrix::${MATRIX_JSON}"
+ echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
+ echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
diffcalc:
name: Run
runs-on: self-hosted
@@ -80,34 +80,34 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"
- echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')"
+ echo "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
+ 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
- name: Checkout osu (master)
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
path: 'master/osu'
- name: Checkout osu (pr)
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
path: 'pr/osu'
repository: ${{ steps.upstreambranch.outputs.repo }}
ref: ${{ steps.upstreambranch.outputs.branchname }}
- name: Checkout osu-difficulty-calculator (master)
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
repository: ppy/osu-difficulty-calculator
path: 'master/osu-difficulty-calculator'
- name: Checkout osu-difficulty-calculator (pr)
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
repository: ppy/osu-difficulty-calculator
path: 'pr/osu-difficulty-calculator'
- name: Install .NET 5.0.x
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v3
with:
dotnet-version: "5.0.x"
diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml
index cce3f23e5f..ff4165c414 100644
--- a/.github/workflows/sentry-release.yml
+++ b/.github/workflows/sentry-release.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
fetch-depth: 0
diff --git a/README.md b/README.md
index f3f025fa10..eb2fe6d0eb 100644
--- a/README.md
+++ b/README.md
@@ -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).
-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
diff --git a/osu.Android.props b/osu.Android.props
index 927d66d93f..4b89e82729 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -11,7 +11,7 @@
manifestmerger.jar
-
+
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index ea5f54a775..cd8894753f 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Catch.Edit
private void load()
{
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
- RightSideToolboxContainer.Alpha = 0;
DistanceSpacingMultiplier.Disabled = true;
LayerBelowRuleset.Add(new PlayfieldBorder
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
index cae19e9468..180cb98ed7 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index e59a0a0431..6efb415880 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
{
Precision = 0.1f,
- MinValue = 1,
+ MinValue = 0,
MaxValue = 10,
ExtendedMaxValue = 11,
ReadCurrentFromDifficulty = diff => diff.CircleSize,
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
{
Precision = 0.1f,
- MinValue = 1,
+ MinValue = 0,
MaxValue = 10,
ExtendedMaxValue = 11,
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
index 57c06e1cd1..83db9f665b 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
index ce06b841aa..3afb8c3d89 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
index 9e38913be7..c537897439 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModNightcore : ModNightcore
{
- public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 1c52c092ec..ab754e51f7 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
Origin = Anchor.TopCentre;
Size = new Vector2(BASE_SIZE);
+
if (difficulty != null)
Scale = calculateScale(difficulty);
@@ -333,8 +334,11 @@ namespace osu.Game.Rulesets.Catch.UI
base.Update();
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
+
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.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
index 98046320cb..4e50fd924c 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs
@@ -3,6 +3,9 @@
#nullable disable
+using osu.Framework.Extensions.ObjectExtensions;
+using osu.Game.Rulesets.Mania.Configuration;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
@@ -10,5 +13,19 @@ namespace osu.Game.Rulesets.Mania.Tests
public partial class TestSceneManiaPlayer : PlayerTestScene
{
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);
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
index bec0a6a1d3..309393b664 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.5;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
index a302f95966..f4b9cf3b88 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => 1;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
index 014954dd60..8d48e3acde 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.5;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
index 4cc712060c..748725af9f 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNightcore : ModNightcore
{
- public override double ScoreMultiplier => 1;
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
index 8e27b4abd7..d490d3f944 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
@@ -43,8 +43,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
largeFaint = new Container
{
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
Masking = true,
@@ -81,11 +79,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
if (direction.NewValue == ScrollingDirection.Up)
{
Anchor = Anchor.TopCentre;
+ largeFaint.Anchor = Anchor.TopCentre;
+ largeFaint.Origin = Anchor.TopCentre;
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
}
else
{
Anchor = Anchor.BottomCentre;
+ largeFaint.Anchor = Anchor.BottomCentre;
+ largeFaint.Origin = Anchor.BottomCentre;
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
index b2e4e07526..bb424eb587 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
@@ -21,7 +21,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI.Cursor;
-using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
@@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
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!;
@@ -59,14 +59,14 @@ namespace osu.Game.Rulesets.Osu.Tests
Origin = Anchor.Centre,
Children = new Drawable[]
{
- leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
+ leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Depth = float.MinValue,
X = -100,
},
- rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
+ rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
@@ -598,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
private void assertKeyCounter(int left, int right)
{
- AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
- AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
+ AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
+ AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
}
private void releaseAllTouches()
@@ -615,11 +615,11 @@ namespace osu.Game.Rulesets.Osu.Tests
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));
- public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler
+ public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler
{
public OsuAction Action { get; }
- public TestActionKeyCounter(OsuAction action)
+ public TestActionKeyCounterTrigger(OsuAction action)
: base(action.ToString())
{
Action = action;
@@ -629,8 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
if (e.Action == Action)
{
- IsLit = true;
- Increment();
+ Activate();
}
return false;
@@ -638,7 +637,8 @@ namespace osu.Game.Rulesets.Osu.Tests
public void OnReleased(KeyBindingReleaseEvent e)
{
- if (e.Action == Action) IsLit = false;
+ if (e.Action == Action)
+ Deactivate();
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
index 3e161089cd..d6409279a4 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
protected override bool AlwaysShowWhenSelected => true;
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)
: base(hitObject)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
index 371dfe6a1a..1de6b9ce55 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
index 700a3f44bc..5569df8d95 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
index 4769e7660b..bf65a6c9d3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
index b7838ebaa7..661cc948c5 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModNightcore : ModNightcore
{
- public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
index 0a1f5380b5..8b1a4f688c 100644
--- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Objects;
@@ -35,20 +33,11 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e)
{
- switch (e.Button)
- {
- case MouseButton.Left:
- HitObject.Type = HitType.Centre;
- EndPlacement(true);
- return true;
+ if (e.Button != MouseButton.Left)
+ return false;
- case MouseButton.Right:
- HitObject.Type = HitType.Rim;
- EndPlacement(true);
- return true;
- }
-
- return false;
+ EndPlacement(true);
+ return true;
}
public override void UpdateTimeAndPosition(SnapResult result)
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
index 84aa5e6bba..f442435d9c 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs
index 89581c57bd..e517439ba4 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
index 68d6305fbf..9ef6fe8649 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
index 7cb14635ff..ad5da3d601 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModNightcore : ModNightcore
{
- public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 85d304da9c..518981980b 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using NUnit.Framework;
@@ -161,6 +160,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [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]
public void TestDecodeBeatmapTimingPoints()
{
@@ -320,6 +334,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var comboColors = decoder.Decode(stream).ComboColours;
+ Debug.Assert(comboColors != null);
+
Color4[] expectedColors =
{
new Color4(142, 199, 255, 255),
@@ -330,7 +346,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
new Color4(255, 177, 140, 255),
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++)
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
@@ -415,14 +431,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(positionData);
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.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = hitObjects[1] as IHasPosition;
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.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
}
@@ -578,8 +594,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestFallbackDecoderForCorruptedHeader()
{
- Decoder decoder = null;
- Beatmap beatmap = null;
+ Decoder decoder = null!;
+ Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@@ -596,8 +612,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestFallbackDecoderForMissingHeader()
{
- Decoder decoder = null;
- Beatmap beatmap = null;
+ Decoder decoder = null!;
+ Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("missing-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@@ -614,8 +630,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeFileWithEmptyLinesAtStart()
{
- Decoder decoder = null;
- Beatmap beatmap = null;
+ Decoder decoder = null!;
+ Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
using (var stream = new LineBufferedReader(resStream))
@@ -632,8 +648,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeFileWithEmptyLinesAndNoHeader()
{
- Decoder decoder = null;
- Beatmap beatmap = null;
+ Decoder decoder = null!;
+ Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@@ -650,8 +666,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeFileWithContentImmediatelyAfterHeader()
{
- Decoder decoder = null;
- Beatmap beatmap = null;
+ Decoder decoder = null!;
+ Beatmap beatmap = null!;
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
using (var stream = new LineBufferedReader(resStream))
@@ -678,7 +694,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestAllowFallbackDecoderOverwrite()
{
- Decoder decoder = null;
+ Decoder decoder = null!;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 281ea4e4ff..3a776ac225 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osuTK;
@@ -30,35 +28,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(storyboard.HasDrawable);
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.AreEqual(16, background.Elements.Count);
Assert.IsTrue(background.VisibleWhenFailing);
Assert.IsTrue(background.VisibleWhenPassing);
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.AreEqual(0, fail.Elements.Count);
Assert.IsTrue(fail.VisibleWhenFailing);
Assert.IsFalse(fail.VisibleWhenPassing);
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.AreEqual(0, pass.Elements.Count);
Assert.IsFalse(pass.VisibleWhenFailing);
Assert.IsTrue(pass.VisibleWhenPassing);
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.AreEqual(151, foreground.Elements.Count);
Assert.IsTrue(foreground.VisibleWhenFailing);
Assert.IsTrue(foreground.VisibleWhenPassing);
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.IsEmpty(overlay.Elements);
Assert.IsTrue(overlay.VisibleWhenFailing);
@@ -76,7 +74,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
Assert.NotNull(sprite);
- Assert.IsTrue(sprite.HasCommands);
+ Assert.IsTrue(sprite!.HasCommands);
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
@@ -171,6 +169,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [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 foreground = storyboard.Layers.Single(l => l.Name == "Video");
+ Assert.That(foreground.Elements.Count, Is.Zero);
+ }
+ }
+
[Test]
public void TestDecodeOutOfRangeLoopAnimationType()
{
diff --git a/osu.Game.Tests/Resources/image-specified-as-video.osb b/osu.Game.Tests/Resources/image-specified-as-video.osb
new file mode 100644
index 0000000000..9cea7dd4e7
--- /dev/null
+++ b/osu.Game.Tests/Resources/image-specified-as-video.osb
@@ -0,0 +1,4 @@
+osu file format v14
+
+[Events]
+Video,0,"BG.jpg",0,0
diff --git a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
index f1533a32b9..a5a83d7231 100644
--- a/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
+++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using NUnit.Framework;
@@ -51,9 +49,11 @@ namespace osu.Game.Tests.Testing
[Test]
public void TestRetrieveShader()
{
- AddAssert("ruleset shaders retrieved", () =>
- Dependencies.Get().LoadRaw(@"sh_TestVertex.vs") != null &&
- Dependencies.Get().LoadRaw(@"sh_TestFragment.fs") != null);
+ AddStep("ruleset shaders retrieved without error", () =>
+ {
+ Dependencies.Get().LoadRaw(@"sh_TestVertex.vs");
+ Dependencies.Get().LoadRaw(@"sh_TestFragment.fs");
+ });
}
[Test]
@@ -76,12 +76,12 @@ namespace osu.Game.Tests.Testing
}
public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources");
- public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
+ public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
public override IEnumerable GetModsFor(ModType type) => Array.Empty();
- public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null;
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
- public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => null!;
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
+ public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
}
private class TestRulesetConfigManager : IRulesetConfigManager
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 5442b3bfef..f3f942b74b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
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);
- 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().Single().AccuracyDisplay.Current.Value == 1);
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());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 1dffeed01b..751aeb4e13 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000);
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());
addSeekStep(0);
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);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index b918c5e64a..eecead5415 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
- private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
+ private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().Single();
[BackgroundDependencyLoader]
private void load()
@@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
hudOverlay = new HUDOverlay(null, Array.Empty());
// 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;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
index 890ac21b40..46d5e6c4d2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
@@ -7,7 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Utils;
-using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@@ -17,28 +17,29 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public TestSceneKeyCounter()
{
- KeyCounterKeyboard testCounter;
-
- KeyCounterDisplay kc = new KeyCounterDisplay
+ KeyCounterDisplay kc = new DefaultKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Children = new KeyCounter[]
- {
- testCounter = new KeyCounterKeyboard(Key.X),
- new KeyCounterKeyboard(Key.X),
- new KeyCounterMouse(MouseButton.Left),
- new KeyCounterMouse(MouseButton.Right),
- },
};
+ kc.AddRange(new InputTrigger[]
+ {
+ new KeyCounterKeyboardTrigger(Key.X),
+ new KeyCounterKeyboardTrigger(Key.X),
+ new KeyCounterMouseTrigger(MouseButton.Left),
+ new KeyCounterMouseTrigger(MouseButton.Right),
+ });
+
+ var testCounter = (DefaultKeyCounter)kc.Counters.First();
+
AddStep("Add random", () =>
{
Key key = (Key)((int)Key.A + RNG.Next(26));
- kc.Add(new KeyCounterKeyboard(key));
+ kc.Add(new KeyCounterKeyboardTrigger(key));
});
- Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
+ Key testKey = ((KeyCounterKeyboardTrigger)kc.Counters.First().Trigger).Key;
void addPressKeyStep()
{
@@ -46,12 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay
}
addPressKeyStep();
- AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
+ AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
addPressKeyStep();
- AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
- AddStep("Disable counting", () => testCounter.IsCounting = false);
+ AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
+ AddStep("Disable counting", () => testCounter.IsCounting.Value = false);
addPressKeyStep();
- AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
+ AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
Add(kc);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 2ea27c2fef..dbd1ce1f6e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -45,6 +45,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private SessionStatics sessionStatics { get; set; }
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[Cached(typeof(INotificationOverlay))]
private readonly NotificationOverlay notificationOverlay;
@@ -317,6 +320,7 @@ namespace osu.Game.Tests.Visual.Gameplay
saveVolumes();
setFullVolume();
+ AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true));
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
AddStep("load dummy beatmap", () => resetPlayer(false));
@@ -333,12 +337,30 @@ namespace osu.Game.Tests.Visual.Gameplay
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]
public void TestEpilepsyWarningEarlyExit()
{
saveVolumes();
setFullVolume();
+ AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true));
AddStep("set epilepsy warning", () => epilepsyWarning = true);
AddStep("load dummy beatmap", () => resetPlayer(false));
@@ -449,7 +471,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("click notification", () => notification.TriggerClick());
}
- private EpilepsyWarning getWarning() => loader.ChildrenOfType().SingleOrDefault();
+ private EpilepsyWarning getWarning() => loader.ChildrenOfType().SingleOrDefault(w => w.IsAlive);
private partial class TestPlayerLoader : PlayerLoader
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
index c476aae202..bf9b13b320 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps()
{
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);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
index 3e415af86e..ae10207de0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osuTK.Input;
@@ -45,6 +46,18 @@ namespace osu.Game.Tests.Visual.Gameplay
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]
public void TestPauseViaSpaceWithSkip()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
index a7da8f9832..93fec60de4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
@@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
};
// 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;
return new Container
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
index 1f2329af4a..7bbfc6a62b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
@@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
@@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
- private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
+ private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().Single();
[Test]
public void TestComboCounterIncrementing()
@@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay
hudOverlay = new HUDOverlay(null, Array.Empty());
// 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);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
index 5c69062e67..3f78dbfd96 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -22,8 +20,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneSkinnableSound : OsuTestScene
{
- private TestSkinSourceContainer skinSource;
- private PausableSkinnableSound skinnableSound;
+ private TestSkinSourceContainer skinSource = null!;
+ private PausableSkinnableSound skinnableSound = null!;
+
+ private const string sample_lookup = "Gameplay/normal-sliderslide";
[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.
- 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);
}
+ [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().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().Single().Name, () => Is.EqualTo("new skin"));
+ }
+
[Test]
public void TestSkinChangeDoesntPlayOnPause()
{
- DrawableSample sample = null;
+ DrawableSample? sample = null;
AddStep("start sample", () =>
{
skinnableSound.Play();
@@ -118,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("retrieve and ensure current sample is different", () =>
{
- DrawableSample oldSample = sample;
+ DrawableSample? oldSample = sample;
sample = skinnableSound.ChildrenOfType().Single();
return sample != oldSample;
});
@@ -134,20 +152,29 @@ namespace osu.Game.Tests.Visual.Gameplay
private partial class TestSkinSourceContainer : Container, ISkinSource, ISamplePlaybackDisabler
{
[Resolved]
- private ISkinSource source { get; set; }
+ private ISkinSource source { get; set; } = null!;
- public event Action SourceChanged;
+ public event Action? SourceChanged;
public Bindable SamplePlaybackDisabled { get; } = new Bindable();
+ public ISample? OverridingSample;
+
IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => SamplePlaybackDisabled;
- public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => source?.GetDrawableComponent(lookup);
- public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
- public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
- public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup);
- public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction);
- public IEnumerable AllSources => new[] { this }.Concat(source?.AllSources ?? Enumerable.Empty());
+ public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => source.GetDrawableComponent(lookup);
+ public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source.GetTexture(componentName, wrapModeS, wrapModeT);
+ public ISample? GetSample(ISampleInfo sampleInfo) => OverridingSample ?? source.GetSample(sampleInfo);
+
+ public IBindable? GetConfig(TLookup lookup)
+ where TLookup : notnull
+ where TValue : notnull
+ {
+ return source.GetConfig(lookup);
+ }
+
+ public ISkin? FindProvider(Func lookupFunction) => lookupFunction(this) ? this : source.FindProvider(lookupFunction);
+ public IEnumerable AllSources => new[] { this }.Concat(source.AllSources);
public void TriggerSourceChanged()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index 0d88fb01a8..283866bef2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -13,6 +13,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
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);
}
+ [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().First().ChildrenOfType().First().Enabled.Value);
+ AddStep("click save button", () => Player.ChildrenOfType().First().ChildrenOfType().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]
public void TestShowResultsFalse()
{
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index aef6f9ade0..22c7bb64b2 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -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().Count(), () => Is.Zero);
+ else
+ AddAssert("check buttons accepting input", () => InputManager.NonPositionalInputQueue.OfType().Count(), () => Is.Not.Zero);
+ }
+
[TestCase(OverlayActivation.All)]
[TestCase(OverlayActivation.Disabled)]
public void TestRespectsOverlayActivation(OverlayActivation mode)
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs
index d937b9e6d7..224e7e411e 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs
@@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
@@ -69,10 +70,10 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
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));
- AddAssert("key counter did increase", () => keyCounter.CountPresses == 1);
+ AddAssert("key counter did increase", () => keyCounter.CountPresses.Value == 1);
}
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 5d13421195..a27c4ddad2 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
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
{
- public partial class TestSceneBeatmapSetOverlay : OsuTestScene
+ public partial class TestSceneBeatmapSetOverlay : OsuManualInputManagerTestScene
{
private readonly TestBeatmapSetOverlay overlay;
@@ -281,6 +282,22 @@ namespace osu.Game.Tests.Visual.Online
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().ElementAt(0));
+ });
+ AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().All(s => s.Text != "BanchoBot"));
+ AddStep("move mouse to guest difficulty", () =>
+ {
+ InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1));
+ });
+ AddAssert("guest mapper information shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().Any(s => s.Text == "BanchoBot"));
+ }
+
private APIBeatmapSet createManyDifficultiesBeatmapSet()
{
var set = getBeatmapSet();
@@ -320,6 +337,60 @@ namespace osu.Game.Tests.Visual.Online
return beatmapSet;
}
+ private APIBeatmapSet createBeatmapSetWithGuestDifficulty()
+ {
+ var set = getBeatmapSet();
+
+ var beatmaps = new List();
+
+ 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)
{
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index feab86d3ee..a103cf759f 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -1068,6 +1068,21 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("options disabled", () => !songSelect.ChildrenOfType().Single().Enabled.Value);
}
+ [Test]
+ public void TestTextBoxBeatmapDifficultyCount()
+ {
+ createSongSelect();
+
+ AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matching beatmaps");
+
+ addRulesetImportStep(0);
+
+ AddAssert("3 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "3 matching beatmaps");
+ AddStep("delete all beatmaps", () => manager.Delete());
+ AddUntilStep("wait for no beatmap", () => Beatmap.IsDefault);
+ AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matching beatmaps");
+ }
+
private void waitForInitialSelection()
{
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs
index 316035275f..dd7bf48791 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSortTabControl.cs
@@ -14,10 +14,11 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public partial class TestSceneBeatmapListingSortTabControl : OsuTestScene
+ public partial class TestSceneBeatmapListingSortTabControl : OsuManualInputManagerTestScene
{
private readonly BeatmapListingSortTabControl control;
@@ -111,6 +112,29 @@ namespace osu.Game.Tests.Visual.UserInterface
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().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().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)
{
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index eabc63b341..a9bdd21b64 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -363,6 +363,19 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
break;
+ case LegacyEventType.Video:
+ string filename = CleanFilename(split[2]);
+
+ // Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO
+ // instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported
+ // video extensions and handle similar to a background if it doesn't match.
+ if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename)))
+ {
+ beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
+ }
+
+ break;
+
case LegacyEventType.Background:
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
break;
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 44dbb3cc9f..f8308fe431 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -109,6 +109,14 @@ namespace osu.Game.Beatmaps.Formats
int offset = Parsing.ParseInt(split[1]);
string path = CleanFilename(split[2]);
+ // See handling in LegacyBeatmapDecoder for the special case where a video type is used but
+ // the file extension is not a valid video.
+ //
+ // This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video
+ // (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451).
+ if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path)))
+ break;
+
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
break;
}
@@ -276,7 +284,8 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
case "A":
- timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive, startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
+ timelineGroup?.BlendingParameters.Add(easing, startTime, endTime, BlendingParameters.Additive,
+ startTime == endTime ? BlendingParameters.Additive : BlendingParameters.Inherit);
break;
case "H":
diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs
index f4e23ae7cb..7c9b52275d 100644
--- a/osu.Game/Localisation/EditorStrings.cs
+++ b/osu.Game/Localisation/EditorStrings.cs
@@ -99,6 +99,16 @@ namespace osu.Game.Localisation
///
public static LocalisableString TimelineTicks => new TranslatableString(getKey(@"timeline_ticks"), @"Ticks");
+ ///
+ /// "{0:0.0}°"
+ ///
+ public static LocalisableString RotationUnsnapped(float newRotation) => new TranslatableString(getKey(@"rotation_unsnapped"), @"{0:0.0}°", newRotation);
+
+ ///
+ /// "{0:0.0}° (snapped)"
+ ///
+ public static LocalisableString RotationSnapped(float newRotation) => new TranslatableString(getKey(@"rotation_snapped"), @"{0:0.0}° (snapped)", newRotation);
+
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs
index 6a9793b20c..5e2600bc50 100644
--- a/osu.Game/Localisation/NotificationsStrings.cs
+++ b/osu.Game/Localisation/NotificationsStrings.cs
@@ -50,16 +50,18 @@ namespace osu.Game.Localisation
public static LocalisableString NoAutoplayMod => new TranslatableString(getKey(@"no_autoplay_mod"), @"The current ruleset doesn't have an autoplay mod available!");
///
- /// "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting."
+ /// "osu! doesn't seem to be able to play audio correctly.
+ ///
+ /// Please try changing your audio device to a working setting."
///
- public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"),
- @"osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting.");
+ public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"), @"osu! doesn't seem to be able to play audio correctly.
+
+Please try changing your audio device to a working setting.");
///
/// "The score overlay is currently disabled. You can toggle this by pressing {0}."
///
- public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"),
- @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
+ public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
private static string getKey(string key) => $@"{prefix}:{key}";
}
diff --git a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
index d2ff783413..3fa86c188c 100644
--- a/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
+++ b/osu.Game/Localisation/ResourceManagerLocalisationStore.cs
@@ -65,6 +65,11 @@ namespace osu.Game.Localisation
if (manager == null)
return null;
+ // When using the English culture, prefer the fallbacks rather than osu-resources baked strings.
+ // They are guaranteed to be up-to-date, and is also what a developer expects to see when making changes to `xxxStrings.cs` files.
+ if (EffectiveCulture.Name == @"en")
+ return null;
+
try
{
return manager.GetString(key, EffectiveCulture);
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index cf58d07b9e..8f27e5dc53 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -71,7 +71,7 @@ namespace osu.Game
[Cached(typeof(OsuGameBase))]
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider
{
- public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" };
+ public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv", ".mpg", ".wmv", ".m4v" };
public const string OSU_PROTOCOL = "osu://";
diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
index 2e20f83e9e..219cbe7eef 100644
--- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
+++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
@@ -209,7 +209,7 @@ namespace osu.Game.Overlays.AccountCreation
if (!string.IsNullOrEmpty(errors.Message))
passwordDescription.AddErrors(new[] { errors.Message });
- game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}");
+ game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", true);
}
}
else
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs
index 76b6dec65b..3336c383ff 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs
@@ -3,6 +3,7 @@
#nullable disable
+using osu.Framework.Graphics;
using osu.Game.Localisation;
using osu.Game.Resources.Localisation.Web;
@@ -10,8 +11,12 @@ namespace osu.Game.Overlays.BeatmapListing
{
public partial class BeatmapListingHeader : OverlayHeader
{
+ public BeatmapListingFilterControl FilterControl { get; private set; }
+
protected override OverlayTitle CreateTitle() => new BeatmapListingTitle();
+ protected override Drawable CreateContent() => FilterControl = new BeatmapListingFilterControl();
+
private partial class BeatmapListingTitle : OverlayTitle
{
public BeatmapListingTitle()
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
index 23de1cf76d..3fa0fc7a77 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Overlays.BeatmapListing
Padding = new MarginPadding
{
Vertical = 20,
- Horizontal = 40,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING,
},
Child = new FillFlowContainer
{
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
index 025738710f..2f290d05e9 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs
@@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapListing
if (currentParameters == null)
Reset(SearchCategory.Leaderboard, false);
+
+ Current.BindValueChanged(_ => SortDirection.Value = Overlays.SortDirection.Descending);
}
public void Reset(SearchCategory category, bool hasQuery)
@@ -102,7 +104,7 @@ namespace osu.Game.Overlays.BeatmapListing
};
}
- private partial class BeatmapTabButton : TabButton
+ public partial class BeatmapTabButton : TabButton
{
public readonly Bindable SortDirection = new Bindable();
@@ -136,7 +138,7 @@ namespace osu.Game.Overlays.BeatmapListing
SortDirection.BindValueChanged(direction =>
{
- icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown;
+ icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending && Active.Value ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown;
}, true);
}
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 73961487ed..f8784504b8 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -43,7 +43,8 @@ namespace osu.Game.Overlays
private Container panelTarget;
private FillFlowContainer foundContent;
- private BeatmapListingFilterControl filterControl;
+
+ private BeatmapListingFilterControl filterControl => Header.FilterControl;
public BeatmapListingOverlay()
: base(OverlayColourScheme.Blue)
@@ -60,12 +61,6 @@ namespace osu.Game.Overlays
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- filterControl = new BeatmapListingFilterControl
- {
- TypingStarted = onTypingStarted,
- SearchStarted = onSearchStarted,
- SearchFinished = onSearchFinished,
- },
new Container
{
AutoSizeAxes = Axes.Y,
@@ -88,6 +83,10 @@ namespace osu.Game.Overlays
},
}
};
+
+ filterControl.TypingStarted = onTypingStarted;
+ filterControl.SearchStarted = onSearchStarted;
+ filterControl.SearchFinished = onSearchFinished;
}
protected override void LoadComplete()
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
index 585e0dd1a2..104f861df7 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
@@ -31,6 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet
private const float tile_spacing = 2;
private readonly OsuSpriteText version, starRating, starRatingText;
+ private readonly LinkFlowContainer guestMapperContainer;
private readonly FillFlowContainer starRatingContainer;
private readonly Statistic plays, favourites;
@@ -88,6 +89,14 @@ namespace osu.Game.Overlays.BeatmapSet
Origin = Anchor.BottomLeft,
Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold)
},
+ guestMapperContainer = new LinkFlowContainer(s =>
+ s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11))
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Margin = new MarginPadding { Bottom = 1 },
+ },
starRatingContainer = new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
@@ -198,8 +207,21 @@ namespace osu.Game.Overlays.BeatmapSet
updateDifficultyButtons();
}
- private void showBeatmap(IBeatmapInfo? beatmapInfo)
+ private void showBeatmap(APIBeatmap? beatmapInfo)
{
+ guestMapperContainer.Clear();
+
+ if (beatmapInfo?.AuthorID != BeatmapSet?.AuthorID)
+ {
+ APIUser? user = BeatmapSet?.RelatedUsers?.SingleOrDefault(u => u.OnlineID == beatmapInfo?.AuthorID);
+
+ if (user != null)
+ {
+ guestMapperContainer.AddText("mapped by ");
+ guestMapperContainer.AddUserLink(user);
+ }
+ }
+
version.Text = beatmapInfo?.DifficultyName ?? string.Empty;
}
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
index 26e6b1f158..7ff8352054 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
@@ -97,8 +97,8 @@ namespace osu.Game.Overlays.BeatmapSet
Padding = new MarginPadding
{
Vertical = BeatmapSetOverlay.Y_PADDING,
- Left = BeatmapSetOverlay.X_PADDING,
- Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH,
+ Left = WaveOverlayContainer.HORIZONTAL_PADDING,
+ Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH,
},
Children = new Drawable[]
{
@@ -170,7 +170,7 @@ namespace osu.Game.Overlays.BeatmapSet
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
AutoSizeAxes = Axes.Both,
- Margin = new MarginPadding { Top = BeatmapSetOverlay.Y_PADDING, Right = BeatmapSetOverlay.X_PADDING },
+ Margin = new MarginPadding { Top = BeatmapSetOverlay.Y_PADDING, Right = WaveOverlayContainer.HORIZONTAL_PADDING },
Direction = FillDirection.Vertical,
Spacing = new Vector2(10),
Children = new Drawable[]
diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs
index 58739eb471..8758b9c5cf 100644
--- a/osu.Game/Overlays/BeatmapSet/Info.cs
+++ b/osu.Game/Overlays/BeatmapSet/Info.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet
new Container
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Top = 15, Horizontal = BeatmapSetOverlay.X_PADDING },
+ Padding = new MarginPadding { Top = 15, Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING },
Children = new Drawable[]
{
new Container
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index 9eb04d9cc5..6d89313979 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Padding = new MarginPadding { Horizontal = 50 },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING },
Margin = new MarginPadding { Vertical = 20 },
Children = new Drawable[]
{
diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs
index 237ce22767..873336bb6e 100644
--- a/osu.Game/Overlays/BeatmapSetOverlay.cs
+++ b/osu.Game/Overlays/BeatmapSetOverlay.cs
@@ -25,7 +25,6 @@ namespace osu.Game.Overlays
{
public partial class BeatmapSetOverlay : OnlineOverlay
{
- public const float X_PADDING = 40;
public const float Y_PADDING = 25;
public const float RIGHT_WIDTH = 275;
diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
index 96d5203d14..08978ac2ab 100644
--- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
@@ -18,8 +18,6 @@ namespace osu.Game.Overlays.Changelog
{
public partial class ChangelogBuild : FillFlowContainer
{
- public const float HORIZONTAL_PADDING = 70;
-
public Action SelectBuild;
protected readonly APIChangelogBuild Build;
@@ -33,7 +31,7 @@ namespace osu.Game.Overlays.Changelog
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Vertical;
- Padding = new MarginPadding { Horizontal = HORIZONTAL_PADDING };
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING };
Children = new Drawable[]
{
diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs
index 54ada24987..e9be67e977 100644
--- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs
@@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Changelog
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding
{
- Horizontal = 65,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING - ChangelogUpdateStreamItem.PADDING,
Vertical = 20
},
Child = Streams = new ChangelogUpdateStreamControl { Current = currentStream },
diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs
index d7c9ff67fe..4b784c7a28 100644
--- a/osu.Game/Overlays/Changelog/ChangelogListing.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Changelog
{
RelativeSizeAxes = Axes.X,
Height = 1,
- Padding = new MarginPadding { Horizontal = ChangelogBuild.HORIZONTAL_PADDING },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING },
Margin = new MarginPadding { Top = 30 },
Child = new Box
{
diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
index 04526eb7ba..4aded1dd59 100644
--- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Changelog
Padding = new MarginPadding
{
Vertical = 20,
- Horizontal = 50,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING,
};
}
@@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Changelog
Direction = FillDirection.Vertical,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Padding = new MarginPadding { Right = 50 + image_container_width },
+ Padding = new MarginPadding { Right = WaveOverlayContainer.HORIZONTAL_PADDING + image_container_width },
Children = new Drawable[]
{
new OsuSpriteText
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index c4e4700674..24536fe460 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Comments
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = 50, Vertical = 20 },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 20 },
Children = new Drawable[]
{
avatar = new UpdateableAvatar(api.LocalUser.Value)
@@ -152,7 +152,7 @@ namespace osu.Game.Overlays.Comments
ShowDeleted = { BindTarget = ShowDeleted },
Margin = new MarginPadding
{
- Horizontal = 70,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING,
Vertical = 10
}
},
@@ -393,7 +393,7 @@ namespace osu.Game.Overlays.Comments
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Margin = new MarginPadding { Left = 50 },
+ Margin = new MarginPadding { Left = WaveOverlayContainer.HORIZONTAL_PADDING },
Text = CommentsStrings.Empty
}
});
diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs
index e6d44e618b..0ae1f839a1 100644
--- a/osu.Game/Overlays/Comments/CommentsHeader.cs
+++ b/osu.Game/Overlays/Comments/CommentsHeader.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Comments
new Container
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Horizontal = 50 },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING },
Children = new Drawable[]
{
new OverlaySortTabControl
diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs
index 397dd46cdc..a710406548 100644
--- a/osu.Game/Overlays/Comments/DrawableComment.cs
+++ b/osu.Game/Overlays/Comments/DrawableComment.cs
@@ -537,7 +537,7 @@ namespace osu.Game.Overlays.Comments
{
return new MarginPadding
{
- Horizontal = 70,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING,
Vertical = 15
};
}
diff --git a/osu.Game/Overlays/Comments/TotalCommentsCounter.cs b/osu.Game/Overlays/Comments/TotalCommentsCounter.cs
index 38928f6f3d..2065f7a76b 100644
--- a/osu.Game/Overlays/Comments/TotalCommentsCounter.cs
+++ b/osu.Game/Overlays/Comments/TotalCommentsCounter.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Comments
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
- Margin = new MarginPadding { Left = 50 },
+ Margin = new MarginPadding { Left = WaveOverlayContainer.HORIZONTAL_PADDING },
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs
index 6cfa5cb9e8..dd418a9e58 100644
--- a/osu.Game/Overlays/Comments/VotePill.cs
+++ b/osu.Game/Overlays/Comments/VotePill.cs
@@ -132,11 +132,10 @@ namespace osu.Game.Overlays.Comments
},
sideNumber = new OsuSpriteText
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreRight,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.BottomCentre,
Text = "+1",
Font = OsuFont.GetFont(size: 14),
- Margin = new MarginPadding { Right = 3 },
Alpha = 0,
},
votesCounter = new OsuSpriteText
@@ -189,7 +188,7 @@ namespace osu.Game.Overlays.Comments
else
sideNumber.FadeTo(IsHovered ? 1 : 0);
- borderContainer.BorderThickness = IsHovered ? 3 : 0;
+ borderContainer.BorderThickness = IsHovered ? 2 : 0;
}
private void onHoverAction()
diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
index 1540aa8fbb..5047992c8b 100644
--- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Dashboard
new Container
{
RelativeSizeAxes = Axes.X,
- Padding = new MarginPadding(padding),
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = padding },
Child = searchTextBox = new BasicSearchTextBox
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
index 73fab6d62b..e3accfd2ad 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
Padding = new MarginPadding
{
Top = 20,
- Horizontal = 45
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING - FriendsOnlineStatusItem.PADDING
},
Child = onlineStreamControl = new FriendOnlineStreamControl(),
}
@@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = 50 }
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING }
},
loading = new LoadingLayer(true)
}
diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs
index b6ce16ae7d..4fc9dde156 100644
--- a/osu.Game/Overlays/News/Displays/ArticleListing.cs
+++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Overlays.News.Displays
{
Vertical = 20,
Left = 30,
- Right = 50
+ Right = WaveOverlayContainer.HORIZONTAL_PADDING
};
InternalChild = new FillFlowContainer
diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs
index 4fdf7cb2b6..4d2c6bc9d0 100644
--- a/osu.Game/Overlays/OnlineOverlay.cs
+++ b/osu.Game/Overlays/OnlineOverlay.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -22,6 +23,7 @@ namespace osu.Game.Overlays
protected readonly OverlayScrollContainer ScrollFlow;
protected readonly LoadingLayer Loading;
+ private readonly Container loadingContainer;
private readonly Container content;
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
@@ -65,10 +67,22 @@ namespace osu.Game.Overlays
},
}
},
- Loading = new LoadingLayer(true)
+ loadingContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = Loading = new LoadingLayer(true),
+ }
});
base.Content.Add(mainContent);
}
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ // don't block header by applying padding equal to the visible header height
+ loadingContainer.Padding = new MarginPadding { Top = Math.Max(0, Header.Height - ScrollFlow.Current) };
+ }
}
}
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
index f28d40c429..93de463204 100644
--- a/osu.Game/Overlays/OverlayHeader.cs
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Overlays
}
});
- ContentSidePadding = 50;
+ ContentSidePadding = WaveOverlayContainer.HORIZONTAL_PADDING;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/OverlaySidebar.cs b/osu.Game/Overlays/OverlaySidebar.cs
index b8c0032e87..93e5e83ffc 100644
--- a/osu.Game/Overlays/OverlaySidebar.cs
+++ b/osu.Game/Overlays/OverlaySidebar.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Overlays
Padding = new MarginPadding
{
Vertical = 20,
- Left = 50,
+ Left = WaveOverlayContainer.HORIZONTAL_PADDING,
Right = 30
},
Child = CreateContent()
diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs
index 8af2ab3823..5c51f5e4d0 100644
--- a/osu.Game/Overlays/OverlaySortTabControl.cs
+++ b/osu.Game/Overlays/OverlaySortTabControl.cs
@@ -117,7 +117,7 @@ namespace osu.Game.Overlays
}
}
- protected partial class TabButton : HeaderButton
+ public partial class TabButton : HeaderButton
{
public readonly BindableBool Active = new BindableBool();
diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs
index 9b18e5cccf..45181c13e4 100644
--- a/osu.Game/Overlays/OverlayStreamItem.cs
+++ b/osu.Game/Overlays/OverlayStreamItem.cs
@@ -39,12 +39,14 @@ namespace osu.Game.Overlays
private FillFlowContainer text;
private ExpandingBar expandingBar;
+ public const float PADDING = 5;
+
protected OverlayStreamItem(T value)
: base(value)
{
Height = 50;
Width = 90;
- Margin = new MarginPadding(5);
+ Margin = new MarginPadding(PADDING);
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/Profile/Header/BadgeHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeHeaderContainer.cs
index 508041eb76..24be6ce2f5 100644
--- a/osu.Game/Overlays/Profile/Header/BadgeHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/BadgeHeaderContainer.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Header
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10, 10),
- Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Top = 10 },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Top = 10 },
}
};
}
diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
index 1e80257a57..08a816930e 100644
--- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Profile.Header
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs
index 0dab4d582d..d964364510 100644
--- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Header
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Vertical = 10 },
- Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
+ Margin = new MarginPadding { Left = WaveOverlayContainer.HORIZONTAL_PADDING },
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
@@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Header
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
- Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN },
+ Margin = new MarginPadding { Right = WaveOverlayContainer.HORIZONTAL_PADDING },
Children = new Drawable[]
{
levelBadge = new LevelBadge
@@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Profile.Header
Origin = Anchor.CentreRight,
Width = 200,
Height = 6,
- Margin = new MarginPadding { Right = 50 },
+ Margin = new MarginPadding { Right = WaveOverlayContainer.HORIZONTAL_PADDING },
Child = new LevelProgressBar
{
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs
index 1cc3aae735..1f35f39b49 100644
--- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Profile.Header
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs
index 2f4f49788f..d04329430b 100644
--- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs
@@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Profile.Header
Direction = FillDirection.Horizontal,
Padding = new MarginPadding
{
- Left = UserProfileOverlay.CONTENT_X_MARGIN,
+ Left = WaveOverlayContainer.HORIZONTAL_PADDING,
Vertical = vertical_padding
},
Height = content_height + 2 * vertical_padding,
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 363eb5d58e..80d48ae09e 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Profile
public ProfileHeader()
{
- ContentSidePadding = UserProfileOverlay.CONTENT_X_MARGIN;
+ ContentSidePadding = WaveOverlayContainer.HORIZONTAL_PADDING;
TabControl.AddItem(LayoutStrings.HeaderUsersShow);
diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs
index 4ac86924f8..a8a240ddde 100644
--- a/osu.Game/Overlays/Profile/ProfileSection.cs
+++ b/osu.Game/Overlays/Profile/ProfileSection.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Profile
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding
{
- Horizontal = UserProfileOverlay.CONTENT_X_MARGIN - outer_gutter_width,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING - outer_gutter_width,
Top = 20,
Bottom = 20,
},
@@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Profile
RelativeSizeAxes = Axes.X,
Padding = new MarginPadding
{
- Horizontal = UserProfileOverlay.CONTENT_X_MARGIN - outer_gutter_width,
+ Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING - outer_gutter_width,
Bottom = 20
}
},
diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
index 7b26640e50..1a44262ef8 100644
--- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
+++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
set => valueText.Text = value.ToLocalisableString("N0");
}
- public CountSection(LocalisableString header)
+ protected CountSection(LocalisableString header)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs
index e27fa7c7bd..525816f8fd 100644
--- a/osu.Game/Overlays/Rankings/CountryFilter.cs
+++ b/osu.Game/Overlays/Rankings/CountryFilter.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Rankings
Origin = Anchor.CentreLeft,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
- Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
+ Margin = new MarginPadding { Left = WaveOverlayContainer.HORIZONTAL_PADDING },
Children = new Drawable[]
{
new OsuSpriteText
diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs
index 31273e3b01..190da04a5d 100644
--- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Rankings
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING },
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
index affd9a2c44..27d894cdc2 100644
--- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
+++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs
@@ -23,7 +23,6 @@ namespace osu.Game.Overlays.Rankings.Tables
public abstract partial class RankingsTable : TableContainer
{
protected const int TEXT_SIZE = 12;
- private const float horizontal_inset = 20;
private const float row_height = 32;
private const float row_spacing = 3;
private const int items_per_page = 50;
@@ -39,7 +38,7 @@ namespace osu.Game.Overlays.Rankings.Tables
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Padding = new MarginPadding { Horizontal = horizontal_inset };
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING };
RowSize = new Dimension(GridSizeMode.Absolute, row_height + row_spacing);
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 6465d62ef0..2765d2b437 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private FillFlowContainer> scalingSettings = null!;
private readonly Bindable currentDisplay = new Bindable();
- private readonly IBindableList windowModes = new BindableList();
private Bindable scalingMode = null!;
private Bindable sizeFullscreen = null!;
@@ -75,7 +74,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
if (window != null)
{
currentDisplay.BindTo(window.CurrentDisplayBindable);
- windowModes.BindTo(window.SupportedWindowModes);
window.DisplaysChanged += onDisplaysChanged;
}
@@ -87,7 +85,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown = new SettingsDropdown
{
LabelText = GraphicsSettingsStrings.ScreenMode,
- ItemSource = windowModes,
+ Items = window?.SupportedWindowModes,
+ CanBeShown = { Value = window?.SupportedWindowModes.Count() > 1 },
Current = config.GetBindable(FrameworkSetting.WindowMode),
},
displayDropdown = new DisplaySettingsDropdown
@@ -181,8 +180,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
updateScreenModeWarning();
}, true);
- windowModes.BindCollectionChanged((_, _) => updateDisplaySettingsVisibility());
-
currentDisplay.BindValueChanged(display => Schedule(() =>
{
resolutions.RemoveRange(1, resolutions.Count - 1);
@@ -236,7 +233,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private void updateDisplaySettingsVisibility()
{
- windowModeDropdown.CanBeShown.Value = windowModes.Count > 1;
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index f21ef0ee98..93294a9d30 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Toolbar
protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All);
// Toolbar and its components need keyboard input even when hidden.
- public override bool PropagateNonPositionalInputSubTree => true;
+ public override bool PropagateNonPositionalInputSubTree => OverlayActivationMode.Value != OverlayActivation.Disabled;
public Toolbar()
{
diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs
index c5f8a820ea..d1fe877e55 100644
--- a/osu.Game/Overlays/UserProfileOverlay.cs
+++ b/osu.Game/Overlays/UserProfileOverlay.cs
@@ -45,8 +45,6 @@ namespace osu.Game.Overlays
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
- public const float CONTENT_X_MARGIN = 50;
-
public UserProfileOverlay()
: base(OverlayColourScheme.Pink)
{
@@ -184,7 +182,7 @@ namespace osu.Game.Overlays
public ProfileSectionTabControl()
{
Height = 40;
- Padding = new MarginPadding { Horizontal = CONTENT_X_MARGIN };
+ Padding = new MarginPadding { Horizontal = HORIZONTAL_PADDING };
TabContainer.Spacing = new Vector2(20);
}
diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs
index d25f6a9ae5..00474cc0d8 100644
--- a/osu.Game/Overlays/WaveOverlayContainer.cs
+++ b/osu.Game/Overlays/WaveOverlayContainer.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Overlays
protected override string PopInSampleName => "UI/wave-pop-in";
+ public const float HORIZONTAL_PADDING = 50;
+
protected WaveOverlayContainer()
{
AddInternal(Waves = new WaveContainer
diff --git a/osu.Game/Overlays/Wiki/WikiArticlePage.cs b/osu.Game/Overlays/Wiki/WikiArticlePage.cs
index 6c1dbe3181..342a395871 100644
--- a/osu.Game/Overlays/Wiki/WikiArticlePage.cs
+++ b/osu.Game/Overlays/Wiki/WikiArticlePage.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Wiki
{
Vertical = 20,
Left = 30,
- Right = 50,
+ Right = WaveOverlayContainer.HORIZONTAL_PADDING,
},
OnAddHeading = sidebar.AddEntry,
}
diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs
index 88dc2cd7a4..2444aa4fa2 100644
--- a/osu.Game/Overlays/WikiOverlay.cs
+++ b/osu.Game/Overlays/WikiOverlay.cs
@@ -145,7 +145,7 @@ namespace osu.Game.Overlays
Padding = new MarginPadding
{
Vertical = 20,
- Horizontal = 50,
+ Horizontal = HORIZONTAL_PADDING,
},
});
}
diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
index 0df481737e..aa47b4f424 100644
--- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
@@ -11,8 +11,6 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@@ -47,8 +45,6 @@ namespace osu.Game.Rulesets.Edit
IBindable IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
- protected ExpandingToolboxContainer RightSideToolboxContainer { get; private set; }
-
private ExpandableSlider> distanceSpacingSlider;
private ExpandableButton currentDistanceSpacingButton;
@@ -67,47 +63,29 @@ namespace osu.Game.Rulesets.Edit
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
- AddInternal(new Container
+ RightToolbox.Add(new EditorToolboxGroup("snapping")
{
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X,
+ Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
Children = new Drawable[]
{
- new Box
+ distanceSpacingSlider = new ExpandableSlider>
{
- Colour = colourProvider.Background5,
- RelativeSizeAxes = Axes.Both,
+ KeyboardStep = adjust_step,
+ // Manual binding in LoadComplete to handle one-way event flow.
+ Current = DistanceSpacingMultiplier.GetUnboundCopy(),
},
- RightSideToolboxContainer = new ExpandingToolboxContainer(130, 250)
+ currentDistanceSpacingButton = new ExpandableButton
{
- Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
- Child = new EditorToolboxGroup("snapping")
+ Action = () =>
{
- Children = new Drawable[]
- {
- distanceSpacingSlider = new ExpandableSlider>
- {
- KeyboardStep = adjust_step,
- // Manual binding in LoadComplete to handle one-way event flow.
- Current = DistanceSpacingMultiplier.GetUnboundCopy(),
- },
- currentDistanceSpacingButton = new ExpandableButton
- {
- Action = () =>
- {
- (HitObject before, HitObject after)? objects = getObjectsOnEitherSideOfCurrentTime();
+ (HitObject before, HitObject after)? objects = getObjectsOnEitherSideOfCurrentTime();
- Debug.Assert(objects != null);
+ Debug.Assert(objects != null);
- DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
- DistanceSnapToggle.Value = TernaryState.True;
- },
- RelativeSizeAxes = Axes.X,
- }
- }
- }
+ DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
+ DistanceSnapToggle.Value = TernaryState.True;
+ },
+ RelativeSizeAxes = Axes.X,
}
}
});
@@ -261,7 +239,8 @@ namespace osu.Game.Rulesets.Edit
public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true)
{
- return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor);
+ return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1
+ / BeatSnapProvider.BeatDivisor);
}
public virtual float DurationToDistance(HitObject referenceObject, double duration)
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index aee86fd942..653861c11c 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -58,8 +58,15 @@ namespace osu.Game.Rulesets.Edit
[Resolved]
protected IBeatSnapProvider BeatSnapProvider { get; private set; }
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; }
+
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
+ protected ExpandingToolboxContainer LeftToolbox { get; private set; }
+
+ protected ExpandingToolboxContainer RightToolbox { get; private set; }
+
private DrawableEditorRulesetWrapper drawableRulesetWrapper;
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both };
@@ -82,7 +89,7 @@ namespace osu.Game.Rulesets.Edit
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
[BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider, OsuConfigManager config)
+ private void load(OsuConfigManager config)
{
autoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement);
@@ -131,7 +138,7 @@ namespace osu.Game.Rulesets.Edit
Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
},
- new ExpandingToolboxContainer(60, 200)
+ LeftToolbox = new ExpandingToolboxContainer(60, 200)
{
Children = new Drawable[]
{
@@ -153,6 +160,28 @@ namespace osu.Game.Rulesets.Edit
},
}
},
+ new Container
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = colourProvider.Background5,
+ RelativeSizeAxes = Axes.Both,
+ },
+ RightToolbox = new ExpandingToolboxContainer(130, 250)
+ {
+ Child = new EditorToolboxGroup("inspector")
+ {
+ Child = new HitObjectInspector()
+ },
+ }
+ }
+ }
};
toolboxCollection.Items = CompositionTools
diff --git a/osu.Game/Rulesets/Edit/HitObjectInspector.cs b/osu.Game/Rulesets/Edit/HitObjectInspector.cs
new file mode 100644
index 0000000000..977d00ede2
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/HitObjectInspector.cs
@@ -0,0 +1,146 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.TypeExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Threading;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Overlays;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Screens.Edit;
+
+namespace osu.Game.Rulesets.Edit
+{
+ internal partial class HitObjectInspector : CompositeDrawable
+ {
+ private OsuTextFlowContainer inspectorText = null!;
+
+ [Resolved]
+ protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
+
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; } = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
+
+ InternalChild = inspectorText = new OsuTextFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
+ EditorBeatmap.TransactionBegan += updateInspectorText;
+ EditorBeatmap.TransactionEnded += updateInspectorText;
+ updateInspectorText();
+ }
+
+ private ScheduledDelegate? rollingTextUpdate;
+
+ private void updateInspectorText()
+ {
+ inspectorText.Clear();
+ rollingTextUpdate?.Cancel();
+ rollingTextUpdate = null;
+
+ switch (EditorBeatmap.SelectedHitObjects.Count)
+ {
+ case 0:
+ addValue("No selection");
+ break;
+
+ case 1:
+ var selected = EditorBeatmap.SelectedHitObjects.Single();
+
+ addHeader("Type");
+ addValue($"{selected.GetType().ReadableName()}");
+
+ addHeader("Time");
+ addValue($"{selected.StartTime:#,0.##}ms");
+
+ switch (selected)
+ {
+ case IHasPosition pos:
+ addHeader("Position");
+ addValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}");
+ break;
+
+ case IHasXPosition x:
+ addHeader("Position");
+
+ addValue($"x:{x.X:#,0.##} ");
+ break;
+
+ case IHasYPosition y:
+ addHeader("Position");
+
+ addValue($"y:{y.Y:#,0.##}");
+ break;
+ }
+
+ if (selected is IHasDistance distance)
+ {
+ addHeader("Distance");
+ addValue($"{distance.Distance:#,0.##}px");
+ }
+
+ if (selected is IHasRepeats repeats)
+ {
+ addHeader("Repeats");
+ addValue($"{repeats.RepeatCount:#,0.##}");
+ }
+
+ if (selected is IHasDuration duration)
+ {
+ addHeader("End Time");
+ addValue($"{duration.EndTime:#,0.##}ms");
+ addHeader("Duration");
+ addValue($"{duration.Duration:#,0.##}ms");
+ }
+
+ // I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
+ // This is a good middle-ground for the time being.
+ rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
+ break;
+
+ default:
+ addHeader("Selected Objects");
+ addValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
+
+ addHeader("Start Time");
+ addValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
+
+ addHeader("End Time");
+ addValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
+ break;
+ }
+
+ void addHeader(string header) => inspectorText.AddParagraph($"{header}: ", s =>
+ {
+ s.Padding = new MarginPadding { Top = 2 };
+ s.Font = s.Font.With(size: 12);
+ s.Colour = colourProvider.Content2;
+ });
+
+ void addValue(string value) => inspectorText.AddParagraph(value, s =>
+ {
+ s.Font = s.Font.With(weight: FontWeight.SemiBold);
+ s.Colour = colourProvider.Content1;
+ });
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs
index 9e4469bf25..733610c040 100644
--- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs
+++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs
@@ -24,5 +24,22 @@ namespace osu.Game.Rulesets.Mods
MaxValue = 2,
Precision = 0.01,
};
+
+ public override double ScoreMultiplier
+ {
+ get
+ {
+ // Round to the nearest multiple of 0.1.
+ double value = (int)(SpeedChange.Value * 10) / 10.0;
+
+ // Offset back to 0.
+ value -= 1;
+
+ // Each 0.1 multiple changes score multiplier by 0.02.
+ value /= 5;
+
+ return 1 + value;
+ }
+ }
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs
index 7d858dca6f..06c7750035 100644
--- a/osu.Game/Rulesets/Mods/ModHalfTime.cs
+++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs
@@ -24,5 +24,19 @@ namespace osu.Game.Rulesets.Mods
MaxValue = 0.99,
Precision = 0.01,
};
+
+ public override double ScoreMultiplier
+ {
+ get
+ {
+ // Round to the nearest multiple of 0.1.
+ double value = (int)(SpeedChange.Value * 10) / 10.0;
+
+ // Offset back to 0.
+ value -= 1;
+
+ return 1 + value;
+ }
+ }
}
}
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index 64fe9c8a86..4f22c0c617 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -30,6 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using osuTK;
diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
index 96b02ee4dc..76d84000f1 100644
--- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
+++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
@@ -25,21 +25,28 @@ namespace osu.Game.Rulesets.UI
///
/// The texture store to be used for the ruleset.
///
+ ///
+ /// Reads textures from the "Textures" folder in ruleset resources.
+ /// If not available locally, lookups will fallback to the global texture store.
+ ///
public TextureStore TextureStore { get; }
///
/// The sample store to be used for the ruleset.
///
///
- /// This is the local sample store pointing to the ruleset sample resources,
- /// the cached sample store () retrieves from
- /// this store and falls back to the parent store if this store doesn't have the requested sample.
+ /// Reads samples from the "Samples" folder in ruleset resources.
+ /// If not available locally, lookups will fallback to the global sample store.
///
public ISampleStore SampleStore { get; }
///
/// The shader manager to be used for the ruleset.
///
+ ///
+ /// Reads shaders from the "Shaders" folder in ruleset resources.
+ /// If not available locally, lookups will fallback to the global shader manager.
+ ///
public ShaderManager ShaderManager { get; }
///
@@ -61,8 +68,7 @@ namespace osu.Game.Rulesets.UI
SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get()));
- ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore(resources, @"Shaders"));
- CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get()));
+ CacheAs(ShaderManager = new RulesetShaderManager(host.Renderer, new NamespacedResourceStore(resources, @"Shaders"), parent.Get()));
RulesetConfigManager = parent.Get().GetConfigFor(ruleset);
if (RulesetConfigManager != null)
@@ -92,7 +98,7 @@ namespace osu.Game.Rulesets.UI
isDisposed = true;
- if (ShaderManager.IsNotNull()) SampleStore.Dispose();
+ if (SampleStore.IsNotNull()) SampleStore.Dispose();
if (TextureStore.IsNotNull()) TextureStore.Dispose();
if (ShaderManager.IsNotNull()) ShaderManager.Dispose();
}
@@ -190,24 +196,36 @@ namespace osu.Game.Rulesets.UI
}
}
- private class FallbackShaderManager : ShaderManager
+ private class RulesetShaderManager : ShaderManager
{
- private readonly ShaderManager primary;
- private readonly ShaderManager fallback;
+ private readonly ShaderManager parent;
- public FallbackShaderManager(IRenderer renderer, ShaderManager primary, ShaderManager fallback)
- : base(renderer, new ResourceStore())
+ public RulesetShaderManager(IRenderer renderer, NamespacedResourceStore rulesetResources, ShaderManager parent)
+ : base(renderer, rulesetResources)
{
- this.primary = primary;
- this.fallback = fallback;
+ this.parent = parent;
}
- public override byte[]? LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name);
+ // When the debugger is attached, exceptions are expensive.
+ // Manually work around this by caching failed lookups and falling back straight to parent.
+ private readonly HashSet<(string, string)> failedLookups = new HashSet<(string, string)>();
- protected override void Dispose(bool disposing)
+ public override IShader Load(string vertex, string fragment)
{
- base.Dispose(disposing);
- if (primary.IsNotNull()) primary.Dispose();
+ if (!failedLookups.Contains((vertex, fragment)))
+ {
+ try
+ {
+ return base.Load(vertex, fragment);
+ }
+ catch
+ {
+ // Shader lookup is very non-standard. Rather than returning null on missing shaders, exceptions are thrown.
+ failedLookups.Add((vertex, fragment));
+ }
+ }
+
+ return parent.Load(vertex, fragment);
}
}
}
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 7bf0482673..2ae54a3afe 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -19,7 +19,7 @@ using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using static osu.Game.Input.Handlers.ReplayInputHandler;
@@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.UI
.Select(b => b.GetAction())
.Distinct()
.OrderBy(action => action)
- .Select(action => new KeyCounterAction(action)));
+ .Select(action => new KeyCounterActionTrigger(action)));
}
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler
@@ -179,11 +179,14 @@ namespace osu.Game.Rulesets.UI
{
}
- public bool OnPressed(KeyBindingPressEvent e) => Target.Children.OfType>().Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
+ public bool OnPressed(KeyBindingPressEvent e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger)
+ .Select(c => (KeyCounterActionTrigger)c.Trigger)
+ .Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
public void OnReleased(KeyBindingReleaseEvent e)
{
- foreach (var c in Target.Children.OfType>())
+ foreach (var c
+ in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger).Select(c => (KeyCounterActionTrigger)c.Trigger))
c.OnReleased(e.Action, Clock.Rate >= 0);
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs
index 0f702e1c49..305f5bf3c4 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs
@@ -7,14 +7,15 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
-using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osuTK;
using osuTK.Graphics;
+using Key = osuTK.Input.Key;
namespace osu.Game.Screens.Edit.Compose.Components
{
@@ -26,6 +27,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private SpriteIcon icon;
+ private const float snap_step = 15;
+
private readonly Bindable cumulativeRotation = new Bindable();
[Resolved]
@@ -50,18 +53,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
});
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- cumulativeRotation.BindValueChanged(_ => updateTooltipText(), true);
- }
-
protected override void UpdateHoverState()
{
base.UpdateHoverState();
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
}
+ private float rawCumulativeRotation;
+
protected override bool OnDragStart(DragStartEvent e)
{
bool handle = base.OnDragStart(e);
@@ -74,21 +73,36 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
base.OnDrag(e);
- float instantaneousAngle = convertDragEventToAngleOfRotation(e);
- cumulativeRotation.Value += instantaneousAngle;
+ rawCumulativeRotation += convertDragEventToAngleOfRotation(e);
- if (cumulativeRotation.Value < -180)
- cumulativeRotation.Value += 360;
- else if (cumulativeRotation.Value > 180)
- cumulativeRotation.Value -= 360;
+ applyRotation(shouldSnap: e.ShiftPressed);
+ }
- HandleRotate?.Invoke(instantaneousAngle);
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight))
+ {
+ applyRotation(shouldSnap: true);
+ return true;
+ }
+
+ return base.OnKeyDown(e);
+ }
+
+ protected override void OnKeyUp(KeyUpEvent e)
+ {
+ base.OnKeyUp(e);
+
+ if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight))
+ applyRotation(shouldSnap: false);
}
protected override void OnDragEnd(DragEndEvent e)
{
base.OnDragEnd(e);
cumulativeRotation.Value = null;
+ rawCumulativeRotation = 0;
+ TooltipText = default;
}
private float convertDragEventToAngleOfRotation(DragEvent e)
@@ -100,9 +114,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
return (endAngle - startAngle) * 180 / MathF.PI;
}
- private void updateTooltipText()
+ private void applyRotation(bool shouldSnap)
{
- TooltipText = cumulativeRotation.Value?.ToLocalisableString("0.0°") ?? default;
+ float oldRotation = cumulativeRotation.Value ?? 0;
+
+ float newRotation = shouldSnap ? snap(rawCumulativeRotation, snap_step) : rawCumulativeRotation;
+ newRotation = (newRotation - 180) % 360 + 180;
+
+ cumulativeRotation.Value = newRotation;
+
+ HandleRotate?.Invoke(newRotation - oldRotation);
+ TooltipText = shouldSnap ? EditorStrings.RotationSnapped(newRotation) : EditorStrings.RotationUnsnapped(newRotation);
}
+
+ private float snap(float value, float step) => MathF.Round(value / step) * step;
}
}
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index b70c1f7ddf..372cfe748e 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -130,6 +130,8 @@ namespace osu.Game.Screens
loadTargets.Add(manager.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE));
+ loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"));
+
loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE));
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
index 207b9c378b..66acd6d1b0 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
@@ -73,11 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private OsuSpriteText typeLabel = null!;
private LoadingLayer loadingLayer = null!;
- public void SelectBeatmap()
- {
- if (matchSubScreen.IsCurrentScreen())
- matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room));
- }
+ public void SelectBeatmap() => selectBeatmapButton.TriggerClick();
[Resolved]
private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!;
@@ -97,6 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private IDisposable? applyingSettingsOperation;
private Drawable playlistContainer = null!;
private DrawableRoomPlaylist drawablePlaylist = null!;
+ private RoundedButton selectBeatmapButton = null!;
public MatchSettings(Room room)
{
@@ -275,12 +272,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.X,
Height = DrawableRoomPlaylistItem.HEIGHT
},
- new RoundedButton
+ selectBeatmapButton = new RoundedButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
Text = "Select beatmap",
- Action = SelectBeatmap
+ Action = () =>
+ {
+ if (matchSubScreen.IsCurrentScreen())
+ matchSubScreen.Push(new MultiplayerMatchSongSelect(matchSubScreen.Room));
+ }
}
}
}
diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs
index 0214d33549..57bdad079e 100644
--- a/osu.Game/Screens/Play/FailAnimation.cs
+++ b/osu.Game/Screens/Play/FailAnimation.cs
@@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Audio;
-using osu.Framework.Bindables;
-using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using ManagedBass.Fx;
using osu.Framework.Allocation;
-using osu.Framework.Audio.Sample;
+using osu.Framework.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -21,6 +19,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -50,8 +49,7 @@ namespace osu.Game.Screens.Play
private const float duration = 2500;
- private ISample? failSample;
- private SampleChannel? failSampleChannel;
+ private SkinnableSound failSample = null!;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
@@ -76,10 +74,10 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio, ISkinSource skin, IBindable beatmap)
+ private void load(AudioManager audio, IBindable beatmap)
{
track = beatmap.Value.Track;
- failSample = skin.GetSample(new SampleInfo(@"Gameplay/failsound"));
+ AddInternal(failSample = new SkinnableSound(new SampleInfo("Gameplay/failsound")));
AddRangeInternal(new Drawable[]
{
@@ -126,7 +124,7 @@ namespace osu.Game.Screens.Play
failHighPassFilter.CutoffTo(300);
failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic);
- failSampleChannel = failSample?.Play();
+ failSample.Play();
track.AddAdjustment(AdjustableProperty.Frequency, trackFreq);
track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment);
@@ -159,7 +157,7 @@ namespace osu.Game.Screens.Play
///
public void Stop()
{
- failSampleChannel?.Stop();
+ failSample.Stop();
removeFilters();
}
diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/HUD/DefaultKeyCounter.cs
similarity index 70%
rename from osu.Game/Screens/Play/KeyCounter.cs
rename to osu.Game/Screens/Play/HUD/DefaultKeyCounter.cs
index 4405542b3b..69a3e53dfc 100644
--- a/osu.Game/Screens/Play/KeyCounter.cs
+++ b/osu.Game/Screens/Play/HUD/DefaultKeyCounter.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -13,70 +11,23 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Screens.Play
+namespace osu.Game.Screens.Play.HUD
{
- public abstract partial class KeyCounter : Container
+ public partial class DefaultKeyCounter : KeyCounter
{
- private Sprite buttonSprite;
- private Sprite glowSprite;
- private Container textLayer;
- private SpriteText countSpriteText;
-
- public bool IsCounting { get; set; } = true;
- private int countPresses;
-
- public int CountPresses
- {
- get => countPresses;
- private set
- {
- if (countPresses != value)
- {
- countPresses = value;
- countSpriteText.Text = value.ToString(@"#,0");
- }
- }
- }
-
- private bool isLit;
-
- public bool IsLit
- {
- get => isLit;
- protected set
- {
- if (isLit != value)
- {
- isLit = value;
- updateGlowSprite(value);
- }
- }
- }
-
- public void Increment()
- {
- if (!IsCounting)
- return;
-
- CountPresses++;
- }
-
- public void Decrement()
- {
- if (!IsCounting)
- return;
-
- CountPresses--;
- }
+ private Sprite buttonSprite = null!;
+ private Sprite glowSprite = null!;
+ private Container textLayer = null!;
+ private SpriteText countSpriteText = null!;
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
public Color4 KeyUpTextColor { get; set; } = Color4.White;
public double FadeTime { get; set; }
- protected KeyCounter(string name)
+ public DefaultKeyCounter(InputTrigger trigger)
+ : base(trigger)
{
- Name = name;
}
[BackgroundDependencyLoader(true)]
@@ -116,7 +67,7 @@ namespace osu.Game.Screens.Play
},
countSpriteText = new OsuSpriteText
{
- Text = CountPresses.ToString(@"#,0"),
+ Text = CountPresses.Value.ToString(@"#,0"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
@@ -130,6 +81,9 @@ namespace osu.Game.Screens.Play
// so the size can be changing between buttonSprite and glowSprite.
Height = buttonSprite.DrawHeight;
Width = buttonSprite.DrawWidth;
+
+ IsActive.BindValueChanged(e => updateGlowSprite(e.NewValue), true);
+ CountPresses.BindValueChanged(e => countSpriteText.Text = e.NewValue.ToString(@"#,0"), true);
}
private void updateGlowSprite(bool show)
diff --git a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs
new file mode 100644
index 0000000000..14d7f56093
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs
@@ -0,0 +1,83 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ public partial class DefaultKeyCounterDisplay : KeyCounterDisplay
+ {
+ private const int duration = 100;
+ private const double key_fade_time = 80;
+
+ private readonly FillFlowContainer keyFlow;
+
+ public override IEnumerable Counters => keyFlow;
+
+ public DefaultKeyCounterDisplay()
+ {
+ InternalChild = keyFlow = new FillFlowContainer
+ {
+ Direction = FillDirection.Horizontal,
+ AutoSizeAxes = Axes.Both,
+ Alpha = 0,
+ };
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Don't use autosize as it will shrink to zero when KeyFlow is hidden.
+ // In turn this can cause the display to be masked off screen and never become visible again.
+ Size = keyFlow.Size;
+ }
+
+ public override void Add(InputTrigger trigger) =>
+ keyFlow.Add(new DefaultKeyCounter(trigger)
+ {
+ FadeTime = key_fade_time,
+ KeyDownTextColor = KeyDownTextColor,
+ KeyUpTextColor = KeyUpTextColor,
+ });
+
+ protected override void UpdateVisibility() =>
+ // Isolate changing visibility of the key counters from fading this component.
+ keyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
+
+ private Color4 keyDownTextColor = Color4.DarkGray;
+
+ public Color4 KeyDownTextColor
+ {
+ get => keyDownTextColor;
+ set
+ {
+ if (value != keyDownTextColor)
+ {
+ keyDownTextColor = value;
+ foreach (var child in keyFlow)
+ child.KeyDownTextColor = value;
+ }
+ }
+ }
+
+ private Color4 keyUpTextColor = Color4.White;
+
+ public Color4 KeyUpTextColor
+ {
+ get => keyUpTextColor;
+ set
+ {
+ if (value != keyUpTextColor)
+ {
+ keyUpTextColor = value;
+ foreach (var child in keyFlow)
+ child.KeyUpTextColor = value;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/HUD/InputTrigger.cs b/osu.Game/Screens/Play/HUD/InputTrigger.cs
new file mode 100644
index 0000000000..b57f2cdf91
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/InputTrigger.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ ///
+ /// An event trigger which can be used with to create visual tracking of button/key presses.
+ ///
+ public abstract partial class InputTrigger : Component
+ {
+ ///
+ /// Callback to invoke when the associated input has been activated.
+ ///
+ /// Whether gameplay is progressing in the forward direction time-wise.
+ public delegate void OnActivateCallback(bool forwardPlayback);
+
+ ///
+ /// Callback to invoke when the associated input has been deactivated.
+ ///
+ /// Whether gameplay is progressing in the forward direction time-wise.
+ public delegate void OnDeactivateCallback(bool forwardPlayback);
+
+ public event OnActivateCallback? OnActivate;
+ public event OnDeactivateCallback? OnDeactivate;
+
+ protected InputTrigger(string name)
+ {
+ Name = name;
+ }
+
+ protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback);
+
+ protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback);
+ }
+}
diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs
new file mode 100644
index 0000000000..2a4ab1993a
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ ///
+ /// An individual key display which is intended to be displayed within a .
+ ///
+ public abstract partial class KeyCounter : Container
+ {
+ ///
+ /// The which activates and deactivates this .
+ ///
+ public readonly InputTrigger Trigger;
+
+ ///
+ /// Whether the actions reported by should be counted.
+ ///
+ public Bindable IsCounting { get; } = new BindableBool(true);
+
+ private readonly Bindable countPresses = new BindableInt
+ {
+ MinValue = 0
+ };
+
+ ///
+ /// The current count of registered key presses.
+ ///
+ public IBindable CountPresses => countPresses;
+
+ private readonly Container content;
+
+ protected override Container Content => content;
+
+ ///
+ /// Whether this is currently in the "activated" state because the associated key is currently pressed.
+ ///
+ protected readonly Bindable IsActive = new BindableBool();
+
+ protected KeyCounter(InputTrigger trigger)
+ {
+ InternalChildren = new Drawable[]
+ {
+ content = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ Trigger = trigger,
+ };
+
+ Trigger.OnActivate += Activate;
+ Trigger.OnDeactivate += Deactivate;
+
+ Name = trigger.Name;
+ }
+
+ private void increment()
+ {
+ if (!IsCounting.Value)
+ return;
+
+ countPresses.Value++;
+ }
+
+ private void decrement()
+ {
+ if (!IsCounting.Value)
+ return;
+
+ countPresses.Value--;
+ }
+
+ protected virtual void Activate(bool forwardPlayback = true)
+ {
+ IsActive.Value = true;
+ if (forwardPlayback)
+ increment();
+ }
+
+ protected virtual void Deactivate(bool forwardPlayback = true)
+ {
+ IsActive.Value = false;
+ if (!forwardPlayback)
+ decrement();
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ Trigger.OnActivate -= Activate;
+ Trigger.OnDeactivate -= Deactivate;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs
similarity index 70%
rename from osu.Game/Screens/Play/KeyCounterAction.cs
rename to osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs
index 900d9bcd0e..e5951a8bf4 100644
--- a/osu.Game/Screens/Play/KeyCounterAction.cs
+++ b/osu.Game/Screens/Play/HUD/KeyCounterActionTrigger.cs
@@ -1,18 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
-namespace osu.Game.Screens.Play
+namespace osu.Game.Screens.Play.HUD
{
- public partial class KeyCounterAction : KeyCounter
+ public partial class KeyCounterActionTrigger : InputTrigger
where T : struct
{
public T Action { get; }
- public KeyCounterAction(T action)
+ public KeyCounterActionTrigger(T action)
: base($"B{(int)(object)action + 1}")
{
Action = action;
@@ -23,9 +21,7 @@ namespace osu.Game.Screens.Play
if (!EqualityComparer.Default.Equals(action, Action))
return false;
- IsLit = true;
- if (forwards)
- Increment();
+ Activate(forwards);
return false;
}
@@ -34,9 +30,7 @@ namespace osu.Game.Screens.Play
if (!EqualityComparer.Default.Equals(action, Action))
return;
- IsLit = false;
- if (!forwards)
- Decrement();
+ Deactivate(forwards);
}
}
}
diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs
new file mode 100644
index 0000000000..49c0da6793
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs
@@ -0,0 +1,109 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Events;
+using osu.Game.Configuration;
+using osuTK;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ ///
+ /// A flowing display of all gameplay keys. Individual keys can be added using implementations.
+ ///
+ public abstract partial class KeyCounterDisplay : CompositeDrawable
+ {
+ ///
+ /// Whether the key counter should be visible regardless of the configuration value.
+ /// This is true by default, but can be changed.
+ ///
+ public Bindable AlwaysVisible { get; } = new Bindable(true);
+
+ ///
+ /// The s contained in this .
+ ///
+ public abstract IEnumerable Counters { get; }
+
+ ///
+ /// Whether the actions reported by all s within this should be counted.
+ ///
+ public Bindable IsCounting { get; } = new BindableBool(true);
+
+ protected readonly Bindable ConfigVisibility = new Bindable();
+
+ protected abstract void UpdateVisibility();
+
+ private Receptor? receptor;
+
+ public void SetReceptor(Receptor receptor)
+ {
+ if (this.receptor != null)
+ throw new InvalidOperationException("Cannot set a new receptor when one is already active");
+
+ this.receptor = receptor;
+ }
+
+ ///
+ /// Add a to this display.
+ ///
+ public abstract void Add(InputTrigger trigger);
+
+ ///
+ /// Add a range of to this display.
+ ///
+ public void AddRange(IEnumerable triggers) => triggers.ForEach(Add);
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
+ ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
+ }
+
+ public override bool HandleNonPositionalInput => receptor == null;
+
+ public override bool HandlePositionalInput => receptor == null;
+
+ public partial class Receptor : Drawable
+ {
+ protected readonly KeyCounterDisplay Target;
+
+ public Receptor(KeyCounterDisplay target)
+ {
+ RelativeSizeAxes = Axes.Both;
+ Depth = float.MinValue;
+ Target = target;
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ protected override bool Handle(UIEvent e)
+ {
+ switch (e)
+ {
+ case KeyDownEvent:
+ case KeyUpEvent:
+ case MouseDownEvent:
+ case MouseUpEvent:
+ return Target.InternalChildren.Any(c => c.TriggerEvent(e));
+ }
+
+ return base.Handle(e);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/HUD/KeyCounterKeyboardTrigger.cs
similarity index 70%
rename from osu.Game/Screens/Play/KeyCounterKeyboard.cs
rename to osu.Game/Screens/Play/HUD/KeyCounterKeyboardTrigger.cs
index c5c8b7eeae..3052c1e666 100644
--- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs
+++ b/osu.Game/Screens/Play/HUD/KeyCounterKeyboardTrigger.cs
@@ -1,18 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Input.Events;
using osuTK.Input;
-namespace osu.Game.Screens.Play
+namespace osu.Game.Screens.Play.HUD
{
- public partial class KeyCounterKeyboard : KeyCounter
+ public partial class KeyCounterKeyboardTrigger : InputTrigger
{
public Key Key { get; }
- public KeyCounterKeyboard(Key key)
+ public KeyCounterKeyboardTrigger(Key key)
: base(key.ToString())
{
Key = key;
@@ -22,8 +20,7 @@ namespace osu.Game.Screens.Play
{
if (e.Key == Key)
{
- IsLit = true;
- Increment();
+ Activate();
}
return base.OnKeyDown(e);
@@ -31,7 +28,9 @@ namespace osu.Game.Screens.Play
protected override void OnKeyUp(KeyUpEvent e)
{
- if (e.Key == Key) IsLit = false;
+ if (e.Key == Key)
+ Deactivate();
+
base.OnKeyUp(e);
}
}
diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/HUD/KeyCounterMouseTrigger.cs
similarity index 79%
rename from osu.Game/Screens/Play/KeyCounterMouse.cs
rename to osu.Game/Screens/Play/HUD/KeyCounterMouseTrigger.cs
index cf9c7c029f..369aaa9f74 100644
--- a/osu.Game/Screens/Play/KeyCounterMouse.cs
+++ b/osu.Game/Screens/Play/HUD/KeyCounterMouseTrigger.cs
@@ -1,19 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Input.Events;
-using osuTK.Input;
using osuTK;
+using osuTK.Input;
-namespace osu.Game.Screens.Play
+namespace osu.Game.Screens.Play.HUD
{
- public partial class KeyCounterMouse : KeyCounter
+ public partial class KeyCounterMouseTrigger : InputTrigger
{
public MouseButton Button { get; }
- public KeyCounterMouse(MouseButton button)
+ public KeyCounterMouseTrigger(MouseButton button)
: base(getStringRepresentation(button))
{
Button = button;
@@ -39,17 +37,16 @@ namespace osu.Game.Screens.Play
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button == Button)
- {
- IsLit = true;
- Increment();
- }
+ Activate();
return base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseUpEvent e)
{
- if (e.Button == Button) IsLit = false;
+ if (e.Button == Button)
+ Deactivate();
+
base.OnMouseUp(e);
}
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index c8d06b82e8..9f050a07bd 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -331,7 +331,7 @@ namespace osu.Game.Screens.Play
ShowHealth = { BindTarget = ShowHealthBar }
};
- protected KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
+ protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs
deleted file mode 100644
index bb50d4a539..0000000000
--- a/osu.Game/Screens/Play/KeyCounterDisplay.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using System;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Events;
-using osu.Game.Configuration;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Screens.Play
-{
- public partial class KeyCounterDisplay : Container
- {
- private const int duration = 100;
- private const double key_fade_time = 80;
-
- private readonly Bindable configVisibility = new Bindable();
-
- protected readonly FillFlowContainer KeyFlow;
-
- protected override Container Content => KeyFlow;
-
- ///
- /// Whether the key counter should be visible regardless of the configuration value.
- /// This is true by default, but can be changed.
- ///
- public readonly Bindable AlwaysVisible = new Bindable(true);
-
- public KeyCounterDisplay()
- {
- InternalChild = KeyFlow = new FillFlowContainer
- {
- Direction = FillDirection.Horizontal,
- AutoSizeAxes = Axes.Both,
- Alpha = 0,
- };
- }
-
- protected override void Update()
- {
- base.Update();
-
- // Don't use autosize as it will shrink to zero when KeyFlow is hidden.
- // In turn this can cause the display to be masked off screen and never become visible again.
- Size = KeyFlow.Size;
- }
-
- public override void Add(KeyCounter key)
- {
- ArgumentNullException.ThrowIfNull(key);
-
- base.Add(key);
- key.IsCounting = IsCounting;
- key.FadeTime = key_fade_time;
- key.KeyDownTextColor = KeyDownTextColor;
- key.KeyUpTextColor = KeyUpTextColor;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
- {
- config.BindWith(OsuSetting.KeyOverlay, configVisibility);
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- AlwaysVisible.BindValueChanged(_ => updateVisibility());
- configVisibility.BindValueChanged(_ => updateVisibility(), true);
- }
-
- private bool isCounting = true;
-
- public bool IsCounting
- {
- get => isCounting;
- set
- {
- if (value == isCounting) return;
-
- isCounting = value;
- foreach (var child in Children)
- child.IsCounting = value;
- }
- }
-
- private Color4 keyDownTextColor = Color4.DarkGray;
-
- public Color4 KeyDownTextColor
- {
- get => keyDownTextColor;
- set
- {
- if (value != keyDownTextColor)
- {
- keyDownTextColor = value;
- foreach (var child in Children)
- child.KeyDownTextColor = value;
- }
- }
- }
-
- private Color4 keyUpTextColor = Color4.White;
-
- public Color4 KeyUpTextColor
- {
- get => keyUpTextColor;
- set
- {
- if (value != keyUpTextColor)
- {
- keyUpTextColor = value;
- foreach (var child in Children)
- child.KeyUpTextColor = value;
- }
- }
- }
-
- private void updateVisibility() =>
- // Isolate changing visibility of the key counters from fading this component.
- KeyFlow.FadeTo(AlwaysVisible.Value || configVisibility.Value ? 1 : 0, duration);
-
- public override bool HandleNonPositionalInput => receptor == null;
- public override bool HandlePositionalInput => receptor == null;
-
- private Receptor receptor;
-
- public void SetReceptor(Receptor receptor)
- {
- if (this.receptor != null)
- throw new InvalidOperationException("Cannot set a new receptor when one is already active");
-
- this.receptor = receptor;
- }
-
- public partial class Receptor : Drawable
- {
- protected readonly KeyCounterDisplay Target;
-
- public Receptor(KeyCounterDisplay target)
- {
- RelativeSizeAxes = Axes.Both;
- Depth = float.MinValue;
- Target = target;
- }
-
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
-
- protected override bool Handle(UIEvent e)
- {
- switch (e)
- {
- case KeyDownEvent:
- case KeyUpEvent:
- case MouseDownEvent:
- case MouseUpEvent:
- return Target.Children.Any(c => c.TriggerEvent(e));
- }
-
- return base.Handle(e);
- }
- }
- }
-}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 45a671fb89..5174adfc06 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -358,14 +358,10 @@ namespace osu.Game.Screens.Play
ScoreProcessor.RevertResult(r);
};
- DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded =>
- {
- if (storyboardEnded.NewValue)
- progressToResults(true);
- };
+ DimmableStoryboard.HasStoryboardEnded.ValueChanged += _ => checkScoreCompleted();
// Bind the judgement processors to ourselves
- ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged);
+ ScoreProcessor.HasCompleted.BindValueChanged(_ => checkScoreCompleted());
HealthProcessor.Failed += onFail;
// Provide judgement processors to mods after they're loaded so that they're on the gameplay clock,
@@ -441,8 +437,11 @@ namespace osu.Game.Screens.Play
},
KeyCounter =
{
+ IsCounting =
+ {
+ Value = false
+ },
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
- IsCounting = false
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre
@@ -482,7 +481,7 @@ namespace osu.Game.Screens.Play
{
updateGameplayState();
updatePauseOnFocusLostState();
- HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
+ HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue;
}
private void updateGameplayState()
@@ -706,19 +705,20 @@ namespace osu.Game.Screens.Play
///
/// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime.
///
- /// Thrown if this method is called more than once without changing state.
- private void scoreCompletionChanged(ValueChangedEvent completed)
+ private void checkScoreCompleted()
{
// If this player instance is in the middle of an exit, don't attempt any kind of state update.
if (!this.IsCurrentScreen())
return;
- // Special case to handle rewinding post-completion. This is the only way already queued forward progress can be cancelled.
- // TODO: Investigate whether this can be moved to a RewindablePlayer subclass or similar.
- // Currently, even if this scenario is hit, prepareScoreForDisplay has already been queued (and potentially run).
- // In scenarios where rewinding is possible (replay, spectating) this is a non-issue as no submission/import work is done,
- // but it still doesn't feel right that this exists here.
- if (!completed.NewValue)
+ // Handle cases of arriving at this method when not in a completed state.
+ // - When a storyboard completion triggered this call earlier than gameplay finishes.
+ // - When a replay has been rewound before a queued resultsDisplayDelegate has run.
+ //
+ // Currently, even if this scenario is hit, prepareAndImportScoreAsync has already been queued (and potentially run).
+ // In the scenarios above, this is a non-issue, but it still feels a bit convoluted to have to cancel in this method.
+ // Maybe this can be improved with further refactoring.
+ if (!ScoreProcessor.HasCompleted.Value)
{
resultsDisplayDelegate?.Cancel();
resultsDisplayDelegate = null;
@@ -742,12 +742,12 @@ namespace osu.Game.Screens.Play
if (!Configuration.ShowResults)
return;
- bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value;
+ bool storyboardStillRunning = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value;
- if (storyboardHasOutro)
+ // If the current beatmap has a storyboard, this method will be called again on storyboard completion.
+ // Alternatively, the user may press the outro skip button, forcing immediate display of the results screen.
+ if (storyboardStillRunning)
{
- // if the current beatmap has a storyboard, the progression to results will be handled by the storyboard ending
- // or the user pressing the skip outro button.
skipOutroOverlay.Show();
return;
}
@@ -793,6 +793,8 @@ namespace osu.Game.Screens.Play
// This player instance may already be in the process of exiting.
return;
+ Debug.Assert(ScoreProcessor.Rank.Value != ScoreRank.F);
+
this.Push(CreateResults(prepareScoreForDisplayTask.GetResultSafely()));
}, Time.Current + delay, 50);
@@ -1112,7 +1114,7 @@ namespace osu.Game.Screens.Play
GameplayState.HasQuit = true;
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
- if (prepareScoreForDisplayTask == null)
+ if (prepareScoreForDisplayTask == null && DrawableRuleset.ReplayScore == null)
ScoreProcessor.FailScore(Score.ScoreInfo);
}
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 4f7e4add32..30ae5ee5aa 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -67,6 +67,8 @@ namespace osu.Game.Screens.Play
private OsuScrollContainer settingsScroll = null!;
+ private Bindable showStoryboards = null!;
+
private bool backgroundBrightnessReduction;
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
@@ -149,10 +151,11 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader]
- private void load(SessionStatics sessionStatics, AudioManager audio)
+ private void load(SessionStatics sessionStatics, AudioManager audio, OsuConfigManager config)
{
muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce);
batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce);
+ showStoryboards = config.GetBindable(OsuSetting.ShowStoryboard);
const float padding = 25;
@@ -463,7 +466,10 @@ namespace osu.Game.Screens.Play
// only show if the warning was created (i.e. the beatmap needs it)
// and this is not a restart of the map (the warning expires after first load).
- if (epilepsyWarning?.IsAlive == true)
+ //
+ // note the late check of storyboard enable as the user may have just changed it
+ // from the settings on the loader screen.
+ if (epilepsyWarning?.IsAlive == true && showStoryboards.Value)
{
const double epilepsy_display_length = 3000;
@@ -483,6 +489,7 @@ namespace osu.Game.Screens.Play
{
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic);
+ epilepsyWarning?.Expire();
}
pushSequence.Schedule(() =>
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index dd3f13373d..d5ddc375b7 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select
public float BleedBottom { get; set; }
///
- /// Triggered when the loaded change and are completely loaded.
+ /// Triggered when finish loading, or are subsequently changed.
///
public Action? BeatmapSetsChanged;
@@ -358,6 +358,8 @@ namespace osu.Game.Screens.Select
if (!Scroll.UserScrolling)
ScrollToSelected(true);
+
+ BeatmapSetsChanged?.Invoke();
});
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs
index 0158c47ea3..76c2c4d7ec 100644
--- a/osu.Game/Skinning/PoolableSkinnableSample.cs
+++ b/osu.Game/Skinning/PoolableSkinnableSample.cs
@@ -9,7 +9,6 @@ using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
@@ -70,20 +69,6 @@ namespace osu.Game.Skinning
updateSample();
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- CurrentSkin.SourceChanged += skinChangedImmediate;
- }
-
- private void skinChangedImmediate()
- {
- // Clean up the previous sample immediately on a source change.
- // This avoids a potential call to Play() of an already disposed sample (samples are disposed along with the skin, but SkinChanged is scheduled).
- clearPreviousSamples();
- }
-
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin);
@@ -109,6 +94,8 @@ namespace osu.Game.Skinning
private void updateSample()
{
+ clearPreviousSamples();
+
if (sampleInfo == null)
return;
@@ -129,6 +116,8 @@ namespace osu.Game.Skinning
///
public void Play()
{
+ FlushPendingSkinChanges();
+
if (Sample == null)
return;
@@ -172,14 +161,6 @@ namespace osu.Game.Skinning
}
}
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (CurrentSkin.IsNotNull())
- CurrentSkin.SourceChanged -= skinChangedImmediate;
- }
-
#region Re-expose AudioContainer
public BindableNumber Volume => sampleContainer.Volume;
diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs
index cef1db4bc0..c7b33dc539 100644
--- a/osu.Game/Skinning/SkinReloadableDrawable.cs
+++ b/osu.Game/Skinning/SkinReloadableDrawable.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Pooling;
+using osu.Framework.Threading;
namespace osu.Game.Skinning
{
@@ -14,6 +15,8 @@ namespace osu.Game.Skinning
///
public abstract partial class SkinReloadableDrawable : PoolableDrawable
{
+ private ScheduledDelegate? pendingSkinChange;
+
///
/// Invoked when has changed.
///
@@ -31,21 +34,30 @@ namespace osu.Game.Skinning
CurrentSkin.SourceChanged += onChange;
}
- private void onChange() =>
- // schedule required to avoid calls after disposed.
- // note that this has the side-effect of components only performing a skin change when they are alive.
- Scheduler.AddOnce(skinChanged);
-
protected override void LoadAsyncComplete()
{
base.LoadAsyncComplete();
skinChanged();
}
- private void skinChanged()
+ ///
+ /// Force any pending calls to be performed immediately.
+ ///
+ ///
+ /// When a skin change occurs, the handling provided by this class is scheduled.
+ /// In some cases, such a sample playback, this can result in the sample being played
+ /// just before it is updated to a potentially different sample.
+ ///
+ /// Calling this method will ensure any pending update operations are run immediately.
+ /// It is recommended to call this before consuming the result of skin changes for anything non-drawable.
+ ///
+ protected void FlushPendingSkinChanges()
{
- SkinChanged(CurrentSkin);
- OnSkinChanged?.Invoke();
+ if (pendingSkinChange == null)
+ return;
+
+ pendingSkinChange.RunTask();
+ pendingSkinChange = null;
}
///
@@ -56,6 +68,22 @@ namespace osu.Game.Skinning
{
}
+ private void onChange()
+ {
+ // schedule required to avoid calls after disposed.
+ // note that this has the side-effect of components only performing a skin change when they are alive.
+ pendingSkinChange?.Cancel();
+ pendingSkinChange = Scheduler.Add(skinChanged);
+ }
+
+ private void skinChanged()
+ {
+ SkinChanged(CurrentSkin);
+ OnSkinChanged?.Invoke();
+
+ pendingSkinChange = null;
+ }
+
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index 475b79053a..59b3799e0a 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -115,6 +115,8 @@ namespace osu.Game.Skinning
///
public virtual void Play()
{
+ FlushPendingSkinChanges();
+
samplesContainer.ForEach(c =>
{
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 3de022e88d..b9c6c1df9d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index eb7ba24336..083d8192ea 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -16,6 +16,6 @@
iossimulator-x64
-
+