mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 16:32:54 +08:00
Merge branch 'master' into ruleset-specific-combo-counter
This commit is contained in:
commit
dce1b4ec6c
@ -196,6 +196,9 @@ csharp_style_prefer_switch_expression = false:none
|
|||||||
|
|
||||||
csharp_style_namespace_declarations = block_scoped:warning
|
csharp_style_namespace_declarations = block_scoped:warning
|
||||||
|
|
||||||
|
#Style - C# 12 features
|
||||||
|
csharp_style_prefer_primary_constructors = false
|
||||||
|
|
||||||
[*.{yaml,yml}]
|
[*.{yaml,yml}]
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -265,6 +265,7 @@ __pycache__/
|
|||||||
.idea/**/usage.statistics.xml
|
.idea/**/usage.statistics.xml
|
||||||
.idea/**/dictionaries
|
.idea/**/dictionaries
|
||||||
.idea/**/shelf
|
.idea/**/shelf
|
||||||
|
.idea/*/.idea/projectSettingsUpdater.xml
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
.idea/**/contentModel.xml
|
.idea/**/contentModel.xml
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RiderProjectSettingsUpdater">
|
|
||||||
<option name="vcsConfiguration" value="2" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RiderProjectSettingsUpdater">
|
|
||||||
<option name="vcsConfiguration" value="2" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RiderProjectSettingsUpdater">
|
|
||||||
<option name="vcsConfiguration" value="2" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RiderProjectSettingsUpdater">
|
|
||||||
<option name="vcsConfiguration" value="2" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.528.1" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.627.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
|||||||
StartTime = 5000,
|
StartTime = 5000,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(2000, 4000),
|
new BreakPeriod(2000, 4000),
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
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.
|
// 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.
|
// 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
|
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
value *= Math.Pow(accuracy(), 5.5);
|
value *= Math.Pow(accuracy(), 5.5);
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModNoFail))
|
if (score.Mods.Any(m => m is ModNoFail))
|
||||||
value *= 0.90;
|
value *= Math.Max(0.90, 1.0 - 0.02 * numMiss);
|
||||||
|
|
||||||
return new CatchPerformanceAttributes
|
return new CatchPerformanceAttributes
|
||||||
{
|
{
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager()!;
|
||||||
|
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
@ -46,16 +47,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public double TickDistanceMultiplier = 1;
|
public double TickDistanceMultiplier = 1;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private double velocityFactor;
|
public double Velocity { get; private set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private double tickDistanceFactor;
|
public double TickDistance { get; private set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public double Velocity => velocityFactor * SliderVelocityMultiplier;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public double TickDistance => tickDistanceFactor * TickDistanceMultiplier;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||||
@ -68,14 +63,21 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
|
||||||
velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength;
|
Velocity = base_scoring_distance * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, CatchRuleset.SHORT_NAME);
|
||||||
tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate;
|
|
||||||
|
// 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)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
|
this.PopulateNodeSamples();
|
||||||
|
|
||||||
var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
|
var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
|
||||||
|
|
||||||
int nodeIndex = 0;
|
int nodeIndex = 0;
|
||||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
var replayState = (GetContainingInputManager()!.CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||||
|
|
||||||
SetCatcherPosition(
|
SetCatcherPosition(
|
||||||
replayState?.CatcherX ??
|
replayState?.CatcherX ??
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -17,6 +18,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -84,6 +86,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
public partial class TestHitObjectComposer : HitObjectComposer
|
public partial class TestHitObjectComposer : HitObjectComposer
|
||||||
{
|
{
|
||||||
public override Playfield Playfield { get; }
|
public override Playfield Playfield { get; }
|
||||||
|
public override ComposeBlueprintContainer BlueprintContainer => throw new NotImplementedException();
|
||||||
public override IEnumerable<DrawableHitObject> HitObjects => Enumerable.Empty<DrawableHitObject>();
|
public override IEnumerable<DrawableHitObject> HitObjects => Enumerable.Empty<DrawableHitObject>();
|
||||||
public override bool CursorInPlacementArea => false;
|
public override bool CursorInPlacementArea => false;
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
// 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.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaEditorSaving : EditorSavingTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyCountChange()
|
||||||
|
{
|
||||||
|
LabelledSliderBar<float> keyCount = null!;
|
||||||
|
|
||||||
|
AddStep("go to setup screen", () => InputManager.Key(Key.F4));
|
||||||
|
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<LabelledSliderBar<float>>().First(), () => Is.Not.Null);
|
||||||
|
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
|
||||||
|
AddStep("change key count to 8", () =>
|
||||||
|
{
|
||||||
|
keyCount.Current.Value = 8;
|
||||||
|
});
|
||||||
|
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().SingleOrDefault()?.CurrentDialog, Is.InstanceOf<ReloadEditorDialog>);
|
||||||
|
AddStep("refuse", () => InputManager.Key(Key.Number2));
|
||||||
|
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
|
||||||
|
|
||||||
|
AddStep("change key count to 8 again", () =>
|
||||||
|
{
|
||||||
|
keyCount.Current.Value = 8;
|
||||||
|
});
|
||||||
|
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().Single().CurrentDialog, Is.InstanceOf<ReloadEditorDialog>);
|
||||||
|
AddStep("acquiesce", () => InputManager.Key(Key.Number1));
|
||||||
|
AddUntilStep("beatmap became 8K", () => Game.Beatmap.Value.BeatmapInfo.Difficulty.CircleSize, () => Is.EqualTo(8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -186,8 +186,106 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
|
||||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragHoldNoteHead()
|
||||||
|
{
|
||||||
|
setScrollStep(ScrollingDirection.Down);
|
||||||
|
|
||||||
|
HoldNote holdNote = null;
|
||||||
|
AddStep("setup beatmap", () =>
|
||||||
|
{
|
||||||
|
composer.EditorBeatmap.Clear();
|
||||||
|
composer.EditorBeatmap.Add(holdNote = new HoldNote
|
||||||
|
{
|
||||||
|
Column = 1,
|
||||||
|
StartTime = 250,
|
||||||
|
EndTime = 750,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
DrawableHoldNote drawableHoldNote = null;
|
||||||
|
EditHoldNoteEndPiece headPiece = null;
|
||||||
|
|
||||||
|
AddStep("select blueprint", () =>
|
||||||
|
{
|
||||||
|
drawableHoldNote = this.ChildrenOfType<DrawableHoldNote>().Single();
|
||||||
|
InputManager.MoveMouseTo(drawableHoldNote);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("grab hold note head", () =>
|
||||||
|
{
|
||||||
|
headPiece = this.ChildrenOfType<EditHoldNoteEndPiece>().First();
|
||||||
|
InputManager.MoveMouseTo(headPiece);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("drag head downwards", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(headPiece, new Vector2(0, 100));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("start time moved back", () => holdNote!.StartTime, () => Is.LessThan(250));
|
||||||
|
AddAssert("end time unchanged", () => holdNote.EndTime, () => Is.EqualTo(750));
|
||||||
|
|
||||||
|
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.BottomLeft, drawableHoldNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.TopLeft, drawableHoldNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
|
||||||
|
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == drawableHoldNote.Head.DrawPosition);
|
||||||
|
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(1).DrawPosition == drawableHoldNote.Tail.DrawPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragHoldNoteTail()
|
||||||
|
{
|
||||||
|
setScrollStep(ScrollingDirection.Down);
|
||||||
|
|
||||||
|
HoldNote holdNote = null;
|
||||||
|
AddStep("setup beatmap", () =>
|
||||||
|
{
|
||||||
|
composer.EditorBeatmap.Clear();
|
||||||
|
composer.EditorBeatmap.Add(holdNote = new HoldNote
|
||||||
|
{
|
||||||
|
Column = 1,
|
||||||
|
StartTime = 250,
|
||||||
|
EndTime = 750,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
DrawableHoldNote drawableHoldNote = null;
|
||||||
|
EditHoldNoteEndPiece tailPiece = null;
|
||||||
|
|
||||||
|
AddStep("select blueprint", () =>
|
||||||
|
{
|
||||||
|
drawableHoldNote = this.ChildrenOfType<DrawableHoldNote>().Single();
|
||||||
|
InputManager.MoveMouseTo(drawableHoldNote);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("grab hold note tail", () =>
|
||||||
|
{
|
||||||
|
tailPiece = this.ChildrenOfType<EditHoldNoteEndPiece>().Last();
|
||||||
|
InputManager.MoveMouseTo(tailPiece);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("drag tail upwards", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(tailPiece, new Vector2(0, -100));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("start time unchanged", () => holdNote!.StartTime, () => Is.EqualTo(250));
|
||||||
|
AddAssert("end time moved forward", () => holdNote.EndTime, () => Is.GreaterThan(750));
|
||||||
|
|
||||||
|
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.BottomLeft, drawableHoldNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(drawableHoldNote.ScreenSpaceDrawQuad.TopLeft, drawableHoldNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
|
||||||
|
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == drawableHoldNote.Head.DrawPosition);
|
||||||
|
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(1).DrawPosition == drawableHoldNote.Tail.DrawPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setScrollStep(ScrollingDirection direction)
|
private void setScrollStep(ScrollingDirection direction)
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
// 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;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaSelectionHandler : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHorizontalFlipOverSelection()
|
||||||
|
{
|
||||||
|
ManiaHitObject first = null!, second = null!, third = null!;
|
||||||
|
|
||||||
|
AddStep("create objects", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 });
|
||||||
|
EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 });
|
||||||
|
EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
AddStep("flip horizontally over selection", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxButton>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first object stayed in place", () => first.Column, () => Is.EqualTo(2));
|
||||||
|
AddAssert("second object flipped", () => second.Column, () => Is.EqualTo(3));
|
||||||
|
AddAssert("third object flipped", () => third.Column, () => Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHorizontalFlipOverPlayfield()
|
||||||
|
{
|
||||||
|
ManiaHitObject first = null!, second = null!, third = null!;
|
||||||
|
|
||||||
|
AddStep("create objects", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 });
|
||||||
|
EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 });
|
||||||
|
EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
AddStep("flip horizontally", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.H);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first object flipped", () => first.Column, () => Is.EqualTo(1));
|
||||||
|
AddAssert("second object flipped", () => second.Column, () => Is.EqualTo(2));
|
||||||
|
AddAssert("third object flipped", () => third.Column, () => Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVerticalFlip()
|
||||||
|
{
|
||||||
|
ManiaHitObject first = null!, second = null!, third = null!;
|
||||||
|
|
||||||
|
AddStep("create objects", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Add(first = new Note { StartTime = 250, Column = 2 });
|
||||||
|
EditorBeatmap.Add(second = new HoldNote { StartTime = 750, Duration = 1500, Column = 1 });
|
||||||
|
EditorBeatmap.Add(third = new Note { StartTime = 1250, Column = 3 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
AddStep("flip vertically", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.J);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first object flipped", () => first.StartTime, () => Is.EqualTo(2250));
|
||||||
|
AddAssert("second object flipped", () => second.StartTime, () => Is.EqualTo(250));
|
||||||
|
AddAssert("third object flipped", () => third.StartTime, () => Is.EqualTo(1250));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddStep("Hold key", () =>
|
AddStep("Hold key", () =>
|
||||||
{
|
{
|
||||||
clock.CurrentTime = 0;
|
clock.CurrentTime = 0;
|
||||||
note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager().CurrentState, ManiaAction.Key1));
|
note.OnPressed(new KeyBindingPressEvent<ManiaAction>(GetContainingInputManager()!.CurrentState, ManiaAction.Key1));
|
||||||
});
|
});
|
||||||
AddStep("progress time", () => clock.CurrentTime = 500);
|
AddStep("progress time", () => clock.CurrentTime = 500);
|
||||||
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
|
||||||
|
{
|
||||||
|
public partial class EditHoldNoteEndPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Action? DragStarted { get; init; }
|
||||||
|
public Action<Vector2>? Dragging { get; init; }
|
||||||
|
public Action? DragEnded { get; init; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Height = DefaultNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
|
CornerRadius = 5;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChild = new DefaultNotePiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
DragStarted?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
base.OnDrag(e);
|
||||||
|
Dragging?.Invoke(e.ScreenSpaceMousePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
DragEnded?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
var colour = colours.Yellow;
|
||||||
|
|
||||||
|
if (IsHovered)
|
||||||
|
colour = colour.Lighten(1);
|
||||||
|
|
||||||
|
Colour = colour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
@ -18,10 +18,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
private EditNotePiece head;
|
[Resolved]
|
||||||
private EditNotePiece tail;
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap? editorBeatmap { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IPositionSnapProvider? positionSnapProvider { get; set; }
|
||||||
|
|
||||||
|
private EditHoldNoteEndPiece head = null!;
|
||||||
|
private EditHoldNoteEndPiece tail = null!;
|
||||||
|
|
||||||
public HoldNoteSelectionBlueprint(HoldNote hold)
|
public HoldNoteSelectionBlueprint(HoldNote hold)
|
||||||
: base(hold)
|
: base(hold)
|
||||||
@ -33,8 +42,43 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
head = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
head = new EditHoldNoteEndPiece
|
||||||
tail = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
DragStarted = () => changeHandler?.BeginChange(),
|
||||||
|
Dragging = pos =>
|
||||||
|
{
|
||||||
|
double endTimeBeforeDrag = HitObject.EndTime;
|
||||||
|
double proposedStartTime = positionSnapProvider?.FindSnappedPositionAndTime(pos).Time ?? HitObjectContainer.TimeAtScreenSpacePosition(pos);
|
||||||
|
double proposedEndTime = endTimeBeforeDrag;
|
||||||
|
|
||||||
|
if (proposedStartTime >= proposedEndTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HitObject.StartTime = proposedStartTime;
|
||||||
|
HitObject.EndTime = proposedEndTime;
|
||||||
|
editorBeatmap?.Update(HitObject);
|
||||||
|
},
|
||||||
|
DragEnded = () => changeHandler?.EndChange(),
|
||||||
|
},
|
||||||
|
tail = new EditHoldNoteEndPiece
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
DragStarted = () => changeHandler?.BeginChange(),
|
||||||
|
Dragging = pos =>
|
||||||
|
{
|
||||||
|
double proposedStartTime = HitObject.StartTime;
|
||||||
|
double proposedEndTime = positionSnapProvider?.FindSnappedPositionAndTime(pos).Time ?? HitObjectContainer.TimeAtScreenSpacePosition(pos);
|
||||||
|
|
||||||
|
if (proposedStartTime >= proposedEndTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HitObject.StartTime = proposedStartTime;
|
||||||
|
HitObject.EndTime = proposedEndTime;
|
||||||
|
editorBeatmap?.Update(HitObject);
|
||||||
|
},
|
||||||
|
DragEnded = () => changeHandler?.EndChange(),
|
||||||
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -16,6 +17,16 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private HitObjectComposer composer { get; set; } = null!;
|
private HitObjectComposer composer { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override void OnSelectionChanged()
|
||||||
|
{
|
||||||
|
base.OnSelectionChanged();
|
||||||
|
|
||||||
|
var selectedObjects = SelectedItems.OfType<ManiaHitObject>().ToArray();
|
||||||
|
|
||||||
|
SelectionBox.CanFlipX = canFlipX(selectedObjects);
|
||||||
|
SelectionBox.CanFlipY = canFlipY(selectedObjects);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||||
{
|
{
|
||||||
var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
|
var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
|
||||||
@ -26,6 +37,58 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool HandleFlip(Direction direction, bool flipOverOrigin)
|
||||||
|
{
|
||||||
|
var selectedObjects = SelectedItems.OfType<ManiaHitObject>().ToArray();
|
||||||
|
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||||
|
|
||||||
|
if (selectedObjects.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case Direction.Horizontal:
|
||||||
|
if (!canFlipX(selectedObjects))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int firstColumn = flipOverOrigin ? 0 : selectedObjects.Min(ho => ho.Column);
|
||||||
|
int lastColumn = flipOverOrigin ? (int)EditorBeatmap.BeatmapInfo.Difficulty.CircleSize - 1 : selectedObjects.Max(ho => ho.Column);
|
||||||
|
|
||||||
|
EditorBeatmap.PerformOnSelection(hitObject =>
|
||||||
|
{
|
||||||
|
var maniaObject = (ManiaHitObject)hitObject;
|
||||||
|
maniaPlayfield.Remove(maniaObject);
|
||||||
|
maniaObject.Column = firstColumn + (lastColumn - maniaObject.Column);
|
||||||
|
maniaPlayfield.Add(maniaObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Direction.Vertical:
|
||||||
|
if (!canFlipY(selectedObjects))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
double selectionStartTime = selectedObjects.Min(ho => ho.StartTime);
|
||||||
|
double selectionEndTime = selectedObjects.Max(ho => ho.GetEndTime());
|
||||||
|
|
||||||
|
EditorBeatmap.PerformOnSelection(hitObject =>
|
||||||
|
{
|
||||||
|
hitObject.StartTime = selectionStartTime + (selectionEndTime - hitObject.GetEndTime());
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(direction), direction, "Cannot flip over the supplied direction.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool canFlipX(ManiaHitObject[] selectedObjects)
|
||||||
|
=> selectedObjects.Select(ho => ho.Column).Distinct().Count() > 1;
|
||||||
|
|
||||||
|
private static bool canFlipY(ManiaHitObject[] selectedObjects)
|
||||||
|
=> selectedObjects.Length > 1 && selectedObjects.Min(ho => ho.StartTime) < selectedObjects.Max(ho => ho.GetEndTime());
|
||||||
|
|
||||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent<HitObject> moveEvent)
|
private void performColumnMovement(int lastColumn, MoveSelectionEvent<HitObject> moveEvent)
|
||||||
{
|
{
|
||||||
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||||
|
@ -3,20 +3,155 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||||
{
|
{
|
||||||
public partial class ManiaDifficultySection : DifficultySection
|
public partial class ManiaDifficultySection : SetupSection
|
||||||
{
|
{
|
||||||
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
|
private LabelledSliderBar<float> keyCountSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Editor? editor { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
CircleSizeSlider.Label = BeatmapsetsStrings.ShowStatsCsMania;
|
Children = new Drawable[]
|
||||||
CircleSizeSlider.Description = "The number of columns in the beatmap";
|
{
|
||||||
if (CircleSizeSlider.Current is BindableNumber<float> circleSizeFloat)
|
keyCountSlider = new LabelledSliderBar<float>
|
||||||
circleSizeFloat.Precision = 1;
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsCsMania,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = "The number of columns in the beatmap",
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
healthDrainSlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.DrainRateDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overallDifficultySlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.OverallDifficultyDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
baseVelocitySlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.BaseVelocity,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||||
|
{
|
||||||
|
Default = 1.4,
|
||||||
|
MinValue = 0.4,
|
||||||
|
MaxValue = 3.6,
|
||||||
|
Precision = 0.01f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tickRateSlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.TickRate,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.TickRateDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 4,
|
||||||
|
Precision = 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
keyCountSlider.Current.BindValueChanged(updateKeyCount);
|
||||||
|
healthDrainSlider.Current.BindValueChanged(_ => updateValues());
|
||||||
|
overallDifficultySlider.Current.BindValueChanged(_ => updateValues());
|
||||||
|
baseVelocitySlider.Current.BindValueChanged(_ => updateValues());
|
||||||
|
tickRateSlider.Current.BindValueChanged(_ => updateValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool updatingKeyCount;
|
||||||
|
|
||||||
|
private void updateKeyCount(ValueChangedEvent<float> keyCount)
|
||||||
|
{
|
||||||
|
if (updatingKeyCount) return;
|
||||||
|
|
||||||
|
updateValues();
|
||||||
|
|
||||||
|
if (editor == null) return;
|
||||||
|
|
||||||
|
updatingKeyCount = true;
|
||||||
|
|
||||||
|
editor.Reload().ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (!t.GetResultSafely())
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
changeHandler!.RestoreState(-1);
|
||||||
|
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value = keyCount.OldValue;
|
||||||
|
updatingKeyCount = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updatingKeyCount = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateValues()
|
||||||
|
{
|
||||||
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
|
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||||
|
|
||||||
|
Beatmap.UpdateAllHitObjects();
|
||||||
|
Beatmap.SaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -421,7 +421,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
||||||
|
|
||||||
public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
||||||
|
|
||||||
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
||||||
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
||||||
|
@ -65,11 +65,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||||
|
|
||||||
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d =>
|
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength)?.With(d =>
|
||||||
{
|
{
|
||||||
if (d == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
d.Origin = Anchor.Centre;
|
d.Origin = Anchor.Centre;
|
||||||
d.Blending = BlendingParameters.Additive;
|
d.Blending = BlendingParameters.Additive;
|
||||||
d.Scale = new Vector2(lightScale);
|
d.Scale = new Vector2(lightScale);
|
||||||
@ -91,11 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
isHitting.BindTo(holdNote.IsHitting);
|
isHitting.BindTo(holdNote.IsHitting);
|
||||||
|
|
||||||
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30).With(d =>
|
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true, frameLength: 30)?.With(d =>
|
||||||
{
|
{
|
||||||
if (d == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (d is TextureAnimation animation)
|
if (d is TextureAnimation animation)
|
||||||
animation.IsPlaying = false;
|
animation.IsPlaying = false;
|
||||||
|
|
||||||
@ -245,7 +239,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
// i dunno this looks about right??
|
// i dunno this looks about right??
|
||||||
// the guard against zero draw height is intended for zero-length hold notes. yes, such cases have been spotted in the wild.
|
// the guard against zero draw height is intended for zero-length hold notes. yes, such cases have been spotted in the wild.
|
||||||
if (sprite.DrawHeight > 0)
|
if (sprite.DrawHeight > 0)
|
||||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
bodySprite.Scale = new Vector2(1, scaleDirection * MathF.Max(1, 32800 / sprite.DrawHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -43,11 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||||
|
|
||||||
explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d =>
|
explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength)?.With(d =>
|
||||||
{
|
{
|
||||||
if (d == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
d.Origin = Anchor.Centre;
|
d.Origin = Anchor.Centre;
|
||||||
d.Blending = BlendingParameters.Additive;
|
d.Blending = BlendingParameters.Additive;
|
||||||
d.Scale = new Vector2(explosionScale);
|
d.Scale = new Vector2(explosionScale);
|
||||||
|
@ -28,13 +28,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
string bottomImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
|
string bottomImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
|
||||||
?? "mania-stage-bottom";
|
?? "mania-stage-bottom";
|
||||||
|
|
||||||
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d =>
|
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => d.Scale = new Vector2(1.6f));
|
||||||
{
|
|
||||||
if (d == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
d.Scale = new Vector2(1.6f);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sprite != null)
|
if (sprite != null)
|
||||||
InternalChild = sprite;
|
InternalChild = sprite;
|
||||||
|
@ -8,7 +8,9 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -25,22 +27,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
|
|
||||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
|
|
||||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
|
|
||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(true);
|
gridActive<RectangularPositionSnapGrid>(true);
|
||||||
|
|
||||||
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
||||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||||
rectangularGridActive(true);
|
gridActive<RectangularPositionSnapGrid>(true);
|
||||||
|
|
||||||
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -117,33 +119,56 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestGridSnapMomentaryToggle()
|
public void TestGridSnapMomentaryToggle()
|
||||||
{
|
{
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||||
rectangularGridActive(true);
|
gridActive<RectangularPositionSnapGrid>(true);
|
||||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||||
rectangularGridActive(false);
|
gridActive<RectangularPositionSnapGrid>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rectangularGridActive(bool active)
|
private void gridActive<T>(bool active) where T : PositionSnapGrid
|
||||||
{
|
{
|
||||||
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
|
AddStep("choose placement tool", () => InputManager.Key(Key.Number2));
|
||||||
AddStep("move cursor to (1, 1)", () =>
|
AddStep("move cursor to spacing + (1, 1)", () =>
|
||||||
{
|
{
|
||||||
var composer = Editor.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single();
|
var composer = Editor.ChildrenOfType<T>().Single();
|
||||||
InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1)));
|
InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1)));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (active)
|
if (active)
|
||||||
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
{
|
||||||
|
AddAssert("placement blueprint at spacing + (0, 0)", () =>
|
||||||
|
{
|
||||||
|
var composer = Editor.ChildrenOfType<T>().Single();
|
||||||
|
return Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position,
|
||||||
|
uniqueSnappingPosition(composer));
|
||||||
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
{
|
||||||
|
AddAssert("placement blueprint at spacing + (1, 1)", () =>
|
||||||
|
{
|
||||||
|
var composer = Editor.ChildrenOfType<T>().Single();
|
||||||
|
return Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position,
|
||||||
|
uniqueSnappingPosition(composer) + new Vector2(1, 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 uniqueSnappingPosition(PositionSnapGrid grid)
|
||||||
|
{
|
||||||
|
return grid switch
|
||||||
|
{
|
||||||
|
RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value),
|
||||||
|
_ => Vector2.Zero
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestGridSizeToggling()
|
public void TestGridSizeToggling()
|
||||||
{
|
{
|
||||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||||
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Any());
|
AddUntilStep("rectangular grid visible", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Any());
|
||||||
gridSizeIs(4);
|
gridSizeIs(4);
|
||||||
|
|
||||||
nextGridSizeIs(8);
|
nextGridSizeIs(8);
|
||||||
@ -159,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void gridSizeIs(int size)
|
private void gridSizeIs(int size)
|
||||||
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single().Spacing == new Vector2(size)
|
=> AddAssert($"grid size is {size}", () => this.ChildrenOfType<RectangularPositionSnapGrid>().Single().Spacing.Value == new Vector2(size)
|
||||||
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
&& EditorBeatmap.BeatmapInfo.GridSize == size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
Autoplay = false,
|
Autoplay = false,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(500, 2000),
|
new BreakPeriod(500, 2000),
|
||||||
},
|
},
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
StartTime = 5000,
|
StartTime = 5000,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(2000, 4000),
|
new BreakPeriod(2000, 4000),
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
Autoplay = false,
|
Autoplay = false,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(500, 2000),
|
new BreakPeriod(500, 2000),
|
||||||
},
|
},
|
||||||
|
@ -161,9 +161,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
pressed = value;
|
pressed = value;
|
||||||
if (value)
|
if (value)
|
||||||
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton));
|
||||||
else
|
else
|
||||||
OnReleased(new KeyBindingReleaseEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
OnReleased(new KeyBindingReleaseEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private void scheduleHit() => AddStep("schedule action", () =>
|
private void scheduleHit() => AddStep("schedule action", () =>
|
||||||
{
|
{
|
||||||
double delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
|
double delay = hitCircle.StartTime - hitCircle.HitWindows.WindowFor(HitResult.Great) - Time.Current;
|
||||||
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton)), delay);
|
Scheduler.AddDelayed(() => hitAreaReceptor.OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton)), delay);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
slider = (DrawableSlider)createSlider(repeats: 1);
|
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||||
Add(slider);
|
Add(slider);
|
||||||
|
slider.HitObject.NodeSamples.Clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -41,22 +42,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
{
|
{
|
||||||
base.PostProcess();
|
base.PostProcess();
|
||||||
|
|
||||||
var osuBeatmap = (Beatmap<OsuHitObject>)Beatmap;
|
var hitObjects = Beatmap.HitObjects as List<OsuHitObject> ?? Beatmap.HitObjects.OfType<OsuHitObject>().ToList();
|
||||||
|
|
||||||
if (osuBeatmap.HitObjects.Count > 0)
|
if (hitObjects.Count > 0)
|
||||||
{
|
{
|
||||||
// Reset stacking
|
// Reset stacking
|
||||||
foreach (var h in osuBeatmap.HitObjects)
|
foreach (var h in hitObjects)
|
||||||
h.StackHeight = 0;
|
h.StackHeight = 0;
|
||||||
|
|
||||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||||
applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
|
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||||
else
|
else
|
||||||
applyStackingOld(osuBeatmap);
|
applyStackingOld(Beatmap.BeatmapInfo, hitObjects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)
|
private void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
||||||
@ -64,24 +65,24 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
|
|
||||||
int extendedEndIndex = endIndex;
|
int extendedEndIndex = endIndex;
|
||||||
|
|
||||||
if (endIndex < beatmap.HitObjects.Count - 1)
|
if (endIndex < hitObjects.Count - 1)
|
||||||
{
|
{
|
||||||
// Extend the end index to include objects they are stacked on
|
// Extend the end index to include objects they are stacked on
|
||||||
for (int i = endIndex; i >= startIndex; i--)
|
for (int i = endIndex; i >= startIndex; i--)
|
||||||
{
|
{
|
||||||
int stackBaseIndex = i;
|
int stackBaseIndex = i;
|
||||||
|
|
||||||
for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++)
|
for (int n = stackBaseIndex + 1; n < hitObjects.Count; n++)
|
||||||
{
|
{
|
||||||
OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex];
|
OsuHitObject stackBaseObject = hitObjects[stackBaseIndex];
|
||||||
if (stackBaseObject is Spinner) break;
|
if (stackBaseObject is Spinner) break;
|
||||||
|
|
||||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
OsuHitObject objectN = hitObjects[n];
|
||||||
if (objectN is Spinner)
|
if (objectN is Spinner)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
double endTime = stackBaseObject.GetEndTime();
|
double endTime = stackBaseObject.GetEndTime();
|
||||||
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency;
|
||||||
|
|
||||||
if (objectN.StartTime - endTime > stackThreshold)
|
if (objectN.StartTime - endTime > stackThreshold)
|
||||||
// We are no longer within stacking range of the next object.
|
// We are no longer within stacking range of the next object.
|
||||||
@ -100,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
if (stackBaseIndex > extendedEndIndex)
|
if (stackBaseIndex > extendedEndIndex)
|
||||||
{
|
{
|
||||||
extendedEndIndex = stackBaseIndex;
|
extendedEndIndex = stackBaseIndex;
|
||||||
if (extendedEndIndex == beatmap.HitObjects.Count - 1)
|
if (extendedEndIndex == hitObjects.Count - 1)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,10 +124,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
* 2 and 1 will be ignored in the i loop because they already have a stack value.
|
* 2 and 1 will be ignored in the i loop because they already have a stack value.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
OsuHitObject objectI = beatmap.HitObjects[i];
|
OsuHitObject objectI = hitObjects[i];
|
||||||
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
|
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
|
||||||
|
|
||||||
double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
double stackThreshold = objectI.TimePreempt * beatmapInfo.StackLeniency;
|
||||||
|
|
||||||
/* If this object is a hitcircle, then we enter this "special" case.
|
/* If this object is a hitcircle, then we enter this "special" case.
|
||||||
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
|
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
|
||||||
@ -136,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
{
|
{
|
||||||
while (--n >= 0)
|
while (--n >= 0)
|
||||||
{
|
{
|
||||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
OsuHitObject objectN = hitObjects[n];
|
||||||
if (objectN is Spinner) continue;
|
if (objectN is Spinner) continue;
|
||||||
|
|
||||||
double endTime = objectN.GetEndTime();
|
double endTime = objectN.GetEndTime();
|
||||||
@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
for (int j = n + 1; j <= i; j++)
|
for (int j = n + 1; j <= i; j++)
|
||||||
{
|
{
|
||||||
// For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
|
// For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above).
|
||||||
OsuHitObject objectJ = beatmap.HitObjects[j];
|
OsuHitObject objectJ = hitObjects[j];
|
||||||
if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
|
if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
|
||||||
objectJ.StackHeight -= offset;
|
objectJ.StackHeight -= offset;
|
||||||
}
|
}
|
||||||
@ -191,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
*/
|
*/
|
||||||
while (--n >= startIndex)
|
while (--n >= startIndex)
|
||||||
{
|
{
|
||||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
OsuHitObject objectN = hitObjects[n];
|
||||||
if (objectN is Spinner) continue;
|
if (objectN is Spinner) continue;
|
||||||
|
|
||||||
if (objectI.StartTime - objectN.StartTime > stackThreshold)
|
if (objectI.StartTime - objectN.StartTime > stackThreshold)
|
||||||
@ -208,11 +209,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyStackingOld(Beatmap<OsuHitObject> beatmap)
|
private void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
for (int i = 0; i < hitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
OsuHitObject currHitObject = beatmap.HitObjects[i];
|
OsuHitObject currHitObject = hitObjects[i];
|
||||||
|
|
||||||
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
|
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
|
||||||
continue;
|
continue;
|
||||||
@ -220,11 +221,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
double startTime = currHitObject.GetEndTime();
|
double startTime = currHitObject.GetEndTime();
|
||||||
int sliderStack = 0;
|
int sliderStack = 0;
|
||||||
|
|
||||||
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
|
for (int j = i + 1; j < hitObjects.Count; j++)
|
||||||
{
|
{
|
||||||
double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
double stackThreshold = hitObjects[i].TimePreempt * beatmapInfo.StackLeniency;
|
||||||
|
|
||||||
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
|
if (hitObjects[j].StartTime - stackThreshold > startTime)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
|
// The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
|
||||||
@ -239,17 +240,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
// Effects of this can be seen on https://osu.ppy.sh/beatmapsets/243#osu/1146 at sliders around 86647 ms, where
|
// Effects of this can be seen on https://osu.ppy.sh/beatmapsets/243#osu/1146 at sliders around 86647 ms, where
|
||||||
// if we use `EndTime` here it would result in unexpected stacking.
|
// if we use `EndTime` here it would result in unexpected stacking.
|
||||||
|
|
||||||
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
|
if (Vector2Extensions.Distance(hitObjects[j].Position, currHitObject.Position) < stack_distance)
|
||||||
{
|
{
|
||||||
currHitObject.StackHeight++;
|
currHitObject.StackHeight++;
|
||||||
startTime = beatmap.HitObjects[j].StartTime;
|
startTime = hitObjects[j].StartTime;
|
||||||
}
|
}
|
||||||
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
|
else if (Vector2Extensions.Distance(hitObjects[j].Position, position2) < stack_distance)
|
||||||
{
|
{
|
||||||
// Case for sliders - bump notes down and right, rather than up and left.
|
// Case for sliders - bump notes down and right, rather than up and left.
|
||||||
sliderStack++;
|
sliderStack++;
|
||||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
hitObjects[j].StackHeight -= sliderStack;
|
||||||
startTime = beatmap.HitObjects[j].StartTime;
|
startTime = hitObjects[j].StartTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -16,7 +15,6 @@ using osu.Game.Rulesets.Osu.Skinning.Default;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||||
{
|
{
|
||||||
@ -48,13 +46,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Circle
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.White,
|
|
||||||
},
|
|
||||||
ring = new RingPiece
|
ring = new RingPiece
|
||||||
{
|
{
|
||||||
BorderThickness = 4,
|
BorderThickness = 4,
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
@ -16,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
|
|
||||||
protected readonly HitCirclePiece CirclePiece;
|
protected readonly HitCirclePiece CirclePiece;
|
||||||
private readonly HitCircleOverlapMarker marker;
|
private readonly HitCircleOverlapMarker marker;
|
||||||
|
private readonly Bindable<bool> showHitMarkers = new Bindable<bool>();
|
||||||
|
|
||||||
public HitCircleSelectionBlueprint(HitCircle circle)
|
public HitCircleSelectionBlueprint(HitCircle circle)
|
||||||
: base(circle)
|
: base(circle)
|
||||||
@ -27,12 +31,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.EditorShowHitMarkers, showHitMarkers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
showHitMarkers.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
if (!showHitMarkers.Value)
|
||||||
|
DrawableObject.RestoreHitAnimations();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
CirclePiece.UpdateFrom(HitObject);
|
CirclePiece.UpdateFrom(HitObject);
|
||||||
marker.UpdateFrom(HitObject);
|
marker.UpdateFrom(HitObject);
|
||||||
|
|
||||||
|
if (showHitMarkers.Value)
|
||||||
|
DrawableObject.SuppressHitAnimations();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -14,18 +13,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
private readonly SliderPosition position;
|
private readonly SliderPosition position;
|
||||||
private readonly HitCircleOverlapMarker marker;
|
private readonly HitCircleOverlapMarker? marker;
|
||||||
|
|
||||||
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
public SliderCircleOverlay(Slider slider, SliderPosition position)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
if (position == SliderPosition.Start)
|
||||||
{
|
AddInternal(marker = new HitCircleOverlapMarker());
|
||||||
marker = new HitCircleOverlapMarker(),
|
|
||||||
CirclePiece = new HitCirclePiece(),
|
AddInternal(CirclePiece = new HitCirclePiece());
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -35,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle;
|
var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle;
|
||||||
|
|
||||||
CirclePiece.UpdateFrom(circle);
|
CirclePiece.UpdateFrom(circle);
|
||||||
marker.UpdateFrom(circle);
|
marker?.UpdateFrom(circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Hide()
|
public override void Hide()
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -59,6 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
private readonly BindableList<HitObject> selectedObjects = new BindableList<HitObject>();
|
private readonly BindableList<HitObject> selectedObjects = new BindableList<HitObject>();
|
||||||
|
private readonly Bindable<bool> showHitMarkers = new Bindable<bool>();
|
||||||
|
|
||||||
public SliderSelectionBlueprint(Slider slider)
|
public SliderSelectionBlueprint(Slider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
|
HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
|
||||||
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.BindWith(OsuSetting.EditorShowHitMarkers, showHitMarkers);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -90,6 +94,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
if (editorBeatmap != null)
|
if (editorBeatmap != null)
|
||||||
selectedObjects.BindTo(editorBeatmap.SelectedHitObjects);
|
selectedObjects.BindTo(editorBeatmap.SelectedHitObjects);
|
||||||
selectedObjects.BindCollectionChanged((_, _) => updateVisualDefinition(), true);
|
selectedObjects.BindCollectionChanged((_, _) => updateVisualDefinition(), true);
|
||||||
|
showHitMarkers.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
if (!showHitMarkers.Value)
|
||||||
|
DrawableObject.RestoreHitAnimations();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HandleQuickDeletion()
|
public override bool HandleQuickDeletion()
|
||||||
@ -110,6 +119,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
if (IsSelected)
|
if (IsSelected)
|
||||||
BodyPiece.UpdateFrom(HitObject);
|
BodyPiece.UpdateFrom(HitObject);
|
||||||
|
|
||||||
|
if (showHitMarkers.Value)
|
||||||
|
DrawableObject.SuppressHitAnimations();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -2,10 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
@ -23,12 +26,32 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private partial class OsuEditorPlayfield : OsuPlayfield
|
private partial class OsuEditorPlayfield : OsuPlayfield
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
protected override GameplayCursorContainer? CreateCursor() => null;
|
protected override GameplayCursorContainer? CreateCursor() => null;
|
||||||
|
|
||||||
public OsuEditorPlayfield()
|
public OsuEditorPlayfield()
|
||||||
{
|
{
|
||||||
HitPolicy = new AnyOrderHitPolicy();
|
HitPolicy = new AnyOrderHitPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
editorBeatmap.BeatmapReprocessed += onBeatmapReprocessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBeatmapReprocessed() => ApplyCircleSizeToPlayfieldBorder(editorBeatmap);
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (editorBeatmap.IsNotNull())
|
||||||
|
editorBeatmap.BeatmapReprocessed -= onBeatmapReprocessed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
Normal file
171
osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
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 osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// X position of the grid's origin.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2)
|
||||||
|
{
|
||||||
|
MinValue = 0f,
|
||||||
|
MaxValue = OsuPlayfield.BASE_SIZE.X,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Y position of the grid's origin.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2)
|
||||||
|
{
|
||||||
|
MinValue = 0f,
|
||||||
|
MaxValue = OsuPlayfield.BASE_SIZE.Y,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spacing between grid lines.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat Spacing { get; } = new BindableFloat(4f)
|
||||||
|
{
|
||||||
|
MinValue = 4f,
|
||||||
|
MaxValue = 128f,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotation of the grid lines in degrees.
|
||||||
|
/// </summary>
|
||||||
|
public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f)
|
||||||
|
{
|
||||||
|
MinValue = -45f,
|
||||||
|
MaxValue = 45f,
|
||||||
|
Precision = 1f
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read-only bindable representing the grid's origin.
|
||||||
|
/// Equivalent to <code>new Vector2(StartPositionX, StartPositionY)</code>
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read-only bindable representing the grid's spacing in both the X and Y dimension.
|
||||||
|
/// Equivalent to <code>new Vector2(Spacing)</code>
|
||||||
|
/// </summary>
|
||||||
|
public Bindable<Vector2> SpacingVector { get; } = new Bindable<Vector2>();
|
||||||
|
|
||||||
|
private ExpandableSlider<float> startPositionXSlider = null!;
|
||||||
|
private ExpandableSlider<float> startPositionYSlider = null!;
|
||||||
|
private ExpandableSlider<float> spacingSlider = null!;
|
||||||
|
private ExpandableSlider<float> gridLinesRotationSlider = null!;
|
||||||
|
|
||||||
|
public OsuGridToolboxGroup()
|
||||||
|
: base("grid")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private const float max_automatic_spacing = 64;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
startPositionXSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = StartPositionX,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
startPositionYSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = StartPositionY,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
spacingSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = Spacing,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
gridLinesRotationSlider = new ExpandableSlider<float>
|
||||||
|
{
|
||||||
|
Current = GridLinesRotation,
|
||||||
|
KeyboardStep = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Spacing.Value = editorBeatmap.BeatmapInfo.GridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
StartPositionX.BindValueChanged(x =>
|
||||||
|
{
|
||||||
|
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
|
||||||
|
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}";
|
||||||
|
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
StartPositionY.BindValueChanged(y =>
|
||||||
|
{
|
||||||
|
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}";
|
||||||
|
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}";
|
||||||
|
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
Spacing.BindValueChanged(spacing =>
|
||||||
|
{
|
||||||
|
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}";
|
||||||
|
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}";
|
||||||
|
SpacingVector.Value = new Vector2(spacing.NewValue);
|
||||||
|
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
GridLinesRotation.BindValueChanged(rotation =>
|
||||||
|
{
|
||||||
|
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
|
||||||
|
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextGridSize()
|
||||||
|
{
|
||||||
|
Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.EditorCycleGridDisplayMode:
|
||||||
|
nextGridSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -50,6 +51,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||||
|
|
||||||
|
protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector();
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
||||||
=> base.CreateTernaryButtons()
|
=> base.CreateTernaryButtons()
|
||||||
.Concat(DistanceSnapProvider.CreateTernaryButtons())
|
.Concat(DistanceSnapProvider.CreateTernaryButtons())
|
||||||
@ -65,6 +68,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
||||||
|
|
||||||
@ -80,10 +86,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
LayerBelowRuleset.AddRange(new Drawable[]
|
LayerBelowRuleset.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
distanceSnapGridContainer = new Container
|
distanceSnapGridContainer = new Container
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
},
|
|
||||||
rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -99,8 +101,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
// we may be entering the screen with a selection already active
|
// we may be entering the screen with a selection already active
|
||||||
updateDistanceSnapGrid();
|
updateDistanceSnapGrid();
|
||||||
|
|
||||||
RightToolbox.AddRange(new EditorToolboxGroup[]
|
updatePositionSnapGrid();
|
||||||
|
|
||||||
|
RightToolbox.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
|
OsuGridToolboxGroup,
|
||||||
new TransformToolboxGroup
|
new TransformToolboxGroup
|
||||||
{
|
{
|
||||||
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||||
@ -111,6 +116,23 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePositionSnapGrid()
|
||||||
|
{
|
||||||
|
if (positionSnapGrid != null)
|
||||||
|
LayerBelowRuleset.Remove(positionSnapGrid, true);
|
||||||
|
|
||||||
|
var rectangularPositionSnapGrid = new RectangularPositionSnapGrid();
|
||||||
|
|
||||||
|
rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition);
|
||||||
|
rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector);
|
||||||
|
rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||||
|
|
||||||
|
positionSnapGrid = rectangularPositionSnapGrid;
|
||||||
|
|
||||||
|
positionSnapGrid.RelativeSizeAxes = Axes.Both;
|
||||||
|
LayerBelowRuleset.Add(positionSnapGrid);
|
||||||
|
}
|
||||||
|
|
||||||
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
protected override ComposeBlueprintContainer CreateBlueprintContainer()
|
||||||
=> new OsuBlueprintContainer(this);
|
=> new OsuBlueprintContainer(this);
|
||||||
|
|
||||||
@ -151,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private readonly Cached distanceSnapGridCache = new Cached();
|
private readonly Cached distanceSnapGridCache = new Cached();
|
||||||
private double? lastDistanceSnapGridTime;
|
private double? lastDistanceSnapGridTime;
|
||||||
|
|
||||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
private PositionSnapGrid positionSnapGrid;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -209,9 +231,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||||
{
|
{
|
||||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
||||||
|
|
||||||
result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos);
|
// A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield.
|
||||||
|
// We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds.
|
||||||
|
pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
||||||
|
|
||||||
|
result.ScreenSpacePosition = positionSnapGrid.ToScreenSpace(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs
Normal file
42
osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class OsuHitObjectInspector : HitObjectInspector
|
||||||
|
{
|
||||||
|
protected override void AddInspectorValues()
|
||||||
|
{
|
||||||
|
base.AddInspectorValues();
|
||||||
|
|
||||||
|
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||||
|
{
|
||||||
|
var firstInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MinBy(ho => ho.StartTime)!;
|
||||||
|
var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!;
|
||||||
|
|
||||||
|
Debug.Assert(firstInSelection != null && lastInSelection != null);
|
||||||
|
|
||||||
|
var precedingObject = (OsuHitObject?)EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstInSelection.StartTime);
|
||||||
|
var nextObject = (OsuHitObject?)EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastInSelection.GetEndTime());
|
||||||
|
|
||||||
|
if (precedingObject != null && precedingObject is not Spinner)
|
||||||
|
{
|
||||||
|
AddHeader("To previous");
|
||||||
|
AddValue($"{(firstInSelection.StackedPosition - precedingObject.StackedEndPosition).Length:#,0.##}px");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextObject != null && nextObject is not Spinner)
|
||||||
|
{
|
||||||
|
AddHeader("To next");
|
||||||
|
AddValue($"{(nextObject.StackedPosition - lastInSelection.StackedEndPosition).Length:#,0.##}px");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,69 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Input.Bindings;
|
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
|
||||||
using osu.Game.Screens.Edit;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
|
||||||
{
|
|
||||||
public partial class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler<GlobalAction>
|
|
||||||
{
|
|
||||||
private static readonly int[] grid_sizes = { 4, 8, 16, 32 };
|
|
||||||
|
|
||||||
private int currentGridSizeIndex = grid_sizes.Length - 1;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
|
||||||
|
|
||||||
public OsuRectangularPositionSnapGrid()
|
|
||||||
: base(OsuPlayfield.BASE_SIZE / 2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize);
|
|
||||||
if (gridSizeIndex >= 0)
|
|
||||||
currentGridSizeIndex = gridSizeIndex;
|
|
||||||
updateSpacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nextGridSize()
|
|
||||||
{
|
|
||||||
currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length;
|
|
||||||
updateSpacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSpacing()
|
|
||||||
{
|
|
||||||
int gridSize = grid_sizes[currentGridSizeIndex];
|
|
||||||
|
|
||||||
editorBeatmap.BeatmapInfo.GridSize = gridSize;
|
|
||||||
Spacing = new Vector2(gridSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
||||||
{
|
|
||||||
switch (e.Action)
|
|
||||||
{
|
|
||||||
case GlobalAction.EditorCycleGridDisplayMode:
|
|
||||||
nextGridSize();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -53,9 +53,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override void Begin()
|
public override void Begin()
|
||||||
{
|
{
|
||||||
if (objectsInRotation != null)
|
if (OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Begin)} a rotate operation while another is in progress!");
|
throw new InvalidOperationException($"Cannot {nameof(Begin)} a rotate operation while another is in progress!");
|
||||||
|
|
||||||
|
base.Begin();
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
objectsInRotation = selectedMovableObjects.ToArray();
|
objectsInRotation = selectedMovableObjects.ToArray();
|
||||||
@ -68,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override void Update(float rotation, Vector2? origin = null)
|
public override void Update(float rotation, Vector2? origin = null)
|
||||||
{
|
{
|
||||||
if (objectsInRotation == null)
|
if (!OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
Debug.Assert(originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null);
|
Debug.Assert(objectsInRotation != null && originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null);
|
||||||
|
|
||||||
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||||
|
|
||||||
@ -91,11 +93,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override void Commit()
|
public override void Commit()
|
||||||
{
|
{
|
||||||
if (objectsInRotation == null)
|
if (!OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
changeHandler?.EndChange();
|
||||||
|
|
||||||
|
base.Commit();
|
||||||
|
|
||||||
objectsInRotation = null;
|
objectsInRotation = null;
|
||||||
originalPositions = null;
|
originalPositions = null;
|
||||||
originalPathControlPointPositions = null;
|
originalPathControlPointPositions = null;
|
||||||
|
@ -72,9 +72,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override void Begin()
|
public override void Begin()
|
||||||
{
|
{
|
||||||
if (objectsInScale != null)
|
if (OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!");
|
throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!");
|
||||||
|
|
||||||
|
base.Begin();
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
||||||
@ -86,10 +88,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both)
|
||||||
{
|
{
|
||||||
if (objectsInScale == null)
|
if (!OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
|
Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||||
|
|
||||||
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||||
|
|
||||||
@ -117,11 +119,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
public override void Commit()
|
public override void Commit()
|
||||||
{
|
{
|
||||||
if (objectsInScale == null)
|
if (!OperationInProgress.Value)
|
||||||
throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!");
|
throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!");
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
changeHandler?.EndChange();
|
||||||
|
|
||||||
|
base.Commit();
|
||||||
|
|
||||||
objectsInScale = null;
|
objectsInScale = null;
|
||||||
OriginalSurroundingQuad = null;
|
OriginalSurroundingQuad = null;
|
||||||
defaultOrigin = null;
|
defaultOrigin = null;
|
||||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
|||||||
private void updateBeatmap()
|
private void updateBeatmap()
|
||||||
{
|
{
|
||||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||||
|
Beatmap.UpdateAllHitObjects();
|
||||||
Beatmap.SaveState();
|
Beatmap.SaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,13 +77,15 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
case GlobalAction.EditorToggleRotateControl:
|
case GlobalAction.EditorToggleRotateControl:
|
||||||
{
|
{
|
||||||
rotateButton.TriggerClick();
|
if (!RotationHandler.OperationInProgress.Value || rotateButton.Selected.Value)
|
||||||
|
rotateButton.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
case GlobalAction.EditorToggleScaleControl:
|
case GlobalAction.EditorToggleScaleControl:
|
||||||
{
|
{
|
||||||
scaleButton.TriggerClick();
|
if (!ScaleHandler.OperationInProgress.Value || scaleButton.Selected.Value)
|
||||||
|
scaleButton.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
|
||||||
@ -23,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
||||||
|
|
||||||
|
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -19,6 +20,7 @@ using osu.Game.Rulesets.Osu.UI;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -319,5 +321,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region FOR EDITOR USE ONLY, DO NOT USE FOR ANY OTHER PURPOSE
|
||||||
|
|
||||||
|
internal void SuppressHitAnimations()
|
||||||
|
{
|
||||||
|
UpdateState(ArmedState.Idle);
|
||||||
|
UpdateComboColour();
|
||||||
|
|
||||||
|
// This method is called every frame in editor contexts, thus the lack of need for transforms.
|
||||||
|
|
||||||
|
if (Time.Current >= HitStateUpdateTime)
|
||||||
|
{
|
||||||
|
// More or less matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
|
||||||
|
AccentColour.Value = Color4.White;
|
||||||
|
Alpha = Interpolation.ValueAt(Time.Current, 1f, 0f, HitStateUpdateTime, HitStateUpdateTime + 700);
|
||||||
|
}
|
||||||
|
|
||||||
|
LifetimeEnd = HitStateUpdateTime + 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RestoreHitAnimations()
|
||||||
|
{
|
||||||
|
UpdateState(ArmedState.Hit, force: true);
|
||||||
|
UpdateComboColour();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse)
|
if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse)
|
||||||
{
|
{
|
||||||
Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!);
|
switch (osuObject)
|
||||||
Scale = new Vector2(osuObject.HitObject.Scale);
|
{
|
||||||
|
case DrawableSlider slider:
|
||||||
|
Position = slider.TailCircle.ToSpaceOfOtherDrawable(slider.TailCircle.OriginPosition, Parent!);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
positionTransferred = true;
|
positionTransferred = true;
|
||||||
|
|
||||||
|
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,5 +370,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private partial class DefaultSliderBody : PlaySliderBody
|
private partial class DefaultSliderBody : PlaySliderBody
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region FOR EDITOR USE ONLY, DO NOT USE FOR ANY OTHER PURPOSE
|
||||||
|
|
||||||
|
internal void SuppressHitAnimations()
|
||||||
|
{
|
||||||
|
UpdateState(ArmedState.Idle);
|
||||||
|
HeadCircle.SuppressHitAnimations();
|
||||||
|
TailCircle.SuppressHitAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RestoreHitAnimations()
|
||||||
|
{
|
||||||
|
UpdateState(ArmedState.Hit);
|
||||||
|
HeadCircle.RestoreHitAnimations();
|
||||||
|
TailCircle.RestoreHitAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -125,5 +127,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (Slider != null)
|
if (Slider != null)
|
||||||
Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
|
Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region FOR EDITOR USE ONLY, DO NOT USE FOR ANY OTHER PURPOSE
|
||||||
|
|
||||||
|
internal void SuppressHitAnimations()
|
||||||
|
{
|
||||||
|
UpdateState(ArmedState.Idle);
|
||||||
|
UpdateComboColour();
|
||||||
|
|
||||||
|
// This method is called every frame in editor contexts, thus the lack of need for transforms.
|
||||||
|
|
||||||
|
if (Time.Current >= HitStateUpdateTime)
|
||||||
|
{
|
||||||
|
// More or less matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
|
||||||
|
AccentColour.Value = Color4.White;
|
||||||
|
Alpha = Interpolation.ValueAt(Time.Current, 1f, 0f, HitStateUpdateTime, HitStateUpdateTime + 700);
|
||||||
|
}
|
||||||
|
|
||||||
|
LifetimeEnd = HitStateUpdateTime + 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RestoreHitAnimations()
|
||||||
|
{
|
||||||
|
UpdateState(ArmedState.Hit);
|
||||||
|
UpdateComboColour();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,6 +252,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
protected void UpdateNestedSamples()
|
protected void UpdateNestedSamples()
|
||||||
{
|
{
|
||||||
|
this.PopulateNodeSamples();
|
||||||
|
|
||||||
// TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
// TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||||
HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault())?.With("slidertick");
|
HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault())?.With("slidertick");
|
||||||
|
|
||||||
|
@ -8,10 +8,12 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Configuration;
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -27,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
[Cached]
|
[Cached]
|
||||||
public partial class OsuPlayfield : Playfield
|
public partial class OsuPlayfield : Playfield
|
||||||
{
|
{
|
||||||
|
private readonly Container borderContainer;
|
||||||
private readonly PlayfieldBorder playfieldBorder;
|
private readonly PlayfieldBorder playfieldBorder;
|
||||||
private readonly ProxyContainer approachCircles;
|
private readonly ProxyContainer approachCircles;
|
||||||
private readonly ProxyContainer spinnerProxies;
|
private readonly ProxyContainer spinnerProxies;
|
||||||
@ -45,6 +48,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
protected override GameplayCursorContainer? CreateCursor() => new OsuCursorContainer();
|
protected override GameplayCursorContainer? CreateCursor() => new OsuCursorContainer();
|
||||||
|
|
||||||
|
public override Quad SkinnableComponentScreenSpaceDrawQuad => playfieldBorder.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
private readonly Container judgementAboveHitObjectLayer;
|
private readonly Container judgementAboveHitObjectLayer;
|
||||||
|
|
||||||
public OsuPlayfield()
|
public OsuPlayfield()
|
||||||
@ -54,7 +59,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
borderContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||||
|
},
|
||||||
Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
||||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||||
@ -151,6 +160,14 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
||||||
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 200);
|
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 200);
|
||||||
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 200);
|
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 200);
|
||||||
|
|
||||||
|
if (beatmap != null)
|
||||||
|
ApplyCircleSizeToPlayfieldBorder(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ApplyCircleSizeToPlayfieldBorder(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
borderContainer.Padding = new MarginPadding(OsuHitObject.OBJECT_RADIUS * -LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Difficulty.CircleSize, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
||||||
|
@ -85,6 +85,42 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
|||||||
AssertResult<Swell>(0, HitResult.IgnoreMiss);
|
AssertResult<Swell>(0, HitResult.IgnoreMiss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAlternatingIsRequired()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
Swell swell = new Swell
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000,
|
||||||
|
RequiredHits = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
List<ReplayFrame> frames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < swell.RequiredHits; i++)
|
||||||
|
{
|
||||||
|
double frameTime = 1000 + i * 50;
|
||||||
|
frames.Add(new TaikoReplayFrame(frameTime, TaikoAction.LeftCentre));
|
||||||
|
frames.Add(new TaikoReplayFrame(frameTime + 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformTest(frames, CreateBeatmap(swell));
|
||||||
|
|
||||||
|
AssertJudgementCount(11);
|
||||||
|
|
||||||
|
AssertResult<SwellTick>(0, HitResult.IgnoreHit);
|
||||||
|
for (int i = 1; i < swell.RequiredHits; i++)
|
||||||
|
AssertResult<SwellTick>(i, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
|
AssertResult<Swell>(0, HitResult.IgnoreMiss);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitNoneSwell()
|
public void TestHitNoneSwell()
|
||||||
{
|
{
|
||||||
|
46
osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs
Normal file
46
osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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.Rulesets.Taiko.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneTaikoModRelax : TaikoModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestRelax()
|
||||||
|
{
|
||||||
|
var beatmap = new TaikoBeatmap
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new Hit { StartTime = 0, Type = HitType.Centre, },
|
||||||
|
new Hit { StartTime = 250, Type = HitType.Rim, },
|
||||||
|
new DrumRoll { StartTime = 500, Duration = 500, },
|
||||||
|
new Swell { StartTime = 1250, Duration = 500 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
foreach (var ho in beatmap.HitObjects)
|
||||||
|
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||||
|
|
||||||
|
var replay = new TaikoAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
foreach (var frame in replay.Frames.OfType<TaikoReplayFrame>().Where(r => r.Actions.Any()))
|
||||||
|
frame.Actions = [TaikoAction.LeftCentre];
|
||||||
|
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TaikoModRelax(),
|
||||||
|
Beatmap = beatmap,
|
||||||
|
ReplayFrames = replay.Frames,
|
||||||
|
Autoplay = false,
|
||||||
|
PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
|||||||
Autoplay = false,
|
Autoplay = false,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(100, 1600),
|
new BreakPeriod(100, 1600),
|
||||||
},
|
},
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|
||||||
{
|
|
||||||
public class Peaks : Skill
|
|
||||||
{
|
|
||||||
private const double rhythm_skill_multiplier = 0.2 * final_multiplier;
|
|
||||||
private const double colour_skill_multiplier = 0.375 * final_multiplier;
|
|
||||||
private const double stamina_skill_multiplier = 0.375 * final_multiplier;
|
|
||||||
|
|
||||||
private const double final_multiplier = 0.0625;
|
|
||||||
|
|
||||||
private readonly Rhythm rhythm;
|
|
||||||
private readonly Colour colour;
|
|
||||||
private readonly Stamina stamina;
|
|
||||||
|
|
||||||
public double ColourDifficultyValue => colour.DifficultyValue() * colour_skill_multiplier;
|
|
||||||
public double RhythmDifficultyValue => rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
|
||||||
public double StaminaDifficultyValue => stamina.DifficultyValue() * stamina_skill_multiplier;
|
|
||||||
|
|
||||||
public Peaks(Mod[] mods)
|
|
||||||
: base(mods)
|
|
||||||
{
|
|
||||||
rhythm = new Rhythm(mods);
|
|
||||||
colour = new Colour(mods);
|
|
||||||
stamina = new Stamina(mods);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
|
|
||||||
/// <param name="values">The coefficients of the vector.</param>
|
|
||||||
private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
|
|
||||||
|
|
||||||
public override void Process(DifficultyHitObject current)
|
|
||||||
{
|
|
||||||
rhythm.Process(current);
|
|
||||||
colour.Process(current);
|
|
||||||
stamina.Process(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the combined star rating of the beatmap, calculated using peak strains from all sections of the map.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
|
||||||
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
|
||||||
/// </remarks>
|
|
||||||
public override double DifficultyValue()
|
|
||||||
{
|
|
||||||
List<double> peaks = new List<double>();
|
|
||||||
|
|
||||||
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
|
|
||||||
var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
|
|
||||||
var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList();
|
|
||||||
|
|
||||||
for (int i = 0; i < colourPeaks.Count; i++)
|
|
||||||
{
|
|
||||||
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
|
||||||
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
|
||||||
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;
|
|
||||||
|
|
||||||
double peak = norm(1.5, colourPeak, staminaPeak);
|
|
||||||
peak = norm(2, peak, rhythmPeak);
|
|
||||||
|
|
||||||
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
|
||||||
// These sections will not contribute to the difficulty.
|
|
||||||
if (peak > 0)
|
|
||||||
peaks.Add(peak);
|
|
||||||
}
|
|
||||||
|
|
||||||
double difficulty = 0;
|
|
||||||
double weight = 1;
|
|
||||||
|
|
||||||
foreach (double strain in peaks.OrderDescending())
|
|
||||||
{
|
|
||||||
difficulty += strain * weight;
|
|
||||||
weight *= 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
return difficulty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,6 +23,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
private const double difficulty_multiplier = 1.35;
|
private const double difficulty_multiplier = 1.35;
|
||||||
|
|
||||||
|
private const double final_multiplier = 0.0625;
|
||||||
|
private const double rhythm_skill_multiplier = 0.2 * final_multiplier;
|
||||||
|
private const double colour_skill_multiplier = 0.375 * final_multiplier;
|
||||||
|
private const double stamina_skill_multiplier = 0.375 * final_multiplier;
|
||||||
|
|
||||||
public override int Version => 20221107;
|
public override int Version => 20221107;
|
||||||
|
|
||||||
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
@ -34,7 +39,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
{
|
{
|
||||||
return new Skill[]
|
return new Skill[]
|
||||||
{
|
{
|
||||||
new Peaks(mods)
|
new Rhythm(mods),
|
||||||
|
new Colour(mods),
|
||||||
|
new Stamina(mods)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +79,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
if (beatmap.HitObjects.Count == 0)
|
if (beatmap.HitObjects.Count == 0)
|
||||||
return new TaikoDifficultyAttributes { Mods = mods };
|
return new TaikoDifficultyAttributes { Mods = mods };
|
||||||
|
|
||||||
var combined = (Peaks)skills[0];
|
Colour colour = (Colour)skills.First(x => x is Colour);
|
||||||
|
Rhythm rhythm = (Rhythm)skills.First(x => x is Rhythm);
|
||||||
|
Stamina stamina = (Stamina)skills.First(x => x is Stamina);
|
||||||
|
|
||||||
double colourRating = combined.ColourDifficultyValue * difficulty_multiplier;
|
double colourRating = colour.DifficultyValue() * colour_skill_multiplier * difficulty_multiplier;
|
||||||
double rhythmRating = combined.RhythmDifficultyValue * difficulty_multiplier;
|
double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier * difficulty_multiplier;
|
||||||
double staminaRating = combined.StaminaDifficultyValue * difficulty_multiplier;
|
double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier * difficulty_multiplier;
|
||||||
|
|
||||||
double combinedRating = combined.DifficultyValue() * difficulty_multiplier;
|
double combinedRating = combinedDifficultyValue(rhythm, colour, stamina) * difficulty_multiplier;
|
||||||
double starRating = rescale(combinedRating * 1.4);
|
double starRating = rescale(combinedRating * 1.4);
|
||||||
|
|
||||||
HitWindows hitWindows = new TaikoHitWindows();
|
HitWindows hitWindows = new TaikoHitWindows();
|
||||||
@ -109,5 +118,54 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
return 10.43 * Math.Log(sr / 8 + 1);
|
return 10.43 * Math.Log(sr / 8 + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the combined star rating of the beatmap, calculated using peak strains from all sections of the map.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
|
||||||
|
/// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
|
||||||
|
/// </remarks>
|
||||||
|
private double combinedDifficultyValue(Rhythm rhythm, Colour colour, Stamina stamina)
|
||||||
|
{
|
||||||
|
List<double> peaks = new List<double>();
|
||||||
|
|
||||||
|
var colourPeaks = colour.GetCurrentStrainPeaks().ToList();
|
||||||
|
var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList();
|
||||||
|
var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < colourPeaks.Count; i++)
|
||||||
|
{
|
||||||
|
double colourPeak = colourPeaks[i] * colour_skill_multiplier;
|
||||||
|
double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier;
|
||||||
|
double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier;
|
||||||
|
|
||||||
|
double peak = norm(1.5, colourPeak, staminaPeak);
|
||||||
|
peak = norm(2, peak, rhythmPeak);
|
||||||
|
|
||||||
|
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||||
|
// These sections will not contribute to the difficulty.
|
||||||
|
if (peak > 0)
|
||||||
|
peaks.Add(peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
double difficulty = 0;
|
||||||
|
double weight = 1;
|
||||||
|
|
||||||
|
foreach (double strain in peaks.OrderDescending())
|
||||||
|
{
|
||||||
|
difficulty += strain * weight;
|
||||||
|
weight *= 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
return difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the <i>p</i>-norm of an <i>n</i>-dimensional vector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="p">The value of <i>p</i> to calculate the norm for.</param>
|
||||||
|
/// <param name="values">The coefficients of the vector.</param>
|
||||||
|
private double norm(double p, params double[] values) => Math.Pow(values.Sum(x => Math.Pow(x, p)), 1 / p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,34 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModRelax : ModRelax
|
public class TaikoModRelax : ModRelax, IApplicableToDrawableHitObject
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katus.";
|
public override LocalisableString Description => @"No need to remember which key is correct anymore!";
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
var allActions = Enum.GetValues<TaikoAction>();
|
||||||
|
|
||||||
|
drawable.HitObjectApplied += dho =>
|
||||||
|
{
|
||||||
|
switch (dho)
|
||||||
|
{
|
||||||
|
case DrawableHit hit:
|
||||||
|
hit.HitActions = allActions;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSwell swell:
|
||||||
|
swell.MustAlternate = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of keys which can result in hits for this HitObject.
|
/// A list of keys which can result in hits for this HitObject.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TaikoAction[] HitActions { get; private set; }
|
public TaikoAction[] HitActions { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The action that caused this <see cref="DrawableHit"/> to be hit.
|
/// The action that caused this <see cref="DrawableHit"/> to be hit.
|
||||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the player must alternate centre and rim hits.
|
||||||
|
/// </summary>
|
||||||
|
public bool MustAlternate { get; internal set; } = true;
|
||||||
|
|
||||||
public DrawableSwell()
|
public DrawableSwell()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -292,7 +297,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre;
|
bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre;
|
||||||
|
|
||||||
// Ensure alternating centre and rim hits
|
// Ensure alternating centre and rim hits
|
||||||
if (lastWasCentre == isCentre)
|
if (lastWasCentre == isCentre && MustAlternate)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If we've already successfully judged a tick this frame, do not judge more.
|
// If we've already successfully judged a tick this frame, do not judge more.
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -29,7 +28,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
var beatmap = new Beatmap<HitObject>
|
var beatmap = new Beatmap<HitObject>
|
||||||
{
|
{
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(0, 649)
|
new BreakPeriod(0, 649)
|
||||||
}
|
}
|
||||||
@ -52,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 0 },
|
new HitCircle { StartTime = 0 },
|
||||||
new HitCircle { StartTime = 1_200 }
|
new HitCircle { StartTime = 1_200 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(100, 751)
|
new BreakPeriod(100, 751)
|
||||||
}
|
}
|
||||||
@ -75,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 0 },
|
new HitCircle { StartTime = 0 },
|
||||||
new HitCircle { StartTime = 1_298 }
|
new HitCircle { StartTime = 1_298 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(200, 850)
|
new BreakPeriod(200, 850)
|
||||||
}
|
}
|
||||||
@ -98,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 0 },
|
new HitCircle { StartTime = 0 },
|
||||||
new HitCircle { StartTime = 1200 }
|
new HitCircle { StartTime = 1200 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(1398, 2300)
|
new BreakPeriod(1398, 2300)
|
||||||
}
|
}
|
||||||
@ -121,7 +120,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 1100 },
|
new HitCircle { StartTime = 1100 },
|
||||||
new HitCircle { StartTime = 1500 }
|
new HitCircle { StartTime = 1500 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(0, 652)
|
new BreakPeriod(0, 652)
|
||||||
}
|
}
|
||||||
@ -145,7 +144,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 1_297 },
|
new HitCircle { StartTime = 1_297 },
|
||||||
new HitCircle { StartTime = 1_298 }
|
new HitCircle { StartTime = 1_298 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(200, 850)
|
new BreakPeriod(200, 850)
|
||||||
}
|
}
|
||||||
@ -168,7 +167,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 0 },
|
new HitCircle { StartTime = 0 },
|
||||||
new HitCircle { StartTime = 1_300 }
|
new HitCircle { StartTime = 1_300 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(200, 850)
|
new BreakPeriod(200, 850)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
new HitCircle { StartTime = 0 },
|
new HitCircle { StartTime = 0 },
|
||||||
new HitCircle { StartTime = 40_000 }
|
new HitCircle { StartTime = 40_000 }
|
||||||
},
|
},
|
||||||
Breaks = new List<BreakPeriod>
|
Breaks =
|
||||||
{
|
{
|
||||||
new BreakPeriod(10_000, 21_000)
|
new BreakPeriod(10_000, 21_000)
|
||||||
}
|
}
|
||||||
|
476
osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Normal file
476
osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneEditorBeatmapProcessor
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyBeatmap()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleObjectBeatmap()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTwoObjectsCloseTogether()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNote()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote { StartTime = 1000, Duration = 10000 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new ManiaRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteWithOverlappingNote()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote { StartTime = 1000, Duration = 10000 },
|
||||||
|
new Note { StartTime = 2000 },
|
||||||
|
new Note { StartTime = 12000 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new ManiaRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTwoObjectsFarApart()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 5000 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreaksAreFused()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 9000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(1200, 4000),
|
||||||
|
new BreakPeriod(5200, 8000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreaksAreSplit()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 5000 },
|
||||||
|
new HitCircle { StartTime = 9000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(1200, 8000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||||
|
Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200));
|
||||||
|
Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreaksAreNudged()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1100 },
|
||||||
|
new HitCircle { StartTime = 9000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(1200, 8000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1300));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualBreaksAreNotFused()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 9000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new ManualBreakPeriod(1200, 4000),
|
||||||
|
new ManualBreakPeriod(5200, 8000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||||
|
Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200));
|
||||||
|
Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualBreaksAreSplit()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 5000 },
|
||||||
|
new HitCircle { StartTime = 9000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new ManualBreakPeriod(1200, 8000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(4000));
|
||||||
|
Assert.That(beatmap.Breaks[1].StartTime, Is.EqualTo(5200));
|
||||||
|
Assert.That(beatmap.Breaks[1].EndTime, Is.EqualTo(8000));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualBreaksAreNotNudged()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 9000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new ManualBreakPeriod(1200, 8800),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(8800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreaksAtEndOfBeatmapAreRemoved()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(10000, 15000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualBreaksAtEndOfBeatmapAreRemoved()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new ManualBreakPeriod(10000, 15000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualBreaksAtEndOfBeatmapAreRemovedCorrectlyEvenWithConcurrentObjects()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote { StartTime = 1000, EndTime = 20000 },
|
||||||
|
new HoldNote { StartTime = 2000, EndTime = 3000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new ManualBreakPeriod(10000, 15000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreaksAtStartOfBeatmapAreRemoved()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 10000 },
|
||||||
|
new HitCircle { StartTime = 11000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(0, 9000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualBreaksAtStartOfBeatmapAreRemoved()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 10000 },
|
||||||
|
new HitCircle { StartTime = 11000 },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new ManualBreakPeriod(0, 9000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.That(beatmap.Breaks, Is.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
public class TestSceneTimedDifficultyCalculation
|
public class TestSceneTimedDifficultyCalculation
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAttributesGeneratedForAllNonSkippedObjects()
|
public void TestAttributesGeneratedForEachObjectOnce()
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap<TestHitObject>
|
var beatmap = new Beatmap<TestHitObject>
|
||||||
{
|
{
|
||||||
@ -40,15 +40,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||||
|
|
||||||
Assert.That(attribs.Count, Is.EqualTo(4));
|
Assert.That(attribs.Count, Is.EqualTo(3));
|
||||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||||
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||||
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1]); // From the nested object.
|
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||||
assertEquals(attribs[3], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAttributesNotGeneratedForSkippedObjects()
|
public void TestAttributesGeneratedForSkippedObjects()
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap<TestHitObject>
|
var beatmap = new Beatmap<TestHitObject>
|
||||||
{
|
{
|
||||||
@ -72,35 +71,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||||
|
|
||||||
Assert.That(attribs.Count, Is.EqualTo(1));
|
Assert.That(attribs.Count, Is.EqualTo(3));
|
||||||
assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestNestedObjectOnlyAddsParentOnce()
|
|
||||||
{
|
|
||||||
var beatmap = new Beatmap<TestHitObject>
|
|
||||||
{
|
|
||||||
HitObjects =
|
|
||||||
{
|
|
||||||
new TestHitObject
|
|
||||||
{
|
|
||||||
StartTime = 1,
|
|
||||||
Skip = true,
|
|
||||||
Nested = 2
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
|
||||||
|
|
||||||
Assert.That(attribs.Count, Is.EqualTo(2));
|
|
||||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||||
assertEquals(attribs[1], beatmap.HitObjects[0]);
|
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||||
|
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSkippedLastObjectAddedInLastIteration()
|
public void TestAttributesGeneratedOnceForSkippedObjects()
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap<TestHitObject>
|
var beatmap = new Beatmap<TestHitObject>
|
||||||
{
|
{
|
||||||
@ -110,6 +88,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
new TestHitObject
|
new TestHitObject
|
||||||
{
|
{
|
||||||
StartTime = 2,
|
StartTime = 2,
|
||||||
|
Nested = 5,
|
||||||
Skip = true
|
Skip = true
|
||||||
},
|
},
|
||||||
new TestHitObject
|
new TestHitObject
|
||||||
@ -122,8 +101,10 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||||
|
|
||||||
Assert.That(attribs.Count, Is.EqualTo(1));
|
Assert.That(attribs.Count, Is.EqualTo(3));
|
||||||
assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||||
|
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||||
|
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertEquals(TimedDifficultyAttributes attribs, params HitObject[] expected)
|
private void assertEquals(TimedDifficultyAttributes attribs, params HitObject[] expected)
|
||||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230809.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230809.osk
Normal file
Binary file not shown.
@ -62,6 +62,10 @@ namespace osu.Game.Tests.Skins
|
|||||||
"Archives/modified-argon-20231108.osk",
|
"Archives/modified-argon-20231108.osk",
|
||||||
// Covers "Argon" performance points counter
|
// Covers "Argon" performance points counter
|
||||||
"Archives/modified-argon-20240305.osk",
|
"Archives/modified-argon-20240305.osk",
|
||||||
|
// Covers default rank display
|
||||||
|
"Archives/modified-default-20230809.osk",
|
||||||
|
// Covers legacy rank display
|
||||||
|
"Archives/modified-classic-20230809.osk"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
// 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.Game.Online.API;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Tests.Visual.OnlinePlay;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class TestSceneDailyChallenge : OnlinePlayTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDailyChallenge()
|
||||||
|
{
|
||||||
|
var room = new Room
|
||||||
|
{
|
||||||
|
RoomID = { Value = 1234 },
|
||||||
|
Name = { Value = "Daily Challenge: June 4, 2024" },
|
||||||
|
Playlist =
|
||||||
|
{
|
||||||
|
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
|
||||||
|
{
|
||||||
|
RequiredMods = [new APIMod(new OsuModTraceable())],
|
||||||
|
AllowedMods = [new APIMod(new OsuModDoubleTime())]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
|
||||||
|
Category = { Value = RoomCategory.DailyChallenge }
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||||
|
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class TestSceneDailyChallengeCarousel : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
private readonly Bindable<Room> room = new Bindable<Room>(new Room());
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
|
||||||
|
{
|
||||||
|
Model = { BindTarget = room }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicAppearance()
|
||||||
|
{
|
||||||
|
DailyChallengeCarousel carousel = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
carousel = new DailyChallengeCarousel
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||||
|
{
|
||||||
|
if (carousel.IsNotNull())
|
||||||
|
carousel.Width = width;
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||||
|
{
|
||||||
|
if (carousel.IsNotNull())
|
||||||
|
carousel.Height = height;
|
||||||
|
});
|
||||||
|
AddRepeatStep("add content", () => carousel.Add(new FakeContent()), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIntegration()
|
||||||
|
{
|
||||||
|
GridContainer grid = null!;
|
||||||
|
DailyChallengeEventFeed feed = null!;
|
||||||
|
DailyChallengeScoreBreakdown breakdown = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
grid = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RowDimensions =
|
||||||
|
[
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension()
|
||||||
|
],
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new DailyChallengeCarousel
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new DailyChallengeTimeRemainingRing(),
|
||||||
|
breakdown = new DailyChallengeScoreBreakdown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
feed = new DailyChallengeEventFeed
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||||
|
{
|
||||||
|
if (grid.IsNotNull())
|
||||||
|
grid.Width = width;
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||||
|
{
|
||||||
|
if (grid.IsNotNull())
|
||||||
|
grid.Height = height;
|
||||||
|
});
|
||||||
|
AddSliderStep("update time remaining", 0f, 1f, 0f, progress =>
|
||||||
|
{
|
||||||
|
var startedTimeAgo = TimeSpan.FromHours(24) * progress;
|
||||||
|
room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo;
|
||||||
|
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||||
|
});
|
||||||
|
AddStep("add normal score", () =>
|
||||||
|
{
|
||||||
|
var testScore = TestResources.CreateTestScoreInfo();
|
||||||
|
testScore.TotalScore = RNG.Next(1_000_000);
|
||||||
|
|
||||||
|
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, null));
|
||||||
|
breakdown.AddNewScore(testScore);
|
||||||
|
});
|
||||||
|
AddStep("add new user best", () =>
|
||||||
|
{
|
||||||
|
var testScore = TestResources.CreateTestScoreInfo();
|
||||||
|
testScore.TotalScore = RNG.Next(1_000_000);
|
||||||
|
|
||||||
|
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 1000)));
|
||||||
|
breakdown.AddNewScore(testScore);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class FakeContent : CompositeDrawable
|
||||||
|
{
|
||||||
|
private OsuSpriteText text = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Colour4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1),
|
||||||
|
},
|
||||||
|
text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Fake Content " + (char)('A' + RNG.Next(26)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
text.FadeOut(500, Easing.OutQuint)
|
||||||
|
.Then().FadeIn(500, Easing.OutQuint)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class TestSceneDailyChallengeEventFeed : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicAppearance()
|
||||||
|
{
|
||||||
|
DailyChallengeEventFeed feed = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
feed = new DailyChallengeEventFeed
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||||
|
{
|
||||||
|
if (feed.IsNotNull())
|
||||||
|
feed.Width = width;
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||||
|
{
|
||||||
|
if (feed.IsNotNull())
|
||||||
|
feed.Height = height;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add normal score", () =>
|
||||||
|
{
|
||||||
|
var testScore = TestResources.CreateTestScoreInfo();
|
||||||
|
testScore.TotalScore = RNG.Next(1_000_000);
|
||||||
|
|
||||||
|
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, null));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add new user best", () =>
|
||||||
|
{
|
||||||
|
var testScore = TestResources.CreateTestScoreInfo();
|
||||||
|
testScore.TotalScore = RNG.Next(1_000_000);
|
||||||
|
|
||||||
|
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 1000)));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add top 10 score", () =>
|
||||||
|
{
|
||||||
|
var testScore = TestResources.CreateTestScoreInfo();
|
||||||
|
testScore.TotalScore = RNG.Next(1_000_000);
|
||||||
|
|
||||||
|
feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 10)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class TestSceneDailyChallengeScoreBreakdown : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicAppearance()
|
||||||
|
{
|
||||||
|
DailyChallengeScoreBreakdown breakdown = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
breakdown = new DailyChallengeScoreBreakdown
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||||
|
{
|
||||||
|
if (breakdown.IsNotNull())
|
||||||
|
breakdown.Width = width;
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||||
|
{
|
||||||
|
if (breakdown.IsNotNull())
|
||||||
|
breakdown.Height = height;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1]));
|
||||||
|
AddStep("add new score", () =>
|
||||||
|
{
|
||||||
|
var testScore = TestResources.CreateTestScoreInfo();
|
||||||
|
testScore.TotalScore = RNG.Next(1_000_000);
|
||||||
|
|
||||||
|
breakdown.AddNewScore(testScore);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class TestSceneDailyChallengeTimeRemainingRing : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly Bindable<Room> room = new Bindable<Room>(new Room());
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
|
||||||
|
{
|
||||||
|
Model = { BindTarget = room }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicAppearance()
|
||||||
|
{
|
||||||
|
DailyChallengeTimeRemainingRing ring = null!;
|
||||||
|
|
||||||
|
AddStep("create content", () => Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4,
|
||||||
|
},
|
||||||
|
ring = new DailyChallengeTimeRemainingRing
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust width", 0.1f, 1, 1, width =>
|
||||||
|
{
|
||||||
|
if (ring.IsNotNull())
|
||||||
|
ring.Width = width;
|
||||||
|
});
|
||||||
|
AddSliderStep("adjust height", 0.1f, 1, 1, height =>
|
||||||
|
{
|
||||||
|
if (ring.IsNotNull())
|
||||||
|
ring.Height = height;
|
||||||
|
});
|
||||||
|
AddStep("just started", () =>
|
||||||
|
{
|
||||||
|
room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1);
|
||||||
|
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||||
|
});
|
||||||
|
AddStep("midway through", () =>
|
||||||
|
{
|
||||||
|
room.Value.StartDate.Value = DateTimeOffset.Now.AddHours(-12);
|
||||||
|
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||||
|
});
|
||||||
|
AddStep("nearing end", () =>
|
||||||
|
{
|
||||||
|
room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-1).AddMinutes(8);
|
||||||
|
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||||
|
});
|
||||||
|
AddStep("already ended", () =>
|
||||||
|
{
|
||||||
|
room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-2);
|
||||||
|
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||||
|
});
|
||||||
|
AddSliderStep("manual progress", 0f, 1f, 0f, progress =>
|
||||||
|
{
|
||||||
|
var startedTimeAgo = TimeSpan.FromHours(24) * progress;
|
||||||
|
room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo;
|
||||||
|
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -84,6 +84,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
targetContainer = getTargetContainer();
|
targetContainer = getTargetContainer();
|
||||||
initialRotation = targetContainer!.Rotation;
|
initialRotation = targetContainer!.Rotation;
|
||||||
|
|
||||||
|
base.Begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float rotation, Vector2? origin = null)
|
public override void Update(float rotation, Vector2? origin = null)
|
||||||
@ -102,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
targetContainer = null;
|
targetContainer = null;
|
||||||
initialRotation = null;
|
initialRotation = null;
|
||||||
|
|
||||||
|
base.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSelectedObjects()
|
public void TestSelectedObjects()
|
||||||
{
|
{
|
||||||
|
@ -193,5 +193,20 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||||
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
|
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatDivisor()
|
||||||
|
{
|
||||||
|
AddStep("Set custom beat divisor", () => Editor.Dependencies.Get<BindableBeatDivisor>().SetArbitraryDivisor(7));
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
AddAssert("Hash updated", () => !string.IsNullOrEmpty(EditorBeatmap.BeatmapInfo.BeatmapSet?.Hash));
|
||||||
|
AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7));
|
||||||
|
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
|
AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7));
|
||||||
|
AddAssert("Correct beat divisor actually active", () => Editor.BeatDivisor, () => Is.EqualTo(7));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,24 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(2000)] // chosen to be after last object in the map
|
||||||
|
[TestCase(22000)] // chosen to be in the middle of the last spinner
|
||||||
|
public void TestGameplayTestAtEndOfBeatmap(int offsetFromEnd)
|
||||||
|
{
|
||||||
|
AddStep($"seek to end minus {offsetFromEnd}ms", () => EditorClock.Seek(importedBeatmapSet.MaxLength - offsetFromEnd));
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("player pushed", () => Stack.CurrentScreen is EditorPlayer);
|
||||||
|
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCancelGameplayTestWithUnsavedChanges()
|
public void TestCancelGameplayTestWithUnsavedChanges()
|
||||||
{
|
{
|
||||||
|
@ -13,9 +13,12 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
@ -79,10 +82,10 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPopoverHasFocus()
|
public void TestPopoverHasNoFocus()
|
||||||
{
|
{
|
||||||
clickSamplePiece(0);
|
clickSamplePiece(0);
|
||||||
samplePopoverHasFocus();
|
samplePopoverHasNoFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -226,6 +229,84 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL);
|
samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPopoverAddSampleAddition()
|
||||||
|
{
|
||||||
|
clickSamplePiece(0);
|
||||||
|
|
||||||
|
setBankViaPopover(HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
|
toggleAdditionViaPopover(0);
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
setAdditionBankViaPopover(HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
|
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
|
toggleAdditionViaPopover(0);
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNodeSamplePopover()
|
||||||
|
{
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.Clear();
|
||||||
|
EditorBeatmap.Add(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 256),
|
||||||
|
StartTime = 0,
|
||||||
|
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
|
||||||
|
Samples =
|
||||||
|
{
|
||||||
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||||
|
},
|
||||||
|
NodeSamples =
|
||||||
|
{
|
||||||
|
new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
|
||||||
|
new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
clickNodeSamplePiece(0, 1);
|
||||||
|
|
||||||
|
setBankViaPopover(HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_NORMAL);
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
|
||||||
|
toggleAdditionViaPopover(0);
|
||||||
|
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_NORMAL);
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
|
||||||
|
|
||||||
|
setAdditionBankViaPopover(HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
|
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_NORMAL);
|
||||||
|
hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSampleAdditionBank(0, 1, HitSampleInfo.BANK_DRUM);
|
||||||
|
|
||||||
|
toggleAdditionViaPopover(0);
|
||||||
|
|
||||||
|
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT);
|
||||||
|
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL);
|
||||||
|
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL);
|
||||||
|
|
||||||
|
setVolumeViaPopover(10);
|
||||||
|
|
||||||
|
hitObjectNodeHasSampleVolume(0, 0, 100);
|
||||||
|
hitObjectNodeHasSampleVolume(0, 1, 10);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHotkeysMultipleSelectionWithSameSampleBank()
|
public void TestHotkeysMultipleSelectionWithSameSampleBank()
|
||||||
{
|
{
|
||||||
@ -329,13 +410,21 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
private void samplePopoverHasFocus() => AddUntilStep("sample popover textbox focused", () =>
|
private void clickNodeSamplePiece(int objectIndex, int nodeIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node sample piece", () =>
|
||||||
|
{
|
||||||
|
var samplePiece = this.ChildrenOfType<NodeSamplePointPiece>().Where(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)).ToArray()[nodeIndex];
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(samplePiece);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void samplePopoverHasNoFocus() => AddUntilStep("sample popover textbox not focused", () =>
|
||||||
{
|
{
|
||||||
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
|
||||||
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
|
var slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().Single();
|
||||||
var textbox = slider?.ChildrenOfType<OsuTextBox>().Single();
|
var textbox = slider?.ChildrenOfType<OsuTextBox>().Single();
|
||||||
|
|
||||||
return textbox?.HasFocus == true;
|
return textbox?.HasFocus == false;
|
||||||
});
|
});
|
||||||
|
|
||||||
private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () =>
|
private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () =>
|
||||||
@ -372,7 +461,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private void dismissPopover()
|
private void dismissPopover()
|
||||||
{
|
{
|
||||||
AddStep("unfocus textbox", () => InputManager.Key(Key.Escape));
|
|
||||||
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
|
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
|
||||||
AddUntilStep("wait for dismiss", () => !this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Any(popover => popover.IsPresent));
|
AddUntilStep("wait for dismiss", () => !this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Any(popover => popover.IsPresent));
|
||||||
}
|
}
|
||||||
@ -390,6 +478,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
return h.Samples.All(o => o.Volume == volume);
|
return h.Samples.All(o => o.Volume == volume);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void hitObjectNodeHasSampleVolume(int objectIndex, int nodeIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has volume {volume}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||||
|
return h is not null && h.NodeSamples[nodeIndex].All(o => o.Volume == volume);
|
||||||
|
});
|
||||||
|
|
||||||
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
|
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
|
||||||
{
|
{
|
||||||
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
|
||||||
@ -401,6 +495,26 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.Key(Key.Enter);
|
InputManager.Key(Key.Enter);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void setAdditionBankViaPopover(string bank) => AddStep($"set addition bank {bank} via popover", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
|
||||||
|
var textBox = popover.ChildrenOfType<LabelledTextBox>().ToArray()[1];
|
||||||
|
textBox.Current.Value = bank;
|
||||||
|
// force a commit via keyboard.
|
||||||
|
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
|
||||||
|
((IFocusManager)InputManager).ChangeFocus(textBox);
|
||||||
|
InputManager.Key(Key.Enter);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void toggleAdditionViaPopover(int index) => AddStep($"toggle addition {index} via popover", () =>
|
||||||
|
{
|
||||||
|
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().First();
|
||||||
|
var ternaryButton = popover.ChildrenOfType<DrawableTernaryButton>().ToArray()[index];
|
||||||
|
InputManager.MoveMouseTo(ternaryButton);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
private void hitObjectHasSamples(int objectIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} has samples {string.Join(',', samples)}", () =>
|
private void hitObjectHasSamples(int objectIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} has samples {string.Join(',', samples)}", () =>
|
||||||
{
|
{
|
||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
@ -412,5 +526,41 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
return h.Samples.All(o => o.Bank == bank);
|
return h.Samples.All(o => o.Bank == bank);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void hitObjectHasSampleNormalBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has normal bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
|
return h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectHasSampleAdditionBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has addition bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
|
return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||||
|
return h is not null && h.NodeSamples[nodeIndex].Select(s => s.Name).SequenceEqual(samples);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectNodeHasSampleBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||||
|
return h is not null && h.NodeSamples[nodeIndex].All(o => o.Bank == bank);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectNodeHasSampleNormalBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has normal bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||||
|
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has addition bank {bank}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats;
|
||||||
|
return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
public partial class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene
|
public partial class TestScenePositionSnapGrid : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private Container content;
|
private Container content;
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
@ -33,28 +33,34 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
},
|
},
|
||||||
content = new Container
|
content = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly object[][] test_cases =
|
private static readonly object[][] test_cases =
|
||||||
{
|
{
|
||||||
new object[] { new Vector2(0, 0), new Vector2(10, 10) },
|
new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f },
|
||||||
new object[] { new Vector2(240, 180), new Vector2(10, 15) },
|
new object[] { new Vector2(240, 180), new Vector2(10, 15), 10f },
|
||||||
new object[] { new Vector2(160, 120), new Vector2(30, 20) },
|
new object[] { new Vector2(160, 120), new Vector2(30, 20), -10f },
|
||||||
new object[] { new Vector2(480, 360), new Vector2(100, 100) },
|
new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f },
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(test_cases))]
|
[TestCaseSource(nameof(test_cases))]
|
||||||
public void TestRectangularGrid(Vector2 position, Vector2 spacing)
|
public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotation)
|
||||||
{
|
{
|
||||||
RectangularPositionSnapGrid grid = null;
|
RectangularPositionSnapGrid grid = null;
|
||||||
|
|
||||||
AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position)
|
AddStep("create grid", () =>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Child = grid = new RectangularPositionSnapGrid
|
||||||
Spacing = spacing
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
grid.StartPosition.Value = position;
|
||||||
|
grid.Spacing.Value = spacing;
|
||||||
|
grid.GridLineRotation.Value = rotation;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
||||||
@ -86,7 +92,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
|
updatePosition(GetContainingInputManager()!.CurrentState.Mouse.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
@ -357,6 +358,51 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count);
|
AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragSelectionDuringPlacement()
|
||||||
|
{
|
||||||
|
var addedObjects = new[]
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 300,
|
||||||
|
Path = new SliderPath([
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(200)),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
};
|
||||||
|
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||||
|
|
||||||
|
AddStep("seek to 700", () => EditorClock.Seek(700));
|
||||||
|
AddStep("select spinner placement tool", () =>
|
||||||
|
{
|
||||||
|
InputManager.Key(Key.Number4);
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<OsuHitObjectComposer>().Single());
|
||||||
|
});
|
||||||
|
AddStep("begin spinner placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("seek to 1500", () => EditorClock.Seek(1500));
|
||||||
|
|
||||||
|
AddStep("start dragging", () =>
|
||||||
|
{
|
||||||
|
var blueprintQuad = blueprintContainer.SelectionBlueprints[1].ScreenSpaceDrawQuad;
|
||||||
|
var dragStartPos = (blueprintQuad.TopLeft + blueprintQuad.BottomLeft) / 2 - new Vector2(30, 0);
|
||||||
|
InputManager.MoveMouseTo(dragStartPos);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select entire object", () =>
|
||||||
|
{
|
||||||
|
var blueprintQuad = blueprintContainer.SelectionBlueprints[1].ScreenSpaceDrawQuad;
|
||||||
|
var dragStartPos = (blueprintQuad.TopRight + blueprintQuad.BottomRight) / 2 + new Vector2(30, 0);
|
||||||
|
InputManager.MoveMouseTo(dragStartPos);
|
||||||
|
});
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddUntilStep("hitobject selected", () => EditorBeatmap.SelectedHitObjects, () => NUnit.Framework.Contains.Item(addedObjects[0]));
|
||||||
|
AddAssert("placement committed", () => EditorBeatmap.HitObjects, () => Has.Count.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
|
private void assertSelectionIs(IEnumerable<HitObject> hitObjects)
|
||||||
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
|
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -19,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapOffsetControl : OsuTestScene
|
public partial class TestSceneBeatmapOffsetControl : OsuTestScene
|
||||||
{
|
{
|
||||||
private BeatmapOffsetControl offsetControl;
|
private BeatmapOffsetControl offsetControl = null!;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
@ -137,5 +135,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCalibrationNoChange()
|
||||||
|
{
|
||||||
|
const double average_error = 0;
|
||||||
|
|
||||||
|
AddAssert("Offset is neutral", () => offsetControl.Current.Value == 0);
|
||||||
|
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
|
AddStep("Set reference score", () =>
|
||||||
|
{
|
||||||
|
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||||
|
{
|
||||||
|
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
|
||||||
|
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
|
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||||
|
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error);
|
||||||
|
|
||||||
|
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||||
|
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||||
|
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public partial class TestSceneSkinnableRankDisplay : SkinnableHUDComponentTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||||
|
|
||||||
|
private Bindable<ScoreRank> rank => (Bindable<ScoreRank>)scoreProcessor.Rank;
|
||||||
|
|
||||||
|
protected override Drawable CreateDefaultImplementation() => new DefaultRankDisplay();
|
||||||
|
|
||||||
|
protected override Drawable CreateLegacyImplementation() => new LegacyRankDisplay();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangingRank()
|
||||||
|
{
|
||||||
|
AddStep("Set rank to SS Hidden", () => rank.Value = ScoreRank.XH);
|
||||||
|
AddStep("Set rank to SS", () => rank.Value = ScoreRank.X);
|
||||||
|
AddStep("Set rank to S Hidden", () => rank.Value = ScoreRank.SH);
|
||||||
|
AddStep("Set rank to S", () => rank.Value = ScoreRank.S);
|
||||||
|
AddStep("Set rank to A", () => rank.Value = ScoreRank.A);
|
||||||
|
AddStep("Set rank to B", () => rank.Value = ScoreRank.B);
|
||||||
|
AddStep("Set rank to C", () => rank.Value = ScoreRank.C);
|
||||||
|
AddStep("Set rank to D", () => rank.Value = ScoreRank.D);
|
||||||
|
AddStep("Set rank to F", () => rank.Value = ScoreRank.F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,8 +14,11 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osu.Game.Storyboards.Drawables;
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
using osu.Game.Tests.Gameplay;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -28,14 +31,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private DrawableStoryboard? storyboard;
|
private DrawableStoryboard? storyboard;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private GameplayState testGameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestStoryboard()
|
public void TestStoryboard()
|
||||||
{
|
{
|
||||||
AddStep("Restart", restart);
|
AddStep("Restart", restart);
|
||||||
AddToggleStep("Passing", passing =>
|
AddToggleStep("Toggle passing state", passing => testGameplayState.HealthProcessor.Health.Value = passing ? 1 : 0);
|
||||||
{
|
|
||||||
if (storyboard != null) storyboard.Passing = passing;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -109,7 +112,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
storyboardContainer.Clock = new FramedClock(Beatmap.Value.Track);
|
storyboardContainer.Clock = new FramedClock(Beatmap.Value.Track);
|
||||||
|
|
||||||
storyboard = toLoad.CreateDrawable(SelectedMods.Value);
|
storyboard = toLoad.CreateDrawable(SelectedMods.Value);
|
||||||
storyboard.Passing = false;
|
|
||||||
|
|
||||||
storyboardContainer.Add(storyboard);
|
storyboardContainer.Add(storyboard);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false);
|
AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false);
|
||||||
|
AddStep("disable return to top on idle", () => Game.ChildrenOfType<ButtonSystem>().Single().ReturnToTopOnIdle = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -644,7 +644,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("open mod overlay", () => this.ChildrenOfType<RoomSubScreen.UserModSelectButton>().Single().TriggerClick());
|
AddStep("open mod overlay", () => this.ChildrenOfType<UserModSelectButton>().Single().TriggerClick());
|
||||||
|
|
||||||
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddUntilStep("wait for join", () => RoomJoined);
|
AddUntilStep("wait for join", () => RoomJoined);
|
||||||
|
|
||||||
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||||
|
|
||||||
AddUntilStep("mod select contents loaded",
|
AddUntilStep("mod select contents loaded",
|
||||||
() => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
() => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
||||||
@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddUntilStep("wait for join", () => RoomJoined);
|
AddUntilStep("wait for join", () => RoomJoined);
|
||||||
|
|
||||||
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||||
AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
|
AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
|
||||||
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
{
|
{
|
||||||
AddStep("create", () =>
|
AddStep("create", () =>
|
||||||
{
|
{
|
||||||
Cell(0, 0).Children = new Drawable[]
|
ContentContainer.Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
|
184
osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs
Normal file
184
osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public partial class TestSceneLeaderboardScoreV2 : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
private FillFlowContainer? fillFlow;
|
||||||
|
private OsuSpriteText? drawWidthText;
|
||||||
|
private float relativeWidth;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// TODO: invalidation seems to be one-off when clicking slider to a certain value, so drag for now
|
||||||
|
// doesn't seem to happen in-game (when toggling window mode)
|
||||||
|
AddSliderStep("change relative width", 0, 1f, 0.6f, v =>
|
||||||
|
{
|
||||||
|
relativeWidth = v;
|
||||||
|
if (fillFlow != null) fillFlow.Width = v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
fillFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = relativeWidth,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0f, 2f),
|
||||||
|
Shear = new Vector2(OsuGame.SHEAR, 0)
|
||||||
|
},
|
||||||
|
drawWidthText = new OsuSpriteText(),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var scoreInfo in getTestScores())
|
||||||
|
{
|
||||||
|
fillFlow.Add(new LeaderboardScoreV2(scoreInfo, scoreInfo.Position, scoreInfo.User.Id == 2)
|
||||||
|
{
|
||||||
|
Shear = Vector2.Zero,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var score in fillFlow.Children)
|
||||||
|
score.Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
|
if (drawWidthText != null) drawWidthText.Text = $"DrawWidth: {fillFlow?.DrawWidth}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScoreInfo[] getTestScores()
|
||||||
|
{
|
||||||
|
var scores = new[]
|
||||||
|
{
|
||||||
|
new ScoreInfo
|
||||||
|
{
|
||||||
|
Position = 999,
|
||||||
|
Rank = ScoreRank.X,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = RNG.Next(1_800_000, 2_000_000),
|
||||||
|
MaximumStatistics = { { HitResult.Great, 3000 } },
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Id = 6602580,
|
||||||
|
Username = @"waaiiru",
|
||||||
|
CountryCode = CountryCode.ES,
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
|
||||||
|
},
|
||||||
|
Date = DateTimeOffset.Now.AddYears(-2),
|
||||||
|
},
|
||||||
|
new ScoreInfo
|
||||||
|
{
|
||||||
|
Position = 22333,
|
||||||
|
Rank = ScoreRank.S,
|
||||||
|
Accuracy = 0.1f,
|
||||||
|
MaxCombo = 32040,
|
||||||
|
TotalScore = RNG.Next(1_200_000, 1_500_000),
|
||||||
|
MaximumStatistics = { { HitResult.Great, 3000 } },
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Id = 1541390,
|
||||||
|
Username = @"Toukai",
|
||||||
|
CountryCode = CountryCode.CA,
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
|
},
|
||||||
|
Date = DateTimeOffset.Now.AddMonths(-6),
|
||||||
|
},
|
||||||
|
TestResources.CreateTestScoreInfo(),
|
||||||
|
new ScoreInfo
|
||||||
|
{
|
||||||
|
Position = 110000,
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = RNG.Next(1_000_000, 1_200_000),
|
||||||
|
MaximumStatistics = { { HitResult.Great, 3000 } },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Username = @"No cover",
|
||||||
|
CountryCode = CountryCode.BR,
|
||||||
|
},
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
},
|
||||||
|
new ScoreInfo
|
||||||
|
{
|
||||||
|
Position = 110000,
|
||||||
|
Rank = ScoreRank.D,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = RNG.Next(500_000, 1_000_000),
|
||||||
|
MaximumStatistics = { { HitResult.Great, 3000 } },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Id = 226597,
|
||||||
|
Username = @"WWWWWWWWWWWWWWWWWWWW",
|
||||||
|
CountryCode = CountryCode.US,
|
||||||
|
},
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
scores[2].Rank = ScoreRank.A;
|
||||||
|
scores[2].TotalScore = RNG.Next(120_000, 400_000);
|
||||||
|
scores[2].MaximumStatistics[HitResult.Great] = 3000;
|
||||||
|
|
||||||
|
scores[1].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight() };
|
||||||
|
scores[2].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight(), new OsuModClassic() };
|
||||||
|
scores[3].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight(), new OsuModClassic(), new OsuModDifficultyAdjust() };
|
||||||
|
scores[4].Mods = new ManiaRuleset().CreateAllMods().ToArray();
|
||||||
|
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
204
osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs
Normal file
204
osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
using osu.Game.Screens.SelectV2.Footer;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public partial class TestSceneSongSelectV2 : ScreenTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly ScreenFooter screenScreenFooter;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OsuLogo logo;
|
||||||
|
|
||||||
|
public TestSceneSongSelectV2()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new PopoverContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = screenScreenFooter = new ScreenFooter
|
||||||
|
{
|
||||||
|
OnBack = () => Stack.CurrentScreen.Exit(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logo = new OsuLogo
|
||||||
|
{
|
||||||
|
Alpha = 0f,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Stack.ScreenPushed += updateFooter;
|
||||||
|
Stack.ScreenExited += updateFooter;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("load screen", () => Stack.Push(new SongSelectV2()));
|
||||||
|
AddUntilStep("wait for load", () => Stack.CurrentScreen is SongSelectV2 songSelect && songSelect.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Footer
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMods()
|
||||||
|
{
|
||||||
|
AddStep("one mod", () => SelectedMods.Value = new List<Mod> { new OsuModHidden() });
|
||||||
|
AddStep("two mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock() });
|
||||||
|
AddStep("three mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() });
|
||||||
|
AddStep("four mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() });
|
||||||
|
AddStep("five mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() });
|
||||||
|
|
||||||
|
AddStep("modified", () => SelectedMods.Value = new List<Mod> { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||||
|
AddStep("modified + one", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||||
|
AddStep("modified + two", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||||
|
AddStep("modified + three", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||||
|
AddStep("modified + four", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
||||||
|
|
||||||
|
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
AddWaitStep("wait", 3);
|
||||||
|
AddStep("one mod", () => SelectedMods.Value = new List<Mod> { new OsuModHidden() });
|
||||||
|
|
||||||
|
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
AddWaitStep("wait", 3);
|
||||||
|
AddStep("five mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowOptions()
|
||||||
|
{
|
||||||
|
AddStep("enable options", () =>
|
||||||
|
{
|
||||||
|
var optionsButton = this.ChildrenOfType<ScreenFooterButton>().Last();
|
||||||
|
|
||||||
|
optionsButton.Enabled.Value = true;
|
||||||
|
optionsButton.TriggerClick();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestState()
|
||||||
|
{
|
||||||
|
AddToggleStep("set options enabled state", state => this.ChildrenOfType<ScreenFooterButton>().Last().Enabled.Value = state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add these test cases when functionality is implemented.
|
||||||
|
// [Test]
|
||||||
|
// public void TestFooterRandom()
|
||||||
|
// {
|
||||||
|
// AddStep("press F2", () => InputManager.Key(Key.F2));
|
||||||
|
// AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// [Test]
|
||||||
|
// public void TestFooterRandomViaMouse()
|
||||||
|
// {
|
||||||
|
// AddStep("click button", () =>
|
||||||
|
// {
|
||||||
|
// InputManager.MoveMouseTo(randomButton);
|
||||||
|
// InputManager.Click(MouseButton.Left);
|
||||||
|
// });
|
||||||
|
// AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// [Test]
|
||||||
|
// public void TestFooterRewind()
|
||||||
|
// {
|
||||||
|
// AddStep("press Shift+F2", () =>
|
||||||
|
// {
|
||||||
|
// InputManager.PressKey(Key.LShift);
|
||||||
|
// InputManager.PressKey(Key.F2);
|
||||||
|
// InputManager.ReleaseKey(Key.F2);
|
||||||
|
// InputManager.ReleaseKey(Key.LShift);
|
||||||
|
// });
|
||||||
|
// AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// [Test]
|
||||||
|
// public void TestFooterRewindViaShiftMouseLeft()
|
||||||
|
// {
|
||||||
|
// AddStep("shift + click button", () =>
|
||||||
|
// {
|
||||||
|
// InputManager.PressKey(Key.LShift);
|
||||||
|
// InputManager.MoveMouseTo(randomButton);
|
||||||
|
// InputManager.Click(MouseButton.Left);
|
||||||
|
// InputManager.ReleaseKey(Key.LShift);
|
||||||
|
// });
|
||||||
|
// AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// [Test]
|
||||||
|
// public void TestFooterRewindViaMouseRight()
|
||||||
|
// {
|
||||||
|
// AddStep("right click button", () =>
|
||||||
|
// {
|
||||||
|
// InputManager.MoveMouseTo(randomButton);
|
||||||
|
// InputManager.Click(MouseButton.Right);
|
||||||
|
// });
|
||||||
|
// AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||||
|
// }
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlayPresent()
|
||||||
|
{
|
||||||
|
AddStep("Press F1", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<ScreenFooterButtonMods>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("Overlay visible", () => this.ChildrenOfType<ModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||||
|
AddStep("Hide", () => this.ChildrenOfType<ModSelectOverlay>().Single().Hide());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
Stack.Padding = new MarginPadding { Bottom = screenScreenFooter.DrawHeight - screenScreenFooter.Y };
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFooter(IScreen? _, IScreen? newScreen)
|
||||||
|
{
|
||||||
|
if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter)
|
||||||
|
{
|
||||||
|
screenScreenFooter.Show();
|
||||||
|
screenScreenFooter.SetButtons(osuScreen.CreateFooterButtons());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
screenScreenFooter.Hide();
|
||||||
|
screenScreenFooter.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
// 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.Screens.Menu;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene
|
||||||
|
{
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||||
|
AddWaitStep("wait", 5);
|
||||||
|
PushAndConfirm(() => new SongSelectV2());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClickLogo()
|
||||||
|
{
|
||||||
|
AddStep("click", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(Game.ChildrenOfType<OsuLogo>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public TestSceneBackButton()
|
public TestSceneBackButton()
|
||||||
{
|
{
|
||||||
BackButton button;
|
BackButton button;
|
||||||
BackButton.Receptor receptor = new BackButton.Receptor();
|
ScreenFooter.BackReceptor receptor = new ScreenFooter.BackReceptor();
|
||||||
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
{
|
{
|
||||||
|
@ -6,23 +6,51 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Input.States;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Input.Bindings;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneOsuDropdown : ThemeComparisonTestScene
|
public partial class TestSceneOsuDropdown : ThemeComparisonTestScene
|
||||||
{
|
{
|
||||||
protected override Drawable CreateContent() =>
|
protected override Drawable CreateContent() => new OsuEnumDropdown<TestEnum>
|
||||||
new OsuEnumDropdown<TestEnum>
|
{
|
||||||
{
|
Anchor = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Origin = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Width = 150
|
||||||
Width = 150
|
};
|
||||||
};
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackAction()
|
||||||
|
{
|
||||||
|
AddStep("open", () => dropdownMenu.Open());
|
||||||
|
AddStep("press back", () => InputManager.Key(Key.Escape));
|
||||||
|
AddAssert("closed", () => dropdownMenu.State == MenuState.Closed);
|
||||||
|
|
||||||
|
AddStep("open", () => dropdownMenu.Open());
|
||||||
|
AddStep("type something", () => dropdownSearchBar.SearchTerm.Value = "something");
|
||||||
|
AddAssert("search bar visible", () => dropdownSearchBar.State.Value == Visibility.Visible);
|
||||||
|
AddStep("press back", () => InputManager.Key(Key.Escape));
|
||||||
|
AddAssert("text clear", () => dropdownSearchBar.SearchTerm.Value == string.Empty);
|
||||||
|
AddAssert("search bar hidden", () => dropdownSearchBar.State.Value == Visibility.Hidden);
|
||||||
|
AddAssert("still open", () => dropdownMenu.State == MenuState.Open);
|
||||||
|
AddStep("press back", () => InputManager.Key(Key.Escape));
|
||||||
|
AddAssert("closed", () => dropdownMenu.State == MenuState.Closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectAction()
|
||||||
|
{
|
||||||
|
AddStep("open", () => dropdownMenu.Open());
|
||||||
|
AddStep("press down", () => InputManager.Key(Key.Down));
|
||||||
|
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||||
|
AddAssert("second selected", () => dropdown.Current.Value == TestEnum.ReallyLongOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OsuEnumDropdown<TestEnum> dropdown => this.ChildrenOfType<OsuEnumDropdown<TestEnum>>().Last();
|
||||||
|
private Menu dropdownMenu => dropdown.ChildrenOfType<Menu>().Single();
|
||||||
|
private DropdownSearchBar dropdownSearchBar => dropdown.ChildrenOfType<DropdownSearchBar>().Single();
|
||||||
|
|
||||||
private enum TestEnum
|
private enum TestEnum
|
||||||
{
|
{
|
||||||
@ -32,26 +60,5 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[System.ComponentModel.Description("Really lonnnnnnng option")]
|
[System.ComponentModel.Description("Really lonnnnnnng option")]
|
||||||
ReallyLongOption,
|
ReallyLongOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
// todo: this can be written much better if ThemeComparisonTestScene has a manual input manager
|
|
||||||
public void TestBackAction()
|
|
||||||
{
|
|
||||||
AddStep("open", () => dropdown().ChildrenOfType<Menu>().Single().Open());
|
|
||||||
AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent<GlobalAction>(new InputState(), GlobalAction.Back)));
|
|
||||||
AddAssert("closed", () => dropdown().ChildrenOfType<Menu>().Single().State == MenuState.Closed);
|
|
||||||
|
|
||||||
AddStep("open", () => dropdown().ChildrenOfType<Menu>().Single().Open());
|
|
||||||
AddStep("type something", () => dropdown().ChildrenOfType<DropdownSearchBar>().Single().SearchTerm.Value = "something");
|
|
||||||
AddAssert("search bar visible", () => dropdown().ChildrenOfType<DropdownSearchBar>().Single().State.Value == Visibility.Visible);
|
|
||||||
AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent<GlobalAction>(new InputState(), GlobalAction.Back)));
|
|
||||||
AddAssert("text clear", () => dropdown().ChildrenOfType<DropdownSearchBar>().Single().SearchTerm.Value == string.Empty);
|
|
||||||
AddAssert("search bar hidden", () => dropdown().ChildrenOfType<DropdownSearchBar>().Single().State.Value == Visibility.Hidden);
|
|
||||||
AddAssert("still open", () => dropdown().ChildrenOfType<Menu>().Single().State == MenuState.Open);
|
|
||||||
AddStep("press back", () => dropdown().OnPressed(new KeyBindingPressEvent<GlobalAction>(new InputState(), GlobalAction.Back)));
|
|
||||||
AddAssert("closed", () => dropdown().ChildrenOfType<Menu>().Single().State == MenuState.Closed);
|
|
||||||
|
|
||||||
OsuEnumDropdown<TestEnum> dropdown() => this.ChildrenOfType<OsuEnumDropdown<TestEnum>>().First();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public void TestBackgroundColour()
|
public void TestBackgroundColour()
|
||||||
{
|
{
|
||||||
AddStep("set red scheme", () => CreateThemedContent(OverlayColourScheme.Red));
|
AddStep("set red scheme", () => CreateThemedContent(OverlayColourScheme.Red));
|
||||||
AddAssert("rounded button has correct colour", () => Cell(0, 1).ChildrenOfType<RoundedButton>().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Colour3);
|
AddAssert("rounded button has correct colour", () => ContentContainer.ChildrenOfType<RoundedButton>().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Colour3);
|
||||||
AddAssert("settings button has correct colour", () => Cell(0, 1).ChildrenOfType<SettingsButton>().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Colour3);
|
AddAssert("settings button has correct colour", () => ContentContainer.ChildrenOfType<SettingsButton>().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Colour3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,194 +2,93 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
using osu.Game.Screens.Footer;
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.SelectV2.Footer;
|
using osu.Game.Screens.SelectV2.Footer;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private ScreenFooterButtonRandom randomButton = null!;
|
private ScreenFooter screenFooter = null!;
|
||||||
private ScreenFooterButtonMods modsButton = null!;
|
private TestModSelectOverlay overlay = null!;
|
||||||
|
|
||||||
private bool nextRandomCalled;
|
|
||||||
private bool previousRandomCalled;
|
|
||||||
|
|
||||||
private DummyOverlay overlay = null!;
|
|
||||||
|
|
||||||
[Cached]
|
|
||||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
nextRandomCalled = false;
|
|
||||||
previousRandomCalled = false;
|
|
||||||
|
|
||||||
ScreenFooter footer;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
overlay = new TestModSelectOverlay
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Bottom = ScreenFooter.HEIGHT
|
||||||
|
}
|
||||||
|
},
|
||||||
new PopoverContainer
|
new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = footer = new ScreenFooter(),
|
Child = screenFooter = new ScreenFooter(),
|
||||||
},
|
},
|
||||||
overlay = new DummyOverlay()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
footer.AddButton(modsButton = new ScreenFooterButtonMods { Current = SelectedMods }, overlay);
|
screenFooter.SetButtons(new ScreenFooterButton[]
|
||||||
footer.AddButton(randomButton = new ScreenFooterButtonRandom
|
|
||||||
{
|
{
|
||||||
NextRandom = () => nextRandomCalled = true,
|
new ScreenFooterButtonMods(overlay) { Current = SelectedMods },
|
||||||
PreviousRandom = () => previousRandomCalled = true
|
new ScreenFooterButtonRandom(),
|
||||||
|
new ScreenFooterButtonOptions(),
|
||||||
});
|
});
|
||||||
footer.AddButton(new ScreenFooterButtonOptions());
|
|
||||||
|
|
||||||
overlay.Hide();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
AddStep("show footer", () => screenFooter.Show());
|
||||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transition when moving from a screen with no buttons to a screen with buttons.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMods()
|
public void TestButtonsIn()
|
||||||
{
|
{
|
||||||
AddStep("one mod", () => SelectedMods.Value = new List<Mod> { new OsuModHidden() });
|
|
||||||
AddStep("two mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock() });
|
|
||||||
AddStep("three mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() });
|
|
||||||
AddStep("four mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() });
|
|
||||||
AddStep("five mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() });
|
|
||||||
|
|
||||||
AddStep("modified", () => SelectedMods.Value = new List<Mod> { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
|
||||||
AddStep("modified + one", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
|
||||||
AddStep("modified + two", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
|
||||||
AddStep("modified + three", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
|
||||||
AddStep("modified + four", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } });
|
|
||||||
|
|
||||||
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
|
||||||
AddWaitStep("wait", 3);
|
|
||||||
AddStep("one mod", () => SelectedMods.Value = new List<Mod> { new OsuModHidden() });
|
|
||||||
|
|
||||||
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
|
||||||
AddWaitStep("wait", 3);
|
|
||||||
AddStep("five mods", () => SelectedMods.Value = new List<Mod> { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transition when moving from a screen with buttons to a screen with no buttons.
|
||||||
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestShowOptions()
|
public void TestButtonsOut()
|
||||||
{
|
{
|
||||||
AddStep("enable options", () =>
|
AddStep("clear buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transition when moving from a screen with buttons to a screen with buttons.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestReplaceButtons()
|
||||||
|
{
|
||||||
|
AddStep("replace buttons", () => screenFooter.SetButtons(new[]
|
||||||
{
|
{
|
||||||
var optionsButton = this.ChildrenOfType<ScreenFooterButton>().Last();
|
new ScreenFooterButton { Text = "One", Action = () => { } },
|
||||||
|
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||||
optionsButton.Enabled.Value = true;
|
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||||
optionsButton.TriggerClick();
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||||
public void TestState()
|
|
||||||
{
|
{
|
||||||
AddToggleStep("set options enabled state", state => this.ChildrenOfType<ScreenFooterButton>().Last().Enabled.Value = state);
|
protected override bool ShowPresets => true;
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
public TestModSelectOverlay()
|
||||||
public void TestFooterRandom()
|
: base(OverlayColourScheme.Aquamarine)
|
||||||
{
|
|
||||||
AddStep("press F2", () => InputManager.Key(Key.F2));
|
|
||||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestFooterRandomViaMouse()
|
|
||||||
{
|
|
||||||
AddStep("click button", () =>
|
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(randomButton);
|
|
||||||
InputManager.Click(MouseButton.Left);
|
|
||||||
});
|
|
||||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestFooterRewind()
|
|
||||||
{
|
|
||||||
AddStep("press Shift+F2", () =>
|
|
||||||
{
|
|
||||||
InputManager.PressKey(Key.LShift);
|
|
||||||
InputManager.PressKey(Key.F2);
|
|
||||||
InputManager.ReleaseKey(Key.F2);
|
|
||||||
InputManager.ReleaseKey(Key.LShift);
|
|
||||||
});
|
|
||||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestFooterRewindViaShiftMouseLeft()
|
|
||||||
{
|
|
||||||
AddStep("shift + click button", () =>
|
|
||||||
{
|
|
||||||
InputManager.PressKey(Key.LShift);
|
|
||||||
InputManager.MoveMouseTo(randomButton);
|
|
||||||
InputManager.Click(MouseButton.Left);
|
|
||||||
InputManager.ReleaseKey(Key.LShift);
|
|
||||||
});
|
|
||||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestFooterRewindViaMouseRight()
|
|
||||||
{
|
|
||||||
AddStep("right click button", () =>
|
|
||||||
{
|
|
||||||
InputManager.MoveMouseTo(randomButton);
|
|
||||||
InputManager.Click(MouseButton.Right);
|
|
||||||
});
|
|
||||||
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestOverlayPresent()
|
|
||||||
{
|
|
||||||
AddStep("Press F1", () =>
|
|
||||||
{
|
|
||||||
InputManager.MoveMouseTo(modsButton);
|
|
||||||
InputManager.Click(MouseButton.Left);
|
|
||||||
});
|
|
||||||
AddAssert("Overlay visible", () => overlay.State.Value == Visibility.Visible);
|
|
||||||
AddStep("Hide", () => overlay.Hide());
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class DummyOverlay : ShearedOverlayContainer
|
|
||||||
{
|
|
||||||
public DummyOverlay()
|
|
||||||
: base(OverlayColourScheme.Green)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Header.Title = "An overlay";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.SelectV2.Footer;
|
using osu.Game.Screens.SelectV2.Footer;
|
||||||
@ -26,12 +27,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
public TestSceneScreenFooterButtonMods()
|
public TestSceneScreenFooterButtonMods()
|
||||||
{
|
{
|
||||||
Add(footerButtonMods = new TestScreenFooterButtonMods
|
Add(footerButtonMods = new TestScreenFooterButtonMods(new TestModSelectOverlay())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
X = -100,
|
|
||||||
Action = () => { },
|
Action = () => { },
|
||||||
|
X = -100,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +113,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
|
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||||
|
{
|
||||||
|
protected override bool ShowPresets => true;
|
||||||
|
|
||||||
|
public TestModSelectOverlay()
|
||||||
|
: base(OverlayColourScheme.Aquamarine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods
|
private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods
|
||||||
{
|
{
|
||||||
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
||||||
|
|
||||||
|
public TestScreenFooterButtonMods(ModSelectOverlay overlay)
|
||||||
|
: base(overlay)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,21 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public abstract partial class ThemeComparisonTestScene : OsuGridTestScene
|
public abstract partial class ThemeComparisonTestScene : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly bool showWithoutColourProvider;
|
private readonly bool showWithoutColourProvider;
|
||||||
|
|
||||||
|
public Container ContentContainer { get; private set; } = null!;
|
||||||
|
|
||||||
protected ThemeComparisonTestScene(bool showWithoutColourProvider = true)
|
protected ThemeComparisonTestScene(bool showWithoutColourProvider = true)
|
||||||
: base(1, showWithoutColourProvider ? 2 : 1)
|
|
||||||
{
|
{
|
||||||
this.showWithoutColourProvider = showWithoutColourProvider;
|
this.showWithoutColourProvider = showWithoutColourProvider;
|
||||||
}
|
}
|
||||||
@ -25,16 +28,32 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
|
Child = ContentContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
|
||||||
if (showWithoutColourProvider)
|
if (showWithoutColourProvider)
|
||||||
{
|
{
|
||||||
Cell(0, 0).AddRange(new[]
|
ContentContainer.Size = new Vector2(0.5f, 1f);
|
||||||
|
|
||||||
|
Add(new Container
|
||||||
{
|
{
|
||||||
new Box
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.5f, 1f),
|
||||||
|
Children = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Box
|
||||||
Colour = colours.GreySeaFoam
|
{
|
||||||
},
|
RelativeSizeAxes = Axes.Both,
|
||||||
CreateContent()
|
Colour = colours.GreySeaFoam
|
||||||
|
},
|
||||||
|
CreateContent()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,10 +62,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
var colourProvider = new OverlayColourProvider(colourScheme);
|
var colourProvider = new OverlayColourProvider(colourScheme);
|
||||||
|
|
||||||
int col = showWithoutColourProvider ? 1 : 0;
|
ContentContainer.Clear();
|
||||||
|
ContentContainer.Add(new DependencyProvidingContainer
|
||||||
Cell(0, col).Clear();
|
|
||||||
Cell(0, col).Add(new DependencyProvidingContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
CachedDependencies = new (Type, object)[]
|
CachedDependencies = new (Type, object)[]
|
||||||
|
@ -51,15 +51,16 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
|
|
||||||
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
|
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
|
||||||
|
|
||||||
ScrollContent.Add(grid = new RectangularPositionSnapGrid(Vector2.Zero)
|
ScrollContent.Add(grid = new RectangularPositionSnapGrid
|
||||||
{
|
{
|
||||||
Spacing = new Vector2(GRID_SPACING),
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
BypassAutoSizeAxes = Axes.Both,
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
Depth = float.MaxValue
|
Depth = float.MaxValue
|
||||||
});
|
});
|
||||||
|
|
||||||
|
grid.Spacing.Value = new Vector2(GRID_SPACING);
|
||||||
|
|
||||||
LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
|
LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
|
||||||
updateMessage();
|
updateMessage();
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
|||||||
editorInfo.Selected.ValueChanged += selection =>
|
editorInfo.Selected.ValueChanged += selection =>
|
||||||
{
|
{
|
||||||
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
|
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
|
||||||
GetContainingFocusManager().TriggerFocusContention(null);
|
GetContainingFocusManager()?.TriggerFocusContention(null);
|
||||||
|
|
||||||
// Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process).
|
// Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process).
|
||||||
// Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best.
|
// Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user