mirror of
https://github.com/ppy/osu.git
synced 2026-05-18 18:29:58 +08:00
Compare commits
1 Commits
@@ -67,7 +67,7 @@ jobs:
|
||||
- { prettyname: macOS, fullname: macos-latest }
|
||||
- { prettyname: Linux, fullname: ubuntu-latest }
|
||||
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||
timeout-minutes: 120
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check permissions
|
||||
run: |
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte)
|
||||
ALLOWED_USERS=(smoogipoo peppy bdach)
|
||||
for i in "${ALLOWED_USERS[@]}"; do
|
||||
if [[ "${{ github.actor }}" == "$i" ]]; then
|
||||
exit 0
|
||||
|
||||
@@ -265,8 +265,6 @@ __pycache__/
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/*/.idea/projectSettingsUpdater.xml
|
||||
.idea/*/.idea/encodings.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RiderProjectSettingsUpdater">
|
||||
<option name="vcsConfiguration" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -7,6 +7,7 @@ T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> ins
|
||||
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
||||
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
||||
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
|
||||
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.
|
||||
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
|
||||
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
|
||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.720.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.625.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Security.Principal;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -20,14 +21,48 @@ namespace osu.Desktop.Security
|
||||
[Resolved]
|
||||
private INotificationOverlay notifications { get; set; } = null!;
|
||||
|
||||
private bool elevated;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
elevated = checkElevated();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (Environment.IsPrivilegedProcess)
|
||||
if (elevated)
|
||||
notifications.Post(new ElevatedPrivilegesNotification());
|
||||
}
|
||||
|
||||
private bool checkElevated()
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
if (!OperatingSystem.IsWindows()) return false;
|
||||
|
||||
var windowsIdentity = WindowsIdentity.GetCurrent();
|
||||
var windowsPrincipal = new WindowsPrincipal(windowsIdentity);
|
||||
|
||||
return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
|
||||
case RuntimeInfo.Platform.macOS:
|
||||
case RuntimeInfo.Platform.Linux:
|
||||
return Mono.Unix.Native.Syscall.geteuid() == 0;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private partial class ElevatedPrivilegesNotification : SimpleNotification
|
||||
{
|
||||
public override bool IsImportant => true;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Clowd.Squirrel" Version="2.11.1" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneCatchEditorSaving : EditorSavingTestScene
|
||||
{
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestCatchJuiceStreamTickCorrect()
|
||||
{
|
||||
AddStep("enter timing mode", () => InputManager.Key(Key.F3));
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
|
||||
|
||||
Vector2 startPoint = Vector2.Zero;
|
||||
float increment = 0;
|
||||
|
||||
AddUntilStep("wait for playfield", () => this.ChildrenOfType<CatchPlayfield>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddStep("move to centre", () =>
|
||||
{
|
||||
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
|
||||
startPoint = playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Height / 3);
|
||||
increment = playfield.ScreenSpaceDrawQuad.Height / 10;
|
||||
InputManager.MoveMouseTo(startPoint);
|
||||
});
|
||||
AddStep("choose juice stream placing tool", () => InputManager.Key(Key.Number3));
|
||||
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(2 * increment, -increment)));
|
||||
AddStep("add node", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(-2 * increment, -2 * increment)));
|
||||
AddStep("add node", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(0, -3 * increment)));
|
||||
AddStep("end placement", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddUntilStep("juice stream placed", () => EditorBeatmap.HitObjects, () => Has.Count.EqualTo(1));
|
||||
|
||||
int largeDropletCount = 0, tinyDropletCount = 0;
|
||||
AddStep("store droplet count", () =>
|
||||
{
|
||||
largeDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet));
|
||||
tinyDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet));
|
||||
});
|
||||
|
||||
SaveEditor();
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("large droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet)), () => Is.EqualTo(largeDropletCount));
|
||||
AddAssert("tiny droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet)), () => Is.EqualTo(tinyDropletCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
|
||||
AddMouseMoveStep(-100, 100);
|
||||
addVertexCheckStep(3, 1, times[0], positions[0]);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -101,9 +100,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddMouseMoveStep(times[2] - 50, positions[2] - 50);
|
||||
addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50);
|
||||
addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50);
|
||||
|
||||
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -117,7 +113,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addDragStartStep(times[1], positions[1]);
|
||||
AddMouseMoveStep(times[1], 400);
|
||||
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -134,7 +129,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddStep("scroll playfield", () => manualClock.CurrentTime += 200);
|
||||
AddMouseMoveStep(times[1] + 200, positions[1] + 100);
|
||||
addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100);
|
||||
addDragEndStep();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -167,18 +161,18 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addAddVertexSteps(500, 150);
|
||||
addVertexCheckStep(3, 1, 500, 150);
|
||||
|
||||
addAddVertexSteps(160, 200);
|
||||
addVertexCheckStep(4, 1, 160, 200);
|
||||
addAddVertexSteps(90, 200);
|
||||
addVertexCheckStep(4, 1, times[0], positions[0]);
|
||||
|
||||
addAddVertexSteps(750, 180);
|
||||
addVertexCheckStep(5, 4, 800, 160);
|
||||
addVertexCheckStep(5, 4, 750, 180);
|
||||
AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteVertex()
|
||||
{
|
||||
double[] times = { 100, 300, 400 };
|
||||
double[] times = { 100, 300, 500 };
|
||||
float[] positions = { 100, 200, 150 };
|
||||
addBlueprintStep(times, positions);
|
||||
|
||||
@@ -271,7 +265,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddStep("delete vertex", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public partial class TestSceneCatchReplayHandling : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestReplayDetach()
|
||||
{
|
||||
DrawableCatchRuleset drawableRuleset = null!;
|
||||
float catcherPosition = 0;
|
||||
|
||||
AddStep("create drawable ruleset", () => Child = drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), []));
|
||||
AddStep("attach replay", () => drawableRuleset.SetReplayScore(new Score()));
|
||||
AddStep("store catcher position", () => catcherPosition = drawableRuleset.ChildrenOfType<Catcher>().Single().X);
|
||||
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||
AddAssert("catcher didn't move", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.EqualTo(catcherPosition));
|
||||
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||
|
||||
AddStep("detach replay", () => drawableRuleset.SetReplayScore(null));
|
||||
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||
AddUntilStep("catcher moved", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.Not.EqualTo(catcherPosition));
|
||||
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity : 1,
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1 : ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity,
|
||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||
}.Yield();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@@ -28,7 +29,6 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@@ -62,43 +62,43 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
yield return new CatchModNightcore();
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
yield return new CatchModDoubleTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
yield return new CatchModPerfect();
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
yield return new CatchModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
yield return new CatchModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
yield return new CatchModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
yield return new CatchModEasy();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
yield return new CatchModFlashlight();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
yield return new CatchModHalfTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
yield return new CatchModHardRock();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
yield return new CatchModHidden();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
yield return new CatchModNoFail();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
yield return new CatchModRelax();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@@ -223,12 +223,6 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||
|
||||
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||
[
|
||||
new DifficultySection(),
|
||||
new ColoursSection(),
|
||||
];
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||
|
||||
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
@@ -43,9 +42,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
[Resolved]
|
||||
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap? EditorBeatmap { get; private set; }
|
||||
|
||||
protected EditablePath(Func<float, double> positionToTime)
|
||||
{
|
||||
PositionToTime = positionToTime;
|
||||
@@ -107,23 +103,15 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
//
|
||||
// The value is clamped here by the bindable min and max values.
|
||||
// In case the required velocity is too large, the path is not preserved.
|
||||
double previousVelocity = svBindable.Value;
|
||||
svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor);
|
||||
|
||||
// adjust velocity locally, so that once the SV change is applied by applying defaults
|
||||
// (triggered by `EditorBeatmap.Update()` call at end of method),
|
||||
// it results in the outcome desired by the user.
|
||||
double relativeChange = svBindable.Value / previousVelocity;
|
||||
double localVelocity = hitObject.Velocity * relativeChange;
|
||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, localVelocity);
|
||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity);
|
||||
|
||||
if (beatSnapProvider == null) return;
|
||||
|
||||
double endTime = hitObject.StartTime + path.Duration;
|
||||
double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime);
|
||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * localVelocity;
|
||||
|
||||
EditorBeatmap?.Update(hitObject);
|
||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity;
|
||||
}
|
||||
|
||||
public Vector2 ToRelativePosition(Vector2 screenSpacePosition)
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -18,27 +19,22 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray();
|
||||
|
||||
private readonly JuiceStream juiceStream;
|
||||
|
||||
// To handle when the editor is scrolled while dragging.
|
||||
private Vector2 dragStartPosition;
|
||||
|
||||
public SelectionEditablePath(JuiceStream juiceStream, Func<float, double> positionToTime)
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
public SelectionEditablePath(Func<float, double> positionToTime)
|
||||
: base(positionToTime)
|
||||
{
|
||||
this.juiceStream = juiceStream;
|
||||
}
|
||||
|
||||
public void AddVertex(Vector2 relativePosition)
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
|
||||
double time = Math.Max(0, PositionToTime(relativePosition.Y));
|
||||
int index = AddVertex(time, relativePosition.X);
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
selectOnly(index);
|
||||
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||
@@ -49,13 +45,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
if (index == -1 || VertexStates[index].IsFixed)
|
||||
return false;
|
||||
|
||||
if (e.Button == MouseButton.Right && e.ShiftPressed)
|
||||
if (e.Button == MouseButton.Left && e.ShiftPressed)
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
RemoveVertex(index);
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
EditorBeatmap?.EndChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
for (int i = 0; i < VertexCount; i++)
|
||||
VertexStates[i].VertexBeforeChange = Vertices[i];
|
||||
|
||||
EditorBeatmap?.BeginChange();
|
||||
changeHandler?.BeginChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -96,7 +88,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
EditorBeatmap?.EndChange();
|
||||
changeHandler?.EndChange();
|
||||
}
|
||||
|
||||
private int getMouseTargetVertex(Vector2 screenSpacePosition)
|
||||
@@ -126,17 +118,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
private void deleteSelectedVertices()
|
||||
{
|
||||
EditorBeatmap?.BeginChange();
|
||||
|
||||
for (int i = VertexCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (VertexStates[i].IsSelected)
|
||||
RemoveVertex(i);
|
||||
}
|
||||
|
||||
UpdateHitObjectFromPath(juiceStream);
|
||||
|
||||
EditorBeatmap?.EndChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
|
||||
@@ -13,8 +12,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
{
|
||||
public partial class VertexPiece : Circle
|
||||
{
|
||||
private VertexState state = new VertexState();
|
||||
|
||||
[Resolved]
|
||||
private OsuColour osuColour { get; set; } = null!;
|
||||
|
||||
@@ -27,32 +24,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
|
||||
public void UpdateFrom(VertexState state)
|
||||
{
|
||||
this.state = state;
|
||||
updateMarkerDisplay();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateMarkerDisplay();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateMarkerDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the circular control point marker.
|
||||
/// </summary>
|
||||
private void updateMarkerDisplay()
|
||||
{
|
||||
var colour = osuColour.Yellow;
|
||||
|
||||
if (IsHovered || state.IsSelected)
|
||||
colour = colour.Lighten(1);
|
||||
|
||||
Colour = colour;
|
||||
Colour = state.IsSelected ? osuColour.Yellow.Lighten(1) : osuColour.Yellow;
|
||||
Alpha = state.IsFixed ? 0.5f : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
scrollingPath = new ScrollingPath(),
|
||||
nestedOutlineContainer = new NestedOutlineContainer(),
|
||||
editablePath = new SelectionEditablePath(hitObject, positionToTime)
|
||||
editablePath = new SelectionEditablePath(positionToTime)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
@@ -120,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||
|
||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
{
|
||||
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
||||
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
||||
|
||||
@@ -15,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt
|
||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
|
||||
{
|
||||
public const float OBJECT_RADIUS = 64;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
@@ -47,10 +46,16 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
public double TickDistanceMultiplier = 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Velocity { get; private set; }
|
||||
private double velocityFactor;
|
||||
|
||||
[JsonIgnore]
|
||||
public double TickDistance { get; private set; }
|
||||
private double tickDistanceFactor;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Velocity => velocityFactor * SliderVelocityMultiplier;
|
||||
|
||||
[JsonIgnore]
|
||||
public double TickDistance => tickDistanceFactor * TickDistanceMultiplier;
|
||||
|
||||
/// <summary>
|
||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||
@@ -63,13 +68,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
|
||||
Velocity = base_scoring_distance * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, CatchRuleset.SHORT_NAME);
|
||||
|
||||
// WARNING: this is intentionally not computed as `BASE_SCORING_DISTANCE * difficulty.SliderMultiplier`
|
||||
// for backwards compatibility reasons (intentionally introducing floating point errors to match stable).
|
||||
double scoringDistance = Velocity * timingPoint.BeatLength;
|
||||
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||
velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength;
|
||||
tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
|
||||
@@ -1,18 +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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
@@ -35,43 +30,5 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReloadOnBPMChange()
|
||||
{
|
||||
HitObjectComposer oldComposer = null!;
|
||||
|
||||
AddStep("store composer", () => oldComposer = this.ChildrenOfType<HitObjectComposer>().Single());
|
||||
AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null);
|
||||
AddStep("switch to timing tab", () => InputManager.Key(Key.F3));
|
||||
AddUntilStep("wait for loaded", () => this.ChildrenOfType<TimingAdjustButton>().ElementAtOrDefault(1), () => Is.Not.Null);
|
||||
AddStep("change timing point BPM", () =>
|
||||
{
|
||||
var bpmControl = this.ChildrenOfType<TimingAdjustButton>().ElementAt(1);
|
||||
InputManager.MoveMouseTo(bpmControl);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("switch back to composer", () => InputManager.Key(Key.F1));
|
||||
AddUntilStep("composer reloaded", () =>
|
||||
{
|
||||
var composer = this.ChildrenOfType<HitObjectComposer>().SingleOrDefault();
|
||||
return composer != null && composer != oldComposer;
|
||||
});
|
||||
|
||||
AddStep("store composer", () => oldComposer = this.ChildrenOfType<HitObjectComposer>().Single());
|
||||
AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null);
|
||||
AddStep("undo", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.Z);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
AddUntilStep("composer reloaded", () =>
|
||||
{
|
||||
var composer = this.ChildrenOfType<HitObjectComposer>().SingleOrDefault();
|
||||
return composer != null && composer != oldComposer;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@@ -19,22 +17,5 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
Mod = new ManiaModInvert(),
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBreaksPreservedOnOriginalBeatmap()
|
||||
{
|
||||
var beatmap = CreateBeatmap(new ManiaRuleset().RulesetInfo);
|
||||
beatmap.Breaks.Clear();
|
||||
beatmap.Breaks.Add(new BreakPeriod(0, 1000));
|
||||
|
||||
var workingBeatmap = new FlatWorkingBeatmap(beatmap);
|
||||
|
||||
var playableWithInvert = workingBeatmap.GetPlayableBeatmap(new ManiaRuleset().RulesetInfo, new[] { new ManiaModInvert() });
|
||||
Assert.That(playableWithInvert.Breaks.Count, Is.Zero);
|
||||
|
||||
var playableWithoutInvert = workingBeatmap.GetPlayableBeatmap(new ManiaRuleset().RulesetInfo);
|
||||
Assert.That(playableWithoutInvert.Breaks.Count, Is.Not.Zero);
|
||||
Assert.That(playableWithoutInvert.Breaks[0], Is.EqualTo(new BreakPeriod(0, 1000)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,643 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModNoRelease : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
private const double time_before_head = 250;
|
||||
private const double time_head = 1500;
|
||||
private const double time_during_hold_1 = 2500;
|
||||
private const double time_tail = 4000;
|
||||
private const double time_after_tail = 5250;
|
||||
|
||||
private List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// o o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestNoInput()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCorrectInput()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
assertNoteJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLateRelease()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
assertNoteJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_before_head + 10),
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_before_head + 10),
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartAndBreak()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 10),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xox o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartThenReleaseAndImmediatelyRepress()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 1),
|
||||
new ManiaReplayFrame(time_head + 2, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertComboAtJudgement(0, 1);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
assertComboAtJudgement(1, 0);
|
||||
assertComboAtJudgement(3, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 10),
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo x o o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + 10),
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressDuringNoteAndReleaseAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressDuringNoteAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]--------------
|
||||
/// xo
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseAfterTailWithCloseByHead()
|
||||
{
|
||||
const int duration = 30;
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
// hold note is very short, to make the head still in range
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = duration,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head + duration + 60, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + duration + 70),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Ok);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-O-------------
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustBeforeTailWithNearbyNoteAndCloseByHead()
|
||||
{
|
||||
Note note;
|
||||
|
||||
const int duration = 50;
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
// hold note is very short, to make the head still in range
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = duration,
|
||||
Column = 0,
|
||||
},
|
||||
{
|
||||
// Next note within tail lenience
|
||||
note = new Note
|
||||
{
|
||||
StartTime = time_head + duration + 10
|
||||
}
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head + duration, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_head + duration + 10),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Good);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
|
||||
assertHitObjectJudgement(note, HitResult.Miss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]--O--
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustBeforeTailWithNearbyNote()
|
||||
{
|
||||
Note note;
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = time_tail - time_head,
|
||||
Column = 0,
|
||||
},
|
||||
{
|
||||
// Next note within tail lenience
|
||||
note = new Note
|
||||
{
|
||||
StartTime = time_tail + 50
|
||||
}
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail - 10, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
|
||||
assertHitObjectJudgement(note, HitResult.Good);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustAfterTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail + 20, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail + 30),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]--O--
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseJustAfterTailWithNearbyNote()
|
||||
{
|
||||
// Next note within tail lenience
|
||||
Note note = new Note { StartTime = time_tail + 50 };
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = time_tail - time_head,
|
||||
Column = 0,
|
||||
},
|
||||
note
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail + 10, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail + 20),
|
||||
}, beatmap);
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
|
||||
assertHitObjectJudgement(note, HitResult.Great);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// xo o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPressAndReleaseAtTail()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_tail, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail + 10),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissReleaseAndHitSecondRelease()
|
||||
{
|
||||
var windows = new ManiaHitWindows();
|
||||
windows.SetDifficulty(10);
|
||||
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 500,
|
||||
Column = 0,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10,
|
||||
Duration = 500,
|
||||
Column = 0,
|
||||
},
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderTickRate = 4,
|
||||
OverallDifficulty = 10,
|
||||
},
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
},
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(beatmap.HitObjects[1].StartTime, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(beatmap.HitObjects[1].GetEndTime()),
|
||||
}, beatmap);
|
||||
|
||||
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => !j.Type.IsHit()));
|
||||
|
||||
AddAssert("second hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestZeroLength()
|
||||
{
|
||||
var beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
Duration = 0,
|
||||
Column = 0,
|
||||
},
|
||||
},
|
||||
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||
};
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(beatmap.HitObjects[0].StartTime, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(beatmap.HitObjects[0].GetEndTime() + 1),
|
||||
}, beatmap);
|
||||
|
||||
AddAssert("hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
private void assertHitObjectJudgement(HitObject hitObject, HitResult result)
|
||||
=> AddAssert($"object judged as {result}", () => judgementResults.First(j => j.HitObject == hitObject).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertHeadJudgement(HitResult result)
|
||||
=> AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertTailJudgement(HitResult result)
|
||||
=> AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertNoteJudgement(HitResult result)
|
||||
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
|
||||
|
||||
private void assertComboAtJudgement(int judgementIndex, int combo)
|
||||
=> AddAssert($"combo at judgement {judgementIndex} is {combo}", () => judgementResults.ElementAt(judgementIndex).ComboAfterJudgement, () => Is.EqualTo(combo));
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject>? beatmap = null)
|
||||
{
|
||||
if (beatmap == null)
|
||||
{
|
||||
beatmap = new Beatmap<ManiaHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = time_head,
|
||||
Duration = time_tail - time_head,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||
}
|
||||
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
SelectedMods.Value = new List<Mod>
|
||||
{
|
||||
new ManiaModNoRelease()
|
||||
};
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
{
|
||||
p.ScoreProcessor.NewJudgement += result =>
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,8 +474,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => !j.Type.IsHit()));
|
||||
|
||||
AddAssert("second hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||
.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
}
|
||||
|
||||
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
|
||||
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor);
|
||||
=> hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
|
||||
|
||||
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
|
||||
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osuTK;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
else
|
||||
convertType |= PatternType.LowProbability;
|
||||
|
||||
if (!convertType.HasFlag(PatternType.KeepSingle))
|
||||
if (!convertType.HasFlagFast(PatternType.KeepSingle))
|
||||
{
|
||||
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
|
||||
convertType |= PatternType.Mirror;
|
||||
@@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||
|
||||
if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
||||
if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by copying the last hit objects in reverse-column order
|
||||
for (int i = RandomStart; i < TotalColumns; i++)
|
||||
@@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||
if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||
// If we convert to 7K + 1, let's not overload the special key
|
||||
&& (TotalColumns != 8 || lastColumn != 0)
|
||||
// Make sure the last column was not the centre column
|
||||
@@ -126,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||
if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by placing on the already filled columns
|
||||
for (int i = RandomStart; i < TotalColumns; i++)
|
||||
@@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (PreviousPattern.HitObjects.Count() == 1)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.Stair))
|
||||
if (convertType.HasFlagFast(PatternType.Stair))
|
||||
{
|
||||
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
|
||||
int targetColumn = lastColumn + 1;
|
||||
@@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlag(PatternType.ReverseStair))
|
||||
if (convertType.HasFlagFast(PatternType.ReverseStair))
|
||||
{
|
||||
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
|
||||
int targetColumn = lastColumn - 1;
|
||||
@@ -163,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
if (convertType.HasFlag(PatternType.KeepSingle))
|
||||
if (convertType.HasFlagFast(PatternType.KeepSingle))
|
||||
return generateRandomNotes(1);
|
||||
|
||||
if (convertType.HasFlag(PatternType.Mirror))
|
||||
if (convertType.HasFlagFast(PatternType.Mirror))
|
||||
{
|
||||
if (ConversionDifficulty > 6.5)
|
||||
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||
@@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.78, 0.42, 0, 0);
|
||||
|
||||
return generateRandomPattern(1, 0.62, 0, 0);
|
||||
@@ -186,7 +187,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.35, 0.08, 0, 0);
|
||||
|
||||
return generateRandomPattern(0.52, 0.15, 0, 0);
|
||||
@@ -194,7 +195,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 2)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.18, 0, 0, 0);
|
||||
|
||||
return generateRandomPattern(0.45, 0, 0, 0);
|
||||
@@ -207,9 +208,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
foreach (var obj in p.HitObjects)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||
if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||
StairType = PatternType.ReverseStair;
|
||||
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||
if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||
StairType = PatternType.Stair;
|
||||
}
|
||||
|
||||
@@ -229,7 +230,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
|
||||
bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
|
||||
|
||||
if (!allowStacking)
|
||||
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
|
||||
@@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int getNextColumn(int last)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.Gathered))
|
||||
if (convertType.HasFlagFast(PatternType.Gathered))
|
||||
{
|
||||
last++;
|
||||
if (last == TotalColumns)
|
||||
@@ -296,7 +297,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack))
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack))
|
||||
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@@ -138,7 +139,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
|
||||
@@ -146,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
|
||||
@@ -154,13 +155,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 2.5)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.3, 0, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
|
||||
}
|
||||
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.17, 0, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.27, 0, 0);
|
||||
@@ -218,7 +219,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
int lastColumn = nextColumn;
|
||||
@@ -370,7 +371,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
||||
|
||||
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
||||
bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
|
||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
|
||||
|
||||
if (canGenerateTwoNotes)
|
||||
@@ -403,7 +404,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
int endTime = startTime + SegmentDuration * SpanCount;
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
for (int i = 0; i < columnRepeat; i++)
|
||||
@@ -432,7 +433,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
|
||||
|
||||
// Create the hold note
|
||||
|
||||
@@ -6,7 +6,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -19,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public BindableBool ShowSpeedChanges { get; } = new BindableBool();
|
||||
|
||||
public double? TimelineTimeRange { get; set; }
|
||||
|
||||
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
|
||||
|
||||
public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods)
|
||||
@@ -41,11 +38,5 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
Origin = Anchor.Centre,
|
||||
Size = Vector2.One
|
||||
};
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get<int>(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value;
|
||||
base.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
@@ -13,7 +14,6 @@ using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
@@ -21,10 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
||||
{
|
||||
private DrawableManiaEditorRuleset drawableRuleset = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorScreenWithTimeline? screenWithTimeline { get; set; }
|
||||
private DrawableManiaEditorRuleset drawableRuleset;
|
||||
|
||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
@@ -75,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
||||
continue;
|
||||
|
||||
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||
ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||
|
||||
if (current == null)
|
||||
continue;
|
||||
@@ -86,13 +83,5 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (screenWithTimeline?.TimelineArea.Timeline != null)
|
||||
drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||
|
||||
private LabelledSliderBar<float> keyCountSlider { get; set; } = null!;
|
||||
private LabelledSwitchButton specialStyle { get; set; } = null!;
|
||||
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||
@@ -50,13 +49,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
Precision = 1,
|
||||
}
|
||||
},
|
||||
specialStyle = new LabelledSwitchButton
|
||||
{
|
||||
Label = "Use special (N+1) style",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||
},
|
||||
healthDrainSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||
@@ -153,7 +145,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
{
|
||||
public partial class ManiaSetupSection : RulesetSetupSection
|
||||
{
|
||||
private LabelledSwitchButton specialStyle;
|
||||
|
||||
public ManiaSetupSection()
|
||||
: base(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
specialStyle = new LabelledSwitchButton
|
||||
{
|
||||
Label = "Use special (N+1) style",
|
||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
specialStyle.Current.BindValueChanged(_ => updateBeatmap());
|
||||
}
|
||||
|
||||
private void updateBeatmap()
|
||||
{
|
||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@@ -88,79 +89,79 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
yield return new ManiaModNightcore();
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
yield return new ManiaModDoubleTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
yield return new ManiaModPerfect();
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
yield return new ManiaModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
yield return new ManiaModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
yield return new ManiaModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
yield return new ManiaModEasy();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.FadeIn))
|
||||
if (mods.HasFlagFast(LegacyMods.FadeIn))
|
||||
yield return new ManiaModFadeIn();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
yield return new ManiaModFlashlight();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
yield return new ManiaModHalfTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
yield return new ManiaModHardRock();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
yield return new ManiaModHidden();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key1))
|
||||
if (mods.HasFlagFast(LegacyMods.Key1))
|
||||
yield return new ManiaModKey1();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key2))
|
||||
if (mods.HasFlagFast(LegacyMods.Key2))
|
||||
yield return new ManiaModKey2();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key3))
|
||||
if (mods.HasFlagFast(LegacyMods.Key3))
|
||||
yield return new ManiaModKey3();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key4))
|
||||
if (mods.HasFlagFast(LegacyMods.Key4))
|
||||
yield return new ManiaModKey4();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key5))
|
||||
if (mods.HasFlagFast(LegacyMods.Key5))
|
||||
yield return new ManiaModKey5();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key6))
|
||||
if (mods.HasFlagFast(LegacyMods.Key6))
|
||||
yield return new ManiaModKey6();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key7))
|
||||
if (mods.HasFlagFast(LegacyMods.Key7))
|
||||
yield return new ManiaModKey7();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key8))
|
||||
if (mods.HasFlagFast(LegacyMods.Key8))
|
||||
yield return new ManiaModKey8();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Key9))
|
||||
if (mods.HasFlagFast(LegacyMods.Key9))
|
||||
yield return new ManiaModKey9();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.KeyCoop))
|
||||
if (mods.HasFlagFast(LegacyMods.KeyCoop))
|
||||
yield return new ManiaModDualStages();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
yield return new ManiaModNoFail();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Random))
|
||||
if (mods.HasFlagFast(LegacyMods.Random))
|
||||
yield return new ManiaModRandom();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Mirror))
|
||||
if (mods.HasFlagFast(LegacyMods.Mirror))
|
||||
yield return new ManiaModMirror();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@@ -240,7 +241,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModEasy(),
|
||||
new ManiaModNoFail(),
|
||||
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
|
||||
new ManiaModNoRelease(),
|
||||
};
|
||||
|
||||
case ModType.DifficultyIncrease:
|
||||
@@ -419,10 +419,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
return new ManiaFilterCriteria();
|
||||
}
|
||||
|
||||
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||
[
|
||||
new ManiaDifficultySection(),
|
||||
];
|
||||
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
||||
|
||||
public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
||||
|
||||
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
||||
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert), typeof(ManiaModNoRelease) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public partial class ManiaModNoRelease : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||
{
|
||||
public override string Name => "No Release";
|
||||
|
||||
public override string Acronym => "NR";
|
||||
|
||||
public override LocalisableString Description => "No more timing the end of hold notes.";
|
||||
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||
var hitObjects = maniaBeatmap.HitObjects.Select(obj =>
|
||||
{
|
||||
if (obj is HoldNote hold)
|
||||
return new NoReleaseHoldNote(hold);
|
||||
|
||||
return obj;
|
||||
}).ToList();
|
||||
|
||||
maniaBeatmap.HitObjects = hitObjects;
|
||||
}
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||
{
|
||||
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
||||
|
||||
foreach (var stage in maniaRuleset.Playfield.Stages)
|
||||
{
|
||||
foreach (var column in stage.Columns)
|
||||
{
|
||||
column.RegisterPool<NoReleaseTailNote, NoReleaseDrawableHoldNoteTail>(10, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class NoReleaseDrawableHoldNoteTail : DrawableHoldNoteTail
|
||||
{
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
// apply perfect once the tail is reached
|
||||
if (HoldNote.HoldStartTime != null && timeOffset >= 0)
|
||||
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||
else
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private class NoReleaseTailNote : TailNote
|
||||
{
|
||||
}
|
||||
|
||||
private class NoReleaseHoldNote : HoldNote
|
||||
{
|
||||
public NoReleaseHoldNote(HoldNote hold)
|
||||
{
|
||||
StartTime = hold.StartTime;
|
||||
Duration = hold.Duration;
|
||||
Column = hold.Column;
|
||||
NodeSamples = hold.NodeSamples;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
AddNested(Head = new HeadNote
|
||||
{
|
||||
StartTime = StartTime,
|
||||
Column = Column,
|
||||
Samples = GetNodeSamples(0),
|
||||
});
|
||||
|
||||
AddNested(Tail = new NoReleaseTailNote
|
||||
{
|
||||
StartTime = EndTime,
|
||||
Column = Column,
|
||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||
});
|
||||
|
||||
AddNested(Body = new HoldNoteBody
|
||||
{
|
||||
StartTime = StartTime,
|
||||
Column = Column
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,14 +268,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
ApplyMaxResult();
|
||||
else
|
||||
MissForcefully();
|
||||
|
||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||
if (!Body.AllJudged)
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
|
||||
// Important that this is always called when a result is applied.
|
||||
endHold();
|
||||
}
|
||||
|
||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||
if (Tail.AllJudged && !Body.AllJudged)
|
||||
Body.TriggerResult(Tail.IsHit);
|
||||
}
|
||||
|
||||
public override void MissForcefully()
|
||||
|
||||
@@ -72,18 +72,18 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// <summary>
|
||||
/// The head note of the hold.
|
||||
/// </summary>
|
||||
public HeadNote Head { get; protected set; }
|
||||
public HeadNote Head { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tail note of the hold.
|
||||
/// </summary>
|
||||
public TailNote Tail { get; protected set; }
|
||||
public TailNote Tail { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body of the hold.
|
||||
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
|
||||
/// </summary>
|
||||
public HoldNoteBody Body { get; protected set; }
|
||||
public HoldNoteBody Body { get; private set; }
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
|
||||
@@ -8,10 +8,9 @@ using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Input.Handlers;
|
||||
@@ -57,18 +56,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||
private readonly BindableInt configScrollSpeed = new BindableInt();
|
||||
|
||||
private double currentTimeRange;
|
||||
protected double TargetTimeRange;
|
||||
private double smoothTimeRange;
|
||||
|
||||
// Stores the current speed adjustment active in gameplay.
|
||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||
|
||||
private ISkinSource currentSkin = null!;
|
||||
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; } = null!;
|
||||
|
||||
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
@@ -107,9 +101,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||
|
||||
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
|
||||
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
|
||||
configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
|
||||
|
||||
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||
TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||
|
||||
KeyBindingInputManager.Add(new ManiaTouchInputArea());
|
||||
}
|
||||
@@ -150,9 +144,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
// This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position.
|
||||
float scale = lengthToHitPosition / length_to_default_hit_position;
|
||||
|
||||
// we're intentionally using the game host's update clock here to decouple the time range tween from the gameplay clock (which can be arbitrarily paused, or even rewinding)
|
||||
currentTimeRange = Interpolation.DampContinuously(currentTimeRange, TargetTimeRange, 50, gameHost.UpdateThread.Clock.ElapsedFrameTime);
|
||||
TimeRange.Value = currentTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||
TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -192,12 +192,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
if (press)
|
||||
{
|
||||
inputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
|
||||
inputManager?.KeyBindingContainer?.TriggerPressed(Action.Value);
|
||||
highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputManager?.KeyBindingContainer.TriggerReleased(Action.Value);
|
||||
inputManager?.KeyBindingContainer?.TriggerReleased(Action.Value);
|
||||
highlightOverlay.FadeTo(0, 400, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +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.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
@@ -161,8 +160,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
return grid switch
|
||||
{
|
||||
RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value),
|
||||
TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value),
|
||||
CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45),
|
||||
_ => Vector2.Zero
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
@@ -178,79 +177,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addAssertPointPositionChanged(points, i);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangingControlPointTypeViaTab()
|
||||
{
|
||||
createVisualiser(true);
|
||||
|
||||
addControlPointStep(new Vector2(200), PathType.LINEAR);
|
||||
addControlPointStep(new Vector2(300));
|
||||
addControlPointStep(new Vector2(500, 300));
|
||||
addControlPointStep(new Vector2(700, 200));
|
||||
addControlPointStep(new Vector2(500, 100));
|
||||
|
||||
AddStep("select first control point", () => visualiser.Pieces[0].IsSelected.Value = true);
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
assertControlPointPathType(0, PathType.BEZIER);
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.LINEAR);
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.BSpline(4));
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointPathType(2, PathType.BSpline(4));
|
||||
|
||||
AddStep("select third last control point", () =>
|
||||
{
|
||||
visualiser.Pieces[0].IsSelected.Value = false;
|
||||
visualiser.Pieces[2].IsSelected.Value = true;
|
||||
});
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
assertControlPointPathType(2, PathType.PERFECT_CURVE);
|
||||
|
||||
AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
|
||||
assertControlPointPathType(0, PathType.BEZIER);
|
||||
assertControlPointPathType(2, null);
|
||||
|
||||
AddStep("select first and third control points", () =>
|
||||
{
|
||||
visualiser.Pieces[0].IsSelected.Value = true;
|
||||
visualiser.Pieces[2].IsSelected.Value = true;
|
||||
});
|
||||
AddStep("press alt-1", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.Key(Key.Number1);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
});
|
||||
assertControlPointPathType(0, PathType.LINEAR);
|
||||
assertControlPointPathType(2, PathType.LINEAR);
|
||||
}
|
||||
|
||||
private void addAssertPointPositionChanged(Vector2[] points, int index)
|
||||
{
|
||||
AddAssert($"Point at {points.ElementAt(index)} changed",
|
||||
|
||||
@@ -2,16 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -60,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(200);
|
||||
assertControlPointCount(2);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -74,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -92,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -114,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(4);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100, 100));
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -133,8 +130,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.LINEAR);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -152,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertLength(100);
|
||||
}
|
||||
|
||||
@@ -174,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -198,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(4);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -218,8 +215,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.LINEAR);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -242,8 +239,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(4);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -271,46 +268,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointPosition(3, new Vector2(200, 100));
|
||||
assertControlPointPosition(4, new Vector2(200));
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualPathTypeControlViaKeyboard()
|
||||
{
|
||||
addMovementStep(new Vector2(200));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(300, 200));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(300));
|
||||
|
||||
assertControlPointTypeDuringPlacement(0, PathType.PERFECT_CURVE);
|
||||
|
||||
AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
|
||||
assertControlPointTypeDuringPlacement(0, PathType.LINEAR);
|
||||
|
||||
AddStep("press shift-tab", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Key(Key.Tab);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
|
||||
|
||||
AddStep("start new segment via S", () => InputManager.Key(Key.S));
|
||||
assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
|
||||
|
||||
addMovementStep(new Vector2(400, 300));
|
||||
addClickStep(MouseButton.Left);
|
||||
|
||||
addMovementStep(new Vector2(400));
|
||||
addClickStep(MouseButton.Right);
|
||||
|
||||
assertPlaced(true);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(2, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -334,7 +293,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addClickStep(MouseButton.Right);
|
||||
assertPlaced(true);
|
||||
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -353,11 +312,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(808, tolerance: 10);
|
||||
assertControlPointCount(5);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(1, null);
|
||||
assertFinalControlPointType(2, null);
|
||||
assertFinalControlPointType(3, null);
|
||||
assertFinalControlPointType(4, null);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, null);
|
||||
assertControlPointType(2, null);
|
||||
assertControlPointType(3, null);
|
||||
assertControlPointType(4, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -378,10 +337,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(600, tolerance: 10);
|
||||
assertControlPointCount(4);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(1, PathType.BSpline(4));
|
||||
assertFinalControlPointType(2, PathType.BSpline(4));
|
||||
assertFinalControlPointType(3, null);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, PathType.BSpline(4));
|
||||
assertControlPointType(2, PathType.BSpline(4));
|
||||
assertControlPointType(3, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -400,7 +359,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -420,7 +379,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -441,7 +400,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -462,7 +421,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -479,7 +438,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||
@@ -495,10 +454,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected));
|
||||
|
||||
private void assertControlPointTypeDuringPlacement(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}",
|
||||
() => this.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(index).ControlPoint.Type, () => Is.EqualTo(type));
|
||||
|
||||
private void assertFinalControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
||||
private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
||||
|
||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1));
|
||||
|
||||
+45
-152
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||
where T : OsuHitObject, IHasPath
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside the playfield.
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
||||
|
||||
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
||||
|
||||
@@ -196,7 +196,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (allowSelection)
|
||||
d.RequestSelection = selectionRequested;
|
||||
|
||||
d.ControlPoint.Changed += controlPointChanged;
|
||||
d.DragStarted = DragStarted;
|
||||
d.DragInProgress = DragInProgress;
|
||||
d.DragEnded = DragEnded;
|
||||
@@ -210,8 +209,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
foreach (var point in e.OldItems.Cast<PathControlPoint>())
|
||||
{
|
||||
point.Changed -= controlPointChanged;
|
||||
|
||||
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
|
||||
piece.RemoveAndDisposeImmediately();
|
||||
}
|
||||
@@ -220,8 +217,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void controlPointChanged() => updateCurveMenuItems();
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Pieces.Any(piece => piece.IsHovered))
|
||||
@@ -250,86 +245,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
}
|
||||
|
||||
// ReSharper disable once StaticMemberInGenericType
|
||||
private static readonly PathType?[] path_types =
|
||||
[
|
||||
PathType.LINEAR,
|
||||
PathType.BEZIER,
|
||||
PathType.PERFECT_CURVE,
|
||||
PathType.BSpline(4),
|
||||
null,
|
||||
];
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Tab:
|
||||
{
|
||||
var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToArray();
|
||||
if (selectedPieces.Length != 1)
|
||||
return false;
|
||||
|
||||
var selectedPiece = selectedPieces.Single();
|
||||
var selectedPoint = selectedPiece.ControlPoint;
|
||||
|
||||
var validTypes = path_types;
|
||||
|
||||
if (selectedPoint == controlPoints[0])
|
||||
validTypes = validTypes.Where(t => t != null).ToArray();
|
||||
|
||||
int currentTypeIndex = Array.IndexOf(validTypes, selectedPoint.Type);
|
||||
|
||||
if (currentTypeIndex < 0 && e.ShiftPressed)
|
||||
currentTypeIndex = 0;
|
||||
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
do
|
||||
{
|
||||
currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
|
||||
|
||||
updatePathTypeOfSelectedPieces(validTypes[currentTypeIndex]);
|
||||
} while (selectedPoint.Type != validTypes[currentTypeIndex]);
|
||||
|
||||
changeHandler?.EndChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case Key.Number1:
|
||||
case Key.Number2:
|
||||
case Key.Number3:
|
||||
case Key.Number4:
|
||||
case Key.Number5:
|
||||
{
|
||||
if (!e.AltPressed)
|
||||
return false;
|
||||
|
||||
var type = path_types[e.Key - Key.Number1];
|
||||
|
||||
if (Pieces[0].IsSelected.Value && type == null)
|
||||
return false;
|
||||
|
||||
updatePathTypeOfSelectedPieces(type);
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
foreach (var p in Pieces)
|
||||
p.ControlPoint.Changed -= controlPointChanged;
|
||||
}
|
||||
|
||||
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
||||
@@ -339,38 +254,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set all selected control point pieces to the given path type.
|
||||
/// If that fails, try to change the path such that it instead succeeds
|
||||
/// Attempts to set the given control point piece to the given path type.
|
||||
/// If that would fail, try to change the path such that it instead succeeds
|
||||
/// in a UX-friendly way.
|
||||
/// </summary>
|
||||
/// <param name="piece">The control point piece that we want to change the path type of.</param>
|
||||
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
||||
private void updatePathTypeOfSelectedPieces(PathType? type)
|
||||
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
||||
{
|
||||
changeHandler?.BeginChange();
|
||||
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
|
||||
int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
|
||||
|
||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||
if (type?.Type == SplineType.PerfectCurve)
|
||||
{
|
||||
var pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint);
|
||||
int indexInSegment = pointsInSegment.IndexOf(p.ControlPoint);
|
||||
// Can't always create a circular arc out of 4 or more points,
|
||||
// so we split the segment into one 3-point circular arc segment
|
||||
// and one segment of the previous type.
|
||||
int thirdPointIndex = indexInSegment + 2;
|
||||
|
||||
if (type?.Type == SplineType.PerfectCurve)
|
||||
{
|
||||
// Can't always create a circular arc out of 4 or more points,
|
||||
// so we split the segment into one 3-point circular arc segment
|
||||
// and one segment of the previous type.
|
||||
int thirdPointIndex = indexInSegment + 2;
|
||||
|
||||
if (pointsInSegment.Count > thirdPointIndex + 1)
|
||||
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
||||
}
|
||||
|
||||
hitObject.Path.ExpectedDistance.Value = null;
|
||||
p.ControlPoint.Type = type;
|
||||
if (pointsInSegment.Count > thirdPointIndex + 1)
|
||||
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
||||
}
|
||||
|
||||
EnsureValidPathTypes();
|
||||
|
||||
changeHandler?.EndChange();
|
||||
hitObject.Path.ExpectedDistance.Value = null;
|
||||
piece.ControlPoint.Type = type;
|
||||
}
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
@@ -383,8 +290,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
private int draggedControlPointIndex;
|
||||
private HashSet<PathControlPoint> selectedControlPoints;
|
||||
|
||||
private List<MenuItem> curveTypeItems;
|
||||
|
||||
public void DragStarted(PathControlPoint controlPoint)
|
||||
{
|
||||
dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
|
||||
@@ -481,27 +386,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
var splittablePieces = selectedPieces.Where(isSplittable).ToList();
|
||||
int splittableCount = splittablePieces.Count;
|
||||
|
||||
curveTypeItems = new List<MenuItem>();
|
||||
List<MenuItem> curveTypeItems = new List<MenuItem>();
|
||||
|
||||
foreach (PathType? type in path_types)
|
||||
if (!selectedPieces.Contains(Pieces[0]))
|
||||
{
|
||||
// special inherit case
|
||||
if (type == null)
|
||||
{
|
||||
if (selectedPieces.Contains(Pieces[0]))
|
||||
continue;
|
||||
|
||||
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||
}
|
||||
|
||||
curveTypeItems.Add(createMenuItemForPathType(type));
|
||||
curveTypeItems.Add(createMenuItemForPathType(null));
|
||||
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||
}
|
||||
|
||||
// todo: hide/disable items which aren't valid for selected points
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(4)));
|
||||
|
||||
if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull))
|
||||
{
|
||||
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL));
|
||||
}
|
||||
|
||||
var menuItems = new List<MenuItem>
|
||||
{
|
||||
@@ -524,42 +424,35 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
() => DeleteSelected())
|
||||
);
|
||||
|
||||
updateCurveMenuItems();
|
||||
|
||||
return menuItems.ToArray();
|
||||
|
||||
CurveTypeMenuItem createMenuItemForPathType(PathType? type) => new CurveTypeMenuItem(type, _ => updatePathTypeOfSelectedPieces(type));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurveMenuItems()
|
||||
private MenuItem createMenuItemForPathType(PathType? type)
|
||||
{
|
||||
if (curveTypeItems == null)
|
||||
return;
|
||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type);
|
||||
|
||||
foreach (var item in curveTypeItems.OfType<CurveTypeMenuItem>())
|
||||
var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ =>
|
||||
{
|
||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == item.PathType);
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
if (countOfState == totalCount)
|
||||
item.State.Value = TernaryState.True;
|
||||
else if (countOfState > 0)
|
||||
item.State.Value = TernaryState.Indeterminate;
|
||||
else
|
||||
item.State.Value = TernaryState.False;
|
||||
}
|
||||
}
|
||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||
updatePathType(p, type);
|
||||
|
||||
private class CurveTypeMenuItem : TernaryStateRadioMenuItem
|
||||
{
|
||||
public readonly PathType? PathType;
|
||||
EnsureValidPathTypes();
|
||||
|
||||
public CurveTypeMenuItem(PathType? pathType, Action<TernaryState> action)
|
||||
: base(pathType?.Description ?? "Inherit", MenuItemType.Standard, action)
|
||||
{
|
||||
PathType = pathType;
|
||||
}
|
||||
changeHandler?.EndChange();
|
||||
});
|
||||
|
||||
if (countOfState == totalCount)
|
||||
item.State.Value = TernaryState.True;
|
||||
else if (countOfState > 0)
|
||||
item.State.Value = TernaryState.Indeterminate;
|
||||
else
|
||||
item.State.Value = TernaryState.False;
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private PathControlPoint segmentStart;
|
||||
private PathControlPoint cursor;
|
||||
private int currentSegmentLength;
|
||||
private bool usingCustomSegmentType;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
@@ -150,9 +149,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
case SliderPlacementState.ControlPoints:
|
||||
if (canPlaceNewControlPoint(out var lastPoint))
|
||||
placeNewControlPoint();
|
||||
{
|
||||
// Place a new point by detatching the current cursor.
|
||||
updateCursor();
|
||||
cursor = null;
|
||||
}
|
||||
else
|
||||
beginNewSegment(lastPoint);
|
||||
{
|
||||
// Transform the last point into a new segment.
|
||||
Debug.Assert(lastPoint != null);
|
||||
|
||||
segmentStart = lastPoint;
|
||||
segmentStart.Type = PathType.LINEAR;
|
||||
|
||||
currentSegmentLength = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -160,18 +171,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
return true;
|
||||
}
|
||||
|
||||
private void beginNewSegment(PathControlPoint lastPoint)
|
||||
{
|
||||
// Transform the last point into a new segment.
|
||||
Debug.Assert(lastPoint != null);
|
||||
|
||||
segmentStart = lastPoint;
|
||||
segmentStart.Type = PathType.LINEAR;
|
||||
|
||||
currentSegmentLength = 1;
|
||||
usingCustomSegmentType = false;
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
if (e.Button != MouseButton.Left)
|
||||
@@ -224,72 +223,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
private static readonly PathType[] path_types =
|
||||
[
|
||||
PathType.LINEAR,
|
||||
PathType.BEZIER,
|
||||
PathType.PERFECT_CURVE,
|
||||
PathType.BSpline(4),
|
||||
];
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
if (state != SliderPlacementState.ControlPoints)
|
||||
return false;
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.S:
|
||||
{
|
||||
if (!canPlaceNewControlPoint(out _))
|
||||
return false;
|
||||
|
||||
placeNewControlPoint();
|
||||
var last = HitObject.Path.ControlPoints.Last(p => p != cursor);
|
||||
beginNewSegment(last);
|
||||
return true;
|
||||
}
|
||||
|
||||
case Key.Number1:
|
||||
case Key.Number2:
|
||||
case Key.Number3:
|
||||
case Key.Number4:
|
||||
{
|
||||
if (!e.AltPressed)
|
||||
return false;
|
||||
|
||||
usingCustomSegmentType = true;
|
||||
segmentStart.Type = path_types[e.Key - Key.Number1];
|
||||
controlPointVisualiser.EnsureValidPathTypes();
|
||||
return true;
|
||||
}
|
||||
|
||||
case Key.Tab:
|
||||
{
|
||||
usingCustomSegmentType = true;
|
||||
|
||||
int currentTypeIndex = segmentStart.Type.HasValue ? Array.IndexOf(path_types, segmentStart.Type.Value) : -1;
|
||||
|
||||
if (currentTypeIndex < 0 && e.ShiftPressed)
|
||||
currentTypeIndex = 0;
|
||||
|
||||
do
|
||||
{
|
||||
currentTypeIndex = (path_types.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % path_types.Length;
|
||||
segmentStart.Type = path_types[currentTypeIndex];
|
||||
controlPointVisualiser.EnsureValidPathTypes();
|
||||
} while (segmentStart.Type != path_types[currentTypeIndex]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@@ -313,12 +246,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private void updatePathType()
|
||||
{
|
||||
if (usingCustomSegmentType)
|
||||
{
|
||||
controlPointVisualiser.EnsureValidPathTypes();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SliderPlacementState.Drawing)
|
||||
{
|
||||
segmentStart.Type = PathType.BSpline(4);
|
||||
@@ -389,13 +316,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
return lastPiece.IsHovered != true;
|
||||
}
|
||||
|
||||
private void placeNewControlPoint()
|
||||
{
|
||||
// Place a new point by detatching the current cursor.
|
||||
updateCursor();
|
||||
cursor = null;
|
||||
}
|
||||
|
||||
private void updateSlider()
|
||||
{
|
||||
if (state == SliderPlacementState.Drawing)
|
||||
|
||||
@@ -55,21 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
public override Quad SelectionQuad
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = BodyPiece.ScreenSpaceDrawQuad.AABBFloat;
|
||||
|
||||
if (ControlPointVisualiser != null)
|
||||
{
|
||||
foreach (var piece in ControlPointVisualiser.Pieces)
|
||||
result = RectangleF.Union(result, piece.ScreenSpaceDrawQuad.AABBFloat);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
|
||||
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
||||
@@ -1,24 +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.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
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;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
@@ -27,9 +20,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IExpandingContainer? expandingContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// X position of the grid's origin.
|
||||
/// </summary>
|
||||
@@ -65,8 +55,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// </summary>
|
||||
public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f)
|
||||
{
|
||||
MinValue = -180f,
|
||||
MaxValue = 180f,
|
||||
MinValue = -45f,
|
||||
MaxValue = 45f,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
@@ -82,13 +72,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// </summary>
|
||||
public Bindable<Vector2> SpacingVector { get; } = new Bindable<Vector2>();
|
||||
|
||||
public Bindable<PositionSnapGridType> GridType { get; } = new Bindable<PositionSnapGridType>();
|
||||
|
||||
private ExpandableSlider<float> startPositionXSlider = null!;
|
||||
private ExpandableSlider<float> startPositionYSlider = null!;
|
||||
private ExpandableSlider<float> spacingSlider = null!;
|
||||
private ExpandableSlider<float> gridLinesRotationSlider = null!;
|
||||
private EditorRadioButtonCollection gridTypeButtons = null!;
|
||||
|
||||
public OsuGridToolboxGroup()
|
||||
: base("grid")
|
||||
@@ -122,31 +109,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Current = GridLinesRotation,
|
||||
KeyboardStep = 1,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 10f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
gridTypeButtons = new EditorRadioButtonCollection
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Items = new[]
|
||||
{
|
||||
new RadioButton("Square",
|
||||
() => GridType.Value = PositionSnapGridType.Square,
|
||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||
new RadioButton("Triangle",
|
||||
() => GridType.Value = PositionSnapGridType.Triangle,
|
||||
() => new OutlineTriangle(true, 20)),
|
||||
new RadioButton("Circle",
|
||||
() => GridType.Value = PositionSnapGridType.Circle,
|
||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Circle }),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Spacing.Value = editorBeatmap.BeatmapInfo.GridSize;
|
||||
@@ -156,8 +118,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
gridTypeButtons.Items.First().Select();
|
||||
|
||||
StartPositionX.BindValueChanged(x =>
|
||||
{
|
||||
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
|
||||
@@ -185,32 +145,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
|
||||
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
|
||||
}, true);
|
||||
|
||||
expandingContainer?.Expanded.BindValueChanged(v =>
|
||||
{
|
||||
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
|
||||
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
}, true);
|
||||
|
||||
GridType.BindValueChanged(v =>
|
||||
{
|
||||
GridLinesRotation.Disabled = v.NewValue == PositionSnapGridType.Circle;
|
||||
|
||||
switch (v.NewValue)
|
||||
{
|
||||
case PositionSnapGridType.Square:
|
||||
GridLinesRotation.Value = ((GridLinesRotation.Value + 405) % 90) - 45;
|
||||
GridLinesRotation.MinValue = -45;
|
||||
GridLinesRotation.MaxValue = 45;
|
||||
break;
|
||||
|
||||
case PositionSnapGridType.Triangle:
|
||||
GridLinesRotation.Value = ((GridLinesRotation.Value + 390) % 60) - 30;
|
||||
GridLinesRotation.MinValue = -30;
|
||||
GridLinesRotation.MaxValue = 30;
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void nextGridSize()
|
||||
@@ -233,42 +167,5 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
public partial class OutlineTriangle : BufferedContainer
|
||||
{
|
||||
public OutlineTriangle(bool outlineOnly, float size)
|
||||
: base(cachedFrameBuffer: true)
|
||||
{
|
||||
Size = new Vector2(size);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new EquilateralTriangle { RelativeSizeAxes = Axes.Both },
|
||||
};
|
||||
|
||||
if (outlineOnly)
|
||||
{
|
||||
AddInternal(new EquilateralTriangle
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
Y = 0.48f,
|
||||
Colour = Color4.Black,
|
||||
Size = new Vector2(size - 7),
|
||||
Blending = BlendingParameters.None,
|
||||
});
|
||||
}
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PositionSnapGridType
|
||||
{
|
||||
Square,
|
||||
Triangle,
|
||||
Circle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@@ -50,8 +51,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector();
|
||||
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
||||
=> base.CreateTernaryButtons()
|
||||
.Concat(DistanceSnapProvider.CreateTernaryButtons())
|
||||
@@ -100,9 +99,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// we may be entering the screen with a selection already active
|
||||
updateDistanceSnapGrid();
|
||||
|
||||
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
|
||||
updatePositionSnapGrid();
|
||||
|
||||
RightToolbox.AddRange(new Drawable[]
|
||||
RightToolbox.AddRange(new EditorToolboxGroup[]
|
||||
{
|
||||
OsuGridToolboxGroup,
|
||||
new TransformToolboxGroup
|
||||
@@ -115,45 +114,18 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
);
|
||||
}
|
||||
|
||||
private void updatePositionSnapGrid(ValueChangedEvent<PositionSnapGridType> obj)
|
||||
private void updatePositionSnapGrid()
|
||||
{
|
||||
if (positionSnapGrid != null)
|
||||
LayerBelowRuleset.Remove(positionSnapGrid, true);
|
||||
|
||||
switch (obj.NewValue)
|
||||
{
|
||||
case PositionSnapGridType.Square:
|
||||
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
|
||||
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
|
||||
|
||||
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
|
||||
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
|
||||
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
|
||||
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
|
||||
positionSnapGrid = rectangularPositionSnapGrid;
|
||||
break;
|
||||
|
||||
case PositionSnapGridType.Triangle:
|
||||
var triangularPositionSnapGrid = new TriangularPositionSnapGrid();
|
||||
|
||||
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
|
||||
triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
|
||||
positionSnapGrid = triangularPositionSnapGrid;
|
||||
break;
|
||||
|
||||
case PositionSnapGridType.Circle:
|
||||
var circularPositionSnapGrid = new CircularPositionSnapGrid();
|
||||
|
||||
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
|
||||
|
||||
positionSnapGrid = circularPositionSnapGrid;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(OsuGridToolboxGroup.GridType), OsuGridToolboxGroup.GridType, "Unsupported grid type.");
|
||||
}
|
||||
|
||||
// Bind the start position to the toolbox sliders.
|
||||
positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
|
||||
positionSnapGrid = rectangularPositionSnapGrid;
|
||||
|
||||
positionSnapGrid.RelativeSizeAxes = Axes.Both;
|
||||
LayerBelowRuleset.Add(positionSnapGrid);
|
||||
@@ -220,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||
{
|
||||
if (snapType.HasFlag(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
{
|
||||
// In the case of snapping to nearby objects, a time value is not provided.
|
||||
// This matches the stable editor (which also uses current time), but with the introduction of time-snapping distance snap
|
||||
@@ -230,7 +202,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
||||
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
||||
// the time value if the proposed positions are roughly the same.
|
||||
if (snapType.HasFlag(SnapType.RelativeGrids) && DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids) && DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
||||
if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
|
||||
@@ -242,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
|
||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
{
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
@@ -253,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
}
|
||||
|
||||
if (snapType.HasFlag(SnapType.GlobalGrids))
|
||||
if (snapType.HasFlagFast(SnapType.GlobalGrids))
|
||||
{
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
{
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class OsuHitObjectInspector : HitObjectInspector
|
||||
{
|
||||
protected override void AddInspectorValues()
|
||||
{
|
||||
base.AddInspectorValues();
|
||||
|
||||
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||
{
|
||||
var firstInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MinBy(ho => ho.StartTime)!;
|
||||
var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!;
|
||||
|
||||
Debug.Assert(firstInSelection != null && lastInSelection != null);
|
||||
|
||||
var precedingObject = (OsuHitObject?)EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstInSelection.StartTime);
|
||||
var nextObject = (OsuHitObject?)EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastInSelection.GetEndTime());
|
||||
|
||||
if (precedingObject != null && precedingObject is not Spinner)
|
||||
{
|
||||
AddHeader("To previous");
|
||||
AddValue($"{(firstInSelection.StackedPosition - precedingObject.StackedEndPosition).Length:#,0.##}px");
|
||||
}
|
||||
|
||||
if (nextObject != null && nextObject is not Spinner)
|
||||
{
|
||||
AddHeader("To next");
|
||||
AddValue($"{(nextObject.StackedPosition - lastInSelection.StackedEndPosition).Length:#,0.##}px");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
{
|
||||
public partial class OsuDifficultySection : SetupSection
|
||||
{
|
||||
private LabelledSliderBar<float> circleSizeSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> approachRateSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> stackLeniency { get; set; } = null!;
|
||||
|
||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
circleSizeSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsCs,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.CircleSizeDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
healthDrainSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.DrainRateDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
approachRateSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsAr,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.ApproachRateDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.ApproachRate)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
overallDifficultySlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.OverallDifficultyDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
baseVelocitySlider = new LabelledSliderBar<double>
|
||||
{
|
||||
Label = EditorSetupStrings.BaseVelocity,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
MinValue = 0.4,
|
||||
MaxValue = 3.6,
|
||||
Precision = 0.01f,
|
||||
}
|
||||
},
|
||||
tickRateSlider = new LabelledSliderBar<double>
|
||||
{
|
||||
Label = EditorSetupStrings.TickRate,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.TickRateDescription,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
MinValue = 1,
|
||||
MaxValue = 4,
|
||||
Precision = 1,
|
||||
}
|
||||
},
|
||||
stackLeniency = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Stack Leniency",
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency)
|
||||
{
|
||||
Default = 0.7f,
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Precision = 0.1f
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
|
||||
item.Current.ValueChanged += _ => updateValues();
|
||||
|
||||
foreach (var item in Children.OfType<LabelledSliderBar<double>>())
|
||||
item.Current.ValueChanged += _ => updateValues();
|
||||
}
|
||||
|
||||
private void updateValues()
|
||||
{
|
||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||
Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value;
|
||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
|
||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||
|
||||
Beatmap.UpdateAllHitObjects();
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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.Bindables;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
{
|
||||
public partial class OsuSetupSection : RulesetSetupSection
|
||||
{
|
||||
private LabelledSliderBar<float> stackLeniency;
|
||||
|
||||
public OsuSetupSection()
|
||||
: base(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
stackLeniency = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = "Stack Leniency",
|
||||
Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
||||
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency)
|
||||
{
|
||||
Default = 0.7f,
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Precision = 0.1f
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
stackLeniency.Current.BindValueChanged(_ => updateBeatmap());
|
||||
}
|
||||
|
||||
private void updateBeatmap()
|
||||
{
|
||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||
Beatmap.UpdateAllHitObjects();
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
public SliderCompositionTool()
|
||||
: base(nameof(Slider))
|
||||
{
|
||||
TooltipText = """
|
||||
Left click for new point.
|
||||
Left click twice or S key for new segment.
|
||||
Tab, Shift-Tab, or Alt-1~4 to change current segment type.
|
||||
Right click to finish.
|
||||
Click and drag for drawing mode.
|
||||
""";
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||
|
||||
@@ -6,9 +6,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
|
||||
@@ -25,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
||||
|
||||
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@@ -326,19 +325,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
internal void SuppressHitAnimations()
|
||||
{
|
||||
UpdateState(ArmedState.Idle);
|
||||
UpdateState(ArmedState.Idle, true);
|
||||
UpdateComboColour();
|
||||
|
||||
// This method is called every frame in editor contexts, thus the lack of need for transforms.
|
||||
// This method is called every frame. If we need to, the following can likely be converted
|
||||
// to code which doesn't use transforms at all.
|
||||
|
||||
if (Time.Current >= HitStateUpdateTime)
|
||||
{
|
||||
// More or less matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
|
||||
AccentColour.Value = Color4.White;
|
||||
Alpha = Interpolation.ValueAt(Time.Current, 1f, 0f, HitStateUpdateTime, HitStateUpdateTime + 700);
|
||||
}
|
||||
// Matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
|
||||
|
||||
LifetimeEnd = HitStateUpdateTime + 700;
|
||||
using (BeginAbsoluteSequence(StateUpdateTime - 5))
|
||||
this.TransformBindableTo(AccentColour, Color4.White, Math.Max(0, HitStateUpdateTime - StateUpdateTime));
|
||||
|
||||
using (BeginAbsoluteSequence(HitStateUpdateTime))
|
||||
this.FadeOut(700).Expire();
|
||||
}
|
||||
|
||||
internal void RestoreHitAnimations()
|
||||
|
||||
@@ -5,23 +5,19 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableOsuJudgement : DrawableJudgement
|
||||
{
|
||||
internal Color4 AccentColour { get; private set; }
|
||||
|
||||
internal SkinnableLighting Lighting { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private Vector2 screenSpacePosition;
|
||||
private bool positionTransferred;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -36,36 +32,37 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
});
|
||||
}
|
||||
|
||||
public override void Apply(JudgementResult result, DrawableHitObject? judgedObject)
|
||||
{
|
||||
base.Apply(result, judgedObject);
|
||||
|
||||
if (judgedObject is not DrawableOsuHitObject osuObject)
|
||||
return;
|
||||
|
||||
AccentColour = osuObject.AccentColour.Value;
|
||||
|
||||
switch (osuObject)
|
||||
{
|
||||
case DrawableSlider slider:
|
||||
screenSpacePosition = slider.TailCircle.ToScreenSpace(slider.TailCircle.OriginPosition);
|
||||
break;
|
||||
|
||||
default:
|
||||
screenSpacePosition = osuObject.ToScreenSpace(osuObject.OriginPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
Lighting.ResetAnimation();
|
||||
Lighting.SetColourFrom(this, Result);
|
||||
Position = Parent!.ToLocalSpace(screenSpacePosition);
|
||||
Lighting.SetColourFrom(JudgedObject, Result);
|
||||
|
||||
positionTransferred = false;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse)
|
||||
{
|
||||
switch (osuObject)
|
||||
{
|
||||
case DrawableSlider slider:
|
||||
Position = slider.TailCircle.ToSpaceOfOtherDrawable(slider.TailCircle.OriginPosition, Parent!);
|
||||
break;
|
||||
|
||||
default:
|
||||
Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!);
|
||||
break;
|
||||
}
|
||||
|
||||
positionTransferred = true;
|
||||
|
||||
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyHitAnimations()
|
||||
|
||||
@@ -375,14 +375,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
internal void SuppressHitAnimations()
|
||||
{
|
||||
UpdateState(ArmedState.Idle);
|
||||
UpdateState(ArmedState.Idle, true);
|
||||
HeadCircle.SuppressHitAnimations();
|
||||
TailCircle.SuppressHitAnimations();
|
||||
}
|
||||
|
||||
internal void RestoreHitAnimations()
|
||||
{
|
||||
UpdateState(ArmedState.Hit);
|
||||
UpdateState(ArmedState.Hit, force: true);
|
||||
HeadCircle.RestoreHitAnimations();
|
||||
TailCircle.RestoreHitAnimations();
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Skinning;
|
||||
@@ -132,19 +132,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
internal void SuppressHitAnimations()
|
||||
{
|
||||
UpdateState(ArmedState.Idle);
|
||||
UpdateState(ArmedState.Idle, true);
|
||||
UpdateComboColour();
|
||||
|
||||
// This method is called every frame in editor contexts, thus the lack of need for transforms.
|
||||
using (BeginAbsoluteSequence(StateUpdateTime - 5))
|
||||
this.TransformBindableTo(AccentColour, Color4.White, Math.Max(0, HitStateUpdateTime - StateUpdateTime));
|
||||
|
||||
if (Time.Current >= HitStateUpdateTime)
|
||||
{
|
||||
// More or less matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
|
||||
AccentColour.Value = Color4.White;
|
||||
Alpha = Interpolation.ValueAt(Time.Current, 1f, 0f, HitStateUpdateTime, HitStateUpdateTime + 700);
|
||||
}
|
||||
|
||||
LifetimeEnd = HitStateUpdateTime + 700;
|
||||
using (BeginAbsoluteSequence(HitStateUpdateTime))
|
||||
this.FadeOut(700).Expire();
|
||||
}
|
||||
|
||||
internal void RestoreHitAnimations()
|
||||
|
||||
@@ -1,6 +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 osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
@@ -10,8 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
internal partial class SkinnableLighting : SkinnableSprite
|
||||
{
|
||||
private DrawableOsuJudgement? targetJudgement;
|
||||
private JudgementResult? targetResult;
|
||||
private DrawableHitObject targetObject;
|
||||
private JudgementResult targetResult;
|
||||
|
||||
public SkinnableLighting()
|
||||
: base("lighting")
|
||||
@@ -27,11 +29,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
/// <summary>
|
||||
/// Updates the lighting colour from a given hitobject and result.
|
||||
/// </summary>
|
||||
/// <param name="targetJudgement">The <see cref="DrawableHitObject"/> that's been judged.</param>
|
||||
/// <param name="targetResult">The <see cref="JudgementResult"/> that <paramref name="targetJudgement"/> was judged with.</param>
|
||||
public void SetColourFrom(DrawableOsuJudgement targetJudgement, JudgementResult? targetResult)
|
||||
/// <param name="targetObject">The <see cref="DrawableHitObject"/> that's been judged.</param>
|
||||
/// <param name="targetResult">The <see cref="JudgementResult"/> that <paramref name="targetObject"/> was judged with.</param>
|
||||
public void SetColourFrom(DrawableHitObject targetObject, JudgementResult targetResult)
|
||||
{
|
||||
this.targetJudgement = targetJudgement;
|
||||
this.targetObject = targetObject;
|
||||
this.targetResult = targetResult;
|
||||
|
||||
updateColour();
|
||||
@@ -39,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
if (targetJudgement == null || targetResult == null)
|
||||
if (targetObject == null || targetResult == null)
|
||||
Colour = Color4.White;
|
||||
else
|
||||
Colour = targetResult.IsHit ? targetJudgement.AccentColour : Color4.Transparent;
|
||||
Colour = targetResult.IsHit ? targetObject.AccentColour.Value : Color4.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition, IHasTimePreempt
|
||||
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius of hit objects (ie. the radius of a <see cref="HitCircle"/>).
|
||||
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public const double PREEMPT_MAX = 1800;
|
||||
|
||||
public double TimePreempt { get; set; } = 600;
|
||||
public double TimePreempt = 600;
|
||||
public double TimeFadeIn = 400;
|
||||
|
||||
private HitObjectProperty<Vector2> position;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@@ -69,55 +70,55 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
yield return new OsuModNightcore();
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
yield return new OsuModDoubleTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
yield return new OsuModPerfect();
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
yield return new OsuModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Autopilot))
|
||||
if (mods.HasFlagFast(LegacyMods.Autopilot))
|
||||
yield return new OsuModAutopilot();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
yield return new OsuModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
yield return new OsuModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
yield return new OsuModEasy();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
yield return new OsuModFlashlight();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
yield return new OsuModHalfTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
yield return new OsuModHardRock();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
yield return new OsuModHidden();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
yield return new OsuModNoFail();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
yield return new OsuModRelax();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.SpunOut))
|
||||
if (mods.HasFlagFast(LegacyMods.SpunOut))
|
||||
yield return new OsuModSpunOut();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Target))
|
||||
if (mods.HasFlagFast(LegacyMods.Target))
|
||||
yield return new OsuModTargetPractice();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.TouchDevice))
|
||||
if (mods.HasFlagFast(LegacyMods.TouchDevice))
|
||||
yield return new OsuModTouchDevice();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@@ -336,11 +337,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||
[
|
||||
new OsuDifficultySection(),
|
||||
new ColoursSection(),
|
||||
];
|
||||
public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection();
|
||||
|
||||
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
|
||||
/// <seealso cref="OsuHitWindows"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
@@ -242,14 +243,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
originPosition = Vector2.Zero;
|
||||
|
||||
if (Source.TrailOrigin.HasFlag(Anchor.x1))
|
||||
if (Source.TrailOrigin.HasFlagFast(Anchor.x1))
|
||||
originPosition.X = 0.5f;
|
||||
else if (Source.TrailOrigin.HasFlag(Anchor.x2))
|
||||
else if (Source.TrailOrigin.HasFlagFast(Anchor.x2))
|
||||
originPosition.X = 1f;
|
||||
|
||||
if (Source.TrailOrigin.HasFlag(Anchor.y1))
|
||||
if (Source.TrailOrigin.HasFlagFast(Anchor.y1))
|
||||
originPosition.Y = 0.5f;
|
||||
else if (Source.TrailOrigin.HasFlag(Anchor.y2))
|
||||
else if (Source.TrailOrigin.HasFlagFast(Anchor.y2))
|
||||
originPosition.Y = 1f;
|
||||
|
||||
Source.parts.CopyTo(parts, 0);
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -48,8 +47,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override GameplayCursorContainer? CreateCursor() => new OsuCursorContainer();
|
||||
|
||||
public override Quad SkinnableComponentScreenSpaceDrawQuad => playfieldBorder.ScreenSpaceDrawQuad;
|
||||
|
||||
private readonly Container judgementAboveHitObjectLayer;
|
||||
|
||||
public OsuPlayfield()
|
||||
|
||||
@@ -116,9 +116,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||
|
||||
// When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score.
|
||||
// To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events.
|
||||
Schedule(() => ResumeRequested?.Invoke());
|
||||
ResumeRequested?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
public class Peaks : Skill
|
||||
{
|
||||
private const double rhythm_skill_multiplier = 0.2 * final_multiplier;
|
||||
private const double colour_skill_multiplier = 0.375 * final_multiplier;
|
||||
private const double stamina_skill_multiplier = 0.375 * final_multiplier;
|
||||
|
||||
private const double final_multiplier = 0.0625;
|
||||
|
||||
private readonly Rhythm rhythm;
|
||||
private readonly Colour colour;
|
||||
private readonly Stamina stamina;
|
||||
|
||||
public double ColourDifficultyValue => colour.DifficultyValue() * colour_skill_multiplier;
|
||||
public double RhythmDifficultyValue => rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||
public double StaminaDifficultyValue => stamina.DifficultyValue() * stamina_skill_multiplier;
|
||||
|
||||
public Peaks(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
rhythm = new Rhythm(mods);
|
||||
colour = new Colour(mods);
|
||||
stamina = new Stamina(mods);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector.
|
||||
/// </summary>
|
||||
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
|
||||
/// <param name="values">The coefficients of the vector.</param>
|
||||
private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
|
||||
|
||||
public override void Process(DifficultyHitObject current)
|
||||
{
|
||||
rhythm.Process(current);
|
||||
colour.Process(current);
|
||||
stamina.Process(current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the combined star rating of the beatmap, calculated using peak strains from all sections of the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
||||
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
||||
/// </remarks>
|
||||
public override double DifficultyValue()
|
||||
{
|
||||
List<double> peaks = new List<double>();
|
||||
|
||||
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
|
||||
var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
|
||||
var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList();
|
||||
|
||||
for (int i = 0; i < colourPeaks.Count; i++)
|
||||
{
|
||||
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
||||
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
||||
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;
|
||||
|
||||
double peak = norm(1.5, colourPeak, staminaPeak);
|
||||
peak = norm(2, peak, rhythmPeak);
|
||||
|
||||
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// These sections will not contribute to the difficulty.
|
||||
if (peak > 0)
|
||||
peaks.Add(peak);
|
||||
}
|
||||
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
foreach (double strain in peaks.OrderDescending())
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
private const double difficulty_multiplier = 1.35;
|
||||
|
||||
private const double final_multiplier = 0.0625;
|
||||
private const double rhythm_skill_multiplier = 0.2 * final_multiplier;
|
||||
private const double colour_skill_multiplier = 0.375 * final_multiplier;
|
||||
private const double stamina_skill_multiplier = 0.375 * final_multiplier;
|
||||
|
||||
public override int Version => 20221107;
|
||||
|
||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
@@ -39,9 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
return new Skill[]
|
||||
{
|
||||
new Rhythm(mods),
|
||||
new Colour(mods),
|
||||
new Stamina(mods)
|
||||
new Peaks(mods)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,15 +72,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods };
|
||||
|
||||
Colour colour = (Colour)skills.First(x => x is Colour);
|
||||
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
|
||||
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
|
||||
var combined = (Peaks)skills[0];
|
||||
|
||||
double colourRating = colour.DifficultyValue() * colour_skill_multiplier * difficulty_multiplier;
|
||||
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier * difficulty_multiplier;
|
||||
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier * difficulty_multiplier;
|
||||
double colourRating = combined.ColourDifficultyValue * difficulty_multiplier;
|
||||
double rhythmRating = combined.RhythmDifficultyValue * difficulty_multiplier;
|
||||
double staminaRating = combined.StaminaDifficultyValue * difficulty_multiplier;
|
||||
|
||||
double combinedRating = combinedDifficultyValue(rhythm, colour, stamina) * difficulty_multiplier;
|
||||
double combinedRating = combined.DifficultyValue() * difficulty_multiplier;
|
||||
double starRating = rescale(combinedRating * 1.4);
|
||||
|
||||
HitWindows hitWindows = new TaikoHitWindows();
|
||||
@@ -118,54 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
return 10.43 * Math.Log(sr / 8 + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the combined star rating of the beatmap, calculated using peak strains from all sections of the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
||||
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
||||
/// </remarks>
|
||||
private double combinedDifficultyValue(Rhythm rhythm, Colour colour, Stamina stamina)
|
||||
{
|
||||
List<double> peaks = new List<double>();
|
||||
|
||||
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
|
||||
var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
|
||||
var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList();
|
||||
|
||||
for (int i = 0; i < colourPeaks.Count; i++)
|
||||
{
|
||||
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
||||
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
||||
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;
|
||||
|
||||
double peak = norm(1.5, colourPeak, staminaPeak);
|
||||
peak = norm(2, peak, rhythmPeak);
|
||||
|
||||
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// These sections will not contribute to the difficulty.
|
||||
if (peak > 0)
|
||||
peaks.Add(peak);
|
||||
}
|
||||
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
foreach (double strain in peaks.OrderDescending())
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector.
|
||||
/// </summary>
|
||||
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
|
||||
/// <param name="values">The coefficients of the vector.</param>
|
||||
private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Edit.Setup
|
||||
{
|
||||
public partial class TaikoDifficultySection : SetupSection
|
||||
{
|
||||
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||
|
||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
healthDrainSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.DrainRateDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
overallDifficultySlider = new LabelledSliderBar<float>
|
||||
{
|
||||
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.OverallDifficultyDescription,
|
||||
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||
{
|
||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
}
|
||||
},
|
||||
baseVelocitySlider = new LabelledSliderBar<double>
|
||||
{
|
||||
Label = EditorSetupStrings.BaseVelocity,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
MinValue = 0.4,
|
||||
MaxValue = 3.6,
|
||||
Precision = 0.01f,
|
||||
}
|
||||
},
|
||||
tickRateSlider = new LabelledSliderBar<double>
|
||||
{
|
||||
Label = EditorSetupStrings.TickRate,
|
||||
FixedLabelWidth = LABEL_WIDTH,
|
||||
Description = EditorSetupStrings.TickRateDescription,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
MinValue = 1,
|
||||
MaxValue = 4,
|
||||
Precision = 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
|
||||
item.Current.ValueChanged += _ => updateValues();
|
||||
|
||||
foreach (var item in Children.OfType<LabelledSliderBar<double>>())
|
||||
item.Current.ValueChanged += _ => updateValues();
|
||||
}
|
||||
|
||||
private void updateValues()
|
||||
{
|
||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||
|
||||
Beatmap.UpdateAllHitObjects();
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@@ -35,8 +36,6 @@ using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
using osu.Game.Rulesets.Taiko.Edit.Setup;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
@@ -80,43 +79,43 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
yield return new TaikoModNightcore();
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
yield return new TaikoModDoubleTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
yield return new TaikoModPerfect();
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
yield return new TaikoModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
yield return new TaikoModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
yield return new TaikoModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
yield return new TaikoModEasy();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
yield return new TaikoModFlashlight();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
yield return new TaikoModHalfTime();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
yield return new TaikoModHardRock();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
yield return new TaikoModHidden();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
yield return new TaikoModNoFail();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
yield return new TaikoModRelax();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@@ -190,11 +189,6 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this);
|
||||
|
||||
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||
[
|
||||
new TaikoDifficultySection(),
|
||||
];
|
||||
|
||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new TaikoBeatmapVerifier();
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
|
||||
|
||||
@@ -528,17 +528,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
|
||||
|
||||
// The fourth object is a slider.
|
||||
// `Samples` of a slider are presumed to control the volume of sounds that last the entire duration of the slider
|
||||
// (such as ticks, slider slide sounds, etc.)
|
||||
// Thus, the point of query of control points used for `Samples` is just beyond the start time of the slider.
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal11", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
||||
|
||||
// That said, the `NodeSamples` of the slider are responsible for the sounds of the slider's head / tail / repeats / large ticks etc.
|
||||
// Therefore, they should be read at the time instant correspondent to the given node.
|
||||
// This means that the tail should use bank 8 rather than 11.
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal11", ((ConvertSlider)hitObjects[4]).NodeSamples[0][0].LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal8", ((ConvertSlider)hitObjects[4]).NodeSamples[1][0].LookupNames.First());
|
||||
// The control point at the end time of the slider should be applied
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
|
||||
@@ -157,9 +157,8 @@ namespace osu.Game.Tests.Database
|
||||
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
||||
}
|
||||
|
||||
[TestCase(30000002)]
|
||||
[TestCase(30000013)]
|
||||
public void TestScoreUpgradeFailed(int scoreVersion)
|
||||
[Test]
|
||||
public void TestScoreUpgradeFailed()
|
||||
{
|
||||
ScoreInfo scoreInfo = null!;
|
||||
|
||||
@@ -173,18 +172,16 @@ namespace osu.Game.Tests.Database
|
||||
Ruleset = r.All<RulesetInfo>().First(),
|
||||
})
|
||||
{
|
||||
TotalScoreVersion = scoreVersion,
|
||||
TotalScoreVersion = 30000002,
|
||||
IsLegacyScore = true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
TestBackgroundDataStoreProcessor processor = null!;
|
||||
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||
AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor()));
|
||||
|
||||
AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion));
|
||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -168,12 +168,12 @@ namespace osu.Game.Tests.Database
|
||||
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||
Debug.Assert(importAfterUpdate != null);
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
// should only contain the modified beatmap (others purged).
|
||||
Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1));
|
||||
Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps));
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
|
||||
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
|
||||
|
||||
@@ -479,7 +479,6 @@ namespace osu.Game.Tests.Database
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
using var __ = getBeatmapArchive(out string pathOriginal);
|
||||
|
||||
using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
|
||||
{
|
||||
// arbitrary beatmap removal
|
||||
@@ -497,7 +496,7 @@ namespace osu.Game.Tests.Database
|
||||
Debug.Assert(importAfterUpdate != null);
|
||||
|
||||
Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID));
|
||||
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded).Within(TimeSpan.FromSeconds(1)));
|
||||
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -71,35 +71,6 @@ namespace osu.Game.Tests.Database
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSubscriptionInitialChangeSetNull()
|
||||
{
|
||||
ChangeSet? firstChanges = null;
|
||||
int receivedChangesCount = 0;
|
||||
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), onChanged);
|
||||
|
||||
realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely();
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
Assert.That(receivedChangesCount, Is.EqualTo(1));
|
||||
Assert.That(firstChanges, Is.Null);
|
||||
|
||||
registration.Dispose();
|
||||
});
|
||||
|
||||
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
|
||||
{
|
||||
if (receivedChangesCount == 0)
|
||||
firstChanges = changes;
|
||||
|
||||
receivedChangesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSubscriptionWithAsyncWrite()
|
||||
{
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckTitleMarkersTest
|
||||
{
|
||||
private CheckTitleMarkers check = null!;
|
||||
|
||||
private IBeatmap beatmap = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckTitleMarkers();
|
||||
|
||||
beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = "Egao no Kanata",
|
||||
TitleUnicode = "エガオノカナタ"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoTitleMarkers()
|
||||
{
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTvSizeMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (TV Size)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (TV Size)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedTvSizeMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (tv size)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (tv size)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Game Ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Game Ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedGameVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (game ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (game ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShortVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Short Ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Short Ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedShortVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (short ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (short ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCutVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Cut Ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Cut Ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedCutVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (cut ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (cut ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpedUpVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Sped Up Ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Sped Up Ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedSpedUpVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (sped up ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (sped up ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNightcoreMixMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Nightcore Mix)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Nightcore Mix)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedNightcoreMixMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (nightcore mix)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (nightcore mix)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpedUpCutVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Sped Up & Cut Ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Sped Up & Cut Ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedSpedUpCutVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (sped up & cut ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (sped up & cut ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNightcoreCutVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (Nightcore & Cut Ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Nightcore & Cut Ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMalformedNightcoreCutVerMarker()
|
||||
{
|
||||
beatmap.BeatmapInfo.Metadata.Title += " (nightcore & cut ver.)";
|
||||
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (nightcore & cut ver.)";
|
||||
|
||||
var issues = check.Run(getContext(beatmap)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(2));
|
||||
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(IBeatmap beatmap)
|
||||
{
|
||||
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
|
||||
namespace osu.Game.Tests.Editing
|
||||
{
|
||||
[TestFixture]
|
||||
public class EditorTimestampParserTest
|
||||
{
|
||||
private static readonly object?[][] test_cases =
|
||||
{
|
||||
new object?[] { ":", false, null, null },
|
||||
new object?[] { "1", true, TimeSpan.FromMilliseconds(1), null },
|
||||
new object?[] { "99", true, TimeSpan.FromMilliseconds(99), null },
|
||||
new object?[] { "320000", true, TimeSpan.FromMilliseconds(320000), null },
|
||||
new object?[] { "1:2", true, new TimeSpan(0, 0, 1, 2), null },
|
||||
new object?[] { "1:02", true, new TimeSpan(0, 0, 1, 2), null },
|
||||
new object?[] { "1:92", false, null, null },
|
||||
new object?[] { "1:002", false, null, null },
|
||||
new object?[] { "1:02:3", true, new TimeSpan(0, 0, 1, 2, 3), null },
|
||||
new object?[] { "1:02:300", true, new TimeSpan(0, 0, 1, 2, 300), null },
|
||||
new object?[] { "1:02:3000", false, null, null },
|
||||
new object?[] { "1:02:300 ()", false, null, null },
|
||||
new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestTryParse(string timestamp, bool expectedSuccess, TimeSpan? expectedParsedTime, string? expectedSelection)
|
||||
{
|
||||
bool actualSuccess = EditorTimestampParser.TryParse(timestamp, out var actualParsedTime, out string? actualSelection);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(actualSuccess, Is.EqualTo(expectedSuccess));
|
||||
Assert.That(actualParsedTime, Is.EqualTo(expectedParsedTime));
|
||||
Assert.That(actualSelection, Is.EqualTo(expectedSelection));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
@@ -21,11 +19,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -39,15 +36,14 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -61,16 +57,15 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -79,67 +74,20 @@ namespace osu.Game.Tests.Editing
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNote()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote { StartTime = 1000, Duration = 10000 },
|
||||
}
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new ManiaRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoldNoteWithOverlappingNote()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote { StartTime = 1000, Duration = 10000 },
|
||||
new Note { StartTime = 2000 },
|
||||
new Note { StartTime = 12000 },
|
||||
}
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new ManiaRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTwoObjectsFarApart()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -158,21 +106,20 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 9000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1200, 4000),
|
||||
new BreakPeriod(5200, 8000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -191,21 +138,20 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 5000 },
|
||||
new Note { StartTime = 9000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1200, 8000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -226,20 +172,19 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1100 },
|
||||
new Note { StartTime = 9000 },
|
||||
new HitCircle { StartTime = 1100 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1200, 8000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -258,21 +203,20 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 9000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(1200, 4000),
|
||||
new ManualBreakPeriod(5200, 8000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -293,21 +237,20 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 5000 },
|
||||
new Note { StartTime = 9000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(1200, 8000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -328,20 +271,19 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 9000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 9000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(1200, 8800),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -360,20 +302,19 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(10000, 15000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -387,47 +328,19 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 1000 },
|
||||
new Note { StartTime = 2000 },
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(10000, 15000),
|
||||
}
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManualBreaksAtEndOfBeatmapAreRemovedCorrectlyEvenWithConcurrentObjects()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote { StartTime = 1000, EndTime = 20000 },
|
||||
new HoldNote { StartTime = 2000, EndTime = 3000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(10000, 15000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -441,20 +354,19 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 10000 },
|
||||
new Note { StartTime = 11000 },
|
||||
new HitCircle { StartTime = 10000 },
|
||||
new HitCircle { StartTime = 11000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(0, 9000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -468,20 +380,19 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new Note { StartTime = 10000 },
|
||||
new Note { StartTime = 11000 },
|
||||
new HitCircle { StartTime = 10000 },
|
||||
new HitCircle { StartTime = 11000 },
|
||||
},
|
||||
Breaks =
|
||||
{
|
||||
new ManualBreakPeriod(0, 9000),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@@ -489,55 +400,5 @@ namespace osu.Game.Tests.Editing
|
||||
|
||||
Assert.That(beatmap.Breaks, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTimePreemptIsRespected()
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
Difficulty =
|
||||
{
|
||||
ApproachRate = 10,
|
||||
},
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var ho in beatmap.HitObjects)
|
||||
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MIN));
|
||||
});
|
||||
|
||||
beatmap.Difficulty.ApproachRate = 0;
|
||||
|
||||
foreach (var ho in beatmap.HitObjects)
|
||||
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||
|
||||
beatmapProcessor.PreProcess();
|
||||
beatmapProcessor.PostProcess();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
public class TestSceneTimedDifficultyCalculation
|
||||
{
|
||||
[Test]
|
||||
public void TestAttributesGeneratedForEachObjectOnce()
|
||||
public void TestAttributesGeneratedForAllNonSkippedObjects()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
@@ -40,14 +40,15 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(3));
|
||||
Assert.That(attribs.Count, Is.EqualTo(4));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1]); // From the nested object.
|
||||
assertEquals(attribs[3], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttributesGeneratedForSkippedObjects()
|
||||
public void TestAttributesNotGeneratedForSkippedObjects()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
@@ -71,14 +72,35 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(3));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
Assert.That(attribs.Count, Is.EqualTo(1));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttributesGeneratedOnceForSkippedObjects()
|
||||
public void TestNestedObjectOnlyAddsParentOnce()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 1,
|
||||
Skip = true,
|
||||
Nested = 2
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(2));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||
assertEquals(attribs[1], beatmap.HitObjects[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkippedLastObjectAddedInLastIteration()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
@@ -88,7 +110,6 @@ namespace osu.Game.Tests.NonVisual
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 2,
|
||||
Nested = 5,
|
||||
Skip = true
|
||||
},
|
||||
new TestHitObject
|
||||
@@ -101,10 +122,8 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(3));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
Assert.That(attribs.Count, Is.EqualTo(1));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
}
|
||||
|
||||
private void assertEquals(TimedDifficultyAttributes attribs, params HitObject[] expected)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
osu file format v128
|
||||
|
||||
[General]
|
||||
SampleSet: Normal
|
||||
|
||||
[TimingPoints]
|
||||
15,1000,4,1,0,100,1,0
|
||||
2271,-100,4,1,0,5,0,0
|
||||
6021,-100,4,1,0,100,0,0
|
||||
8515,-100,4,1,0,5,0,0
|
||||
12765,-100,4,1,0,100,0,0
|
||||
14764,-100,4,1,0,5,0,0
|
||||
14770,-100,4,1,0,50,0,0
|
||||
17264,-100,4,1,0,5,0,0
|
||||
17270,-100,4,1,0,50,0,0
|
||||
22264,-100,4,1,0,100,0,0
|
||||
|
||||
[HitObjects]
|
||||
113,54,2265,6,0,L|422:55,1,300,0|0,1:0|1:0,1:0:0:0:
|
||||
82,206,6015,2,0,L|457:204,1,350,0|0,2:0|2:0,2:0:0:0:
|
||||
75,310,10265,2,0,L|435:312,1,350,0|0,3:0|3:0,3:0:0:0:
|
||||
75,310,14764,2,0,L|435:312,3,350,0|0|0|0,3:0|3:0|3:0|3:0,3:0:0:0:
|
||||
@@ -1,185 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
public partial class TestSceneDailyChallengeCarousel : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
private readonly Bindable<Room> room = new Bindable<Room>(new Room());
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
|
||||
{
|
||||
Model = { BindTarget = room }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void TestBasicAppearance()
|
||||
{
|
||||
DailyChallengeCarousel carousel = null!;
|
||||
|
||||
AddStep("create content", () => Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
carousel = new DailyChallengeCarousel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||
{
|
||||
if (carousel.IsNotNull())
|
||||
carousel.Width = width;
|
||||
});
|
||||
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||
{
|
||||
if (carousel.IsNotNull())
|
||||
carousel.Height = height;
|
||||
});
|
||||
AddRepeatStep("add content", () => carousel.Add(new FakeContent()), 3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIntegration()
|
||||
{
|
||||
GridContainer grid = null!;
|
||||
DailyChallengeEventFeed feed = null!;
|
||||
DailyChallengeScoreBreakdown breakdown = null!;
|
||||
|
||||
AddStep("create content", () => Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
grid = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RowDimensions =
|
||||
[
|
||||
new Dimension(),
|
||||
new Dimension()
|
||||
],
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new DailyChallengeCarousel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DailyChallengeTimeRemainingRing(),
|
||||
breakdown = new DailyChallengeScoreBreakdown(),
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
feed = new DailyChallengeEventFeed
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
});
|
||||
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||
{
|
||||
if (grid.IsNotNull())
|
||||
grid.Width = width;
|
||||
});
|
||||
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||
{
|
||||
if (grid.IsNotNull())
|
||||
grid.Height = height;
|
||||
});
|
||||
AddSliderStep("update time remaining", 0f, 1f, 0f, progress =>
|
||||
{
|
||||
var startedTimeAgo = TimeSpan.FromHours(24) * progress;
|
||||
room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo;
|
||||
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||
});
|
||||
AddStep("add normal score", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), null);
|
||||
|
||||
feed.AddNewScore(ev);
|
||||
breakdown.AddNewScore(ev);
|
||||
});
|
||||
AddStep("add new user best", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), RNG.Next(1, 1000));
|
||||
|
||||
feed.AddNewScore(ev);
|
||||
breakdown.AddNewScore(ev);
|
||||
});
|
||||
}
|
||||
|
||||
private partial class FakeContent : CompositeDrawable
|
||||
{
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = new Colour4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1),
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Fake Content " + (char)('A' + RNG.Next(26)),
|
||||
},
|
||||
};
|
||||
|
||||
text.FadeOut(500, Easing.OutQuint)
|
||||
.Then().FadeIn(500, Easing.OutQuint)
|
||||
.Loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,8 @@ using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
@@ -52,41 +50,26 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
|
||||
AddStep("add normal score", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), null);
|
||||
var testScore = TestResources.CreateTestScoreInfo();
|
||||
testScore.TotalScore = RNG.Next(1_000_000);
|
||||
|
||||
feed.AddNewScore(ev);
|
||||
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, null));
|
||||
});
|
||||
|
||||
AddStep("add new user best", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), RNG.Next(11, 1000));
|
||||
|
||||
var testScore = TestResources.CreateTestScoreInfo();
|
||||
testScore.TotalScore = RNG.Next(1_000_000);
|
||||
|
||||
feed.AddNewScore(ev);
|
||||
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 1000)));
|
||||
});
|
||||
|
||||
AddStep("add top 10 score", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), RNG.Next(1, 10));
|
||||
var testScore = TestResources.CreateTestScoreInfo();
|
||||
testScore.TotalScore = RNG.Next(1_000_000);
|
||||
|
||||
feed.AddNewScore(ev);
|
||||
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 10)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
public partial class TestSceneDailyChallengeLeaderboard : OsuTestScene
|
||||
{
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
[Test]
|
||||
public void TestBasicBehaviour()
|
||||
{
|
||||
DailyChallengeLeaderboard leaderboard = null!;
|
||||
|
||||
AddStep("set up response without user best", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = req =>
|
||||
{
|
||||
if (req is IndexPlaylistScoresRequest indexRequest)
|
||||
{
|
||||
indexRequest.TriggerSuccess(createResponse(50, false));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.8f),
|
||||
});
|
||||
|
||||
AddStep("set up response with user best", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = req =>
|
||||
{
|
||||
if (req is IndexPlaylistScoresRequest indexRequest)
|
||||
{
|
||||
indexRequest.TriggerSuccess(createResponse(50, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("force refetch", () => leaderboard.RefetchScores());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoadingBehaviour()
|
||||
{
|
||||
IndexPlaylistScoresRequest pendingRequest = null!;
|
||||
DailyChallengeLeaderboard leaderboard = null!;
|
||||
|
||||
AddStep("set up requests handler", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = req =>
|
||||
{
|
||||
if (req is IndexPlaylistScoresRequest indexRequest)
|
||||
{
|
||||
pendingRequest = indexRequest;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.8f),
|
||||
});
|
||||
AddStep("complete load", () => pendingRequest.TriggerSuccess(createResponse(3, true)));
|
||||
AddStep("force refetch", () => leaderboard.RefetchScores());
|
||||
AddStep("complete load", () => pendingRequest.TriggerSuccess(createResponse(4, true)));
|
||||
}
|
||||
|
||||
private IndexedMultiplayerScores createResponse(int scoreCount, bool returnUserBest)
|
||||
{
|
||||
var result = new IndexedMultiplayerScores();
|
||||
|
||||
for (int i = 0; i < scoreCount; ++i)
|
||||
{
|
||||
result.Scores.Add(new MultiplayerScore
|
||||
{
|
||||
ID = i,
|
||||
Accuracy = 1 - (float)i / (2 * scoreCount),
|
||||
Position = i + 1,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
Passed = true,
|
||||
Rank = (ScoreRank)RNG.Next((int)ScoreRank.D, (int)ScoreRank.XH),
|
||||
MaxCombo = 1000 - i,
|
||||
TotalScore = (long)(1_000_000 * (1 - (float)i / (2 * scoreCount))),
|
||||
User = new APIUser { Username = $"user {i}" },
|
||||
Statistics = new Dictionary<HitResult, int>()
|
||||
});
|
||||
}
|
||||
|
||||
if (returnUserBest)
|
||||
{
|
||||
result.UserScore = new MultiplayerScore
|
||||
{
|
||||
ID = 99999,
|
||||
Accuracy = 0.91,
|
||||
Position = 4,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
Passed = true,
|
||||
Rank = ScoreRank.A,
|
||||
MaxCombo = 100,
|
||||
TotalScore = 800000,
|
||||
User = dummyAPI.LocalUser.Value,
|
||||
Statistics = new Dictionary<HitResult, int>()
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,9 @@ using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
@@ -52,14 +51,10 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1]));
|
||||
AddStep("add new score", () =>
|
||||
{
|
||||
var ev = new NewScoreEvent(1, new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
}, RNG.Next(1_000_000), null);
|
||||
var testScore = TestResources.CreateTestScoreInfo();
|
||||
testScore.TotalScore = RNG.Next(1_000_000);
|
||||
|
||||
breakdown.AddNewScore(ev);
|
||||
breakdown.AddNewScore(testScore);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Storyboards;
|
||||
@@ -171,24 +169,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSwitchToDifficultyOfAnotherRuleset()
|
||||
{
|
||||
BeatmapInfo targetDifficulty = null;
|
||||
|
||||
AddAssert("ruleset is catch", () => Ruleset.Value.CreateInstance() is CatchRuleset);
|
||||
|
||||
AddStep("set taiko difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1));
|
||||
switchToDifficulty(() => targetDifficulty);
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
|
||||
AddAssert("ruleset switched to taiko", () => Ruleset.Value.CreateInstance() is TaikoRuleset);
|
||||
|
||||
AddStep("exit editor forcefully", () => Stack.Exit());
|
||||
// ensure editor loader didn't resume.
|
||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||
}
|
||||
|
||||
private void switchToDifficulty(Func<BeatmapInfo> difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
|
||||
|
||||
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
@@ -22,10 +19,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Cached]
|
||||
private EditorBeatmap editorBeatmap = new EditorBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo));
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
public TestSceneEditorClock()
|
||||
{
|
||||
AddStep("create content", () => Add(new FillFlowContainer
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
@@ -43,17 +39,19 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
Size = new Vector2(200, 100)
|
||||
}
|
||||
}
|
||||
}));
|
||||
AddStep("set working beatmap", () =>
|
||||
{
|
||||
Beatmap.Disabled = false;
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
// ensure that music controller does not change this beatmap due to it
|
||||
// completing naturally as part of the test.
|
||||
Beatmap.Disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
// ensure that music controller does not change this beatmap due to it
|
||||
// completing naturally as part of the test.
|
||||
Beatmap.Disabled = true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStopAtTrackEnd()
|
||||
{
|
||||
@@ -104,29 +102,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCurrentTimeDoubleTransform()
|
||||
{
|
||||
AddAssert("seek smoothly twice and current time is accurate", () =>
|
||||
{
|
||||
EditorClock.SeekSmoothlyTo(1000);
|
||||
EditorClock.SeekSmoothlyTo(2000);
|
||||
return 2000 == EditorClock.CurrentTimeAccurate;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdjustmentsRemovedOnDisposal()
|
||||
{
|
||||
AddStep("reset clock", () => EditorClock.Seek(0));
|
||||
|
||||
AddStep("set 0.25x speed", () => this.ChildrenOfType<OsuTabControl<double>>().First().Current.Value = 0.25);
|
||||
AddAssert("track has 0.25x tempo", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(0.25));
|
||||
|
||||
AddStep("dispose playback control", () => Clear(disposeChildren: true));
|
||||
AddAssert("track has 1x tempo", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Beatmap.Disabled = false;
|
||||
|
||||
@@ -193,20 +193,5 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatDivisor()
|
||||
{
|
||||
AddStep("Set custom beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().SetArbitraryDivisor(7));
|
||||
|
||||
SaveEditor();
|
||||
AddAssert("Hash updated", () => !string.IsNullOrEmpty(EditorBeatmap.BeatmapInfo.BeatmapSet?.Hash));
|
||||
AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7));
|
||||
|
||||
ReloadEditorToSameBeatmap();
|
||||
|
||||
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7));
|
||||
AddAssert("Correct beat divisor actually active", () => Editor.BeatDivisor, () => Is.EqualTo(7));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||
@@ -127,24 +126,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
||||
}
|
||||
|
||||
[TestCase(2000)] // chosen to be after last object in the map
|
||||
[TestCase(22000)] // chosen to be in the middle of the last spinner
|
||||
public void TestGameplayTestAtEndOfBeatmap(int offsetFromEnd)
|
||||
{
|
||||
AddStep($"seek to end minus {offsetFromEnd}ms", () => EditorClock.Seek(importedBeatmapSet.MaxLength - offsetFromEnd));
|
||||
AddStep("click test gameplay button", () =>
|
||||
{
|
||||
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(button);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("player pushed", () => Stack.CurrentScreen is EditorPlayer);
|
||||
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancelGameplayTestWithUnsavedChanges()
|
||||
{
|
||||
@@ -225,116 +206,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("time reverted to 00:01:00", () => EditorClock.CurrentTime, () => Is.EqualTo(60_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoplayToggle()
|
||||
{
|
||||
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);
|
||||
AddUntilStep("no replay active", () => editorPlayer.ChildrenOfType<DrawableRuleset>().Single().ReplayScore, () => Is.Null);
|
||||
AddStep("press Tab", () => InputManager.Key(Key.Tab));
|
||||
AddUntilStep("replay active", () => editorPlayer.ChildrenOfType<DrawableRuleset>().Single().ReplayScore, () => Is.Not.Null);
|
||||
AddStep("press Tab", () => InputManager.Key(Key.Tab));
|
||||
AddUntilStep("no replay active", () => editorPlayer.ChildrenOfType<DrawableRuleset>().Single().ReplayScore, () => Is.Null);
|
||||
AddStep("exit player", () => editorPlayer.Exit());
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuickPause()
|
||||
{
|
||||
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);
|
||||
AddUntilStep("clock running", () => editorPlayer.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value, () => Is.False);
|
||||
AddStep("press Ctrl-P", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.P);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
AddUntilStep("clock not running", () => editorPlayer.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value, () => Is.True);
|
||||
AddStep("press Ctrl-P", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.P);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
AddUntilStep("clock running", () => editorPlayer.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value, () => Is.False);
|
||||
AddStep("exit player", () => editorPlayer.Exit());
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuickExitAtInitialPosition()
|
||||
{
|
||||
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);
|
||||
// when the gameplay test is entered, the clock is expected to continue from where it was in the main editor...
|
||||
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
|
||||
|
||||
AddWaitStep("wait some", 5);
|
||||
|
||||
AddStep("exit player", () => InputManager.PressKey(Key.F1));
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||
AddAssert("time reverted to 00:01:00", () => EditorClock.CurrentTime, () => Is.EqualTo(60_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuickExitAtCurrentPosition()
|
||||
{
|
||||
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);
|
||||
// when the gameplay test is entered, the clock is expected to continue from where it was in the main editor...
|
||||
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
|
||||
|
||||
AddWaitStep("wait some", 5);
|
||||
|
||||
AddStep("exit player", () => InputManager.PressKey(Key.F2));
|
||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||
AddAssert("time moved forward", () => EditorClock.CurrentTime, () => Is.GreaterThan(60_000));
|
||||
}
|
||||
|
||||
public override void TearDownSteps()
|
||||
{
|
||||
base.TearDownSteps();
|
||||
|
||||
@@ -402,171 +402,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PopoverForMultipleSelectionChangesAllSamples()
|
||||
{
|
||||
AddStep("add slider", () =>
|
||||
{
|
||||
EditorBeatmap.Add(new Slider
|
||||
{
|
||||
Position = new Vector2(256, 256),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||
Samples =
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
},
|
||||
NodeSamples = new List<IList<HitSampleInfo>>
|
||||
{
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
|
||||
},
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||
|
||||
clickSamplePiece(0);
|
||||
|
||||
setBankViaPopover(HitSampleInfo.BANK_DRUM);
|
||||
samplePopoverHasSingleBank(HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSampleNormalBank(2, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSampleNormalBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
|
||||
|
||||
setVolumeViaPopover(30);
|
||||
samplePopoverHasSingleVolume(30);
|
||||
hitObjectHasSampleVolume(0, 30);
|
||||
hitObjectHasSampleVolume(1, 30);
|
||||
hitObjectHasSampleVolume(2, 30);
|
||||
hitObjectNodeHasSampleVolume(2, 0, 30);
|
||||
hitObjectNodeHasSampleVolume(2, 1, 30);
|
||||
|
||||
toggleAdditionViaPopover(0);
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
|
||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
|
||||
setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSampleAdditionBank(2, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectNodeHasSampleAdditionBank(2, 0, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHotkeysAffectNodeSamples()
|
||||
{
|
||||
AddStep("add slider", () =>
|
||||
{
|
||||
EditorBeatmap.Add(new Slider
|
||||
{
|
||||
Position = new Vector2(256, 256),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||
Samples =
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
},
|
||||
NodeSamples = new List<IList<HitSampleInfo>>
|
||||
{
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
|
||||
},
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||
|
||||
AddStep("add clap addition", () => InputManager.Key(Key.R));
|
||||
|
||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||
|
||||
hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||
|
||||
hitObjectHasSampleBank(2, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||
hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_CLAP);
|
||||
|
||||
AddStep("remove clap addition", () => InputManager.Key(Key.R));
|
||||
|
||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
|
||||
|
||||
hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL);
|
||||
|
||||
hitObjectHasSampleBank(2, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
|
||||
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
|
||||
hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_SOFT);
|
||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
|
||||
AddStep("set drum bank", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.R);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
|
||||
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
|
||||
|
||||
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL);
|
||||
|
||||
hitObjectHasSampleBank(2, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
|
||||
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
|
||||
hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_DRUM);
|
||||
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectingObjectDoesNotMutateSamples()
|
||||
{
|
||||
clickSamplePiece(0);
|
||||
toggleAdditionViaPopover(1);
|
||||
setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT);
|
||||
dismissPopover();
|
||||
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
|
||||
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]));
|
||||
|
||||
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
|
||||
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
|
||||
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
|
||||
}
|
||||
|
||||
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
|
||||
{
|
||||
var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
|
||||
|
||||
@@ -5,14 +5,11 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@@ -105,120 +102,5 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("change tool to circle", () => InputManager.Key(Key.Number2));
|
||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutomaticBankAssignment()
|
||||
{
|
||||
AddStep("add object with soft bank", () => EditorBeatmap.Add(new HitCircle
|
||||
{
|
||||
StartTime = 0,
|
||||
Samples =
|
||||
{
|
||||
new HitSampleInfo(name: HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, volume: 70),
|
||||
new HitSampleInfo(name: HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_DRUM, volume: 70),
|
||||
}
|
||||
}));
|
||||
|
||||
AddStep("seek to 500", () => EditorClock.Seek(500)); // previous object is the one at time 0
|
||||
AddStep("enable automatic bank assignment", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.Q);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("circle has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single().Bank, () => Is.EqualTo(HitSampleInfo.BANK_SOFT));
|
||||
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70));
|
||||
|
||||
AddStep("seek to 250", () => EditorClock.Seek(250)); // previous object is the one at time 0
|
||||
AddStep("enable clap addition", () => InputManager.Key(Key.R));
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[1].Samples, () => Has.Count.EqualTo(2));
|
||||
AddAssert("normal sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank,
|
||||
() => Is.EqualTo(HitSampleInfo.BANK_SOFT));
|
||||
AddAssert("clap sample has drum bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank,
|
||||
() => Is.EqualTo(HitSampleInfo.BANK_DRUM));
|
||||
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70));
|
||||
|
||||
AddStep("seek to 1000", () => EditorClock.Seek(1000)); // previous object is the one at time 500, which has no additions
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[3].Samples, () => Has.Count.EqualTo(2));
|
||||
AddAssert("all samples have soft bank", () => EditorBeatmap.HitObjects[3].Samples.All(s => s.Bank == HitSampleInfo.BANK_SOFT));
|
||||
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[3].Samples.All(s => s.Volume == 70));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVolumeIsInheritedFromLastObject()
|
||||
{
|
||||
AddStep("add object with soft bank", () => EditorBeatmap.Add(new HitCircle
|
||||
{
|
||||
StartTime = 0,
|
||||
Samples =
|
||||
{
|
||||
new HitSampleInfo(name: HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, volume: 70),
|
||||
}
|
||||
}));
|
||||
AddStep("seek to 500", () => EditorClock.Seek(500));
|
||||
AddStep("select drum bank", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.R);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("circle has drum bank", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM));
|
||||
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNodeSamplesAndSamplesAreSame()
|
||||
{
|
||||
Playfield playfield = null!;
|
||||
|
||||
AddStep("select drum bank", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.R);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
});
|
||||
AddStep("enable clap addition", () => InputManager.Key(Key.R));
|
||||
|
||||
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
||||
AddStep("move mouse to top left of playfield", () =>
|
||||
{
|
||||
playfield = this.ChildrenOfType<Playfield>().Single();
|
||||
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("move mouse to bottom right of playfield", () =>
|
||||
{
|
||||
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
AddStep("confirm via global action", () =>
|
||||
{
|
||||
globalActionContainer.TriggerPressed(GlobalAction.Select);
|
||||
globalActionContainer.TriggerReleased(GlobalAction.Select);
|
||||
});
|
||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||
|
||||
AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM));
|
||||
AddAssert("slider node samples have drum bank",
|
||||
() => ((IHasRepeats)EditorBeatmap.HitObjects[0]).NodeSamples.SelectMany(s => s).All(s => s.Bank == HitSampleInfo.BANK_DRUM));
|
||||
|
||||
AddAssert("slider samples have clap addition",
|
||||
() => EditorBeatmap.HitObjects[0].Samples.Select(s => s.Name), () => Does.Contain(HitSampleInfo.HIT_CLAP));
|
||||
AddAssert("slider node samples have clap addition",
|
||||
() => ((IHasRepeats)EditorBeatmap.HitObjects[0]).NodeSamples.All(samples => samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,51 +70,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation)
|
||||
{
|
||||
TriangularPositionSnapGrid grid = null;
|
||||
|
||||
AddStep("create grid", () =>
|
||||
{
|
||||
Child = grid = new TriangularPositionSnapGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
grid.StartPosition.Value = position;
|
||||
grid.Spacing.Value = spacing.X;
|
||||
grid.GridLineRotation.Value = rotation;
|
||||
});
|
||||
|
||||
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos))
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation)
|
||||
{
|
||||
CircularPositionSnapGrid grid = null;
|
||||
|
||||
AddStep("create grid", () =>
|
||||
{
|
||||
Child = grid = new CircularPositionSnapGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
grid.StartPosition.Value = position;
|
||||
grid.Spacing.Value = spacing.X;
|
||||
});
|
||||
|
||||
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos))
|
||||
}));
|
||||
}
|
||||
|
||||
private partial class SnappingCursorContainer : CompositeDrawable
|
||||
{
|
||||
public Func<Vector2, Vector2> GetSnapPosition;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1);
|
||||
AddAssert("preview time line should not show", () => !Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Any());
|
||||
AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000);
|
||||
AddAssert("preview time line should show", () => Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Single().Alpha, () => Is.GreaterThan(0));
|
||||
AddAssert("preview time line should show", () => Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Single().Alpha == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,28 +403,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("placement committed", () => EditorBeatmap.HitObjects, () => Has.Count.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBreakRemoval()
|
||||
{
|
||||
var addedObjects = new[]
|
||||
{
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
};
|
||||
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||
AddAssert("beatmap has one break", () => EditorBeatmap.Breaks, () => Has.Count.EqualTo(1));
|
||||
|
||||
AddStep("move mouse to break", () => InputManager.MoveMouseTo(this.ChildrenOfType<TimelineBreak>().Single()));
|
||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||
|
||||
AddStep("move mouse to delete menu item", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuContextMenu>().First().ChildrenOfType<DrawableOsuMenuItem>().First()));
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("beatmap has no breaks", () => EditorBeatmap.Breaks, () => Is.Empty);
|
||||
AddAssert("break piece went away", () => this.ChildrenOfType<TimelineBreak>().Count(), () => Is.Zero);
|
||||
}
|
||||
|
||||
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
|
||||
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
Scheduler.AddDelayed(applyMiss, 500 + 30);
|
||||
});
|
||||
AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -121,7 +120,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
}
|
||||
});
|
||||
AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
return true;
|
||||
});
|
||||
|
||||
AddUntilStep("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
|
||||
AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
|
||||
|
||||
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
|
||||
// the important thing is that at least one started, and that sample has since stopped.
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneJudgementContainer : OsuTestScene
|
||||
{
|
||||
private JudgementContainer<DrawableOsuJudgement> judgementContainer = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUp()
|
||||
{
|
||||
AddStep("create judgement container", () => Child = judgementContainer = new JudgementContainer<DrawableOsuJudgement>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJudgementFromSameHitObjectIsRemoved()
|
||||
{
|
||||
DrawableHitCircle drawableHitCircle1 = null!;
|
||||
DrawableHitCircle drawableHitCircle2 = null!;
|
||||
|
||||
AddStep("create hit circles", () =>
|
||||
{
|
||||
Add(drawableHitCircle1 = new DrawableHitCircle(createHitCircle()));
|
||||
Add(drawableHitCircle2 = new DrawableHitCircle(createHitCircle()));
|
||||
});
|
||||
|
||||
int judgementCount = 0;
|
||||
|
||||
AddStep("judge the same hitobject twice via different drawables", () =>
|
||||
{
|
||||
addDrawableJudgement(drawableHitCircle1);
|
||||
drawableHitCircle2.Apply(drawableHitCircle1.HitObject);
|
||||
addDrawableJudgement(drawableHitCircle2);
|
||||
judgementCount = judgementContainer.Count;
|
||||
});
|
||||
|
||||
AddAssert("one judgement in container", () => judgementCount, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJudgementFromDifferentHitObjectIsNotRemoved()
|
||||
{
|
||||
DrawableHitCircle drawableHitCircle = null!;
|
||||
|
||||
AddStep("create hit circle", () => Add(drawableHitCircle = new DrawableHitCircle(createHitCircle())));
|
||||
|
||||
int judgementCount = 0;
|
||||
|
||||
AddStep("judge two hitobjects via the same drawable", () =>
|
||||
{
|
||||
addDrawableJudgement(drawableHitCircle);
|
||||
drawableHitCircle.Apply(createHitCircle());
|
||||
addDrawableJudgement(drawableHitCircle);
|
||||
judgementCount = judgementContainer.Count;
|
||||
});
|
||||
|
||||
AddAssert("two judgements in container", () => judgementCount, () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
private void addDrawableJudgement(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
var judgement = new DrawableOsuJudgement();
|
||||
|
||||
judgement.Apply(new JudgementResult(drawableHitObject.HitObject, new OsuJudgement())
|
||||
{
|
||||
Type = HitResult.Great,
|
||||
TimeOffset = Time.Current
|
||||
}, drawableHitObject);
|
||||
|
||||
judgementContainer.Add(judgement);
|
||||
}
|
||||
|
||||
private HitCircle createHitCircle()
|
||||
{
|
||||
var circle = new HitCircle();
|
||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestScenePauseInputHandling : PlayerTestScene
|
||||
{
|
||||
private Ruleset currentRuleset = new OsuRuleset();
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => currentRuleset;
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; } = null!;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
foreach (var key in InputManager.CurrentState.Keyboard.Keys)
|
||||
InputManager.ReleaseKey(key);
|
||||
|
||||
InputManager.MoveMouseTo(Content);
|
||||
LocalConfig.SetValue(OsuSetting.KeyOverlay, true);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestOsuInputNotReceivedWhilePaused()
|
||||
{
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new OsuRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||
checkKey(() => counter, 0, false);
|
||||
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
|
||||
// Z key was released before pause, resuming should not trigger it
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counter, 2, true);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counter, 2, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManiaInputNotReceivedWhilePaused()
|
||||
{
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new ManiaRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||
checkKey(() => counter, 0, false);
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
checkKey(() => counter, 2, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
checkKey(() => counter, 2, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOsuPreviouslyHeldInputReleaseOnResume()
|
||||
{
|
||||
KeyCounter counterZ = null!;
|
||||
KeyCounter counterX = null!;
|
||||
|
||||
loadPlayer(() => new OsuRuleset());
|
||||
AddStep("get key counter Z", () => counterZ = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||
AddStep("get key counter X", () => counterX = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.RightButton));
|
||||
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press and release Z", () => InputManager.Key(Key.Z));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
|
||||
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
checkKey(() => counterX, 1, true);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterX, 1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManiaPreviouslyHeldInputReleaseOnResume()
|
||||
{
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new ManiaRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||
checkKey(() => counter, 1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOsuHeldInputRemainHeldAfterResume()
|
||||
{
|
||||
KeyCounter counterZ = null!;
|
||||
KeyCounter counterX = null!;
|
||||
|
||||
loadPlayer(() => new OsuRuleset());
|
||||
AddStep("get key counter Z", () => counterZ = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||
AddStep("get key counter X", () => counterX = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.RightButton));
|
||||
|
||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counterZ, 1, true);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
|
||||
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||
checkKey(() => counterX, 1, true);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterX, 1, true);
|
||||
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterX, 1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManiaHeldInputRemainHeldAfterResume()
|
||||
{
|
||||
KeyCounter counter = null!;
|
||||
|
||||
loadPlayer(() => new ManiaRuleset());
|
||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
checkKey(() => counter, 1, false);
|
||||
}
|
||||
|
||||
private void loadPlayer(Func<Ruleset> createRuleset)
|
||||
{
|
||||
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
||||
AddStep("load player", LoadPlayer);
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
||||
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000));
|
||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500));
|
||||
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
||||
}
|
||||
|
||||
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
||||
{
|
||||
AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count));
|
||||
AddAssert($"key active = {active}", () => counter().IsActive.Value, () => Is.EqualTo(active));
|
||||
}
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer();
|
||||
|
||||
private partial class PausePlayer : TestPlayer
|
||||
{
|
||||
protected override double PauseCooldownDuration => 0;
|
||||
|
||||
public PausePlayer()
|
||||
: base(allowPause: true, showResults: false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Storyboards;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneUnorderedBreaks : OsuPlayerTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; } = null!;
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new OsuBeatmap();
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = 0 });
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = 5000 });
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = 10000 });
|
||||
beatmap.Breaks.Add(new BreakPeriod(6000, 9000));
|
||||
beatmap.Breaks.Add(new BreakPeriod(1000, 4000));
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBreakOverlayVisibility()
|
||||
{
|
||||
AddAssert("break overlay hidden", () => !this.ChildrenOfType<BreakOverlay>().Single().Child.IsPresent);
|
||||
addSeekStep(2000);
|
||||
AddUntilStep("break overlay visible", () => this.ChildrenOfType<BreakOverlay>().Single().Child.IsPresent);
|
||||
addSeekStep(5000);
|
||||
AddAssert("break overlay hidden", () => !this.ChildrenOfType<BreakOverlay>().Single().Child.IsPresent);
|
||||
addSeekStep(7000);
|
||||
AddUntilStep("break overlay visible", () => this.ChildrenOfType<BreakOverlay>().Single().Child.IsPresent);
|
||||
addSeekStep(10000);
|
||||
AddAssert("break overlay hidden", () => !this.ChildrenOfType<BreakOverlay>().Single().Child.IsPresent);
|
||||
}
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time));
|
||||
|
||||
// Allow a few frames of lenience
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Audio.Effects;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public partial class TestSceneAudioDucking : OsuGameTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestMomentaryDuck()
|
||||
{
|
||||
AddStep("duck momentarily", () => Game.MusicController.DuckMomentarily(1000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucks()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
double normalVolume = 1;
|
||||
|
||||
AddStep("get initial volume", () =>
|
||||
{
|
||||
normalVolume = Game.Audio.Tracks.AggregateVolume.Value;
|
||||
});
|
||||
|
||||
AddStep("duck one", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("duck two", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.2,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01));
|
||||
|
||||
AddStep("restore two", () => duckOp2.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("restore one", () => duckOp1.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucksSameParameters()
|
||||
{
|
||||
var duckParameters = new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.5,
|
||||
};
|
||||
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
double normalVolume = 1;
|
||||
|
||||
AddStep("get initial volume", () =>
|
||||
{
|
||||
normalVolume = Game.Audio.Tracks.AggregateVolume.Value;
|
||||
});
|
||||
|
||||
AddStep("duck one", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(duckParameters);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("duck two", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(duckParameters);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("restore two", () => duckOp2.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("restore one", () => duckOp1.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucksReverseOrder()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
double normalVolume = 1;
|
||||
|
||||
AddStep("get initial volume", () =>
|
||||
{
|
||||
normalVolume = Game.Audio.Tracks.AggregateVolume.Value;
|
||||
});
|
||||
|
||||
AddStep("duck one", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("duck two", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.2,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01));
|
||||
|
||||
AddStep("restore one", () => duckOp1.Dispose());
|
||||
|
||||
// reverse order, less extreme duck removed so won't change
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01));
|
||||
|
||||
AddStep("restore two", () => duckOp2.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDisposalIsNoop()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
|
||||
AddStep("duck", () => duckOp1 = Game.MusicController.Duck());
|
||||
AddStep("restore", () => duckOp1.Dispose());
|
||||
AddStep("restore", () => duckOp1.Dispose());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucksDifferentPieces()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
AddStep("duck volume", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.2,
|
||||
DuckCutoffTo = AudioFilter.MAX_LOWPASS_CUTOFF,
|
||||
DuckDuration = 500,
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("duck lowpass", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 1,
|
||||
DuckCutoffTo = 300,
|
||||
DuckDuration = 500,
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("restore lowpass", () => duckOp2.Dispose());
|
||||
AddStep("restore volume", () => duckOp1.Dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false);
|
||||
AddStep("disable return to top on idle", () => Game.ChildrenOfType<ButtonSystem>().Single().ReturnToTopOnIdle = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@@ -32,7 +34,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
[Test]
|
||||
public void TestMusicNavigationActions()
|
||||
{
|
||||
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!;
|
||||
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
|
||||
|
||||
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
||||
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
|
||||
@@ -60,22 +62,14 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
|
||||
|
||||
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||
AddUntilStep("track changed to previous", () =>
|
||||
AddAssert("track changed to previous", () =>
|
||||
trackChangeQueue.Count == 1 &&
|
||||
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
|
||||
|
||||
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||
AddUntilStep("track changed to next", () =>
|
||||
AddAssert("track changed to next", () =>
|
||||
trackChangeQueue.Count == 1 &&
|
||||
trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next);
|
||||
|
||||
AddUntilStep("wait until track switches", () => trackChangeQueue.Count == 2);
|
||||
|
||||
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||
AddUntilStep("track changed to next", () =>
|
||||
trackChangeQueue.Count == 3 &&
|
||||
trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next);
|
||||
AddAssert("track actually changed", () => !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo));
|
||||
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -320,17 +319,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => playlist.ChildrenOfType<DrawableLinkCompiler>().Any() && playlist.ChildrenOfType<BeatmapCardThumbnail>().First().DrawWidth > 0);
|
||||
|
||||
AddStep("move mouse to first item title", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<LinkFlowContainer>().First().ChildrenOfType<SpriteText>().First()));
|
||||
AddAssert("first item title not hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.False);
|
||||
|
||||
AddStep("click title", () =>
|
||||
AddStep("move mouse to first item title", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(playlist.ChildrenOfType<LinkFlowContainer>().First().ChildrenOfType<SpriteText>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
var drawQuad = playlist.ChildrenOfType<LinkFlowContainer>().First().ScreenSpaceDrawQuad;
|
||||
var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0);
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
|
||||
AddAssert("first item title not hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.False);
|
||||
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
|
||||
AddUntilStep("first item selected", () => playlist.ChildrenOfType<DrawableRoomPlaylistItem>().First().IsSelectedItem, () => Is.True);
|
||||
// implies being clickable.
|
||||
AddUntilStep("first item title hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.True);
|
||||
|
||||
AddStep("move mouse to second item results button", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<GrayButton>().ElementAt(5)));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user