mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 09:02:58 +08:00
Merge branch 'master' into gd-onlinesetoverlay
This commit is contained in:
commit
1f2c96c8e8
@ -11,7 +11,7 @@
|
||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.327.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.403.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||
|
@ -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
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||
{
|
||||
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,
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
@ -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<OsuAction>
|
||||
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
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<OsuAction> e)
|
||||
{
|
||||
if (e.Action == Action) IsLit = false;
|
||||
if (e.Action == Action)
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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)
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModNightcore : ModNightcore<TaikoHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> 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<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> 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<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> 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<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> 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<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> 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<Beatmap> decoder = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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()
|
||||
{
|
||||
|
4
osu.Game.Tests/Resources/image-specified-as-video.osb
Normal file
4
osu.Game.Tests/Resources/image-specified-as-video.osb
Normal file
@ -0,0 +1,4 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
Video,0,"BG.jpg",0,0
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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<ShaderManager>().LoadRaw(@"sh_TestVertex.vs") != null &&
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs") != null);
|
||||
AddStep("ruleset shaders retrieved without error", () =>
|
||||
{
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs");
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -76,12 +76,12 @@ namespace osu.Game.Tests.Testing
|
||||
}
|
||||
|
||||
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null;
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
|
||||
}
|
||||
|
||||
private class TestRulesetConfigManager : IRulesetConfigManager
|
||||
|
@ -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<BreakInfo>().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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// 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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<EpilepsyWarning>().SingleOrDefault();
|
||||
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault(w => w.IsAlive);
|
||||
|
||||
private partial class TestPlayerLoader : PlayerLoader
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestComboCounterIncrementing()
|
||||
@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// 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);
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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<DrawableSample>().Single().Name, () => Is.EqualTo(sample_lookup));
|
||||
|
||||
AddStep("change source", () =>
|
||||
{
|
||||
skinSource.OverridingSample = new SampleVirtual("new skin");
|
||||
skinSource.TriggerSourceChanged();
|
||||
});
|
||||
|
||||
AddStep("start sample", () => skinnableSound.Play());
|
||||
AddUntilStep("sample updated", () => skinnableSound.ChildrenOfType<DrawableSample>().Single().Name, () => Is.EqualTo("new skin"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
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<DrawableSample>().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<bool> SamplePlaybackDisabled { get; } = new Bindable<bool>();
|
||||
|
||||
public ISample? OverridingSample;
|
||||
|
||||
IBindable<bool> 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<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source?.GetConfig<TLookup, TValue>(lookup);
|
||||
public ISkin FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : source?.FindProvider(lookupFunction);
|
||||
public IEnumerable<ISkin> AllSources => new[] { this }.Concat(source?.AllSources ?? Enumerable.Empty<ISkin>());
|
||||
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<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
where TLookup : notnull
|
||||
where TValue : notnull
|
||||
{
|
||||
return source.GetConfig<TLookup, TValue>(lookup);
|
||||
}
|
||||
|
||||
public ISkin? FindProvider(Func<ISkin, bool> lookupFunction) => lookupFunction(this) ? this : source.FindProvider(lookupFunction);
|
||||
public IEnumerable<ISkin> AllSources => new[] { this }.Concat(source.AllSources);
|
||||
|
||||
public void TriggerSourceChanged()
|
||||
{
|
||||
|
@ -114,6 +114,19 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(OverlayActivation.All)]
|
||||
[TestCase(OverlayActivation.Disabled)]
|
||||
public void TestButtonKeyboardInputRespectsOverlayActivation(OverlayActivation mode)
|
||||
{
|
||||
AddStep($"set activation mode to {mode}", () => toolbar.OverlayActivationMode.Value = mode);
|
||||
AddStep("hide toolbar", () => toolbar.Hide());
|
||||
|
||||
if (mode == OverlayActivation.Disabled)
|
||||
AddAssert("check buttons not accepting input", () => InputManager.NonPositionalInputQueue.OfType<ToolbarButton>().Count(), () => Is.Zero);
|
||||
else
|
||||
AddAssert("check buttons accepting input", () => InputManager.NonPositionalInputQueue.OfType<ToolbarButton>().Count(), () => Is.Not.Zero);
|
||||
}
|
||||
|
||||
[TestCase(OverlayActivation.All)]
|
||||
[TestCase(OverlayActivation.Disabled)]
|
||||
public void TestRespectsOverlayActivation(OverlayActivation mode)
|
||||
|
@ -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
|
||||
|
@ -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<BeatmapListingSortTabControl.BeatmapTabButton>().Single(s => s.Active.Value));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("sort direction is ascending", () => control.SortDirection.Value == SortDirection.Ascending);
|
||||
|
||||
AddStep("click first inactive sort button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().First(s => !s.Active.Value));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||
}
|
||||
|
||||
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
||||
{
|
||||
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
||||
|
@ -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;
|
||||
|
@ -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":
|
||||
|
@ -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://";
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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> SortDirection = new Bindable<SortDirection>();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
private Container panelTarget;
|
||||
private FillFlowContainer<BeatmapCard> 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()
|
||||
|
@ -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()
|
||||
|
@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
protected partial class TabButton : HeaderButton
|
||||
public partial class TabButton : HeaderButton
|
||||
{
|
||||
public readonly BindableBool Active = new BindableBool();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
||||
|
||||
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
||||
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
|
||||
|
||||
private Bindable<ScalingMode> scalingMode = null!;
|
||||
private Bindable<Size> 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<WindowMode>
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.ScreenMode,
|
||||
ItemSource = windowModes,
|
||||
Items = window?.SupportedWindowModes,
|
||||
CanBeShown = { Value = window?.SupportedWindowModes.Count() > 1 },
|
||||
Current = config.GetBindable<WindowMode>(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;
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(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()
|
||||
{
|
||||
|
@ -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<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
|
||||
|
||||
protected ExpandingToolboxContainer RightSideToolboxContainer { get; private set; }
|
||||
|
||||
private ExpandableSlider<double, SizeSlider<double>> 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<double, SizeSlider<double>>
|
||||
{
|
||||
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<double, SizeSlider<double>>
|
||||
{
|
||||
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)
|
||||
|
@ -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<TObject> 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<bool>(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
|
||||
|
146
osu.Game/Rulesets/Edit/HitObjectInspector.cs
Normal file
146
osu.Game/Rulesets/Edit/HitObjectInspector.cs
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -25,21 +25,28 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <summary>
|
||||
/// The texture store to be used for the ruleset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reads textures from the "Textures" folder in ruleset resources.
|
||||
/// If not available locally, lookups will fallback to the global texture store.
|
||||
/// </remarks>
|
||||
public TextureStore TextureStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The sample store to be used for the ruleset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the local sample store pointing to the ruleset sample resources,
|
||||
/// the cached sample store (<see cref="FallbackSampleStore"/>) 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.
|
||||
/// </remarks>
|
||||
public ISampleStore SampleStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The shader manager to be used for the ruleset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reads shaders from the "Shaders" folder in ruleset resources.
|
||||
/// If not available locally, lookups will fallback to the global shader manager.
|
||||
/// </remarks>
|
||||
public ShaderManager ShaderManager { get; }
|
||||
|
||||
/// <summary>
|
||||
@ -61,8 +68,7 @@ namespace osu.Game.Rulesets.UI
|
||||
SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
||||
CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
|
||||
|
||||
ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore<byte[]>(resources, @"Shaders"));
|
||||
CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get<ShaderManager>()));
|
||||
CacheAs(ShaderManager = new RulesetShaderManager(host.Renderer, new NamespacedResourceStore<byte[]>(resources, @"Shaders"), parent.Get<ShaderManager>()));
|
||||
|
||||
RulesetConfigManager = parent.Get<IRulesetConfigCache>().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<byte[]>())
|
||||
public RulesetShaderManager(IRenderer renderer, NamespacedResourceStore<byte[]> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<T>())
|
||||
.Distinct()
|
||||
.OrderBy(action => action)
|
||||
.Select(action => new KeyCounterAction<T>(action)));
|
||||
.Select(action => new KeyCounterActionTrigger<T>(action)));
|
||||
}
|
||||
|
||||
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
||||
@ -179,11 +179,14 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>)
|
||||
.Select(c => (KeyCounterActionTrigger<T>)c.Trigger)
|
||||
.Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||
{
|
||||
foreach (var c in Target.Children.OfType<KeyCounterAction<T>>())
|
||||
foreach (var c
|
||||
in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>).Select(c => (KeyCounterActionTrigger<T>)c.Trigger))
|
||||
c.OnReleased(e.Action, Clock.Rate >= 0);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.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<WorkingBeatmap> beatmap)
|
||||
private void load(AudioManager audio, IBindable<WorkingBeatmap> 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
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
failSampleChannel?.Stop();
|
||||
failSample.Stop();
|
||||
removeFilters();
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#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)
|
83
osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs
Normal file
83
osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.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<DefaultKeyCounter> keyFlow;
|
||||
|
||||
public override IEnumerable<KeyCounter> Counters => keyFlow;
|
||||
|
||||
public DefaultKeyCounterDisplay()
|
||||
{
|
||||
InternalChild = keyFlow = new FillFlowContainer<DefaultKeyCounter>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
osu.Game/Screens/Play/HUD/InputTrigger.cs
Normal file
37
osu.Game/Screens/Play/HUD/InputTrigger.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
/// <summary>
|
||||
/// An event trigger which can be used with <see cref="KeyCounter"/> to create visual tracking of button/key presses.
|
||||
/// </summary>
|
||||
public abstract partial class InputTrigger : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback to invoke when the associated input has been activated.
|
||||
/// </summary>
|
||||
/// <param name="forwardPlayback">Whether gameplay is progressing in the forward direction time-wise.</param>
|
||||
public delegate void OnActivateCallback(bool forwardPlayback);
|
||||
|
||||
/// <summary>
|
||||
/// Callback to invoke when the associated input has been deactivated.
|
||||
/// </summary>
|
||||
/// <param name="forwardPlayback">Whether gameplay is progressing in the forward direction time-wise.</param>
|
||||
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);
|
||||
}
|
||||
}
|
98
osu.Game/Screens/Play/HUD/KeyCounter.cs
Normal file
98
osu.Game/Screens/Play/HUD/KeyCounter.cs
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
/// <summary>
|
||||
/// An individual key display which is intended to be displayed within a <see cref="KeyCounterDisplay"/>.
|
||||
/// </summary>
|
||||
public abstract partial class KeyCounter : Container
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="InputTrigger"/> which activates and deactivates this <see cref="KeyCounter"/>.
|
||||
/// </summary>
|
||||
public readonly InputTrigger Trigger;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the actions reported by <see cref="Trigger"/> should be counted.
|
||||
/// </summary>
|
||||
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
||||
|
||||
private readonly Bindable<int> countPresses = new BindableInt
|
||||
{
|
||||
MinValue = 0
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The current count of registered key presses.
|
||||
/// </summary>
|
||||
public IBindable<int> CountPresses => countPresses;
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
|
||||
/// </summary>
|
||||
protected readonly Bindable<bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class KeyCounterAction<T> : KeyCounter
|
||||
public partial class KeyCounterActionTrigger<T> : 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<T>.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<T>.Default.Equals(action, Action))
|
||||
return;
|
||||
|
||||
IsLit = false;
|
||||
if (!forwards)
|
||||
Decrement();
|
||||
Deactivate(forwards);
|
||||
}
|
||||
}
|
||||
}
|
109
osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs
Normal file
109
osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using 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
|
||||
{
|
||||
/// <summary>
|
||||
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
|
||||
/// </summary>
|
||||
public abstract partial class KeyCounterDisplay : CompositeDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the key counter should be visible regardless of the configuration value.
|
||||
/// This is true by default, but can be changed.
|
||||
/// </summary>
|
||||
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
|
||||
/// </summary>
|
||||
public abstract IEnumerable<KeyCounter> Counters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
|
||||
/// </summary>
|
||||
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
||||
|
||||
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="InputTrigger"/> to this display.
|
||||
/// </summary>
|
||||
public abstract void Add(InputTrigger trigger);
|
||||
|
||||
/// <summary>
|
||||
/// Add a range of <see cref="InputTrigger"/> to this display.
|
||||
/// </summary>
|
||||
public void AddRange(IEnumerable<InputTrigger> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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);
|
||||
}
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -1,172 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using 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<KeyCounter>
|
||||
{
|
||||
private const int duration = 100;
|
||||
private const double key_fade_time = 80;
|
||||
|
||||
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
|
||||
|
||||
protected readonly FillFlowContainer<KeyCounter> KeyFlow;
|
||||
|
||||
protected override Container<KeyCounter> Content => KeyFlow;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the key counter should be visible regardless of the configuration value.
|
||||
/// This is true by default, but can be changed.
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> AlwaysVisible = new Bindable<bool>(true);
|
||||
|
||||
public KeyCounterDisplay()
|
||||
{
|
||||
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -437,8 +437,11 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
KeyCounter =
|
||||
{
|
||||
IsCounting =
|
||||
{
|
||||
Value = false
|
||||
},
|
||||
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
||||
IsCounting = false
|
||||
},
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
@ -478,7 +481,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
updateGameplayState();
|
||||
updatePauseOnFocusLostState();
|
||||
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
|
||||
HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue;
|
||||
}
|
||||
|
||||
private void updateGameplayState()
|
||||
@ -1111,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);
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private OsuScrollContainer settingsScroll = null!;
|
||||
|
||||
private Bindable<bool> 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<bool>(Static.MutedAudioNotificationShownOnce);
|
||||
batteryWarningShownOnce = sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce);
|
||||
showStoryboards = config.GetBindable<bool>(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(() =>
|
||||
|
@ -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
|
||||
/// </summary>
|
||||
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<double> Volume => sampleContainer.Volume;
|
||||
|
@ -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
|
||||
/// </summary>
|
||||
public abstract partial class SkinReloadableDrawable : PoolableDrawable
|
||||
{
|
||||
private ScheduledDelegate? pendingSkinChange;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when <see cref="CurrentSkin"/> has changed.
|
||||
/// </summary>
|
||||
@ -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()
|
||||
/// <summary>
|
||||
/// Force any pending <see cref="SkinChanged"/> calls to be performed immediately.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
protected void FlushPendingSkinChanges()
|
||||
{
|
||||
SkinChanged(CurrentSkin);
|
||||
OnSkinChanged?.Invoke();
|
||||
if (pendingSkinChange == null)
|
||||
return;
|
||||
|
||||
pendingSkinChange.RunTask();
|
||||
pendingSkinChange = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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);
|
||||
|
@ -115,6 +115,8 @@ namespace osu.Game.Skinning
|
||||
/// </summary>
|
||||
public virtual void Play()
|
||||
{
|
||||
FlushPendingSkinChanges();
|
||||
|
||||
samplesContainer.ForEach(c =>
|
||||
{
|
||||
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
|
||||
|
@ -36,8 +36,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.20.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.327.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.320.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.403.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.402.0" />
|
||||
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
|
@ -16,6 +16,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.327.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.403.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user