mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 22:22:54 +08:00
Merge branch 'master' into beatmap-collection-inteface-types
This commit is contained in:
commit
726a0cc091
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1108.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1108.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
|
@ -10,9 +10,13 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
|
using osu.Game.Screens.Edit.GameplayTest;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
@ -58,6 +62,42 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
AddStep("exit player", () => editorPlayer.Exit());
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
AddUntilStep("background has correct params", () =>
|
||||||
|
{
|
||||||
|
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
|
||||||
|
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGameplayTestWhenTrackRunning()
|
||||||
|
{
|
||||||
|
AddStep("start track", () => EditorClock.Start());
|
||||||
|
AddAssert("sample playback enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
||||||
|
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
AddAssert("editor track stopped", () => !EditorClock.IsRunning);
|
||||||
|
AddAssert("sample playback disabled", () => Editor.SamplePlaybackDisabled.Value);
|
||||||
|
|
||||||
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
AddUntilStep("background has correct params", () =>
|
||||||
|
{
|
||||||
|
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
|
||||||
|
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start track", () => EditorClock.Start());
|
||||||
|
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -111,6 +151,35 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning);
|
AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSharedClockState()
|
||||||
|
{
|
||||||
|
AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000));
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
|
||||||
|
GameplayClockContainer gameplayClockContainer = null;
|
||||||
|
AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First());
|
||||||
|
AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning);
|
||||||
|
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
|
||||||
|
|
||||||
|
double timeAtPlayerExit = 0;
|
||||||
|
AddWaitStep("wait some", 5);
|
||||||
|
AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime);
|
||||||
|
|
||||||
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
AddAssert("time is past player exit", () => EditorClock.CurrentTime >= timeAtPlayerExit);
|
||||||
|
}
|
||||||
|
|
||||||
public override void TearDownSteps()
|
public override void TearDownSteps()
|
||||||
{
|
{
|
||||||
base.TearDownSteps();
|
base.TearDownSteps();
|
||||||
|
@ -0,0 +1,171 @@
|
|||||||
|
// 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 Humanizer;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneHitObjectDifficultyPointAdjustments : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("add test objects", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
Position = (OsuPlayfield.BASE_SIZE - new Vector2(0, 100)) / 2,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(0, 100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0)),
|
||||||
|
new PathControlPoint(new Vector2(100, 0))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DifficultyControlPoint = new DifficultyControlPoint
|
||||||
|
{
|
||||||
|
SliderVelocity = 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleSelection()
|
||||||
|
{
|
||||||
|
clickDifficultyPiece(0);
|
||||||
|
velocityPopoverHasSingleValue(1);
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
// select first object to ensure that difficulty pieces for unselected objects
|
||||||
|
// work independently from selection state.
|
||||||
|
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
|
||||||
|
|
||||||
|
clickDifficultyPiece(1);
|
||||||
|
velocityPopoverHasSingleValue(2);
|
||||||
|
|
||||||
|
setVelocityViaPopover(5);
|
||||||
|
hitObjectHasVelocity(1, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleSelectionWithSameSliderVelocity()
|
||||||
|
{
|
||||||
|
AddStep("unify slider velocity", () =>
|
||||||
|
{
|
||||||
|
foreach (var h in EditorBeatmap.HitObjects)
|
||||||
|
h.DifficultyControlPoint.SliderVelocity = 1.5;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
clickDifficultyPiece(0);
|
||||||
|
velocityPopoverHasSingleValue(1.5);
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
clickDifficultyPiece(1);
|
||||||
|
velocityPopoverHasSingleValue(1.5);
|
||||||
|
|
||||||
|
setVelocityViaPopover(5);
|
||||||
|
hitObjectHasVelocity(0, 5);
|
||||||
|
hitObjectHasVelocity(1, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleSelectionWithDifferentSliderVelocity()
|
||||||
|
{
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
clickDifficultyPiece(0);
|
||||||
|
velocityPopoverHasIndeterminateValue();
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
clickDifficultyPiece(1);
|
||||||
|
velocityPopoverHasIndeterminateValue();
|
||||||
|
|
||||||
|
setVelocityViaPopover(3);
|
||||||
|
hitObjectHasVelocity(0, 3);
|
||||||
|
hitObjectHasVelocity(1, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clickDifficultyPiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () =>
|
||||||
|
{
|
||||||
|
var difficultyPiece = this.ChildrenOfType<DifficultyPointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(difficultyPiece);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void velocityPopoverHasSingleValue(double velocity) => AddUntilStep($"velocity popover has {velocity}", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().SingleOrDefault();
|
||||||
|
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().Single();
|
||||||
|
|
||||||
|
return slider?.Current.Value == velocity;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void velocityPopoverHasIndeterminateValue() => AddUntilStep("velocity popover has indeterminate value", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().SingleOrDefault();
|
||||||
|
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().Single();
|
||||||
|
|
||||||
|
return slider != null && slider.Current.Value == null;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void dismissPopover()
|
||||||
|
{
|
||||||
|
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
|
||||||
|
AddUntilStep("wait for dismiss", () => !this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().Any(popover => popover.IsPresent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVelocityViaPopover(double velocity) => AddStep($"set {velocity} via popover", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().Single();
|
||||||
|
var slider = popover.ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().Single();
|
||||||
|
slider.Current.Value = velocity;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectHasVelocity(int objectIndex, double velocity) => AddAssert($"{objectIndex.ToOrdinalWords()} has velocity {velocity}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
|
return h.DifficultyControlPoint.SliderVelocity == velocity;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,252 @@
|
|||||||
|
// 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 Humanizer;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneHitObjectSamplePointAdjustments : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("add test objects", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Add(new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
|
||||||
|
SampleControlPoint = new SampleControlPoint
|
||||||
|
{
|
||||||
|
SampleBank = "normal",
|
||||||
|
SampleVolume = 80
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorBeatmap.Add(new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = (OsuPlayfield.BASE_SIZE + new Vector2(100, 0)) / 2,
|
||||||
|
SampleControlPoint = new SampleControlPoint
|
||||||
|
{
|
||||||
|
SampleBank = "soft",
|
||||||
|
SampleVolume = 60
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleSelection()
|
||||||
|
{
|
||||||
|
clickSamplePiece(0);
|
||||||
|
samplePopoverHasSingleBank("normal");
|
||||||
|
samplePopoverHasSingleVolume(80);
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
// select first object to ensure that sample pieces for unselected objects
|
||||||
|
// work independently from selection state.
|
||||||
|
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
|
||||||
|
|
||||||
|
clickSamplePiece(1);
|
||||||
|
samplePopoverHasSingleBank("soft");
|
||||||
|
samplePopoverHasSingleVolume(60);
|
||||||
|
|
||||||
|
setVolumeViaPopover(90);
|
||||||
|
hitObjectHasSampleVolume(1, 90);
|
||||||
|
|
||||||
|
setBankViaPopover("drum");
|
||||||
|
hitObjectHasSampleBank(1, "drum");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleSelectionWithSameSampleVolume()
|
||||||
|
{
|
||||||
|
AddStep("unify sample volume", () =>
|
||||||
|
{
|
||||||
|
foreach (var h in EditorBeatmap.HitObjects)
|
||||||
|
h.SampleControlPoint.SampleVolume = 50;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
clickSamplePiece(0);
|
||||||
|
samplePopoverHasSingleVolume(50);
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
clickSamplePiece(1);
|
||||||
|
samplePopoverHasSingleVolume(50);
|
||||||
|
|
||||||
|
setVolumeViaPopover(75);
|
||||||
|
hitObjectHasSampleVolume(0, 75);
|
||||||
|
hitObjectHasSampleVolume(1, 75);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleSelectionWithDifferentSampleVolume()
|
||||||
|
{
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
clickSamplePiece(0);
|
||||||
|
samplePopoverHasIndeterminateVolume();
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
clickSamplePiece(1);
|
||||||
|
samplePopoverHasIndeterminateVolume();
|
||||||
|
|
||||||
|
setVolumeViaPopover(30);
|
||||||
|
hitObjectHasSampleVolume(0, 30);
|
||||||
|
hitObjectHasSampleVolume(1, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleSelectionWithSameSampleBank()
|
||||||
|
{
|
||||||
|
AddStep("unify sample bank", () =>
|
||||||
|
{
|
||||||
|
foreach (var h in EditorBeatmap.HitObjects)
|
||||||
|
h.SampleControlPoint.SampleBank = "soft";
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
clickSamplePiece(0);
|
||||||
|
samplePopoverHasSingleBank("soft");
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
clickSamplePiece(1);
|
||||||
|
samplePopoverHasSingleBank("soft");
|
||||||
|
|
||||||
|
setBankViaPopover(string.Empty);
|
||||||
|
hitObjectHasSampleBank(0, "soft");
|
||||||
|
hitObjectHasSampleBank(1, "soft");
|
||||||
|
samplePopoverHasSingleBank("soft");
|
||||||
|
|
||||||
|
setBankViaPopover("drum");
|
||||||
|
hitObjectHasSampleBank(0, "drum");
|
||||||
|
hitObjectHasSampleBank(1, "drum");
|
||||||
|
samplePopoverHasSingleBank("drum");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleSelectionWithDifferentSampleBank()
|
||||||
|
{
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
clickSamplePiece(0);
|
||||||
|
samplePopoverHasIndeterminateBank();
|
||||||
|
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
clickSamplePiece(1);
|
||||||
|
samplePopoverHasIndeterminateBank();
|
||||||
|
|
||||||
|
setBankViaPopover(string.Empty);
|
||||||
|
hitObjectHasSampleBank(0, "normal");
|
||||||
|
hitObjectHasSampleBank(1, "soft");
|
||||||
|
samplePopoverHasIndeterminateBank();
|
||||||
|
|
||||||
|
setBankViaPopover("normal");
|
||||||
|
hitObjectHasSampleBank(0, "normal");
|
||||||
|
hitObjectHasSampleBank(1, "normal");
|
||||||
|
samplePopoverHasSingleBank("normal");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} difficulty piece", () =>
|
||||||
|
{
|
||||||
|
var difficultyPiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(difficultyPiece);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
||||||
|
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
|
||||||
|
|
||||||
|
return slider?.Current.Value == volume;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void samplePopoverHasIndeterminateVolume() => AddUntilStep("sample popover has indeterminate volume", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
||||||
|
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
|
||||||
|
|
||||||
|
return slider != null && slider.Current.Value == null;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
||||||
|
var textBox = popover?.ChildrenOfType<OsuTextBox>().First();
|
||||||
|
|
||||||
|
return textBox?.Current.Value == bank && string.IsNullOrEmpty(textBox?.PlaceholderText.ToString());
|
||||||
|
});
|
||||||
|
|
||||||
|
private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
||||||
|
var textBox = popover?.ChildrenOfType<OsuTextBox>().First();
|
||||||
|
|
||||||
|
return textBox != null && string.IsNullOrEmpty(textBox.Current.Value) && !string.IsNullOrEmpty(textBox.PlaceholderText.ToString());
|
||||||
|
});
|
||||||
|
|
||||||
|
private void dismissPopover()
|
||||||
|
{
|
||||||
|
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
|
||||||
|
AddUntilStep("wait for dismiss", () => !this.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().Any(popover => popover.IsPresent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
|
||||||
|
var slider = popover.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
|
||||||
|
slider.Current.Value = volume;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectHasSampleVolume(int objectIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} has volume {volume}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
|
return h.SampleControlPoint.SampleVolume == volume;
|
||||||
|
});
|
||||||
|
|
||||||
|
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
|
||||||
|
var textBox = popover.ChildrenOfType<LabelledTextBox>().First();
|
||||||
|
textBox.Current.Value = bank;
|
||||||
|
// force a commit via keyboard.
|
||||||
|
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
|
||||||
|
InputManager.ChangeFocus(textBox);
|
||||||
|
InputManager.Key(Key.Enter);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
|
return h.SampleControlPoint.SampleBank == bank;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -31,12 +31,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||||
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
// The pause screen and fail animation both ramp frequency.
|
||||||
|
// This tests to ensure that it doesn't reset during that handoff.
|
||||||
|
AddAssert("frequency only ever decreased", () => !((FailPlayer)Player).FrequencyIncreased);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FailPlayer : TestPlayer
|
private class FailPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
public new FailOverlay FailOverlay => base.FailOverlay;
|
public new FailOverlay FailOverlay => base.FailOverlay;
|
||||||
|
|
||||||
|
public bool FrequencyIncreased { get; private set; }
|
||||||
|
|
||||||
public FailPlayer()
|
public FailPlayer()
|
||||||
: base(false, false)
|
: base(false, false)
|
||||||
{
|
{
|
||||||
@ -47,6 +53,19 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
HealthProcessor.FailConditions += (_, __) => true;
|
HealthProcessor.FailConditions += (_, __) => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double lastFrequency = double.MaxValue;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
double freq = Beatmap.Value.Track.AggregateFrequency.Value;
|
||||||
|
|
||||||
|
FrequencyIncreased |= freq > lastFrequency;
|
||||||
|
|
||||||
|
lastFrequency = freq;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,33 +17,69 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
public interface IWorkingBeatmap
|
public interface IWorkingBeatmap
|
||||||
{
|
{
|
||||||
|
IBeatmapInfo BeatmapInfo { get; }
|
||||||
|
|
||||||
|
IBeatmapSetInfo BeatmapSetInfo { get; }
|
||||||
|
|
||||||
|
IBeatmapMetadataInfo Metadata { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="WorkingBeatmap"/> represents.
|
/// Whether the Beatmap has finished loading.
|
||||||
|
///</summary>
|
||||||
|
public bool BeatmapLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the Background has finished loading.
|
||||||
|
///</summary>
|
||||||
|
public bool BackgroundLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the Waveform has finished loading.
|
||||||
|
///</summary>
|
||||||
|
public bool WaveformLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the Storyboard has finished loading.
|
||||||
|
///</summary>
|
||||||
|
public bool StoryboardLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the Skin has finished loading.
|
||||||
|
///</summary>
|
||||||
|
public bool SkinLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the Track has finished loading.
|
||||||
|
///</summary>
|
||||||
|
public bool TrackLoaded { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="IWorkingBeatmap"/> represents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IBeatmap Beatmap { get; }
|
IBeatmap Beatmap { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the background for this <see cref="WorkingBeatmap"/>.
|
/// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Texture Background { get; }
|
Texture Background { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="WorkingBeatmap"/>.
|
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Waveform Waveform { get; }
|
Waveform Waveform { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="Storyboard"/> which this <see cref="WorkingBeatmap"/> provides.
|
/// Retrieves the <see cref="Storyboard"/> which this <see cref="IWorkingBeatmap"/> provides.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Storyboard Storyboard { get; }
|
Storyboard Storyboard { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="Skin"/> which this <see cref="WorkingBeatmap"/> provides.
|
/// Retrieves the <see cref="Skin"/> which this <see cref="IWorkingBeatmap"/> provides.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ISkin Skin { get; }
|
ISkin Skin { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="Track"/> which this <see cref="WorkingBeatmap"/> has loaded.
|
/// Retrieves the <see cref="Track"/> which this <see cref="IWorkingBeatmap"/> has loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Track Track { get; }
|
Track Track { get; }
|
||||||
|
|
||||||
@ -67,7 +103,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// In a standard game context, the loading of the track is managed solely by MusicController, which will
|
/// In a standard game context, the loading of the track is managed solely by MusicController, which will
|
||||||
/// automatically load the track of the current global IBindable WorkingBeatmap.
|
/// automatically load the track of the current global IBindable IWorkingBeatmap.
|
||||||
/// As such, this method should only be called in very special scenarios, such as external tests or apps which are
|
/// As such, this method should only be called in very special scenarios, such as external tests or apps which are
|
||||||
/// outside of the game context.
|
/// outside of the game context.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
@ -79,5 +115,20 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="storagePath">The storage path to the file.</param>
|
/// <param name="storagePath">The storage path to the file.</param>
|
||||||
Stream GetStream(string storagePath);
|
Stream GetStream(string storagePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Beings loading the contents of this <see cref="IWorkingBeatmap"/> asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
public void BeginAsyncLoad();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels the asynchronous loading of the contents of this <see cref="IWorkingBeatmap"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void CancelAsyncLoad();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
||||||
|
/// </summary>
|
||||||
|
void PrepareTrackForPreviewLooping();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,7 @@ namespace osu.Game.Beatmaps
|
|||||||
public abstract class WorkingBeatmap : IWorkingBeatmap
|
public abstract class WorkingBeatmap : IWorkingBeatmap
|
||||||
{
|
{
|
||||||
public readonly BeatmapInfo BeatmapInfo;
|
public readonly BeatmapInfo BeatmapInfo;
|
||||||
|
|
||||||
public readonly BeatmapSetInfo BeatmapSetInfo;
|
public readonly BeatmapSetInfo BeatmapSetInfo;
|
||||||
|
|
||||||
public readonly BeatmapMetadata Metadata;
|
public readonly BeatmapMetadata Metadata;
|
||||||
|
|
||||||
protected AudioManager AudioManager { get; }
|
protected AudioManager AudioManager { get; }
|
||||||
@ -89,6 +87,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
var rulesetInstance = ruleset.CreateInstance();
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
|
||||||
|
if (rulesetInstance == null)
|
||||||
|
throw new RulesetLoadException("Creating ruleset instance failed when attempting to create playable beatmap.");
|
||||||
|
|
||||||
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
|
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
|
||||||
|
|
||||||
// Check if the beatmap can be converted
|
// Check if the beatmap can be converted
|
||||||
@ -176,17 +177,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
private CancellationTokenSource loadCancellation = new CancellationTokenSource();
|
private CancellationTokenSource loadCancellation = new CancellationTokenSource();
|
||||||
|
|
||||||
/// <summary>
|
public void BeginAsyncLoad() => loadBeatmapAsync();
|
||||||
/// Beings loading the contents of this <see cref="WorkingBeatmap"/> asynchronously.
|
|
||||||
/// </summary>
|
|
||||||
public void BeginAsyncLoad()
|
|
||||||
{
|
|
||||||
loadBeatmapAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
|
|
||||||
/// </summary>
|
|
||||||
public void CancelAsyncLoad()
|
public void CancelAsyncLoad()
|
||||||
{
|
{
|
||||||
lock (beatmapFetchLock)
|
lock (beatmapFetchLock)
|
||||||
@ -234,6 +226,10 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
|
public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
|
||||||
|
|
||||||
|
IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo;
|
||||||
|
IBeatmapMetadataInfo IWorkingBeatmap.Metadata => Metadata;
|
||||||
|
IBeatmapSetInfo IWorkingBeatmap.BeatmapSetInfo => BeatmapSetInfo;
|
||||||
|
|
||||||
public IBeatmap Beatmap
|
public IBeatmap Beatmap
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -273,9 +269,6 @@ namespace osu.Game.Beatmaps
|
|||||||
[NotNull]
|
[NotNull]
|
||||||
public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000);
|
public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
|
||||||
/// </summary>
|
|
||||||
public void PrepareTrackForPreviewLooping()
|
public void PrepareTrackForPreviewLooping()
|
||||||
{
|
{
|
||||||
Track.Looping = true;
|
Track.Looping = true;
|
||||||
|
@ -47,10 +47,30 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ArchiveReader GetReader()
|
public ArchiveReader GetReader()
|
||||||
{
|
{
|
||||||
if (Stream != null)
|
return Stream != null
|
||||||
return new ZipArchiveReader(Stream, Path);
|
? getReaderFrom(Stream)
|
||||||
|
: getReaderFrom(Path);
|
||||||
|
}
|
||||||
|
|
||||||
return getReaderFrom(Path);
|
/// <summary>
|
||||||
|
/// Creates an <see cref="ArchiveReader"/> from a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A seekable stream containing the archive content.</param>
|
||||||
|
/// <returns>A reader giving access to the archive's content.</returns>
|
||||||
|
private ArchiveReader getReaderFrom(Stream stream)
|
||||||
|
{
|
||||||
|
if (!(stream is MemoryStream memoryStream))
|
||||||
|
{
|
||||||
|
// This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out).
|
||||||
|
byte[] buffer = new byte[stream.Length];
|
||||||
|
stream.Read(buffer, 0, (int)stream.Length);
|
||||||
|
memoryStream = new MemoryStream(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ZipUtils.IsZipArchive(memoryStream))
|
||||||
|
return new ZipArchiveReader(memoryStream, Path);
|
||||||
|
|
||||||
|
return new LegacyByteArrayReader(memoryStream.ToArray(), Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -52,15 +52,18 @@ namespace osu.Game.Graphics
|
|||||||
|
|
||||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[]
|
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[]
|
||||||
{
|
{
|
||||||
(1.5f, Color4Extensions.FromHex("4fc0ff")),
|
(0.1f, Color4Extensions.FromHex("aaaaaa")),
|
||||||
|
(0.1f, Color4Extensions.FromHex("4290fb")),
|
||||||
|
(1.25f, Color4Extensions.FromHex("4fc0ff")),
|
||||||
(2.0f, Color4Extensions.FromHex("4fffd5")),
|
(2.0f, Color4Extensions.FromHex("4fffd5")),
|
||||||
(2.5f, Color4Extensions.FromHex("7cff4f")),
|
(2.5f, Color4Extensions.FromHex("7cff4f")),
|
||||||
(3.25f, Color4Extensions.FromHex("f6f05c")),
|
(3.3f, Color4Extensions.FromHex("f6f05c")),
|
||||||
(4.5f, Color4Extensions.FromHex("ff8068")),
|
(4.2f, Color4Extensions.FromHex("ff8068")),
|
||||||
(6.0f, Color4Extensions.FromHex("ff3c71")),
|
(4.9f, Color4Extensions.FromHex("ff4e6f")),
|
||||||
(7.0f, Color4Extensions.FromHex("6563de")),
|
(5.8f, Color4Extensions.FromHex("c645b8")),
|
||||||
(8.0f, Color4Extensions.FromHex("18158e")),
|
(6.7f, Color4Extensions.FromHex("6563de")),
|
||||||
(8.0f, Color4.Black),
|
(7.7f, Color4Extensions.FromHex("18158e")),
|
||||||
|
(9.0f, Color4.Black),
|
||||||
}, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
}, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -19,6 +19,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
@ -219,7 +220,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);
|
decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);
|
||||||
|
|
||||||
// Find the number of significant digits (we could have less than 5 after normalize())
|
// Find the number of significant digits (we could have less than 5 after normalize())
|
||||||
int significantDigits = findPrecision(decimalPrecision);
|
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
||||||
|
|
||||||
TooltipText = floatValue.ToString($"N{significantDigits}");
|
TooltipText = floatValue.ToString($"N{significantDigits}");
|
||||||
}
|
}
|
||||||
@ -248,23 +249,5 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// <returns>The normalised decimal.</returns>
|
/// <returns>The normalised decimal.</returns>
|
||||||
private decimal normalise(decimal d, int sd)
|
private decimal normalise(decimal d, int sd)
|
||||||
=> decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
|
=> decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finds the number of digits after the decimal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="d">The value to find the number of decimal digits for.</param>
|
|
||||||
/// <returns>The number decimal digits.</returns>
|
|
||||||
private int findPrecision(decimal d)
|
|
||||||
{
|
|
||||||
int precision = 0;
|
|
||||||
|
|
||||||
while (d != Math.Round(d))
|
|
||||||
{
|
|
||||||
d *= 10;
|
|
||||||
precision++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return precision;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ using System.IO;
|
|||||||
namespace osu.Game.IO.Archives
|
namespace osu.Game.IO.Archives
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows reading a single file from the provided stream.
|
/// Allows reading a single file from the provided byte array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LegacyByteArrayReader : ArchiveReader
|
public class LegacyByteArrayReader : ArchiveReader
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
@ -29,36 +29,26 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new OsuTextFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Direction = FillDirection.Vertical,
|
Padding = new MarginPadding
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
Horizontal = SettingsPanel.CONTENT_MARGINS,
|
||||||
{
|
Top = Toolbar.Toolbar.TOOLTIP_HEIGHT,
|
||||||
Text = heading,
|
Bottom = 30
|
||||||
Font = OsuFont.TorusAlternate.With(size: 40),
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Left = SettingsPanel.CONTENT_MARGINS,
|
|
||||||
Top = Toolbar.Toolbar.TOOLTIP_HEIGHT
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Colour = colourProvider.Content2,
|
|
||||||
Text = subheading,
|
|
||||||
Font = OsuFont.GetFont(size: 18),
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Left = SettingsPanel.CONTENT_MARGINS,
|
|
||||||
Bottom = 30
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}.With(flow =>
|
||||||
|
{
|
||||||
|
flow.AddText(heading, header => header.Font = OsuFont.TorusAlternate.With(size: 40));
|
||||||
|
flow.NewLine();
|
||||||
|
flow.AddText(subheading, subheader =>
|
||||||
|
{
|
||||||
|
subheader.Colour = colourProvider.Content2;
|
||||||
|
subheader.Font = OsuFont.GetFont(size: 18);
|
||||||
|
});
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
@ -14,19 +16,20 @@ using osu.Game.Graphics.Containers;
|
|||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
|
public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
|
||||||
{
|
{
|
||||||
private readonly HitObject hitObject;
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
private readonly BindableNumber<double> speedMultiplier;
|
private readonly BindableNumber<double> speedMultiplier;
|
||||||
|
|
||||||
public DifficultyPointPiece(HitObject hitObject)
|
public DifficultyPointPiece(HitObject hitObject)
|
||||||
: base(hitObject.DifficultyControlPoint)
|
: base(hitObject.DifficultyControlPoint)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
|
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
|
||||||
}
|
}
|
||||||
@ -44,14 +47,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Popover GetPopover() => new DifficultyEditPopover(hitObject);
|
public Popover GetPopover() => new DifficultyEditPopover(HitObject);
|
||||||
|
|
||||||
public class DifficultyEditPopover : OsuPopover
|
public class DifficultyEditPopover : OsuPopover
|
||||||
{
|
{
|
||||||
private readonly HitObject hitObject;
|
private readonly HitObject hitObject;
|
||||||
private readonly DifficultyControlPoint point;
|
|
||||||
|
|
||||||
private SliderWithTextBoxInput<double> sliderVelocitySlider;
|
private IndeterminateSliderWithTextBoxInput<double> sliderVelocitySlider;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; }
|
||||||
@ -59,7 +61,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
public DifficultyEditPopover(HitObject hitObject)
|
public DifficultyEditPopover(HitObject hitObject)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
this.hitObject = hitObject;
|
||||||
point = hitObject.DifficultyControlPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -72,11 +73,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Width = 200,
|
Width = 200,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 15),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
sliderVelocitySlider = new SliderWithTextBoxInput<double>("Velocity")
|
sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput<double>("Velocity", new DifficultyControlPoint().SliderVelocityBindable)
|
||||||
{
|
{
|
||||||
Current = new DifficultyControlPoint().SliderVelocityBindable,
|
|
||||||
KeyboardStep = 0.1f
|
KeyboardStep = 0.1f
|
||||||
},
|
},
|
||||||
new OsuTextFlowContainer
|
new OsuTextFlowContainer
|
||||||
@ -89,17 +90,37 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var selectedPointBindable = point.SliderVelocityBindable;
|
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||||
|
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||||
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||||
|
var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray();
|
||||||
|
|
||||||
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
|
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
||||||
// generally that level of precision could only be set by externally editing the .osu file, so at the point
|
var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null;
|
||||||
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
|
|
||||||
double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision;
|
|
||||||
if (selectedPointBindable.Precision < expectedPrecision)
|
|
||||||
selectedPointBindable.Precision = expectedPrecision;
|
|
||||||
|
|
||||||
sliderVelocitySlider.Current = selectedPointBindable;
|
if (selectedPointBindable != null)
|
||||||
sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject));
|
{
|
||||||
|
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
|
||||||
|
// generally that level of precision could only be set by externally editing the .osu file, so at the point
|
||||||
|
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
|
||||||
|
sliderVelocitySlider.Current.Value = selectedPointBindable.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderVelocitySlider.Current.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
if (val.NewValue == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmap.BeginChange();
|
||||||
|
|
||||||
|
foreach (var h in relevantObjects)
|
||||||
|
{
|
||||||
|
h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value;
|
||||||
|
beatmap.Update(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.EndChange();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
@ -14,12 +19,13 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class SamplePointPiece : HitObjectPointPiece, IHasPopover
|
public class SamplePointPiece : HitObjectPointPiece, IHasPopover
|
||||||
{
|
{
|
||||||
private readonly HitObject hitObject;
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
private readonly Bindable<string> bank;
|
private readonly Bindable<string> bank;
|
||||||
private readonly BindableNumber<int> volume;
|
private readonly BindableNumber<int> volume;
|
||||||
@ -27,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
public SamplePointPiece(HitObject hitObject)
|
public SamplePointPiece(HitObject hitObject)
|
||||||
: base(hitObject.SampleControlPoint)
|
: base(hitObject.SampleControlPoint)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
HitObject = hitObject;
|
||||||
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
|
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
|
||||||
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
|
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
|
||||||
}
|
}
|
||||||
@ -50,23 +56,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Label.Text = $"{bank.Value} {volume.Value}";
|
Label.Text = $"{bank.Value} {volume.Value}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Popover GetPopover() => new SampleEditPopover(hitObject);
|
public Popover GetPopover() => new SampleEditPopover(HitObject);
|
||||||
|
|
||||||
public class SampleEditPopover : OsuPopover
|
public class SampleEditPopover : OsuPopover
|
||||||
{
|
{
|
||||||
private readonly HitObject hitObject;
|
private readonly HitObject hitObject;
|
||||||
private readonly SampleControlPoint point;
|
|
||||||
|
|
||||||
private LabelledTextBox bank;
|
private LabelledTextBox bank = null!;
|
||||||
private SliderWithTextBoxInput<int> volume;
|
private IndeterminateSliderWithTextBoxInput<int> volume = null!;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; } = null!;
|
||||||
|
|
||||||
public SampleEditPopover(HitObject hitObject)
|
public SampleEditPopover(HitObject hitObject)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
this.hitObject = hitObject;
|
||||||
point = hitObject.SampleControlPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -79,25 +83,84 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Width = 200,
|
Width = 200,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 10),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
bank = new LabelledTextBox
|
bank = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Bank Name",
|
Label = "Bank Name",
|
||||||
},
|
},
|
||||||
volume = new SliderWithTextBoxInput<int>("Volume")
|
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new SampleControlPoint().SampleVolumeBindable)
|
||||||
{
|
|
||||||
Current = new SampleControlPoint().SampleVolumeBindable,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bank.Current = point.SampleBankBindable;
|
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||||
bank.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||||
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||||
|
var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray();
|
||||||
|
|
||||||
volume.Current = point.SampleVolumeBindable;
|
// even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value.
|
||||||
volume.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
string? commonBank = getCommonBank(relevantControlPoints);
|
||||||
|
if (!string.IsNullOrEmpty(commonBank))
|
||||||
|
bank.Current.Value = commonBank;
|
||||||
|
|
||||||
|
int? commonVolume = getCommonVolume(relevantControlPoints);
|
||||||
|
if (commonVolume != null)
|
||||||
|
volume.Current.Value = commonVolume.Value;
|
||||||
|
|
||||||
|
updateBankPlaceholderText(relevantObjects);
|
||||||
|
bank.Current.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
updateBankFor(relevantObjects, val.NewValue);
|
||||||
|
updateBankPlaceholderText(relevantObjects);
|
||||||
|
});
|
||||||
|
// on commit, ensure that the value is correct by sourcing it from the objects' control points again.
|
||||||
|
// this ensures that committing empty text causes a revert to the previous value.
|
||||||
|
bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints);
|
||||||
|
|
||||||
|
volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null;
|
||||||
|
private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null;
|
||||||
|
|
||||||
|
private void updateBankFor(IEnumerable<HitObject> objects, string? newBank)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(newBank))
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmap.BeginChange();
|
||||||
|
|
||||||
|
foreach (var h in objects)
|
||||||
|
{
|
||||||
|
h.SampleControlPoint.SampleBank = newBank;
|
||||||
|
beatmap.Update(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.EndChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBankPlaceholderText(IEnumerable<HitObject> objects)
|
||||||
|
{
|
||||||
|
string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray());
|
||||||
|
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateVolumeFor(IEnumerable<HitObject> objects, int? newVolume)
|
||||||
|
{
|
||||||
|
if (newVolume == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmap.BeginChange();
|
||||||
|
|
||||||
|
foreach (var h in objects)
|
||||||
|
{
|
||||||
|
h.SampleControlPoint.SampleVolume = newVolume.Value;
|
||||||
|
beatmap.Update(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.EndChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ using osu.Game.Screens.Edit.Components.Menus;
|
|||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osu.Game.Screens.Edit.Design;
|
using osu.Game.Screens.Edit.Design;
|
||||||
|
using osu.Game.Screens.Edit.GameplayTest;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osu.Game.Screens.Edit.Verify;
|
using osu.Game.Screens.Edit.Verify;
|
||||||
@ -324,6 +325,19 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
|
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="EditorState"/> instance representing the current state of the editor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nextBeatmap">
|
||||||
|
/// The next beatmap to be shown, in the case of difficulty switch.
|
||||||
|
/// <see langword="null"/> indicates that the beatmap will not be changing.
|
||||||
|
/// </param>
|
||||||
|
public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState
|
||||||
|
{
|
||||||
|
Time = clock.CurrentTimeAccurate,
|
||||||
|
ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restore the editor to a provided state.
|
/// Restore the editor to a provided state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -486,7 +500,18 @@ namespace osu.Game.Screens.Edit
|
|||||||
public override void OnEntering(IScreen last)
|
public override void OnEntering(IScreen last)
|
||||||
{
|
{
|
||||||
base.OnEntering(last);
|
base.OnEntering(last);
|
||||||
|
dimBackground();
|
||||||
|
resetTrack(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnResuming(IScreen last)
|
||||||
|
{
|
||||||
|
base.OnResuming(last);
|
||||||
|
dimBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dimBackground()
|
||||||
|
{
|
||||||
ApplyToBackground(b =>
|
ApplyToBackground(b =>
|
||||||
{
|
{
|
||||||
// todo: temporary. we want to be applying dim using the UserDimContainer eventually.
|
// todo: temporary. we want to be applying dim using the UserDimContainer eventually.
|
||||||
@ -495,8 +520,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
b.IgnoreUserSettings.Value = true;
|
b.IgnoreUserSettings.Value = true;
|
||||||
b.BlurAmount.Value = 0;
|
b.BlurAmount.Value = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
resetTrack(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
@ -535,9 +558,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public override void OnSuspending(IScreen next)
|
public override void OnSuspending(IScreen next)
|
||||||
{
|
{
|
||||||
refetchBeatmap();
|
|
||||||
|
|
||||||
base.OnSuspending(next);
|
base.OnSuspending(next);
|
||||||
|
clock.Stop();
|
||||||
|
refetchBeatmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refetchBeatmap()
|
private void refetchBeatmap()
|
||||||
@ -770,11 +793,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
|
return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState
|
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap));
|
||||||
{
|
|
||||||
Time = clock.CurrentTimeAccurate,
|
|
||||||
ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty
|
|
||||||
});
|
|
||||||
|
|
||||||
private void cancelExit()
|
private void cancelExit()
|
||||||
{
|
{
|
||||||
@ -797,7 +816,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
pushEditorPlayer();
|
pushEditorPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer()));
|
void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
|
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
|
||||||
|
@ -3,21 +3,30 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit.GameplayTest
|
||||||
{
|
{
|
||||||
public class EditorPlayer : Player
|
public class EditorPlayer : Player
|
||||||
{
|
{
|
||||||
public EditorPlayer()
|
private readonly Editor editor;
|
||||||
: base(new PlayerConfiguration { ShowResults = false })
|
private readonly EditorState editorState;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController musicController { get; set; }
|
private MusicController musicController { get; set; }
|
||||||
|
|
||||||
|
public EditorPlayer(Editor editor)
|
||||||
|
: base(new PlayerConfiguration { ShowResults = false })
|
||||||
|
{
|
||||||
|
this.editor = editor;
|
||||||
|
editorState = editor.GetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
|
||||||
|
=> new MasterGameplayClockContainer(beatmap, editorState.Time, true);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -35,9 +44,22 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
protected override bool CheckModsAllowFailure() => false; // never fail.
|
protected override bool CheckModsAllowFailure() => false; // never fail.
|
||||||
|
|
||||||
|
public override void OnEntering(IScreen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
|
||||||
|
// finish alpha transforms on entering to avoid gameplay starting in a half-hidden state.
|
||||||
|
// the finish calls are purposefully not propagated to children to avoid messing up their state.
|
||||||
|
FinishTransforms();
|
||||||
|
GameplayClockContainer.FinishTransforms(false, nameof(Alpha));
|
||||||
|
}
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
musicController.Stop();
|
musicController.Stop();
|
||||||
|
|
||||||
|
editorState.Time = GameplayClockContainer.CurrentTime;
|
||||||
|
editor.RestoreState(editorState);
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
44
osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
Normal file
44
osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.GameplayTest
|
||||||
|
{
|
||||||
|
public class EditorPlayerLoader : PlayerLoader
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuLogo osuLogo { get; set; }
|
||||||
|
|
||||||
|
public EditorPlayerLoader(Editor editor)
|
||||||
|
: base(() => new EditorPlayer(editor))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEntering(IScreen last)
|
||||||
|
{
|
||||||
|
base.OnEntering(last);
|
||||||
|
|
||||||
|
MetadataInfo.FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
||||||
|
{
|
||||||
|
// call base with resuming forcefully set to true to reduce logo movements.
|
||||||
|
base.LogoArriving(logo, true);
|
||||||
|
logo.FinishTransforms(true, nameof(Scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ContentOut()
|
||||||
|
{
|
||||||
|
base.ContentOut();
|
||||||
|
osuLogo.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double PlayerPushDelay => 0;
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ using System;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit.GameplayTest
|
||||||
{
|
{
|
||||||
public class SaveBeforeGameplayTestDialog : PopupDialog
|
public class SaveBeforeGameplayTestDialog : PopupDialog
|
||||||
{
|
{
|
@ -0,0 +1,123 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Analogous to <see cref="SliderWithTextBoxInput{T}"/>, but supports scenarios
|
||||||
|
/// where multiple objects with multiple different property values are selected
|
||||||
|
/// by providing an "indeterminate state".
|
||||||
|
/// </summary>
|
||||||
|
public class IndeterminateSliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T?>
|
||||||
|
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A custom step value for each key press which actuates a change on this control.
|
||||||
|
/// </summary>
|
||||||
|
public float KeyboardStep
|
||||||
|
{
|
||||||
|
get => slider.KeyboardStep;
|
||||||
|
set => slider.KeyboardStep = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
||||||
|
|
||||||
|
public Bindable<T?> Current
|
||||||
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SettingsSlider<T> slider;
|
||||||
|
private readonly LabelledTextBox textbox;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an <see cref="IndeterminateSliderWithTextBoxInput{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="labelText">The label text for the slider and text box.</param>
|
||||||
|
/// <param name="indeterminateValue">
|
||||||
|
/// Bindable to use for the slider until a non-null value is set for <see cref="Current"/>.
|
||||||
|
/// In particular, it can be used to control min/max bounds and precision in the case of <see cref="BindableNumber{T}"/>s.
|
||||||
|
/// </param>
|
||||||
|
public IndeterminateSliderWithTextBoxInput(LocalisableString labelText, Bindable<T> indeterminateValue)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
textbox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = labelText,
|
||||||
|
},
|
||||||
|
slider = new SettingsSlider<T>
|
||||||
|
{
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Current = indeterminateValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
textbox.OnCommit += (t, isNew) =>
|
||||||
|
{
|
||||||
|
if (!isNew) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
slider.Current.Parse(t.Text);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// TriggerChange below will restore the previous text value on failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is run regardless of parsing success as the parsed number may not actually trigger a change
|
||||||
|
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
|
||||||
|
Current.TriggerChange();
|
||||||
|
};
|
||||||
|
slider.Current.BindValueChanged(val => Current.Value = val.NewValue);
|
||||||
|
|
||||||
|
Current.BindValueChanged(_ => updateState(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
if (Current.Value is T nonNullValue)
|
||||||
|
{
|
||||||
|
slider.Current.Value = nonNullValue;
|
||||||
|
|
||||||
|
// use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly.
|
||||||
|
decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo);
|
||||||
|
textbox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
|
||||||
|
textbox.PlaceholderText = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
textbox.Text = null;
|
||||||
|
textbox.PlaceholderText = "(multiple)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -107,7 +107,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
|
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
|
||||||
{
|
{
|
||||||
RemoveFilters();
|
// Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep.
|
||||||
|
RemoveFilters(false);
|
||||||
OnComplete?.Invoke();
|
OnComplete?.Invoke();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,15 +138,16 @@ namespace osu.Game.Screens.Play
|
|||||||
Content.FadeColour(Color4.Gray, duration);
|
Content.FadeColour(Color4.Gray, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveFilters()
|
public void RemoveFilters(bool resetTrackFrequency = true)
|
||||||
{
|
{
|
||||||
|
if (resetTrackFrequency)
|
||||||
|
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
|
||||||
|
|
||||||
if (filters.Parent == null)
|
if (filters.Parent == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RemoveInternal(filters);
|
RemoveInternal(filters);
|
||||||
filters.Dispose();
|
filters.Dispose();
|
||||||
|
|
||||||
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using ManagedBass.Fx;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -35,7 +36,9 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
protected const float BACKGROUND_BLUR = 15;
|
protected const float BACKGROUND_BLUR = 15;
|
||||||
|
|
||||||
private const double content_out_duration = 300;
|
protected const double CONTENT_OUT_DURATION = 300;
|
||||||
|
|
||||||
|
protected virtual double PlayerPushDelay => 1800;
|
||||||
|
|
||||||
public override bool HideOverlaysOnEnter => hideOverlays;
|
public override bool HideOverlaysOnEnter => hideOverlays;
|
||||||
|
|
||||||
@ -67,6 +70,7 @@ namespace osu.Game.Screens.Play
|
|||||||
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
|
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
|
||||||
|
|
||||||
private AudioFilter lowPassFilter;
|
private AudioFilter lowPassFilter;
|
||||||
|
private AudioFilter highPassFilter;
|
||||||
|
|
||||||
protected bool BackgroundBrightnessReduction
|
protected bool BackgroundBrightnessReduction
|
||||||
{
|
{
|
||||||
@ -168,7 +172,8 @@ namespace osu.Game.Screens.Play
|
|||||||
},
|
},
|
||||||
idleTracker = new IdleTracker(750),
|
idleTracker = new IdleTracker(750),
|
||||||
}),
|
}),
|
||||||
lowPassFilter = new AudioFilter(audio.TrackMixer)
|
lowPassFilter = new AudioFilter(audio.TrackMixer),
|
||||||
|
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
|
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
|
||||||
@ -210,7 +215,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
// after an initial delay, start the debounced load check.
|
// after an initial delay, start the debounced load check.
|
||||||
// this will continue to execute even after resuming back on restart.
|
// this will continue to execute even after resuming back on restart.
|
||||||
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
|
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + PlayerPushDelay, 0));
|
||||||
|
|
||||||
showMuteWarningIfNeeded();
|
showMuteWarningIfNeeded();
|
||||||
showBatteryWarningIfNeeded();
|
showBatteryWarningIfNeeded();
|
||||||
@ -239,18 +244,19 @@ namespace osu.Game.Screens.Play
|
|||||||
Beatmap.Value.Track.Stop();
|
Beatmap.Value.Track.Stop();
|
||||||
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
|
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
|
||||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||||
|
highPassFilter.CutoffTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
cancelLoad();
|
cancelLoad();
|
||||||
contentOut();
|
ContentOut();
|
||||||
|
|
||||||
// If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed).
|
// If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed).
|
||||||
epilepsyWarning?.Hide();
|
epilepsyWarning?.Hide();
|
||||||
|
|
||||||
// Ensure the screen doesn't expire until all the outwards fade operations have completed.
|
// Ensure the screen doesn't expire until all the outwards fade operations have completed.
|
||||||
this.Delay(content_out_duration).FadeOut();
|
this.Delay(CONTENT_OUT_DURATION).FadeOut();
|
||||||
|
|
||||||
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
|
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
|
||||||
|
|
||||||
@ -266,9 +272,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
const double duration = 300;
|
const double duration = 300;
|
||||||
|
|
||||||
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In);
|
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint);
|
||||||
|
|
||||||
logo.ScaleTo(new Vector2(0.15f), duration, Easing.In);
|
logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint);
|
||||||
logo.FadeIn(350);
|
logo.FadeIn(350);
|
||||||
|
|
||||||
Scheduler.AddDelayed(() =>
|
Scheduler.AddDelayed(() =>
|
||||||
@ -352,18 +358,20 @@ namespace osu.Game.Screens.Play
|
|||||||
content.FadeInFromZero(400);
|
content.FadeInFromZero(400);
|
||||||
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
|
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
|
||||||
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
|
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
|
||||||
|
highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in)
|
||||||
|
|
||||||
ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint));
|
ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void contentOut()
|
protected virtual void ContentOut()
|
||||||
{
|
{
|
||||||
// Ensure the logo is no longer tracking before we scale the content
|
// Ensure the logo is no longer tracking before we scale the content
|
||||||
content.StopTracking();
|
content.StopTracking();
|
||||||
|
|
||||||
content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint);
|
content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
|
||||||
content.FadeOut(content_out_duration, Easing.OutQuint);
|
content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
|
||||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration);
|
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION);
|
||||||
|
highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushWhenLoaded()
|
private void pushWhenLoaded()
|
||||||
@ -388,9 +396,9 @@ namespace osu.Game.Screens.Play
|
|||||||
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
|
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
|
||||||
var consumedPlayer = consumePlayer();
|
var consumedPlayer = consumePlayer();
|
||||||
|
|
||||||
contentOut();
|
ContentOut();
|
||||||
|
|
||||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(content_out_duration);
|
TransformSequence<PlayerLoader> pushSequence = this.Delay(CONTENT_OUT_DURATION);
|
||||||
|
|
||||||
// only show if the warning was created (i.e. the beatmap needs it)
|
// 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).
|
// and this is not a restart of the map (the warning expires after first load).
|
||||||
@ -412,7 +420,7 @@ namespace osu.Game.Screens.Play
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
|
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
|
||||||
this.TransformBindableTo(volumeAdjustment, 0, content_out_duration, Easing.OutCubic);
|
this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic);
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSequence.Schedule(() =>
|
pushSequence.Schedule(() =>
|
||||||
|
@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play
|
|||||||
colourNormal = colours.Yellow;
|
colourNormal = colours.Yellow;
|
||||||
colourHover = colours.YellowDark;
|
colourHover = colours.YellowDark;
|
||||||
|
|
||||||
sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection");
|
sampleConfirm = audio.Samples.Get(@"UI/submit-select");
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
|
|
||||||
sampleHover = audio.Samples.Get("SongSelect/song-ping");
|
sampleHover = audio.Samples.Get("UI/default-hover");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InsetForBorder
|
public bool InsetForBorder
|
||||||
|
@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
|
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
|
||||||
|
|
||||||
|
private double audioFeedbackLastPlaybackTime;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
@ -435,6 +437,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
|
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
|
||||||
|
private BeatmapInfo beatmapInfoPrevious;
|
||||||
private BeatmapInfo beatmapInfoNoDebounce;
|
private BeatmapInfo beatmapInfoNoDebounce;
|
||||||
private RulesetInfo rulesetNoDebounce;
|
private RulesetInfo rulesetNoDebounce;
|
||||||
|
|
||||||
@ -477,6 +480,21 @@ namespace osu.Game.Screens.Select
|
|||||||
else
|
else
|
||||||
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
|
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
|
||||||
|
|
||||||
|
if (beatmap != beatmapInfoPrevious)
|
||||||
|
{
|
||||||
|
if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50)
|
||||||
|
{
|
||||||
|
if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID)
|
||||||
|
sampleChangeDifficulty.Play();
|
||||||
|
else
|
||||||
|
sampleChangeBeatmap.Play();
|
||||||
|
|
||||||
|
audioFeedbackLastPlaybackTime = Time.Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmapInfoPrevious = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
void run()
|
void run()
|
||||||
{
|
{
|
||||||
// clear pending task immediately to track any potential nested debounce operation.
|
// clear pending task immediately to track any potential nested debounce operation.
|
||||||
@ -508,18 +526,7 @@ namespace osu.Game.Screens.Select
|
|||||||
if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
|
if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
|
||||||
{
|
{
|
||||||
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
|
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
|
||||||
|
|
||||||
int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
|
|
||||||
|
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
if (beatmap != null)
|
|
||||||
{
|
|
||||||
if (beatmap.BeatmapSetInfoID == lastSetID)
|
|
||||||
sampleChangeDifficulty.Play();
|
|
||||||
else
|
|
||||||
sampleChangeBeatmap.Play();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.IsCurrentScreen())
|
if (this.IsCurrentScreen())
|
||||||
|
@ -31,5 +31,23 @@ namespace osu.Game.Utils
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rank">The rank/position to be formatted.</param>
|
/// <param name="rank">The rank/position to be formatted.</param>
|
||||||
public static string FormatRank(this int rank) => rank.ToMetric(decimals: rank < 100_000 ? 1 : 0);
|
public static string FormatRank(this int rank) => rank.ToMetric(decimals: rank < 100_000 ? 1 : 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the number of digits after the decimal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="d">The value to find the number of decimal digits for.</param>
|
||||||
|
/// <returns>The number decimal digits.</returns>
|
||||||
|
public static int FindPrecision(decimal d)
|
||||||
|
{
|
||||||
|
int precision = 0;
|
||||||
|
|
||||||
|
while (d != Math.Round(d))
|
||||||
|
{
|
||||||
|
d *= 10;
|
||||||
|
precision++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return precision;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,34 @@ namespace osu.Game.Utils
|
|||||||
{
|
{
|
||||||
public static class ZipUtils
|
public static class ZipUtils
|
||||||
{
|
{
|
||||||
|
public static bool IsZipArchive(MemoryStream stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
using (var arc = ZipArchive.Open(stream))
|
||||||
|
{
|
||||||
|
foreach (var entry in arc.Entries)
|
||||||
|
{
|
||||||
|
using (entry.OpenEntryStream())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsZipArchive(string path)
|
public static bool IsZipArchive(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.6.0" />
|
<PackageReference Include="Realm" Version="10.6.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1108.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.1108.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.10.0" />
|
<PackageReference Include="Sentry" Version="3.10.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1108.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1108.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user