mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 21:02:54 +08:00
Merge branch 'master' into daily-challenge/better-results
This commit is contained in:
commit
f0ad7a97cb
@ -7,7 +7,6 @@ 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.
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.627.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.702.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -0,0 +1,66 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// 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 ? 1 : ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity,
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity : 1,
|
||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||
}.Yield();
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
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;
|
||||
@ -62,43 +61,43 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
yield return new CatchModNightcore();
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
yield return new CatchModDoubleTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
yield return new CatchModPerfect();
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new CatchModSuddenDeath();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new CatchModCinema();
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new CatchModAutoplay();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
yield return new CatchModEasy();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
yield return new CatchModFlashlight();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
yield return new CatchModHalfTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
yield return new CatchModHardRock();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
yield return new CatchModHidden();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new CatchModNoFail();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
yield return new CatchModRelax();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ 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;
|
||||
@ -121,7 +120,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
||||
{
|
||||
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
||||
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
||||
|
@ -12,6 +12,7 @@ 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
|
||||
@ -46,16 +47,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
public double TickDistanceMultiplier = 1;
|
||||
|
||||
[JsonIgnore]
|
||||
private double velocityFactor;
|
||||
public double Velocity { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
private double tickDistanceFactor;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Velocity => velocityFactor * SliderVelocityMultiplier;
|
||||
|
||||
[JsonIgnore]
|
||||
public double TickDistance => tickDistanceFactor * TickDistanceMultiplier;
|
||||
public double TickDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||
@ -68,8 +63,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
|
||||
velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength;
|
||||
tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate;
|
||||
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;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
|
@ -2,6 +2,8 @@
|
||||
// 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;
|
||||
|
||||
@ -17,5 +19,22 @@ 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
643
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Normal file
643
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Normal file
@ -0,0 +1,643 @@
|
||||
// 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 missed", () => judgementResults.Where(j => beatmap.HitObjects[1].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]
|
||||
|
@ -8,7 +8,6 @@ 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;
|
||||
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
}
|
||||
|
||||
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
|
||||
=> hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
|
||||
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor);
|
||||
|
||||
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
|
||||
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osuTK;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -79,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
else
|
||||
convertType |= PatternType.LowProbability;
|
||||
|
||||
if (!convertType.HasFlagFast(PatternType.KeepSingle))
|
||||
if (!convertType.HasFlag(PatternType.KeepSingle))
|
||||
{
|
||||
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
|
||||
convertType |= PatternType.Mirror;
|
||||
@ -102,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
||||
if (convertType.HasFlag(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++)
|
||||
@ -114,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||
if (convertType.HasFlag(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
|
||||
@ -127,7 +126,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||
if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||
{
|
||||
// Generate a new pattern by placing on the already filled columns
|
||||
for (int i = RandomStart; i < TotalColumns; i++)
|
||||
@ -141,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (PreviousPattern.HitObjects.Count() == 1)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.Stair))
|
||||
if (convertType.HasFlag(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;
|
||||
@ -152,7 +151,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.ReverseStair))
|
||||
if (convertType.HasFlag(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;
|
||||
@ -164,10 +163,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.KeepSingle))
|
||||
if (convertType.HasFlag(PatternType.KeepSingle))
|
||||
return generateRandomNotes(1);
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.Mirror))
|
||||
if (convertType.HasFlag(PatternType.Mirror))
|
||||
{
|
||||
if (ConversionDifficulty > 6.5)
|
||||
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||
@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.78, 0.42, 0, 0);
|
||||
|
||||
return generateRandomPattern(1, 0.62, 0, 0);
|
||||
@ -187,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.35, 0.08, 0, 0);
|
||||
|
||||
return generateRandomPattern(0.52, 0.15, 0, 0);
|
||||
@ -195,7 +194,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 2)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateRandomPattern(0.18, 0, 0, 0);
|
||||
|
||||
return generateRandomPattern(0.45, 0, 0, 0);
|
||||
@ -208,9 +207,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
foreach (var obj in p.HitObjects)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||
StairType = PatternType.ReverseStair;
|
||||
if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||
StairType = PatternType.Stair;
|
||||
}
|
||||
|
||||
@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
|
||||
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
|
||||
|
||||
if (!allowStacking)
|
||||
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
|
||||
@ -250,7 +249,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int getNextColumn(int last)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.Gathered))
|
||||
if (convertType.HasFlag(PatternType.Gathered))
|
||||
{
|
||||
last++;
|
||||
if (last == TotalColumns)
|
||||
@ -297,7 +296,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.HasFlagFast(PatternType.ForceNotStack))
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack))
|
||||
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
@ -7,7 +7,6 @@ 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;
|
||||
@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 6.5)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
|
||||
@ -147,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 4)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
|
||||
@ -155,13 +154,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
if (ConversionDifficulty > 2.5)
|
||||
{
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.3, 0, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
|
||||
}
|
||||
|
||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
||||
if (convertType.HasFlag(PatternType.LowProbability))
|
||||
return generateNRandomNotes(StartTime, 0.17, 0, 0);
|
||||
|
||||
return generateNRandomNotes(StartTime, 0.27, 0, 0);
|
||||
@ -219,7 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
int lastColumn = nextColumn;
|
||||
@ -371,7 +370,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.HasFlagFast(PatternType.LowProbability);
|
||||
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
|
||||
|
||||
if (canGenerateTwoNotes)
|
||||
@ -404,7 +403,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.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
for (int i = 0; i < columnRepeat; i++)
|
||||
@ -433,7 +432,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
|
||||
|
||||
// Create the hold note
|
||||
|
@ -4,7 +4,6 @@
|
||||
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;
|
||||
@ -89,79 +88,79 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
yield return new ManiaModNightcore();
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
yield return new ManiaModDoubleTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
yield return new ManiaModPerfect();
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new ManiaModSuddenDeath();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new ManiaModCinema();
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new ManiaModAutoplay();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
yield return new ManiaModEasy();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.FadeIn))
|
||||
if (mods.HasFlag(LegacyMods.FadeIn))
|
||||
yield return new ManiaModFadeIn();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
yield return new ManiaModFlashlight();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
yield return new ManiaModHalfTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
yield return new ManiaModHardRock();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
yield return new ManiaModHidden();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key1))
|
||||
if (mods.HasFlag(LegacyMods.Key1))
|
||||
yield return new ManiaModKey1();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key2))
|
||||
if (mods.HasFlag(LegacyMods.Key2))
|
||||
yield return new ManiaModKey2();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key3))
|
||||
if (mods.HasFlag(LegacyMods.Key3))
|
||||
yield return new ManiaModKey3();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key4))
|
||||
if (mods.HasFlag(LegacyMods.Key4))
|
||||
yield return new ManiaModKey4();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key5))
|
||||
if (mods.HasFlag(LegacyMods.Key5))
|
||||
yield return new ManiaModKey5();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key6))
|
||||
if (mods.HasFlag(LegacyMods.Key6))
|
||||
yield return new ManiaModKey6();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key7))
|
||||
if (mods.HasFlag(LegacyMods.Key7))
|
||||
yield return new ManiaModKey7();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key8))
|
||||
if (mods.HasFlag(LegacyMods.Key8))
|
||||
yield return new ManiaModKey8();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Key9))
|
||||
if (mods.HasFlag(LegacyMods.Key9))
|
||||
yield return new ManiaModKey9();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.KeyCoop))
|
||||
if (mods.HasFlag(LegacyMods.KeyCoop))
|
||||
yield return new ManiaModDualStages();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new ManiaModNoFail();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Random))
|
||||
if (mods.HasFlag(LegacyMods.Random))
|
||||
yield return new ManiaModRandom();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Mirror))
|
||||
if (mods.HasFlag(LegacyMods.Mirror))
|
||||
yield return new ManiaModMirror();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModEasy(),
|
||||
new ManiaModNoFail(),
|
||||
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
|
||||
new ManiaModNoRelease(),
|
||||
};
|
||||
|
||||
case ModType.DifficultyIncrease:
|
||||
|
107
osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs
Normal file
107
osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs
Normal file
@ -0,0 +1,107 @@
|
||||
// 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 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 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -72,18 +72,18 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// <summary>
|
||||
/// The head note of the hold.
|
||||
/// </summary>
|
||||
public HeadNote Head { get; private set; }
|
||||
public HeadNote Head { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tail note of the hold.
|
||||
/// </summary>
|
||||
public TailNote Tail { get; private set; }
|
||||
public TailNote Tail { get; protected 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; private set; }
|
||||
public HoldNoteBody Body { get; protected set; }
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
|
@ -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,6 +1,7 @@
|
||||
// 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;
|
||||
@ -160,6 +161,8 @@ 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,6 +15,7 @@ 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
|
||||
{
|
||||
@ -177,6 +178,79 @@ 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,13 +2,16 @@
|
||||
// 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;
|
||||
@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(200);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -71,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -89,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -111,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(4);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100, 100));
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -130,8 +133,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -149,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(2);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertLength(100);
|
||||
}
|
||||
|
||||
@ -171,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -195,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(4);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -215,8 +218,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(3);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.LINEAR);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.LINEAR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -239,8 +242,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointCount(4);
|
||||
assertControlPointPosition(1, new Vector2(100, 0));
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.LINEAR);
|
||||
assertFinalControlPointType(1, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -268,8 +271,46 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointPosition(2, new Vector2(100));
|
||||
assertControlPointPosition(3, new Vector2(200, 100));
|
||||
assertControlPointPosition(4, new Vector2(200));
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertControlPointType(2, PathType.PERFECT_CURVE);
|
||||
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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -293,7 +334,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
addClickStep(MouseButton.Right);
|
||||
assertPlaced(true);
|
||||
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -312,11 +353,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(808, tolerance: 10);
|
||||
assertControlPointCount(5);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, null);
|
||||
assertControlPointType(2, null);
|
||||
assertControlPointType(3, null);
|
||||
assertControlPointType(4, null);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(1, null);
|
||||
assertFinalControlPointType(2, null);
|
||||
assertFinalControlPointType(3, null);
|
||||
assertFinalControlPointType(4, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -337,10 +378,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(600, tolerance: 10);
|
||||
assertControlPointCount(4);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, PathType.BSpline(4));
|
||||
assertControlPointType(2, PathType.BSpline(4));
|
||||
assertControlPointType(3, null);
|
||||
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||
assertFinalControlPointType(1, PathType.BSpline(4));
|
||||
assertFinalControlPointType(2, PathType.BSpline(4));
|
||||
assertFinalControlPointType(3, null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -359,7 +400,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -379,7 +420,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -400,7 +441,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -421,7 +462,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.BEZIER);
|
||||
assertFinalControlPointType(0, PathType.BEZIER);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -438,7 +479,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
assertPlaced(true);
|
||||
assertControlPointCount(3);
|
||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
||||
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||
}
|
||||
|
||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||
@ -454,7 +495,10 @@ 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 assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
||||
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 assertControlPointPosition(int index, Vector2 position) =>
|
||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1));
|
||||
|
@ -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 of the playfield.
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside the playfield.
|
||||
|
||||
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
||||
|
||||
@ -196,6 +196,7 @@ 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;
|
||||
@ -209,6 +210,8 @@ 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();
|
||||
}
|
||||
@ -217,6 +220,8 @@ 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))
|
||||
@ -245,6 +250,86 @@ 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)
|
||||
@ -254,30 +339,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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 updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
||||
private void updatePathTypeOfSelectedPieces(PathType? type)
|
||||
{
|
||||
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
|
||||
int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
if (type?.Type == SplineType.PerfectCurve)
|
||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||
{
|
||||
// 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;
|
||||
var pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint);
|
||||
int indexInSegment = pointsInSegment.IndexOf(p.ControlPoint);
|
||||
|
||||
if (pointsInSegment.Count > thirdPointIndex + 1)
|
||||
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
||||
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;
|
||||
}
|
||||
|
||||
hitObject.Path.ExpectedDistance.Value = null;
|
||||
piece.ControlPoint.Type = type;
|
||||
EnsureValidPathTypes();
|
||||
|
||||
changeHandler?.EndChange();
|
||||
}
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
@ -290,6 +383,8 @@ 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();
|
||||
@ -386,22 +481,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
var splittablePieces = selectedPieces.Where(isSplittable).ToList();
|
||||
int splittableCount = splittablePieces.Count;
|
||||
|
||||
List<MenuItem> curveTypeItems = new List<MenuItem>();
|
||||
curveTypeItems = new List<MenuItem>();
|
||||
|
||||
if (!selectedPieces.Contains(Pieces[0]))
|
||||
foreach (PathType? type in path_types)
|
||||
{
|
||||
curveTypeItems.Add(createMenuItemForPathType(null));
|
||||
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||
// special inherit case
|
||||
if (type == null)
|
||||
{
|
||||
if (selectedPieces.Contains(Pieces[0]))
|
||||
continue;
|
||||
|
||||
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||
}
|
||||
|
||||
curveTypeItems.Add(createMenuItemForPathType(type));
|
||||
}
|
||||
|
||||
// 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>
|
||||
{
|
||||
@ -424,35 +524,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
() => DeleteSelected())
|
||||
);
|
||||
|
||||
updateCurveMenuItems();
|
||||
|
||||
return menuItems.ToArray();
|
||||
|
||||
CurveTypeMenuItem createMenuItemForPathType(PathType? type) => new CurveTypeMenuItem(type, _ => updatePathTypeOfSelectedPieces(type));
|
||||
}
|
||||
}
|
||||
|
||||
private MenuItem createMenuItemForPathType(PathType? type)
|
||||
private void updateCurveMenuItems()
|
||||
{
|
||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type);
|
||||
if (curveTypeItems == null)
|
||||
return;
|
||||
|
||||
var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ =>
|
||||
foreach (var item in curveTypeItems.OfType<CurveTypeMenuItem>())
|
||||
{
|
||||
changeHandler?.BeginChange();
|
||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == item.PathType);
|
||||
|
||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||
updatePathType(p, type);
|
||||
if (countOfState == totalCount)
|
||||
item.State.Value = TernaryState.True;
|
||||
else if (countOfState > 0)
|
||||
item.State.Value = TernaryState.Indeterminate;
|
||||
else
|
||||
item.State.Value = TernaryState.False;
|
||||
}
|
||||
}
|
||||
|
||||
EnsureValidPathTypes();
|
||||
private class CurveTypeMenuItem : TernaryStateRadioMenuItem
|
||||
{
|
||||
public readonly 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;
|
||||
public CurveTypeMenuItem(PathType? pathType, Action<TernaryState> action)
|
||||
: base(pathType?.Description ?? "Inherit", MenuItemType.Standard, action)
|
||||
{
|
||||
PathType = pathType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private PathControlPoint segmentStart;
|
||||
private PathControlPoint cursor;
|
||||
private int currentSegmentLength;
|
||||
private bool usingCustomSegmentType;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
@ -149,21 +150,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
case SliderPlacementState.ControlPoints:
|
||||
if (canPlaceNewControlPoint(out var lastPoint))
|
||||
{
|
||||
// Place a new point by detatching the current cursor.
|
||||
updateCursor();
|
||||
cursor = null;
|
||||
}
|
||||
placeNewControlPoint();
|
||||
else
|
||||
{
|
||||
// Transform the last point into a new segment.
|
||||
Debug.Assert(lastPoint != null);
|
||||
|
||||
segmentStart = lastPoint;
|
||||
segmentStart.Type = PathType.LINEAR;
|
||||
|
||||
currentSegmentLength = 1;
|
||||
}
|
||||
beginNewSegment(lastPoint);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -171,6 +160,18 @@ 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)
|
||||
@ -223,6 +224,72 @@ 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();
|
||||
@ -246,6 +313,12 @@ 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);
|
||||
@ -316,6 +389,13 @@ 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,7 +55,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
@ -1,17 +1,24 @@
|
||||
// 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
|
||||
{
|
||||
@ -20,6 +27,9 @@ 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>
|
||||
@ -55,8 +65,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// </summary>
|
||||
public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f)
|
||||
{
|
||||
MinValue = -45f,
|
||||
MaxValue = 45f,
|
||||
MinValue = -180f,
|
||||
MaxValue = 180f,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
@ -72,10 +82,13 @@ 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")
|
||||
@ -109,6 +122,31 @@ 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;
|
||||
@ -118,6 +156,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
gridTypeButtons.Items.First().Select();
|
||||
|
||||
StartPositionX.BindValueChanged(x =>
|
||||
{
|
||||
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
|
||||
@ -145,6 +185,32 @@ 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()
|
||||
@ -167,5 +233,42 @@ 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,7 +10,6 @@ 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;
|
||||
@ -101,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// we may be entering the screen with a selection already active
|
||||
updateDistanceSnapGrid();
|
||||
|
||||
updatePositionSnapGrid();
|
||||
OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true);
|
||||
|
||||
RightToolbox.AddRange(new Drawable[]
|
||||
{
|
||||
@ -116,18 +115,45 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
);
|
||||
}
|
||||
|
||||
private void updatePositionSnapGrid()
|
||||
private void updatePositionSnapGrid(ValueChangedEvent<PositionSnapGridType> obj)
|
||||
{
|
||||
if (positionSnapGrid != null)
|
||||
LayerBelowRuleset.Remove(positionSnapGrid, true);
|
||||
|
||||
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
|
||||
switch (obj.NewValue)
|
||||
{
|
||||
case PositionSnapGridType.Square:
|
||||
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
|
||||
|
||||
rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
|
||||
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
|
||||
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
|
||||
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
|
||||
positionSnapGrid = rectangularPositionSnapGrid;
|
||||
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.RelativeSizeAxes = Axes.Both;
|
||||
LayerBelowRuleset.Add(positionSnapGrid);
|
||||
@ -194,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||
{
|
||||
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
if (snapType.HasFlag(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
|
||||
@ -204,7 +230,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.HasFlagFast(SnapType.RelativeGrids) && DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
if (snapType.HasFlag(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))
|
||||
@ -216,7 +242,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
if (snapType.HasFlag(SnapType.RelativeGrids))
|
||||
{
|
||||
if (DistanceSnapProvider.DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
@ -227,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
}
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.GlobalGrids))
|
||||
if (snapType.HasFlag(SnapType.GlobalGrids))
|
||||
{
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
{
|
||||
|
@ -15,6 +15,13 @@ 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);
|
||||
|
@ -4,7 +4,6 @@
|
||||
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;
|
||||
@ -70,55 +69,55 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
yield return new OsuModNightcore();
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
yield return new OsuModDoubleTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
yield return new OsuModPerfect();
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new OsuModSuddenDeath();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Autopilot))
|
||||
if (mods.HasFlag(LegacyMods.Autopilot))
|
||||
yield return new OsuModAutopilot();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new OsuModCinema();
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new OsuModAutoplay();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
yield return new OsuModEasy();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
yield return new OsuModFlashlight();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
yield return new OsuModHalfTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
yield return new OsuModHardRock();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
yield return new OsuModHidden();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new OsuModNoFail();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
yield return new OsuModRelax();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.SpunOut))
|
||||
if (mods.HasFlag(LegacyMods.SpunOut))
|
||||
yield return new OsuModSpunOut();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Target))
|
||||
if (mods.HasFlag(LegacyMods.Target))
|
||||
yield return new OsuModTargetPractice();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.TouchDevice))
|
||||
if (mods.HasFlag(LegacyMods.TouchDevice))
|
||||
yield return new OsuModTouchDevice();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ 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;
|
||||
@ -243,14 +242,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
originPosition = Vector2.Zero;
|
||||
|
||||
if (Source.TrailOrigin.HasFlagFast(Anchor.x1))
|
||||
if (Source.TrailOrigin.HasFlag(Anchor.x1))
|
||||
originPosition.X = 0.5f;
|
||||
else if (Source.TrailOrigin.HasFlagFast(Anchor.x2))
|
||||
else if (Source.TrailOrigin.HasFlag(Anchor.x2))
|
||||
originPosition.X = 1f;
|
||||
|
||||
if (Source.TrailOrigin.HasFlagFast(Anchor.y1))
|
||||
if (Source.TrailOrigin.HasFlag(Anchor.y1))
|
||||
originPosition.Y = 0.5f;
|
||||
else if (Source.TrailOrigin.HasFlagFast(Anchor.y2))
|
||||
else if (Source.TrailOrigin.HasFlag(Anchor.y2))
|
||||
originPosition.Y = 1f;
|
||||
|
||||
Source.parts.CopyTo(parts, 0);
|
||||
|
@ -8,6 +8,7 @@ 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;
|
||||
@ -47,6 +48,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override GameplayCursorContainer? CreateCursor() => new OsuCursorContainer();
|
||||
|
||||
public override Quad SkinnableComponentScreenSpaceDrawQuad => playfieldBorder.ScreenSpaceDrawQuad;
|
||||
|
||||
private readonly Container judgementAboveHitObjectLayer;
|
||||
|
||||
public OsuPlayfield()
|
||||
|
@ -4,7 +4,6 @@
|
||||
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;
|
||||
@ -79,43 +78,43 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||
{
|
||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
||||
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||
yield return new TaikoModNightcore();
|
||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
||||
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||
yield return new TaikoModDoubleTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
||||
if (mods.HasFlag(LegacyMods.Perfect))
|
||||
yield return new TaikoModPerfect();
|
||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new TaikoModSuddenDeath();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new TaikoModCinema();
|
||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new TaikoModAutoplay();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
yield return new TaikoModEasy();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
||||
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||
yield return new TaikoModFlashlight();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
||||
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||
yield return new TaikoModHalfTime();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
||||
if (mods.HasFlag(LegacyMods.HardRock))
|
||||
yield return new TaikoModHardRock();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
||||
if (mods.HasFlag(LegacyMods.Hidden))
|
||||
yield return new TaikoModHidden();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
||||
if (mods.HasFlag(LegacyMods.NoFail))
|
||||
yield return new TaikoModNoFail();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
||||
if (mods.HasFlag(LegacyMods.Relax))
|
||||
yield return new TaikoModRelax();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||
yield return new ModScoreV2();
|
||||
}
|
||||
|
||||
|
@ -528,8 +528,17 @@ 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 control point at the end time of the slider should be applied
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).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());
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
|
235
osu.Game.Tests/Editing/Checks/CheckTitleMarkersTest.cs
Normal file
235
osu.Game.Tests/Editing/Checks/CheckTitleMarkersTest.cs
Normal file
@ -0,0 +1,235 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
43
osu.Game.Tests/Editing/EditorTimestampParserTest.cs
Normal file
43
osu.Game.Tests/Editing/EditorTimestampParserTest.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -21,10 +21,11 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
};
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -38,14 +39,15 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -59,15 +61,16 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 },
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -81,14 +84,15 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
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();
|
||||
@ -102,16 +106,17 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
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();
|
||||
@ -125,15 +130,16 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 5000 },
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -152,9 +158,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -165,7 +172,7 @@ namespace osu.Game.Tests.Editing
|
||||
new BreakPeriod(1200, 4000),
|
||||
new BreakPeriod(5200, 8000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -184,9 +191,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -197,7 +205,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new BreakPeriod(1200, 8000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -218,9 +226,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1100 },
|
||||
@ -230,7 +239,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new BreakPeriod(1200, 8000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -249,9 +258,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -262,7 +272,7 @@ namespace osu.Game.Tests.Editing
|
||||
new ManualBreakPeriod(1200, 4000),
|
||||
new ManualBreakPeriod(5200, 8000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -283,9 +293,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -296,7 +307,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new ManualBreakPeriod(1200, 8000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -317,9 +328,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -329,7 +341,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new ManualBreakPeriod(1200, 8800),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -348,9 +360,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -360,7 +373,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new BreakPeriod(10000, 15000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -374,9 +387,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
@ -386,7 +400,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new ManualBreakPeriod(10000, 15000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -400,9 +414,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HoldNote { StartTime = 1000, EndTime = 20000 },
|
||||
@ -412,7 +427,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new ManualBreakPeriod(10000, 15000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -426,9 +441,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 10000 },
|
||||
@ -438,7 +454,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new BreakPeriod(0, 9000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
@ -452,9 +468,10 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
var beatmap = new Beatmap
|
||||
var beatmap = new EditorBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPoints,
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 10000 },
|
||||
@ -464,7 +481,7 @@ namespace osu.Game.Tests.Editing
|
||||
{
|
||||
new ManualBreakPeriod(0, 9000),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||
beatmapProcessor.PreProcess();
|
||||
|
22
osu.Game.Tests/Resources/per-slider-node-sample-settings.osu
Normal file
22
osu.Game.Tests/Resources/per-slider-node-sample-settings.osu
Normal file
@ -0,0 +1,22 @@
|
||||
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:
|
@ -12,7 +12,9 @@ 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;
|
||||
@ -169,6 +171,24 @@ 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)
|
||||
|
@ -16,6 +16,7 @@ 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;
|
||||
@ -224,6 +225,116 @@ 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,6 +402,88 @@ 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 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);
|
||||
}
|
||||
|
||||
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,11 +5,13 @@ 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.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@ -102,5 +104,56 @@ 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_SOFT, volume: 70),
|
||||
}
|
||||
}));
|
||||
AddStep("seek to 500", () => EditorClock.Seek(500));
|
||||
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.All(s => s.Bank == HitSampleInfo.BANK_SOFT));
|
||||
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,51 @@ 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;
|
||||
|
@ -403,6 +403,28 @@ 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));
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@ -62,7 +61,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();
|
||||
|
||||
public BindableList<BreakPeriod> Breaks { get; set; } = new BindableList<BreakPeriod>();
|
||||
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
|
||||
|
||||
public List<string> UnhandledEventLines { get; set; } = new List<string>();
|
||||
|
||||
|
@ -49,6 +49,9 @@ namespace osu.Game.Beatmaps
|
||||
original.BeatmapInfo = original.BeatmapInfo.Clone();
|
||||
original.ControlPointInfo = original.ControlPointInfo.DeepClone();
|
||||
|
||||
// Used in osu!mania conversion.
|
||||
original.Breaks = original.Breaks.ToList();
|
||||
|
||||
return ConvertBeatmap(original, cancellationToken);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -33,7 +32,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
/// <remarks>
|
||||
/// Compare: https://github.com/peppy/osu-stable-reference/blob/master/osu!/GameplayElements/HitObjects/HitObject.cs#L319
|
||||
/// </remarks>
|
||||
private const double control_point_leniency = 5;
|
||||
public const double CONTROL_POINT_LENIENCY = 5;
|
||||
|
||||
internal static RulesetStore? RulesetStore;
|
||||
|
||||
@ -160,20 +159,24 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
private void applySamples(HitObject hitObject)
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + control_point_leniency) ?? SampleControlPoint.DEFAULT;
|
||||
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
|
||||
if (hitObject is IHasRepeats hasRepeats)
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1) ?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
||||
{
|
||||
double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + control_point_leniency;
|
||||
double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + CONTROL_POINT_LENIENCY;
|
||||
var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT;
|
||||
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY) ?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -528,8 +531,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (split.Length >= 8)
|
||||
{
|
||||
LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]);
|
||||
kiaiMode = effectFlags.HasFlagFast(LegacyEffectFlags.Kiai);
|
||||
omitFirstBarSignature = effectFlags.HasFlagFast(LegacyEffectFlags.OmitFirstBarLine);
|
||||
kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai);
|
||||
omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine);
|
||||
}
|
||||
|
||||
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
|
||||
|
@ -282,19 +282,39 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
foreach (var hitObject in hitObjects)
|
||||
{
|
||||
if (hitObject.Samples.Count > 0)
|
||||
if (hitObject is IHasRepeats hasNodeSamples)
|
||||
{
|
||||
int volume = hitObject.Samples.Max(o => o.Volume);
|
||||
int customIndex = hitObject.Samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo)
|
||||
? hitObject.Samples.OfType<ConvertHitObjectParser.LegacyHitSampleInfo>().Max(o => o.CustomSampleBank)
|
||||
: -1;
|
||||
double spanDuration = hasNodeSamples.Duration / hasNodeSamples.SpanCount();
|
||||
|
||||
yield return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = hitObject.GetEndTime(), SampleVolume = volume, CustomSampleBank = customIndex };
|
||||
for (int i = 0; i < hasNodeSamples.NodeSamples.Count; ++i)
|
||||
{
|
||||
double nodeTime = hitObject.StartTime + i * spanDuration;
|
||||
|
||||
if (hasNodeSamples.NodeSamples[i].Count > 0)
|
||||
yield return createSampleControlPointFor(nodeTime, hasNodeSamples.NodeSamples[i]);
|
||||
|
||||
if (spanDuration > LegacyBeatmapDecoder.CONTROL_POINT_LENIENCY + 1 && hitObject.Samples.Count > 0 && i < hasNodeSamples.NodeSamples.Count - 1)
|
||||
yield return createSampleControlPointFor(nodeTime + LegacyBeatmapDecoder.CONTROL_POINT_LENIENCY + 1, hitObject.Samples);
|
||||
}
|
||||
}
|
||||
else if (hitObject.Samples.Count > 0)
|
||||
{
|
||||
yield return createSampleControlPointFor(hitObject.GetEndTime(), hitObject.Samples);
|
||||
}
|
||||
|
||||
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
|
||||
yield return nested;
|
||||
}
|
||||
|
||||
SampleControlPoint createSampleControlPointFor(double time, IList<HitSampleInfo> samples)
|
||||
{
|
||||
int volume = samples.Max(o => o.Volume);
|
||||
int customIndex = samples.Any(o => o is ConvertHitObjectParser.LegacyHitSampleInfo)
|
||||
? samples.OfType<ConvertHitObjectParser.LegacyHitSampleInfo>().Max(o => o.CustomSampleBank)
|
||||
: -1;
|
||||
|
||||
return new LegacyBeatmapDecoder.LegacySampleControlPoint { Time = time, SampleVolume = volume, CustomSampleBank = customIndex };
|
||||
}
|
||||
}
|
||||
|
||||
void extractSampleControlPoints(IEnumerable<HitObject> hitObject)
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -41,11 +40,11 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// The breaks in this beatmap.
|
||||
/// </summary>
|
||||
BindableList<BreakPeriod> Breaks { get; }
|
||||
List<BreakPeriod> Breaks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All lines from the [Events] section which aren't handled in the encoding process yet.
|
||||
/// These lines shoule be written out to the beatmap file on save or export.
|
||||
/// These lines should be written out to the beatmap file on save or export.
|
||||
/// </summary>
|
||||
List<string> UnhandledEventLines { get; }
|
||||
|
||||
|
@ -10,7 +10,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -164,13 +163,13 @@ namespace osu.Game.Database
|
||||
var importTasks = new List<Task>();
|
||||
|
||||
Task beatmapImportTask = Task.CompletedTask;
|
||||
if (content.HasFlagFast(StableContent.Beatmaps))
|
||||
if (content.HasFlag(StableContent.Beatmaps))
|
||||
importTasks.Add(beatmapImportTask = new LegacyBeatmapImporter(beatmaps).ImportFromStableAsync(stableStorage));
|
||||
|
||||
if (content.HasFlagFast(StableContent.Skins))
|
||||
if (content.HasFlag(StableContent.Skins))
|
||||
importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage));
|
||||
|
||||
if (content.HasFlagFast(StableContent.Collections))
|
||||
if (content.HasFlag(StableContent.Collections))
|
||||
{
|
||||
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess)
|
||||
{
|
||||
@ -180,7 +179,7 @@ namespace osu.Game.Database
|
||||
}.ImportFromStorage(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||
}
|
||||
|
||||
if (content.HasFlagFast(StableContent.Scores))
|
||||
if (content.HasFlag(StableContent.Scores))
|
||||
importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||
|
||||
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
||||
|
@ -15,7 +15,6 @@ using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
@ -1035,7 +1034,7 @@ namespace osu.Game.Database
|
||||
|
||||
var legacyMods = (LegacyMods)sr.ReadInt32();
|
||||
|
||||
if (!legacyMods.HasFlagFast(LegacyMods.ScoreV2) || score.APIMods.Any(mod => mod.Acronym == @"SV2"))
|
||||
if (!legacyMods.HasFlag(LegacyMods.ScoreV2) || score.APIMods.Any(mod => mod.Acronym == @"SV2"))
|
||||
return;
|
||||
|
||||
score.APIMods = score.APIMods.Append(new APIMod(new ModScoreV2())).ToArray();
|
||||
|
@ -6,7 +6,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
@ -190,9 +189,9 @@ namespace osu.Game.Graphics
|
||||
float width = Texture.DisplayWidth * scale;
|
||||
float height = Texture.DisplayHeight * scale;
|
||||
|
||||
if (relativePositionAxes.HasFlagFast(Axes.X))
|
||||
if (relativePositionAxes.HasFlag(Axes.X))
|
||||
position.X *= sourceSize.X;
|
||||
if (relativePositionAxes.HasFlagFast(Axes.Y))
|
||||
if (relativePositionAxes.HasFlag(Axes.Y))
|
||||
position.Y *= sourceSize.Y;
|
||||
|
||||
return new RectangleF(
|
||||
|
@ -119,9 +119,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Expanded.BindValueChanged(v =>
|
||||
{
|
||||
label.Text = v.NewValue ? expandedLabelText : contractedLabelText;
|
||||
slider.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
|
||||
slider.FadeTo(v.NewValue ? Current.Disabled ? 0.3f : 1f : 0f, 500, Easing.OutQuint);
|
||||
slider.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
}, true);
|
||||
|
||||
Current.BindDisabledChanged(disabled =>
|
||||
{
|
||||
slider.Alpha = Expanded.Value ? disabled ? 0.3f : 1 : 0f;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,6 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Framework.Audio.Track;
|
||||
using System;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -57,15 +56,15 @@ namespace osu.Game.Graphics.UserInterface
|
||||
set
|
||||
{
|
||||
base.Origin = value;
|
||||
c1.Origin = c1.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
|
||||
c2.Origin = c2.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
|
||||
c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
|
||||
c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
|
||||
|
||||
X = value.HasFlagFast(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
|
||||
X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
|
||||
|
||||
Remove(c1, false);
|
||||
Remove(c2, false);
|
||||
c1.Depth = value.HasFlagFast(Anchor.x2) ? 0 : 1;
|
||||
c2.Depth = value.HasFlagFast(Anchor.x2) ? 1 : 0;
|
||||
c1.Depth = value.HasFlag(Anchor.x2) ? 0 : 1;
|
||||
c2.Depth = value.HasFlag(Anchor.x2) ? 1 : 0;
|
||||
Add(c1);
|
||||
Add(c2);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ namespace osu.Game.Input.Bindings
|
||||
/// </remarks>
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => globalKeyBindings
|
||||
.Concat(editorKeyBindings)
|
||||
.Concat(editorTestPlayKeyBindings)
|
||||
.Concat(inGameKeyBindings)
|
||||
.Concat(replayKeyBindings)
|
||||
.Concat(songSelectKeyBindings)
|
||||
@ -68,6 +69,9 @@ namespace osu.Game.Input.Bindings
|
||||
case GlobalActionCategory.Overlays:
|
||||
return overlayKeyBindings;
|
||||
|
||||
case GlobalActionCategory.EditorTestPlay:
|
||||
return editorTestPlayKeyBindings;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(category), category, $"Unexpected {nameof(GlobalActionCategory)}");
|
||||
}
|
||||
@ -100,7 +104,6 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.ToggleProfile),
|
||||
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
||||
|
||||
@ -118,6 +121,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.ToggleBeatmapListing),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.ToggleProfile),
|
||||
};
|
||||
|
||||
private static IEnumerable<KeyBinding> editorKeyBindings => new[]
|
||||
@ -145,6 +149,14 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
||||
};
|
||||
|
||||
private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[]
|
||||
{
|
||||
new KeyBinding(new[] { InputKey.Tab }, GlobalAction.EditorTestPlayToggleAutoplay),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.EditorTestPlayToggleQuickPause),
|
||||
new KeyBinding(new[] { InputKey.F1 }, GlobalAction.EditorTestPlayQuickExitToInitialTime),
|
||||
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorTestPlayQuickExitToCurrentTime),
|
||||
};
|
||||
|
||||
private static IEnumerable<KeyBinding> inGameKeyBindings => new[]
|
||||
{
|
||||
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
|
||||
@ -432,6 +444,18 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))]
|
||||
EditorToggleScaleControl,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayToggleAutoplay))]
|
||||
EditorTestPlayToggleAutoplay,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayToggleQuickPause))]
|
||||
EditorTestPlayToggleQuickPause,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToInitialTime))]
|
||||
EditorTestPlayQuickExitToInitialTime,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))]
|
||||
EditorTestPlayQuickExitToCurrentTime,
|
||||
}
|
||||
|
||||
public enum GlobalActionCategory
|
||||
@ -442,6 +466,7 @@ namespace osu.Game.Input.Bindings
|
||||
Replay,
|
||||
SongSelect,
|
||||
AudioControl,
|
||||
Overlays
|
||||
Overlays,
|
||||
EditorTestPlay,
|
||||
}
|
||||
}
|
||||
|
@ -374,6 +374,26 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control");
|
||||
|
||||
/// <summary>
|
||||
/// "Toggle autoplay"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTestPlayToggleAutoplay => new TranslatableString(getKey(@"editor_test_play_toggle_autoplay"), @"Toggle autoplay");
|
||||
|
||||
/// <summary>
|
||||
/// "Toggle quick pause"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTestPlayToggleQuickPause => new TranslatableString(getKey(@"editor_test_play_toggle_quick_pause"), @"Toggle quick pause");
|
||||
|
||||
/// <summary>
|
||||
/// "Quick exit to initial time"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTestPlayQuickExitToInitialTime => new TranslatableString(getKey(@"editor_test_play_quick_exit_to_initial_time"), @"Quick exit to initial time");
|
||||
|
||||
/// <summary>
|
||||
/// "Quick exit to current time"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTestPlayQuickExitToCurrentTime => new TranslatableString(getKey(@"editor_test_play_quick_exit_to_current_time"), @"Quick exit to current time");
|
||||
|
||||
/// <summary>
|
||||
/// "Increase mod speed"
|
||||
/// </summary>
|
||||
|
@ -49,6 +49,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
|
||||
|
||||
/// <summary>
|
||||
/// "Editor: Test play"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTestPlaySection => new TranslatableString(getKey(@"editor_test_play_section"), @"Editor: Test play");
|
||||
|
||||
/// <summary>
|
||||
/// "Reset all bindings in section"
|
||||
/// </summary>
|
||||
|
@ -271,7 +271,7 @@ namespace osu.Game.Online.Chat
|
||||
handleAdvanced(advanced_link_regex, result, startIndex);
|
||||
|
||||
// handle editor times
|
||||
handleMatches(EditorTimestampParser.TIME_REGEX, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp);
|
||||
handleMatches(EditorTimestampParser.TIME_REGEX_STRICT, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp);
|
||||
|
||||
// handle channels
|
||||
handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel);
|
||||
|
@ -596,7 +596,7 @@ namespace osu.Game
|
||||
return;
|
||||
}
|
||||
|
||||
editor.HandleTimestamp(timestamp);
|
||||
editor.HandleTimestamp(timestamp, notifyOnError: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||
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.Effects;
|
||||
@ -131,7 +130,7 @@ namespace osu.Game.Overlays.Music
|
||||
filter.Search.HoldFocus = true;
|
||||
Schedule(() => filter.Search.TakeFocus());
|
||||
|
||||
this.ResizeTo(new Vector2(1, RelativeSizeAxes.HasFlagFast(Axes.Y) ? 1f : PLAYLIST_HEIGHT), transition_duration, Easing.OutQuint);
|
||||
this.ResizeTo(new Vector2(1, RelativeSizeAxes.HasFlag(Axes.Y) ? 1f : PLAYLIST_HEIGHT), transition_duration, Easing.OutQuint);
|
||||
this.FadeIn(transition_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.InGameSection, GlobalActionCategory.InGame),
|
||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.ReplaySection, GlobalActionCategory.Replay),
|
||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.EditorSection, GlobalActionCategory.Editor),
|
||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.EditorTestPlaySection, GlobalActionCategory.EditorTestPlay),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
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.Shapes;
|
||||
@ -161,7 +160,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
if (invalidation.HasFlagFast(Invalidation.DrawSize))
|
||||
if (invalidation.HasFlag(Invalidation.DrawSize))
|
||||
headerTextVisibilityCache.Invalidate();
|
||||
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -145,9 +144,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
var blueprintItem = ((Drawable)blueprint.Item);
|
||||
blueprintItem.Scale = Vector2.One;
|
||||
|
||||
if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.X))
|
||||
if (blueprintItem.RelativeSizeAxes.HasFlag(Axes.X))
|
||||
blueprintItem.Width = 1;
|
||||
if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.Y))
|
||||
if (blueprintItem.RelativeSizeAxes.HasFlag(Axes.Y))
|
||||
blueprintItem.Height = 1;
|
||||
}
|
||||
});
|
||||
|
@ -7,7 +7,6 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
@ -51,7 +50,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
CanScaleDiagonally.Value = true;
|
||||
}
|
||||
|
||||
private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false);
|
||||
private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlag(axis) == false);
|
||||
|
||||
private Dictionary<Drawable, OriginalDrawableState>? objectsInScale;
|
||||
private Vector2? defaultOrigin;
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -150,9 +149,9 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.Both, // stops us being considered in parent's autosize
|
||||
Anchor = TooltipAnchor.HasFlagFast(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight,
|
||||
Anchor = TooltipAnchor.HasFlag(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight,
|
||||
Origin = TooltipAnchor,
|
||||
Position = new Vector2(TooltipAnchor.HasFlagFast(Anchor.x0) ? 5 : -5, 5),
|
||||
Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5),
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using MessagePack;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
@ -32,23 +31,23 @@ namespace osu.Game.Replays.Legacy
|
||||
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
public bool MouseLeft1 => ButtonState.HasFlagFast(ReplayButtonState.Left1);
|
||||
public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1);
|
||||
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
public bool MouseRight1 => ButtonState.HasFlagFast(ReplayButtonState.Right1);
|
||||
public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1);
|
||||
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
public bool MouseLeft2 => ButtonState.HasFlagFast(ReplayButtonState.Left2);
|
||||
public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2);
|
||||
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2);
|
||||
public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2);
|
||||
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
public bool Smoke => ButtonState.HasFlagFast(ReplayButtonState.Smoke);
|
||||
public bool Smoke => ButtonState.HasFlag(ReplayButtonState.Smoke);
|
||||
|
||||
[Key(3)]
|
||||
public ReplayButtonState ButtonState;
|
||||
|
@ -9,7 +9,6 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -328,7 +327,12 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
set => baseBeatmap.Difficulty = value;
|
||||
}
|
||||
|
||||
public BindableList<BreakPeriod> Breaks => baseBeatmap.Breaks;
|
||||
public List<BreakPeriod> Breaks
|
||||
{
|
||||
get => baseBeatmap.Breaks;
|
||||
set => baseBeatmap.Breaks = value;
|
||||
}
|
||||
|
||||
public List<string> UnhandledEventLines => baseBeatmap.UnhandledEventLines;
|
||||
|
||||
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
|
||||
|
@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
// Events
|
||||
new CheckBreaks(),
|
||||
|
||||
// Metadata
|
||||
new CheckTitleMarkers(),
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
|
71
osu.Game/Rulesets/Edit/Checks/CheckTitleMarkers.cs
Normal file
71
osu.Game/Rulesets/Edit/Checks/CheckTitleMarkers.cs
Normal file
@ -0,0 +1,71 @@
|
||||
// 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.Text.RegularExpressions;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckTitleMarkers : ICheck
|
||||
{
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Metadata, "Checks for incorrect formats of (TV Size) / (Game Ver.) / (Short Ver.) / (Cut Ver.) / (Sped Up Ver.) / etc in title.");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateIncorrectMarker(this),
|
||||
};
|
||||
|
||||
private readonly IEnumerable<MarkerCheck> markerChecks =
|
||||
[
|
||||
new MarkerCheck(@"(TV Size)", @"(?i)(tv (size|ver))"),
|
||||
new MarkerCheck(@"(Game Ver.)", @"(?i)(game (size|ver))"),
|
||||
new MarkerCheck(@"(Short Ver.)", @"(?i)(short (size|ver))"),
|
||||
new MarkerCheck(@"(Cut Ver.)", @"(?i)(?<!& )(cut (size|ver))"),
|
||||
new MarkerCheck(@"(Sped Up Ver.)", @"(?i)(?<!& )(sped|speed) ?up ver"),
|
||||
new MarkerCheck(@"(Nightcore Mix)", @"(?i)(?<!& )(nightcore|night core) (ver|mix)"),
|
||||
new MarkerCheck(@"(Sped Up & Cut Ver.)", @"(?i)(sped|speed) ?up (ver)? ?& cut (size|ver)"),
|
||||
new MarkerCheck(@"(Nightcore & Cut Ver.)", @"(?i)(nightcore|night core) (ver|mix)? ?& cut (size|ver)"),
|
||||
];
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
string romanisedTitle = context.Beatmap.Metadata.Title;
|
||||
string unicodeTitle = context.Beatmap.Metadata.TitleUnicode;
|
||||
|
||||
foreach (var check in markerChecks)
|
||||
{
|
||||
bool hasRomanisedTitle = unicodeTitle != romanisedTitle;
|
||||
|
||||
if (check.AnyRegex.IsMatch(unicodeTitle) && !unicodeTitle.Contains(check.CorrectMarkerFormat, StringComparison.Ordinal))
|
||||
yield return new IssueTemplateIncorrectMarker(this).Create("Title", check.CorrectMarkerFormat);
|
||||
|
||||
if (hasRomanisedTitle && check.AnyRegex.IsMatch(romanisedTitle) && !romanisedTitle.Contains(check.CorrectMarkerFormat, StringComparison.Ordinal))
|
||||
yield return new IssueTemplateIncorrectMarker(this).Create("Romanised title", check.CorrectMarkerFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private class MarkerCheck
|
||||
{
|
||||
public readonly string CorrectMarkerFormat;
|
||||
public readonly Regex AnyRegex;
|
||||
|
||||
public MarkerCheck(string exact, string anyRegex)
|
||||
{
|
||||
CorrectMarkerFormat = exact;
|
||||
AnyRegex = new Regex(anyRegex, RegexOptions.Compiled);
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateIncorrectMarker : IssueTemplate
|
||||
{
|
||||
public IssueTemplateIncorrectMarker(ICheck check)
|
||||
: base(check, IssueType.Problem, "{0} field has an incorrect format of marker {1}")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string titleField, string correctMarkerFormat) => new Issue(this, titleField, correctMarkerFormat);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,13 +9,41 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
public static class EditorTimestampParser
|
||||
{
|
||||
// 00:00:000 (...) - test
|
||||
// original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78
|
||||
public static readonly Regex TIME_REGEX = new Regex(@"\b(((?<minutes>\d{2,}):(?<seconds>[0-5]\d)[:.](?<milliseconds>\d{3}))(?<selection>\s\([^)]+\))?)", RegexOptions.Compiled);
|
||||
/// <summary>
|
||||
/// Used for parsing in contexts where we don't want e.g. normal times of day to be parsed as timestamps (e.g. chat)
|
||||
/// Original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// 00:00:000 (...) - test
|
||||
/// </example>
|
||||
public static readonly Regex TIME_REGEX_STRICT = new Regex(@"\b(((?<minutes>\d{2,}):(?<seconds>[0-5]\d)[:.](?<milliseconds>\d{3}))(?<selection>\s\([^)]+\))?)", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Used for editor-specific context wherein we want to try as hard as we can to process user input as a timestamp.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <list type="bullet">
|
||||
/// <item>1 - parses to 00:00:001 (bare numbers are treated as milliseconds)</item>
|
||||
/// <item>1:2 - parses to 01:02:000</item>
|
||||
/// <item>1:02 - parses to 01:02:000</item>
|
||||
/// <item>1:92 - does not parse</item>
|
||||
/// <item>1:02:3 - parses to 01:02:003</item>
|
||||
/// <item>1:02:300 - parses to 01:02:300</item>
|
||||
/// <item>1:02:300 (1,2,3) - parses to 01:02:300 with selection</item>
|
||||
/// </list>
|
||||
/// </example>
|
||||
private static readonly Regex time_regex_lenient = new Regex(@"^(((?<minutes>\d{1,3}):(?<seconds>([0-5]?\d))([:.](?<milliseconds>\d{0,3}))?)(?<selection>\s\([^)]+\))?)$", RegexOptions.Compiled);
|
||||
|
||||
public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection)
|
||||
{
|
||||
Match match = TIME_REGEX.Match(timestamp);
|
||||
if (double.TryParse(timestamp, out double msec))
|
||||
{
|
||||
parsedTime = TimeSpan.FromMilliseconds(msec);
|
||||
parsedSelection = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
Match match = time_regex_lenient.Match(timestamp);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
@ -24,16 +52,14 @@ namespace osu.Game.Rulesets.Edit
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
int timeMin, timeSec, timeMsec;
|
||||
|
||||
result &= int.TryParse(match.Groups[@"minutes"].Value, out int timeMin);
|
||||
result &= int.TryParse(match.Groups[@"seconds"].Value, out int timeSec);
|
||||
result &= int.TryParse(match.Groups[@"milliseconds"].Value, out int timeMsec);
|
||||
int.TryParse(match.Groups[@"minutes"].Value, out timeMin);
|
||||
int.TryParse(match.Groups[@"seconds"].Value, out timeSec);
|
||||
int.TryParse(match.Groups[@"milliseconds"].Value, out timeMsec);
|
||||
|
||||
// somewhat sane limit for timestamp duration (10 hours).
|
||||
result &= timeMin < 600;
|
||||
|
||||
if (!result)
|
||||
if (timeMin >= 600)
|
||||
{
|
||||
parsedTime = null;
|
||||
parsedSelection = null;
|
||||
@ -42,8 +68,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
parsedTime = TimeSpan.FromMinutes(timeMin) + TimeSpan.FromSeconds(timeSec) + TimeSpan.FromMilliseconds(timeMsec);
|
||||
parsedSelection = match.Groups[@"selection"].Value.Trim();
|
||||
if (!string.IsNullOrEmpty(parsedSelection))
|
||||
parsedSelection = parsedSelection[1..^1];
|
||||
parsedSelection = !string.IsNullOrEmpty(parsedSelection) ? parsedSelection[1..^1] : null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -216,14 +215,14 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
toolboxCollection.Items = CompositionTools
|
||||
.Prepend(new SelectTool())
|
||||
.Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon))
|
||||
.Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t)))
|
||||
.ToList();
|
||||
|
||||
foreach (var item in toolboxCollection.Items)
|
||||
{
|
||||
item.Selected.DisabledChanged += isDisabled =>
|
||||
{
|
||||
item.TooltipText = isDisabled ? "Add at least one timing point first!" : string.Empty;
|
||||
item.TooltipText = isDisabled ? "Add at least one timing point first!" : ((HitObjectCompositionToolButton)item).TooltipText;
|
||||
};
|
||||
}
|
||||
|
||||
@ -505,7 +504,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||
double? targetTime = null;
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.GlobalGrids))
|
||||
if (snapType.HasFlag(SnapType.GlobalGrids))
|
||||
{
|
||||
if (playfield is ScrollingPlayfield scrollingPlayfield)
|
||||
{
|
||||
|
22
osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
Normal file
22
osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// 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 osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
public class HitObjectCompositionToolButton : RadioButton
|
||||
{
|
||||
public HitObjectCompositionTool Tool { get; }
|
||||
|
||||
public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action)
|
||||
: base(tool.Name, action, tool.CreateIcon)
|
||||
{
|
||||
Tool = tool;
|
||||
|
||||
TooltipText = tool.TooltipText;
|
||||
}
|
||||
}
|
||||
}
|
@ -156,12 +156,21 @@ namespace osu.Game.Rulesets.Edit
|
||||
comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
|
||||
}
|
||||
|
||||
if (AutomaticBankAssignment)
|
||||
var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||
|
||||
if (lastHitNormal != null)
|
||||
{
|
||||
// Take the hitnormal sample of the last hit object
|
||||
var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||
if (lastHitNormal != null)
|
||||
if (AutomaticBankAssignment)
|
||||
{
|
||||
// Take the hitnormal sample of the last hit object
|
||||
HitObject.Samples[0] = lastHitNormal;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only inherit the volume from the previous hit object
|
||||
for (int i = 0; i < HitObject.Samples.Count; i++)
|
||||
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Tools
|
||||
{
|
||||
@ -11,14 +10,16 @@ namespace osu.Game.Rulesets.Edit.Tools
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public LocalisableString TooltipText { get; init; }
|
||||
|
||||
protected HitObjectCompositionTool(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public abstract PlacementBlueprint CreatePlacementBlueprint();
|
||||
public abstract PlacementBlueprint? CreatePlacementBlueprint();
|
||||
|
||||
public virtual Drawable CreateIcon() => null;
|
||||
public virtual Drawable? CreateIcon() => null;
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Audio;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
@ -58,7 +57,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;
|
||||
type &= ~LegacyHitObjectType.ComboOffset;
|
||||
|
||||
bool combo = type.HasFlagFast(LegacyHitObjectType.NewCombo);
|
||||
bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);
|
||||
type &= ~LegacyHitObjectType.NewCombo;
|
||||
|
||||
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
|
||||
@ -66,14 +65,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
HitObject result = null;
|
||||
|
||||
if (type.HasFlagFast(LegacyHitObjectType.Circle))
|
||||
if (type.HasFlag(LegacyHitObjectType.Circle))
|
||||
{
|
||||
result = CreateHit(pos, combo, comboOffset);
|
||||
|
||||
if (split.Length > 5)
|
||||
readCustomSampleBanks(split[5], bankInfo);
|
||||
}
|
||||
else if (type.HasFlagFast(LegacyHitObjectType.Slider))
|
||||
else if (type.HasFlag(LegacyHitObjectType.Slider))
|
||||
{
|
||||
double? length = null;
|
||||
|
||||
@ -145,7 +144,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
|
||||
}
|
||||
else if (type.HasFlagFast(LegacyHitObjectType.Spinner))
|
||||
else if (type.HasFlag(LegacyHitObjectType.Spinner))
|
||||
{
|
||||
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
|
||||
|
||||
@ -154,7 +153,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
}
|
||||
else if (type.HasFlagFast(LegacyHitObjectType.Hold))
|
||||
else if (type.HasFlag(LegacyHitObjectType.Hold))
|
||||
{
|
||||
// Note: Hold is generated by BMS converts
|
||||
|
||||
@ -472,7 +471,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal)));
|
||||
type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -480,13 +479,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
soundTypes.Add(new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume));
|
||||
}
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
||||
if (type.HasFlag(LegacyHitSoundType.Finish))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
|
||||
if (type.HasFlag(LegacyHitSoundType.Whistle))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Clap))
|
||||
if (type.HasFlag(LegacyHitSoundType.Clap))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
return soundTypes;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -12,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Framework.Input.StateChanges.Events;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Configuration;
|
||||
@ -21,6 +20,7 @@ using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||
using osuTK;
|
||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
@ -32,12 +32,12 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ScoreProcessor scoreProcessor { get; set; }
|
||||
[Resolved]
|
||||
private ScoreProcessor? scoreProcessor { get; set; }
|
||||
|
||||
private ReplayRecorder recorder;
|
||||
private ReplayRecorder? recorder;
|
||||
|
||||
public ReplayRecorder Recorder
|
||||
public ReplayRecorder? Recorder
|
||||
{
|
||||
set
|
||||
{
|
||||
@ -103,14 +103,23 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
#region IHasReplayHandler
|
||||
|
||||
private ReplayInputHandler replayInputHandler;
|
||||
private ReplayInputHandler? replayInputHandler;
|
||||
|
||||
public ReplayInputHandler ReplayInputHandler
|
||||
public ReplayInputHandler? ReplayInputHandler
|
||||
{
|
||||
get => replayInputHandler;
|
||||
set
|
||||
{
|
||||
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
|
||||
if (replayInputHandler == value)
|
||||
return;
|
||||
|
||||
if (replayInputHandler != null)
|
||||
RemoveHandler(replayInputHandler);
|
||||
|
||||
// ensures that all replay keys are released, that the last replay state is correctly cleared,
|
||||
// and that all user-pressed keys are released, so that the replay handler may trigger them itself
|
||||
// setting `UseParentInput` will only sync releases (https://github.com/ppy/osu-framework/blob/17d65f476d51cc5f2aaea818534f8fbac47e5fe6/osu.Framework/Input/PassThroughInputManager.cs#L179-L182)
|
||||
new ReplayStateReset().Apply(CurrentState, this);
|
||||
|
||||
replayInputHandler = value;
|
||||
UseParentInput = replayInputHandler == null;
|
||||
@ -124,8 +133,8 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
#region Setting application (disables etc.)
|
||||
|
||||
private Bindable<bool> mouseDisabled;
|
||||
private Bindable<bool> tapsDisabled;
|
||||
private Bindable<bool> mouseDisabled = null!;
|
||||
private Bindable<bool> tapsDisabled = null!;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
@ -222,14 +231,34 @@ namespace osu.Game.Rulesets.UI
|
||||
RealmKeyBindingStore.ClearDuplicateBindings(KeyBindings);
|
||||
}
|
||||
}
|
||||
|
||||
private class ReplayStateReset : IInput
|
||||
{
|
||||
public void Apply(InputState state, IInputStateChangeHandler handler)
|
||||
{
|
||||
if (!(state is RulesetInputManagerInputState<T> inputState))
|
||||
throw new InvalidOperationException($"{nameof(ReplayState<T>)} should only be applied to a {nameof(RulesetInputManagerInputState<T>)}");
|
||||
|
||||
new MouseButtonInput([], state.Mouse.Buttons).Apply(state, handler);
|
||||
new KeyboardKeyInput([], state.Keyboard.Keys).Apply(state, handler);
|
||||
new TouchInput(Enum.GetValues<TouchSource>().Select(s => new Touch(s, Vector2.Zero)), false).Apply(state, handler);
|
||||
new JoystickButtonInput([], state.Joystick.Buttons).Apply(state, handler);
|
||||
new MidiKeyInput(new MidiState(), state.Midi).Apply(state, handler);
|
||||
new TabletPenButtonInput([], state.Tablet.PenButtons).Apply(state, handler);
|
||||
new TabletAuxiliaryButtonInput([], state.Tablet.AuxiliaryButtons).Apply(state, handler);
|
||||
|
||||
handler.HandleInputStateChange(new ReplayStateChangeEvent<T>(state, this, inputState.LastReplayState?.PressedActions.ToArray() ?? [], []));
|
||||
inputState.LastReplayState = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RulesetInputManagerInputState<T> : InputState
|
||||
where T : struct
|
||||
{
|
||||
public ReplayState<T> LastReplayState;
|
||||
public ReplayState<T>? LastReplayState;
|
||||
|
||||
public RulesetInputManagerInputState(InputState state = null)
|
||||
public RulesetInputManagerInputState(InputState state)
|
||||
: base(state)
|
||||
{
|
||||
}
|
||||
|
@ -10,9 +10,11 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -25,14 +27,16 @@ namespace osu.Game.Screens.Edit.Components
|
||||
public partial class PlaybackControl : BottomBarContainer
|
||||
{
|
||||
private IconButton playButton = null!;
|
||||
private PlaybackSpeedControl playbackSpeedControl = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
private readonly BindableNumber<double> freqAdjust = new BindableDouble(1);
|
||||
private readonly Bindable<EditorScreenMode> currentScreenMode = new Bindable<EditorScreenMode>();
|
||||
private readonly BindableNumber<double> tempoAdjustment = new BindableDouble(1);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
private void load(OverlayColourProvider colourProvider, Editor? editor)
|
||||
{
|
||||
Background.Colour = colourProvider.Background4;
|
||||
|
||||
@ -47,31 +51,61 @@ namespace osu.Game.Screens.Edit.Components
|
||||
Icon = FontAwesome.Regular.PlayCircle,
|
||||
Action = togglePause,
|
||||
},
|
||||
new OsuSpriteText
|
||||
playbackSpeedControl = new PlaybackSpeedControl
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
Text = EditorStrings.PlaybackSpeed,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
Y = 0.5f,
|
||||
Padding = new MarginPadding { Left = 45 }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Padding = new MarginPadding { Left = 45 },
|
||||
Child = new PlaybackTabControl { Current = freqAdjust },
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Left = 45, },
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = EditorStrings.PlaybackSpeed,
|
||||
},
|
||||
new PlaybackTabControl
|
||||
{
|
||||
Current = tempoAdjustment,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 16,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust), true);
|
||||
Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjustment), true);
|
||||
|
||||
if (editor != null)
|
||||
currentScreenMode.BindTo(editor.Mode);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
currentScreenMode.BindValueChanged(_ =>
|
||||
{
|
||||
if (currentScreenMode.Value == EditorScreenMode.Timing)
|
||||
{
|
||||
tempoAdjustment.Value = 1;
|
||||
tempoAdjustment.Disabled = true;
|
||||
playbackSpeedControl.FadeTo(0.5f, 400, Easing.OutQuint);
|
||||
playbackSpeedControl.TooltipText = "Speed adjustment is unavailable in timing mode. Timing at slower speeds is inaccurate due to resampling artifacts.";
|
||||
}
|
||||
else
|
||||
{
|
||||
tempoAdjustment.Disabled = false;
|
||||
playbackSpeedControl.FadeTo(1, 400, Easing.OutQuint);
|
||||
playbackSpeedControl.TooltipText = default;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, tempoAdjustment);
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
@ -109,6 +143,11 @@ namespace osu.Game.Screens.Edit.Components
|
||||
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
|
||||
}
|
||||
|
||||
private partial class PlaybackSpeedControl : FillFlowContainer, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
|
||||
private partial class PlaybackTabControl : OsuTabControl<double>
|
||||
{
|
||||
private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
|
||||
@ -174,7 +213,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateState();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e) => updateState();
|
||||
|
@ -24,11 +24,11 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
||||
/// <summary>
|
||||
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
|
||||
/// </summary>
|
||||
public readonly Func<Drawable>? CreateIcon;
|
||||
public readonly Func<Drawable?>? CreateIcon;
|
||||
|
||||
private readonly Action? action;
|
||||
|
||||
public RadioButton(string label, Action? action, Func<Drawable>? createIcon = null)
|
||||
public RadioButton(string label, Action? action, Func<Drawable?>? createIcon = null)
|
||||
{
|
||||
Label = label;
|
||||
CreateIcon = createIcon;
|
||||
|
@ -4,8 +4,12 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
@ -13,7 +17,6 @@ namespace osu.Game.Screens.Edit.Components
|
||||
{
|
||||
public partial class TimeInfoContainer : BottomBarContainer
|
||||
{
|
||||
private OsuSpriteText trackTimer = null!;
|
||||
private OsuSpriteText bpm = null!;
|
||||
|
||||
[Resolved]
|
||||
@ -29,14 +32,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
trackTimer = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Spacing = new Vector2(-2, 0),
|
||||
Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light),
|
||||
Y = -10,
|
||||
},
|
||||
new TimestampControl(),
|
||||
bpm = new OsuSpriteText
|
||||
{
|
||||
Colour = colours.Orange1,
|
||||
@ -47,19 +43,12 @@ namespace osu.Game.Screens.Edit.Components
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastTime;
|
||||
private double? lastBPM;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (lastTime != editorClock.CurrentTime)
|
||||
{
|
||||
lastTime = editorClock.CurrentTime;
|
||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||
}
|
||||
|
||||
double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM;
|
||||
|
||||
if (lastBPM != newBPM)
|
||||
@ -68,5 +57,109 @@ namespace osu.Game.Screens.Edit.Components
|
||||
bpm.Text = @$"{newBPM:0} BPM";
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TimestampControl : OsuClickableContainer
|
||||
{
|
||||
private Container hoverLayer = null!;
|
||||
private OsuSpriteText trackTimer = null!;
|
||||
private OsuTextBox inputTextBox = null!;
|
||||
|
||||
[Resolved]
|
||||
private Editor? editor { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
public TimestampControl()
|
||||
: base(HoverSampleSet.Button)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
hoverLayer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = 5,
|
||||
Horizontal = -2
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = 5,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box { RelativeSizeAxes = Axes.Both, },
|
||||
}
|
||||
},
|
||||
Alpha = 0,
|
||||
},
|
||||
trackTimer = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Spacing = new Vector2(-2, 0),
|
||||
Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light),
|
||||
},
|
||||
inputTextBox = new OsuTextBox
|
||||
{
|
||||
Width = 150,
|
||||
Height = 36,
|
||||
Alpha = 0,
|
||||
CommitOnFocusLost = true,
|
||||
},
|
||||
});
|
||||
|
||||
Action = () =>
|
||||
{
|
||||
trackTimer.Alpha = 0;
|
||||
inputTextBox.Alpha = 1;
|
||||
inputTextBox.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||
Schedule(() =>
|
||||
{
|
||||
GetContainingFocusManager()!.ChangeFocus(inputTextBox);
|
||||
inputTextBox.SelectAll();
|
||||
});
|
||||
};
|
||||
|
||||
inputTextBox.Current.BindValueChanged(val => editor?.HandleTimestamp(val.NewValue));
|
||||
|
||||
inputTextBox.OnCommit += (_, __) =>
|
||||
{
|
||||
trackTimer.Alpha = 1;
|
||||
inputTextBox.Alpha = 0;
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastTime;
|
||||
private bool showingHoverLayer;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (lastTime != editorClock.CurrentTime)
|
||||
{
|
||||
lastTime = editorClock.CurrentTime;
|
||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||
}
|
||||
|
||||
bool shouldShowHoverLayer = IsHovered && inputTextBox.Alpha == 0;
|
||||
|
||||
if (shouldShowHoverLayer != showingHoverLayer)
|
||||
{
|
||||
hoverLayer.FadeTo(shouldShowHoverLayer ? 0.2f : 0, 400, Easing.OutQuint);
|
||||
showingHoverLayer = shouldShowHoverLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||
breaks.BindTo(beatmap.Breaks);
|
||||
breaks.BindCollectionChanged((_, _) =>
|
||||
{
|
||||
Clear();
|
||||
foreach (var breakPeriod in beatmap.Breaks)
|
||||
Add(new BreakVisualisation(breakPeriod));
|
||||
}, true);
|
||||
|
@ -0,0 +1,98 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public partial class CircularPositionSnapGrid : PositionSnapGrid
|
||||
{
|
||||
/// <summary>
|
||||
/// The spacing between grid lines of this <see cref="CircularPositionSnapGrid"/>.
|
||||
/// </summary>
|
||||
public BindableFloat Spacing { get; } = new BindableFloat(1f)
|
||||
{
|
||||
MinValue = 0f,
|
||||
};
|
||||
|
||||
public CircularPositionSnapGrid()
|
||||
{
|
||||
Spacing.BindValueChanged(_ => GridCache.Invalidate());
|
||||
}
|
||||
|
||||
protected override void CreateContent()
|
||||
{
|
||||
var drawSize = DrawSize;
|
||||
|
||||
// Calculate the required number of circles based on the maximum distance from the origin to the edge of the grid.
|
||||
float dx = Math.Max(StartPosition.Value.X, DrawWidth - StartPosition.Value.X);
|
||||
float dy = Math.Max(StartPosition.Value.Y, DrawHeight - StartPosition.Value.Y);
|
||||
float maxDistance = new Vector2(dx, dy).Length;
|
||||
// We need to add one because the first circle starts at zero radius.
|
||||
int requiredCircles = (int)(maxDistance / Spacing.Value) + 1;
|
||||
|
||||
generateCircles(requiredCircles);
|
||||
GenerateOutline(drawSize);
|
||||
}
|
||||
|
||||
private void generateCircles(int count)
|
||||
{
|
||||
// Make lines the same width independent of display resolution.
|
||||
float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width;
|
||||
|
||||
List<CircularProgress> generatedCircles = new List<CircularProgress>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Add a minimum diameter so the center circle is clearly visible.
|
||||
float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2);
|
||||
|
||||
var gridCircle = new CircularProgress
|
||||
{
|
||||
Position = StartPosition.Value,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(diameter),
|
||||
InnerRadius = lineWidth * 1f / diameter,
|
||||
Colour = Colour4.White,
|
||||
Alpha = 0.2f,
|
||||
Progress = 1,
|
||||
};
|
||||
|
||||
generatedCircles.Add(gridCircle);
|
||||
}
|
||||
|
||||
if (generatedCircles.Count == 0)
|
||||
return;
|
||||
|
||||
generatedCircles.First().Alpha = 0.8f;
|
||||
|
||||
AddInternal(new Container
|
||||
{
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = generatedCircles,
|
||||
});
|
||||
}
|
||||
|
||||
public override Vector2 GetSnappedPosition(Vector2 original)
|
||||
{
|
||||
Vector2 relativeToStart = original - StartPosition.Value;
|
||||
|
||||
if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON)
|
||||
return StartPosition.Value;
|
||||
|
||||
float length = relativeToStart.Length;
|
||||
float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value;
|
||||
|
||||
return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length);
|
||||
}
|
||||
}
|
||||
}
|
@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
break;
|
||||
}
|
||||
|
||||
AddSampleBank(bankName);
|
||||
SetSampleBank(bankName);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -177,14 +177,27 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType<IHasComboInformation>(), h => h.NewCombo);
|
||||
|
||||
var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray();
|
||||
|
||||
foreach ((string sampleName, var bindable) in SelectionSampleStates)
|
||||
{
|
||||
bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName));
|
||||
bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Name == sampleName));
|
||||
}
|
||||
|
||||
foreach ((string bankName, var bindable) in SelectionBankStates)
|
||||
{
|
||||
bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.All(s => s.Bank == bankName));
|
||||
bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Bank == bankName));
|
||||
}
|
||||
|
||||
IEnumerable<IList<HitSampleInfo>> enumerateAllSamples(HitObject hitObject)
|
||||
{
|
||||
yield return hitObject.Samples;
|
||||
|
||||
if (hitObject is IHasRepeats withRepeats)
|
||||
{
|
||||
foreach (var node in withRepeats.NodeSamples)
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,12 +206,25 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
#region Ternary state changes
|
||||
|
||||
/// <summary>
|
||||
/// Adds a sample bank to all selected <see cref="HitObject"/>s.
|
||||
/// Sets the sample bank for all selected <see cref="HitObject"/>s.
|
||||
/// </summary>
|
||||
/// <param name="bankName">The name of the sample bank.</param>
|
||||
public void AddSampleBank(string bankName)
|
||||
public void SetSampleBank(string bankName)
|
||||
{
|
||||
if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName)))
|
||||
bool hasRelevantBank(HitObject hitObject)
|
||||
{
|
||||
bool result = hitObject.Samples.All(s => s.Bank == bankName);
|
||||
|
||||
if (hitObject is IHasRepeats hasRepeats)
|
||||
{
|
||||
foreach (var node in hasRepeats.NodeSamples)
|
||||
result &= node.All(s => s.Bank == bankName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (SelectedItems.All(hasRelevantBank))
|
||||
return;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
@ -207,17 +233,37 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList();
|
||||
|
||||
if (h is IHasRepeats hasRepeats)
|
||||
{
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.With(newBank: bankName)).ToList();
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
private bool hasRelevantSample(HitObject hitObject, string sampleName)
|
||||
{
|
||||
bool result = hitObject.Samples.Any(s => s.Name == sampleName);
|
||||
|
||||
if (hitObject is IHasRepeats hasRepeats)
|
||||
{
|
||||
foreach (var node in hasRepeats.NodeSamples)
|
||||
result &= node.Any(s => s.Name == sampleName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a hit sample to all selected <see cref="HitObject"/>s.
|
||||
/// </summary>
|
||||
/// <param name="sampleName">The name of the hit sample.</param>
|
||||
public void AddHitSample(string sampleName)
|
||||
{
|
||||
if (SelectedItems.All(h => h.Samples.Any(s => s.Name == sampleName)))
|
||||
if (SelectedItems.All(h => hasRelevantSample(h, sampleName)))
|
||||
return;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
@ -228,6 +274,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
|
||||
|
||||
if (h is IHasRepeats hasRepeats)
|
||||
{
|
||||
foreach (var node in hasRepeats.NodeSamples)
|
||||
{
|
||||
if (node.Any(s => s.Name == sampleName))
|
||||
continue;
|
||||
|
||||
var hitSample = h.CreateHitSampleInfo(sampleName);
|
||||
|
||||
string? existingAdditionBank = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL)?.Bank;
|
||||
if (existingAdditionBank != null)
|
||||
hitSample = hitSample.With(newBank: existingAdditionBank);
|
||||
|
||||
node.Add(hitSample);
|
||||
}
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
@ -238,12 +301,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <param name="sampleName">The name of the hit sample.</param>
|
||||
public void RemoveHitSample(string sampleName)
|
||||
{
|
||||
if (SelectedItems.All(h => h.Samples.All(s => s.Name != sampleName)))
|
||||
if (SelectedItems.All(h => !hasRelevantSample(h, sampleName)))
|
||||
return;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||
|
||||
if (h is IHasRepeats hasRepeats)
|
||||
{
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Where(s => s.Name != sampleName).ToList();
|
||||
}
|
||||
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -298,13 +297,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// </remarks>
|
||||
public void PerformFlipFromScaleHandles(Axes axes)
|
||||
{
|
||||
if (axes.HasFlagFast(Axes.X))
|
||||
if (axes.HasFlag(Axes.X))
|
||||
{
|
||||
dragHandles.FlipScaleHandles(Direction.Horizontal);
|
||||
OnFlip?.Invoke(Direction.Horizontal, false);
|
||||
}
|
||||
|
||||
if (axes.HasFlagFast(Axes.Y))
|
||||
if (axes.HasFlag(Axes.Y))
|
||||
{
|
||||
dragHandles.FlipScaleHandles(Direction.Vertical);
|
||||
OnFlip?.Invoke(Direction.Vertical, false);
|
||||
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
@ -74,9 +73,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
foreach (var handle in scaleHandles)
|
||||
{
|
||||
if (direction == Direction.Horizontal && !handle.Anchor.HasFlagFast(Anchor.x1))
|
||||
if (direction == Direction.Horizontal && !handle.Anchor.HasFlag(Anchor.x1))
|
||||
handle.Anchor ^= Anchor.x0 | Anchor.x2;
|
||||
if (direction == Direction.Vertical && !handle.Anchor.HasFlagFast(Anchor.y1))
|
||||
if (direction == Direction.Vertical && !handle.Anchor.HasFlag(Anchor.y1))
|
||||
handle.Anchor ^= Anchor.y0 | Anchor.y2;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@ -46,8 +45,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
Icon = FontAwesome.Solid.Redo,
|
||||
Scale = new Vector2
|
||||
{
|
||||
X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f,
|
||||
Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f
|
||||
X = Anchor.HasFlag(Anchor.x0) ? 1f : -1f,
|
||||
Y = Anchor.HasFlag(Anchor.y0) ? 1f : -1f
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
@ -128,6 +127,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool isCornerAnchor(Anchor anchor) => !anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1);
|
||||
private bool isCornerAnchor(Anchor anchor) => !anchor.HasFlag(Anchor.x1) && !anchor.HasFlag(Anchor.y1);
|
||||
}
|
||||
}
|
||||
|
@ -9,20 +9,31 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public partial class TimelineBreak : CompositeDrawable
|
||||
public partial class TimelineBreak : CompositeDrawable, IHasContextMenu
|
||||
{
|
||||
public Bindable<BreakPeriod> Break { get; } = new Bindable<BreakPeriod>();
|
||||
|
||||
public Action<BreakPeriod>? OnDeleted { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public TimelineBreak(BreakPeriod b)
|
||||
{
|
||||
Break.Value = b;
|
||||
@ -42,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 5 },
|
||||
Child = new Box
|
||||
Child = background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray5,
|
||||
@ -77,6 +88,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
background.FadeColour(IsHovered ? colours.Gray6 : colours.Gray5, 400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => OnDeleted?.Invoke(Break.Value)),
|
||||
};
|
||||
|
||||
private partial class DragHandle : FillFlowContainer
|
||||
{
|
||||
public Bindable<BreakPeriod> Break { get; } = new Bindable<BreakPeriod>();
|
||||
|
@ -15,6 +15,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved]
|
||||
private Timeline timeline { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? editorChangeHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The visible time/position range of the timeline.
|
||||
/// </summary>
|
||||
@ -71,7 +74,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
if (!shouldBeVisible(breakPeriod))
|
||||
continue;
|
||||
|
||||
Add(new TimelineBreak(breakPeriod));
|
||||
Add(new TimelineBreak(breakPeriod)
|
||||
{
|
||||
OnDeleted = b =>
|
||||
{
|
||||
editorChangeHandler?.BeginChange();
|
||||
breaks.Remove(b);
|
||||
editorChangeHandler?.EndChange();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,89 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid
|
||||
{
|
||||
/// <summary>
|
||||
/// The spacing between grid lines of this <see cref="TriangularPositionSnapGrid"/>.
|
||||
/// </summary>
|
||||
public BindableFloat Spacing { get; } = new BindableFloat(1f)
|
||||
{
|
||||
MinValue = 0f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The rotation in degrees of the grid lines of this <see cref="TriangularPositionSnapGrid"/>.
|
||||
/// </summary>
|
||||
public BindableFloat GridLineRotation { get; } = new BindableFloat();
|
||||
|
||||
public TriangularPositionSnapGrid()
|
||||
{
|
||||
Spacing.BindValueChanged(_ => GridCache.Invalidate());
|
||||
GridLineRotation.BindValueChanged(_ => GridCache.Invalidate());
|
||||
}
|
||||
|
||||
private static readonly float sqrt3 = float.Sqrt(3);
|
||||
private static readonly float sqrt3_over2 = sqrt3 / 2;
|
||||
private static readonly float one_over_sqrt3 = 1 / sqrt3;
|
||||
|
||||
protected override void CreateContent()
|
||||
{
|
||||
var drawSize = DrawSize;
|
||||
float stepSpacing = Spacing.Value * sqrt3_over2;
|
||||
var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30);
|
||||
var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90);
|
||||
var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150);
|
||||
|
||||
GenerateGridLines(step1, drawSize);
|
||||
GenerateGridLines(-step1, drawSize);
|
||||
|
||||
GenerateGridLines(step2, drawSize);
|
||||
GenerateGridLines(-step2, drawSize);
|
||||
|
||||
GenerateGridLines(step3, drawSize);
|
||||
GenerateGridLines(-step3, drawSize);
|
||||
|
||||
GenerateOutline(drawSize);
|
||||
}
|
||||
|
||||
public override Vector2 GetSnappedPosition(Vector2 original)
|
||||
{
|
||||
Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value);
|
||||
Vector2 hex = pixelToHex(relativeToStart);
|
||||
|
||||
return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value);
|
||||
}
|
||||
|
||||
private Vector2 pixelToHex(Vector2 pixel)
|
||||
{
|
||||
float x = pixel.X / Spacing.Value;
|
||||
float y = pixel.Y / Spacing.Value;
|
||||
// Algorithm from Charles Chambers
|
||||
// with modifications and comments by Chris Cox 2023
|
||||
// <https://gitlab.com/chriscox/hex-coordinates>
|
||||
float t = sqrt3 * y + 1; // scaled y, plus phase
|
||||
float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor
|
||||
float temp2 = t - x; // (y-x) diagonal, no floor needed
|
||||
float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase
|
||||
float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction
|
||||
float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction
|
||||
float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor
|
||||
float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor
|
||||
return new Vector2(q, r);
|
||||
}
|
||||
|
||||
private Vector2 hexToPixel(Vector2 hex)
|
||||
{
|
||||
// Taken from <https://www.redblobgames.com/grids/hexagons/#hex-to-pixel>
|
||||
// with modifications for the different definition of size.
|
||||
return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y);
|
||||
}
|
||||
}
|
||||
}
|
@ -1284,16 +1284,20 @@ namespace osu.Game.Screens.Edit
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void HandleTimestamp(string timestamp)
|
||||
public bool HandleTimestamp(string timestamp, bool notifyOnError = false)
|
||||
{
|
||||
if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection))
|
||||
{
|
||||
Schedule(() => notifications?.Post(new SimpleErrorNotification
|
||||
if (notifyOnError)
|
||||
{
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Text = EditorStrings.FailedToParseEditorLink
|
||||
}));
|
||||
return;
|
||||
Schedule(() => notifications?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Text = EditorStrings.FailedToParseEditorLink
|
||||
}));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
editorBeatmap.SelectedHitObjects.Clear();
|
||||
@ -1306,7 +1310,7 @@ namespace osu.Game.Screens.Edit
|
||||
if (string.IsNullOrEmpty(selection))
|
||||
{
|
||||
clock.SeekSmoothlyTo(position);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Seek to the next closest HitObject instead
|
||||
@ -1321,6 +1325,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
// Delegate handling the selection to the ruleset.
|
||||
currentScreen.Dependencies.Get<HitObjectComposer>().SelectFromTimestamp(position, selection);
|
||||
return true;
|
||||
}
|
||||
|
||||
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
|
||||
|
@ -110,6 +110,9 @@ namespace osu.Game.Screens.Edit
|
||||
foreach (var obj in HitObjects)
|
||||
trackStartTime(obj);
|
||||
|
||||
Breaks = new BindableList<BreakPeriod>(playableBeatmap.Breaks);
|
||||
Breaks.BindCollectionChanged((_, _) => playableBeatmap.Breaks = Breaks.ToList());
|
||||
|
||||
PreviewTime = new BindableInt(BeatmapInfo.Metadata.PreviewTime);
|
||||
PreviewTime.BindValueChanged(s =>
|
||||
{
|
||||
@ -172,7 +175,13 @@ namespace osu.Game.Screens.Edit
|
||||
set => PlayableBeatmap.ControlPointInfo = value;
|
||||
}
|
||||
|
||||
public BindableList<BreakPeriod> Breaks => PlayableBeatmap.Breaks;
|
||||
public readonly BindableList<BreakPeriod> Breaks;
|
||||
|
||||
List<BreakPeriod> IBeatmap.Breaks
|
||||
{
|
||||
get => PlayableBeatmap.Breaks;
|
||||
set => PlayableBeatmap.Breaks = value;
|
||||
}
|
||||
|
||||
public List<string> UnhandledEventLines => PlayableBeatmap.UnhandledEventLines;
|
||||
|
||||
|
@ -12,11 +12,13 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public class EditorBeatmapProcessor : IBeatmapProcessor
|
||||
{
|
||||
public IBeatmap Beatmap { get; }
|
||||
public EditorBeatmap Beatmap { get; }
|
||||
|
||||
IBeatmap IBeatmapProcessor.Beatmap => Beatmap;
|
||||
|
||||
private readonly IBeatmapProcessor? rulesetBeatmapProcessor;
|
||||
|
||||
public EditorBeatmapProcessor(IBeatmap beatmap, Ruleset ruleset)
|
||||
public EditorBeatmapProcessor(EditorBeatmap beatmap, Ruleset ruleset)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
rulesetBeatmapProcessor = ruleset.CreateBeatmapProcessor(beatmap);
|
||||
|
@ -121,7 +121,11 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
scheduledDifficultySwitch = Schedule(() =>
|
||||
{
|
||||
Beatmap.Value = nextBeatmap.Invoke();
|
||||
var workingBeatmap = nextBeatmap.Invoke();
|
||||
|
||||
Ruleset.Value = workingBeatmap.BeatmapInfo.Ruleset;
|
||||
Beatmap.Value = workingBeatmap;
|
||||
|
||||
state = editorState;
|
||||
|
||||
// This screen is a weird exception to the rule that nothing after song select changes the global beatmap.
|
||||
|
@ -4,17 +4,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Screens.Edit.GameplayTest
|
||||
{
|
||||
public partial class EditorPlayer : Player
|
||||
public partial class EditorPlayer : Player, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly Editor editor;
|
||||
private readonly EditorState editorState;
|
||||
@ -133,6 +137,76 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
|
||||
protected override bool CheckModsAllowFailure() => false; // never fail.
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.EditorTestPlayToggleAutoplay:
|
||||
toggleAutoplay();
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorTestPlayToggleQuickPause:
|
||||
toggleQuickPause();
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorTestPlayQuickExitToInitialTime:
|
||||
quickExit(false);
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorTestPlayQuickExitToCurrentTime:
|
||||
quickExit(true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
private void toggleAutoplay()
|
||||
{
|
||||
if (DrawableRuleset.ReplayScore == null)
|
||||
{
|
||||
var autoplay = Ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||
if (autoplay == null)
|
||||
return;
|
||||
|
||||
var score = autoplay.CreateScoreFromReplayData(GameplayState.Beatmap, [autoplay]);
|
||||
|
||||
// remove past frames to prevent replay frame handler from seeking back to start in an attempt to play back the entirety of the replay.
|
||||
score.Replay.Frames.RemoveAll(f => f.Time <= GameplayClockContainer.CurrentTime);
|
||||
|
||||
DrawableRuleset.SetReplayScore(score);
|
||||
// Without this schedule, the `GlobalCursorDisplay.Update()` machinery will fade the gameplay cursor out, but we still want it to show.
|
||||
Schedule(() => DrawableRuleset.Cursor?.Show());
|
||||
}
|
||||
else
|
||||
DrawableRuleset.SetReplayScore(null);
|
||||
}
|
||||
|
||||
private void toggleQuickPause()
|
||||
{
|
||||
if (GameplayClockContainer.IsPaused.Value)
|
||||
GameplayClockContainer.Start();
|
||||
else
|
||||
GameplayClockContainer.Stop();
|
||||
}
|
||||
|
||||
private void quickExit(bool useCurrentTime)
|
||||
{
|
||||
if (useCurrentTime)
|
||||
editorState.Time = GameplayClockContainer.CurrentTime;
|
||||
|
||||
editor.RestoreState(editorState);
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
@ -295,7 +294,7 @@ namespace osu.Game.Screens.Play
|
||||
Drawable drawable = (Drawable)element;
|
||||
|
||||
// for now align some top components with the bottom-edge of the lowest top-anchored hud element.
|
||||
if (drawable.Anchor.HasFlagFast(Anchor.y0))
|
||||
if (drawable.Anchor.HasFlag(Anchor.y0))
|
||||
{
|
||||
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
|
||||
if (element is LegacyHealthDisplay)
|
||||
@ -305,20 +304,20 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
bool isRelativeX = drawable.RelativeSizeAxes == Axes.X;
|
||||
|
||||
if (drawable.Anchor.HasFlagFast(Anchor.TopRight) || isRelativeX)
|
||||
if (drawable.Anchor.HasFlag(Anchor.TopRight) || isRelativeX)
|
||||
{
|
||||
if (lowestTopScreenSpaceRight == null || bottom > lowestTopScreenSpaceRight.Value)
|
||||
lowestTopScreenSpaceRight = bottom;
|
||||
}
|
||||
|
||||
if (drawable.Anchor.HasFlagFast(Anchor.TopLeft) || isRelativeX)
|
||||
if (drawable.Anchor.HasFlag(Anchor.TopLeft) || isRelativeX)
|
||||
{
|
||||
if (lowestTopScreenSpaceLeft == null || bottom > lowestTopScreenSpaceLeft.Value)
|
||||
lowestTopScreenSpaceLeft = bottom;
|
||||
}
|
||||
}
|
||||
// and align bottom-right components with the top-edge of the highest bottom-anchored hud element.
|
||||
else if (drawable.Anchor.HasFlagFast(Anchor.BottomRight) || (drawable.Anchor.HasFlagFast(Anchor.y2) && drawable.RelativeSizeAxes == Axes.X))
|
||||
else if (drawable.Anchor.HasFlag(Anchor.BottomRight) || (drawable.Anchor.HasFlag(Anchor.y2) && drawable.RelativeSizeAxes == Axes.X))
|
||||
{
|
||||
var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
|
||||
if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -282,7 +281,7 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
if (invalidation.HasFlagFast(Invalidation.DrawSize))
|
||||
if (invalidation.HasFlag(Invalidation.DrawSize))
|
||||
{
|
||||
if (lastDrawHeight != null && lastDrawHeight != DrawHeight)
|
||||
Scheduler.AddOnce(updateMetrics, false);
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
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.Pooling;
|
||||
@ -828,7 +827,7 @@ namespace osu.Game.Screens.Select
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
// handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed).
|
||||
if (invalidation.HasFlagFast(Invalidation.DrawSize))
|
||||
if (invalidation.HasFlag(Invalidation.DrawSize))
|
||||
itemsCache.Invalidate();
|
||||
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
@ -19,9 +18,9 @@ namespace osu.Game.Skinning
|
||||
// todo: can probably make this better via deserialisation directly using a common interface.
|
||||
component.Position = drawableInfo.Position;
|
||||
component.Rotation = drawableInfo.Rotation;
|
||||
if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true)
|
||||
if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlag(Axes.X) != true)
|
||||
component.Width = width;
|
||||
if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true)
|
||||
if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlag(Axes.Y) != true)
|
||||
component.Height = height;
|
||||
component.Scale = drawableInfo.Scale;
|
||||
component.Anchor = drawableInfo.Anchor;
|
||||
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
@ -68,10 +67,10 @@ namespace osu.Game.Skinning
|
||||
Rotation = component.Rotation;
|
||||
Scale = component.Scale;
|
||||
|
||||
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true)
|
||||
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlag(Axes.X) != true)
|
||||
Width = component.Width;
|
||||
|
||||
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true)
|
||||
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlag(Axes.Y) != true)
|
||||
Height = component.Height;
|
||||
|
||||
Anchor = component.Anchor;
|
||||
|
@ -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 osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osuTK;
|
||||
|
||||
@ -22,18 +21,18 @@ namespace osu.Game.Storyboards
|
||||
// Either flip horizontally or negative X scale, but not both.
|
||||
if (flipH ^ (vectorScale.X < 0))
|
||||
{
|
||||
if (origin.HasFlagFast(Anchor.x0))
|
||||
if (origin.HasFlag(Anchor.x0))
|
||||
origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
|
||||
else if (origin.HasFlagFast(Anchor.x2))
|
||||
else if (origin.HasFlag(Anchor.x2))
|
||||
origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
|
||||
}
|
||||
|
||||
// Either flip vertically or negative Y scale, but not both.
|
||||
if (flipV ^ (vectorScale.Y < 0))
|
||||
{
|
||||
if (origin.HasFlagFast(Anchor.y0))
|
||||
if (origin.HasFlag(Anchor.y0))
|
||||
origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
|
||||
else if (origin.HasFlagFast(Anchor.y2))
|
||||
else if (origin.HasFlag(Anchor.y2))
|
||||
origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.627.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.702.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.622.0" />
|
||||
<PackageReference Include="Sentry" Version="4.3.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -23,6 +23,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.627.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.702.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user