mirror of
https://github.com/ppy/osu.git
synced 2024-09-21 22:47:24 +08:00
Merge remote-tracking branch 'upstream/master' into free-sliders
This commit is contained in:
commit
d7fee53d67
@ -196,6 +196,9 @@ csharp_style_prefer_switch_expression = false:none
|
||||
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
|
||||
#Style - C# 12 features
|
||||
csharp_style_prefer_primary_constructors = false
|
||||
|
||||
[*.{yaml,yml}]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.528.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.618.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
||||
[TestCase("112643")]
|
||||
[TestCase("1041052", new[] { typeof(CatchModHardRock) })]
|
||||
[TestCase("high-speed-multiplier-precision")]
|
||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
{"Mappings":[{"StartTime":265568.0,"Objects":[{"StartTime":265568.0,"Position":486.0,"HyperDash":false},{"StartTime":265658.0,"Position":465.1873,"HyperDash":false},{"StartTime":265749.0,"Position":463.208435,"HyperDash":false},{"StartTime":265840.0,"Position":465.146484,"HyperDash":false},{"StartTime":265967.0,"Position":459.5862,"HyperDash":false}]}]}
|
@ -0,0 +1,238 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: 226943
|
||||
Countdown: 0
|
||||
SampleSet: Soft
|
||||
StackLeniency: 0.7
|
||||
Mode: 2
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 1
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 85568,86768,90968,265568
|
||||
DistanceSpacing: 0.9
|
||||
BeatDivisor: 12
|
||||
GridSize: 16
|
||||
TimelineZoom: 1
|
||||
|
||||
[Metadata]
|
||||
Title:Snow
|
||||
TitleUnicode:Snow
|
||||
Artist:Ricky Montgomery
|
||||
ArtistUnicode:Ricky Montgomery
|
||||
Creator:Crowley
|
||||
Version:Bury Me Six Feet in Snow
|
||||
Source:
|
||||
Tags:indie the honeysticks alternative english
|
||||
BeatmapID:2062131
|
||||
BeatmapSetID:971028
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4.2
|
||||
OverallDifficulty:8.3
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:3.59999990463257
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"me.jpg",0,0
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Layer 4 (Overlay)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
368,1200,2,2,1,30,1,0
|
||||
368,-66.6666666666667,2,2,1,30,0,0
|
||||
29168,-58.8235294117647,2,2,1,40,0,0
|
||||
30368,-58.8235294117647,2,2,2,40,0,0
|
||||
30568,-58.8235294117647,2,2,1,40,0,0
|
||||
31368,-58.8235294117647,2,2,2,40,0,0
|
||||
31568,-58.8235294117647,2,2,1,40,0,0
|
||||
32768,-58.8235294117647,2,2,2,40,0,0
|
||||
33568,-58.8235294117647,2,2,2,40,0,0
|
||||
33968,-58.8235294117647,2,2,1,40,0,0
|
||||
35168,-58.8235294117647,2,2,2,40,0,0
|
||||
35968,-58.8235294117647,2,2,1,40,0,0
|
||||
36168,-58.8235294117647,2,2,2,40,0,0
|
||||
36368,-58.8235294117647,2,2,1,40,0,0
|
||||
37568,-58.8235294117647,2,2,2,40,0,0
|
||||
37968,-58.8235294117647,2,2,1,40,0,0
|
||||
38368,-58.8235294117647,2,2,2,40,0,0
|
||||
38768,-58.8235294117647,2,2,1,40,0,0
|
||||
39968,-58.8235294117647,2,2,2,40,0,0
|
||||
40168,-58.8235294117647,2,2,1,40,0,0
|
||||
40968,-58.8235294117647,2,2,2,40,0,0
|
||||
41168,-58.8235294117647,2,2,1,40,0,0
|
||||
42368,-58.8235294117647,2,2,2,40,0,0
|
||||
43168,-58.8235294117647,2,2,2,40,0,0
|
||||
43568,-58.8235294117647,2,2,1,40,0,0
|
||||
44768,-58.8235294117647,2,2,2,40,0,0
|
||||
45768,-58.8235294117647,2,2,2,40,0,0
|
||||
45968,-58.8235294117647,2,2,1,50,0,0
|
||||
47168,-58.8235294117647,2,2,2,50,0,0
|
||||
48368,-62.5,2,2,1,50,0,0
|
||||
67568,-58.8235294117647,2,2,1,70,0,1
|
||||
84668,-58.8235294117647,2,2,1,5,0,1
|
||||
84768,-58.8235294117647,2,2,1,70,0,1
|
||||
85068,-58.8235294117647,2,2,1,5,0,1
|
||||
85168,-58.8235294117647,2,2,1,70,0,1
|
||||
85468,-58.8235294117647,2,2,1,5,0,1
|
||||
85568,-58.8235294117647,2,2,1,70,0,1
|
||||
86768,-58.8235294117647,2,2,1,30,0,0
|
||||
91168,-58.8235294117647,2,2,1,50,0,0
|
||||
91568,1200,2,2,1,50,1,0
|
||||
91568,-58.8235294117647,2,2,1,50,0,1
|
||||
91643,-58.8235294117647,2,2,1,50,0,0
|
||||
92768,-58.8235294117647,2,2,2,50,0,0
|
||||
92968,-58.8235294117647,2,2,1,50,0,0
|
||||
95168,-58.8235294117647,2,2,2,50,0,0
|
||||
95368,-58.8235294117647,2,2,1,50,0,0
|
||||
97568,-58.8235294117647,2,2,2,50,0,0
|
||||
97768,-58.8235294117647,2,2,1,50,0,0
|
||||
99968,-58.8235294117647,2,2,2,50,0,0
|
||||
100168,-58.8235294117647,2,2,1,50,0,0
|
||||
100768,-58.8235294117647,2,2,2,50,0,0
|
||||
101168,-58.8235294117647,2,2,1,50,0,0
|
||||
102368,-58.8235294117647,2,2,2,50,0,0
|
||||
102568,-58.8235294117647,2,2,1,50,0,0
|
||||
104768,-58.8235294117647,2,2,2,50,0,0
|
||||
104968,-58.8235294117647,2,2,1,50,0,0
|
||||
107168,-58.8235294117647,2,2,2,50,0,0
|
||||
107368,-58.8235294117647,2,2,1,50,0,0
|
||||
108968,-58.8235294117647,2,2,2,50,0,0
|
||||
109168,-58.8235294117647,2,2,1,50,0,0
|
||||
109568,-58.8235294117647,2,2,2,50,0,0
|
||||
109968,-58.8235294117647,2,2,1,50,0,0
|
||||
110368,-58.8235294117647,2,2,2,50,0,0
|
||||
110768,-100,2,2,1,40,0,0
|
||||
127568,-62.5,2,2,2,50,0,0
|
||||
127968,-62.5,2,2,1,50,0,0
|
||||
128168,-62.5,2,2,2,50,0,0
|
||||
129968,-58.8235294117647,2,2,1,50,0,0
|
||||
131168,-58.8235294117647,2,2,2,50,0,0
|
||||
131368,-58.8235294117647,2,2,1,50,0,0
|
||||
133568,-58.8235294117647,2,2,2,50,0,0
|
||||
133768,-58.8235294117647,2,2,1,50,0,0
|
||||
135968,-58.8235294117647,2,2,2,50,0,0
|
||||
136168,-58.8235294117647,2,2,1,50,0,0
|
||||
138368,-58.8235294117647,2,2,2,50,0,0
|
||||
138568,-58.8235294117647,2,2,1,50,0,0
|
||||
139168,-58.8235294117647,2,2,2,50,0,0
|
||||
139368,-58.8235294117647,2,2,1,50,0,0
|
||||
139568,-58.8235294117647,2,2,1,50,0,0
|
||||
140768,-58.8235294117647,2,2,2,50,0,0
|
||||
140968,-58.8235294117647,2,2,1,50,0,0
|
||||
143168,-58.8235294117647,2,2,2,50,0,0
|
||||
143368,-58.8235294117647,2,2,1,50,0,0
|
||||
145568,-58.8235294117647,2,2,2,50,0,0
|
||||
145768,-58.8235294117647,2,2,1,50,0,0
|
||||
147368,-58.8235294117647,2,2,2,50,0,0
|
||||
147768,-58.8235294117647,2,2,1,50,0,0
|
||||
147968,-58.8235294117647,2,2,1,60,0,0
|
||||
148768,-58.8235294117647,2,2,2,60,0,0
|
||||
149168,-58.8235294117647,2,2,1,70,0,1
|
||||
158268,-58.8235294117647,2,2,2,70,0,1
|
||||
158568,-58.8235294117647,2,2,1,70,0,1
|
||||
166268,-58.8235294117647,2,2,1,5,0,1
|
||||
166368,-58.8235294117647,2,2,1,70,0,1
|
||||
166668,-58.8235294117647,2,2,1,5,0,1
|
||||
166768,-58.8235294117647,2,2,1,70,0,1
|
||||
167068,-58.8235294117647,2,2,1,5,0,1
|
||||
167168,-58.8235294117647,2,2,1,70,0,1
|
||||
168368,-62.5,2,2,1,50,0,0
|
||||
172368,-62.5,2,2,1,50,0,1
|
||||
173168,-62.5,2,2,1,50,0,0
|
||||
185168,-62.5,2,2,1,60,0,0
|
||||
185468,-62.5,2,2,1,5,0,0
|
||||
185568,-62.5,2,2,1,60,0,0
|
||||
185868,-62.5,2,2,1,5,0,0
|
||||
185968,-62.5,2,2,1,60,0,0
|
||||
186268,-62.5,2,2,1,5,0,0
|
||||
186368,-62.5,2,2,1,60,0,0
|
||||
186668,-62.5,2,2,1,5,0,0
|
||||
186768,-52.6315789473684,2,2,1,60,0,0
|
||||
187068,-62.5,2,2,1,5,0,0
|
||||
187168,-62.5,2,2,1,60,0,0
|
||||
187468,-62.5,2,2,1,5,0,0
|
||||
187568,-62.5,2,2,1,20,0,0
|
||||
187768,-62.5,2,2,1,24,0,0
|
||||
187968,-62.5,2,2,1,28,0,0
|
||||
188168,-62.5,2,2,1,32,0,0
|
||||
188368,-62.5,2,2,1,36,0,0
|
||||
188568,-62.5,2,2,1,40,0,0
|
||||
188768,1200,2,2,1,50,1,1
|
||||
188768,-58.8235294117647,2,2,1,50,0,1
|
||||
188843,-58.8235294117647,2,2,1,50,0,0
|
||||
189968,-58.8235294117647,2,2,2,50,0,0
|
||||
190168,-58.8235294117647,2,2,1,50,0,0
|
||||
192368,-58.8235294117647,2,2,2,50,0,0
|
||||
192568,-58.8235294117647,2,2,1,50,0,0
|
||||
194768,-58.8235294117647,2,2,2,50,0,0
|
||||
194968,-58.8235294117647,2,2,1,50,0,0
|
||||
196568,-58.8235294117647,2,2,2,50,0,0
|
||||
196768,-58.8235294117647,2,2,1,50,0,0
|
||||
197168,-58.8235294117647,2,2,2,50,0,0
|
||||
197368,-58.8235294117647,2,2,1,50,0,0
|
||||
197568,-58.8235294117647,2,2,2,50,0,0
|
||||
197968,-58.8235294117647,2,2,1,50,0,0
|
||||
198368,-58.8235294117647,2,2,1,50,0,0
|
||||
199568,-58.8235294117647,2,2,2,50,0,0
|
||||
199768,-58.8235294117647,2,2,1,50,0,0
|
||||
201968,-58.8235294117647,2,2,2,50,0,0
|
||||
202168,-58.8235294117647,2,2,1,50,0,0
|
||||
204368,-58.8235294117647,2,2,2,50,0,0
|
||||
204568,-58.8235294117647,2,2,1,50,0,0
|
||||
206768,-58.8235294117647,2,2,1,60,0,0
|
||||
207168,-58.8235294117647,2,2,2,60,0,0
|
||||
207968,-58.8235294117647,2,2,1,70,0,1
|
||||
216968,-58.8235294117647,2,2,2,70,0,1
|
||||
217168,-58.8235294117647,2,2,1,70,0,1
|
||||
217368,-58.8235294117647,2,2,2,70,0,1
|
||||
217568,-58.8235294117647,2,2,1,70,0,1
|
||||
225068,-58.8235294117647,2,2,1,5,0,1
|
||||
225168,-58.8235294117647,2,2,1,70,0,1
|
||||
225468,-58.8235294117647,2,2,1,5,0,1
|
||||
225568,-58.8235294117647,2,2,1,70,0,1
|
||||
225868,-58.8235294117647,2,2,1,5,0,1
|
||||
225968,-58.8235294117647,2,2,1,70,0,1
|
||||
227168,-58.8235294117647,2,2,1,30,0,0
|
||||
234368,-58.8235294117647,2,2,1,40,0,0
|
||||
236768,-58.8235294117647,2,2,1,70,0,1
|
||||
255968,-58.8235294117647,2,2,1,70,0,1
|
||||
261168,-58.8235294117647,2,2,1,70,0,1
|
||||
263068,-58.8235294117647,2,2,1,70,0,0
|
||||
263168,-58.8235294117647,2,2,1,60,0,1
|
||||
263243,-58.8235294117647,2,2,1,60,0,0
|
||||
264368,-58.8235294117647,2,2,1,60,0,1
|
||||
264443,-58.8235294117647,2,2,1,60,0,0
|
||||
265568,-444.444444444444,2,2,1,50,0,1
|
||||
265643,-444.444444444444,2,2,1,50,0,0
|
||||
266768,-444.444444444444,2,2,1,40,0,0
|
||||
267968,-444.444444444444,2,2,1,30,0,0
|
||||
269168,-444.444444444444,2,2,1,20,0,0
|
||||
270368,-444.444444444444,2,2,1,10,0,0
|
||||
271168,-444.444444444444,2,2,1,9,0,0
|
||||
271568,-444.444444444444,2,2,1,8,0,0
|
||||
271968,-444.444444444444,2,2,1,7,0,0
|
||||
272368,-444.444444444444,2,2,1,6,0,0
|
||||
272768,-444.444444444444,2,2,1,5,0,0
|
||||
275168,-444.444444444444,2,2,1,5,0,0
|
||||
|
||||
|
||||
[Colours]
|
||||
Combo1 : 255,128,128
|
||||
Combo2 : 72,72,255
|
||||
Combo3 : 192,192,192
|
||||
Combo4 : 255,136,79
|
||||
|
||||
[HitObjects]
|
||||
486,179,265568,6,0,P|461:174|454:174,1,26.999997997284,6|0,1:2|0:0,0:0:0:0:
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
value *= Math.Pow(accuracy(), 5.5);
|
||||
|
||||
if (score.Mods.Any(m => m is ModNoFail))
|
||||
value *= 0.90;
|
||||
value *= Math.Max(0.90, 1.0 - 0.02 * numMiss);
|
||||
|
||||
return new CatchPerformanceAttributes
|
||||
{
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
inputManager = GetContainingInputManager()!;
|
||||
|
||||
BeginPlacement();
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public BindableNumber<double> SliderVelocityMultiplierBindable { get; } = new BindableDouble(1)
|
||||
{
|
||||
Precision = 0.01,
|
||||
MinValue = 0.1,
|
||||
MaxValue = 10
|
||||
};
|
||||
@ -77,6 +76,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
this.PopulateNodeSamples();
|
||||
|
||||
var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
|
||||
|
||||
int nodeIndex = 0;
|
||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
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(
|
||||
replayState?.CatcherX ??
|
||||
|
@ -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("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("tail blueprint positioned correctly", () => this.ChildrenOfType<EditNotePiece>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<EditHoldNoteEndPiece>().ElementAt(0).DrawPosition == holdNote.Head.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)
|
||||
|
@ -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", () =>
|
||||
{
|
||||
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);
|
||||
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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
@ -18,10 +18,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
public partial class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private EditNotePiece head;
|
||||
private EditNotePiece tail;
|
||||
[Resolved]
|
||||
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)
|
||||
: base(hold)
|
||||
@ -33,8 +42,43 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
head = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
||||
tail = new EditNotePiece { RelativeSizeAxes = Axes.X },
|
||||
head = new EditHoldNoteEndPiece
|
||||
{
|
||||
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
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -16,6 +17,16 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
[Resolved]
|
||||
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)
|
||||
{
|
||||
var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
|
||||
@ -26,6 +37,58 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
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)
|
||||
{
|
||||
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||
|
@ -3,20 +3,155 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
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.Screens.Edit;
|
||||
using osu.Game.Screens.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]
|
||||
private void load()
|
||||
{
|
||||
CircleSizeSlider.Label = BeatmapsetsStrings.ShowStatsCsMania;
|
||||
CircleSizeSlider.Description = "The number of columns in the beatmap";
|
||||
if (CircleSizeSlider.Current is BindableNumber<float> circleSizeFloat)
|
||||
circleSizeFloat.Precision = 1;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
keyCountSlider = new LabelledSliderBar<float>
|
||||
{
|
||||
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 DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
||||
public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
||||
|
||||
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
||||
=> 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)
|
||||
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.Blending = BlendingParameters.Additive;
|
||||
d.Scale = new Vector2(lightScale);
|
||||
@ -91,11 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
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)
|
||||
animation.IsPlaying = false;
|
||||
|
||||
@ -140,10 +134,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||
{
|
||||
if (bodySprite is TextureAnimation bodyAnimation)
|
||||
{
|
||||
bodyAnimation.GotoFrame(0);
|
||||
bodyAnimation.IsPlaying = isHitting.NewValue;
|
||||
}
|
||||
|
||||
if (lightContainer == null)
|
||||
return;
|
||||
@ -219,6 +210,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!isHitting.Value)
|
||||
(bodySprite as TextureAnimation)?.GotoFrame(0);
|
||||
|
||||
if (holdNote.Body.HasHoldBreak)
|
||||
missFadeTime.Value = holdNote.Body.Result.TimeAbsolute;
|
||||
|
||||
@ -245,7 +239,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
// 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.
|
||||
if (sprite.DrawHeight > 0)
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
bodySprite.Scale = new Vector2(1, MathF.Max(1, scaleDirection * 32800 / sprite.DrawHeight));
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -43,11 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||
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.Blending = BlendingParameters.Additive;
|
||||
d.Scale = new Vector2(explosionScale);
|
||||
|
@ -28,13 +28,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
string bottomImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
|
||||
?? "mania-stage-bottom";
|
||||
|
||||
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
|
||||
d.Scale = new Vector2(1.6f);
|
||||
});
|
||||
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => d.Scale = new Vector2(1.6f));
|
||||
|
||||
if (sprite != null)
|
||||
InternalChild = sprite;
|
||||
|
@ -8,7 +8,9 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
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)));
|
||||
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
|
||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(true);
|
||||
gridActive<RectangularPositionSnapGrid>(true);
|
||||
|
||||
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
rectangularGridActive(true);
|
||||
gridActive<RectangularPositionSnapGrid>(true);
|
||||
|
||||
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -117,33 +119,56 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[Test]
|
||||
public void TestGridSnapMomentaryToggle()
|
||||
{
|
||||
rectangularGridActive(false);
|
||||
gridActive<RectangularPositionSnapGrid>(false);
|
||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||
rectangularGridActive(true);
|
||||
gridActive<RectangularPositionSnapGrid>(true);
|
||||
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("move cursor to (1, 1)", () =>
|
||||
AddStep("move cursor to spacing + (1, 1)", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<OsuRectangularPositionSnapGrid>().Single();
|
||||
InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1)));
|
||||
var composer = Editor.ChildrenOfType<T>().Single();
|
||||
InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1)));
|
||||
});
|
||||
|
||||
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
|
||||
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]
|
||||
public void TestGridSizeToggling()
|
||||
{
|
||||
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);
|
||||
|
||||
nextGridSizeIs(8);
|
||||
@ -159,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,12 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -71,4 +74,120 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
private void moveMouse(Vector2 pos) =>
|
||||
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class TestSliderNearLinearScaling
|
||||
{
|
||||
private readonly Random rng = new Random(1337);
|
||||
|
||||
[Test]
|
||||
public void TestScalingSliderFlat()
|
||||
{
|
||||
SliderPath sliderPathPerfect = new SliderPath(
|
||||
[
|
||||
new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE),
|
||||
new PathControlPoint(new Vector2(50, 25)),
|
||||
new PathControlPoint(new Vector2(25, 100)),
|
||||
]);
|
||||
|
||||
SliderPath sliderPathBezier = new SliderPath(
|
||||
[
|
||||
new PathControlPoint(new Vector2(0), PathType.BEZIER),
|
||||
new PathControlPoint(new Vector2(50, 25)),
|
||||
new PathControlPoint(new Vector2(25, 100)),
|
||||
]);
|
||||
|
||||
scaleSlider(sliderPathPerfect, new Vector2(0.000001f, 1));
|
||||
scaleSlider(sliderPathBezier, new Vector2(0.000001f, 1));
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
Assert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f)));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerfectCurveMatchesTheoretical()
|
||||
{
|
||||
for (int i = 0; i < 20000; i++)
|
||||
{
|
||||
//Only test points that are in the screen's bounds
|
||||
float p1X = 640.0f * (float)rng.NextDouble();
|
||||
float p2X = 640.0f * (float)rng.NextDouble();
|
||||
|
||||
float p1Y = 480.0f * (float)rng.NextDouble();
|
||||
float p2Y = 480.0f * (float)rng.NextDouble();
|
||||
SliderPath sliderPathPerfect = new SliderPath(
|
||||
[
|
||||
new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE),
|
||||
new PathControlPoint(new Vector2(p1X, p1Y)),
|
||||
new PathControlPoint(new Vector2(p2X, p2Y)),
|
||||
]);
|
||||
|
||||
assertMatchesPerfectCircle(sliderPathPerfect);
|
||||
|
||||
scaleSlider(sliderPathPerfect, new Vector2(0.00001f, 1));
|
||||
|
||||
assertMatchesPerfectCircle(sliderPathPerfect);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertMatchesPerfectCircle(SliderPath path)
|
||||
{
|
||||
if (path.ControlPoints.Count != 3)
|
||||
return;
|
||||
|
||||
//Replication of PathApproximator.CircularArcToPiecewiseLinear
|
||||
CircularArcProperties circularArcProperties = new CircularArcProperties(path.ControlPoints.Select(x => x.Position).ToArray());
|
||||
|
||||
if (!circularArcProperties.IsValid)
|
||||
return;
|
||||
|
||||
//Addresses cases where circularArcProperties.ThetaRange>0.5
|
||||
//Occurs in code in PathControlPointVisualiser.ensureValidPathType
|
||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(path.ControlPoints.Select(x => x.Position).ToArray());
|
||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||
return;
|
||||
|
||||
int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius)))));
|
||||
|
||||
//ignore cases where subpoints is int.MaxValue, result will be garbage
|
||||
//as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage
|
||||
if (subpoints == int.MaxValue)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Math.Min(subpoints, 100); i++)
|
||||
{
|
||||
float progress = (float)rng.NextDouble();
|
||||
|
||||
//To avoid errors from interpolating points, ensure we check only positions that would be subpoints.
|
||||
progress = (float)Math.Ceiling(progress * (subpoints - 1)) / (subpoints - 1);
|
||||
|
||||
//Special case - if few subpoints, ensure checking every single one rather than randomly
|
||||
if (subpoints < 100)
|
||||
progress = i / (float)(subpoints - 1);
|
||||
|
||||
//edge points cause issue with interpolation, so ignore the last two points and first
|
||||
if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1))
|
||||
continue;
|
||||
|
||||
double theta = circularArcProperties.ThetaStart + (circularArcProperties.Direction * progress * circularArcProperties.ThetaRange);
|
||||
Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius;
|
||||
|
||||
Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f),
|
||||
"A perfect circle with points " + string.Join(", ", path.ControlPoints.Select(x => x.Position)) + " and radius" + circularArcProperties.Radius + "from SliderPath does not almost equal a theoretical perfect circle with " + subpoints + " subpoints"
|
||||
+ ": " + (circularArcProperties.Centre + vector) + " - " + path.PositionAt(progress)
|
||||
+ " = " + (circularArcProperties.Centre + vector - path.PositionAt(progress))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void scaleSlider(SliderPath path, Vector2 scale)
|
||||
{
|
||||
for (int i = 0; i < path.ControlPoints.Count; i++)
|
||||
{
|
||||
path.ControlPoints[i].Position *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
StartTime = 5000,
|
||||
}
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(2000, 4000),
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private partial class TestDrawableOsuJudgement : DrawableOsuJudgement
|
||||
{
|
||||
public new SkinnableSprite Lighting => base.Lighting;
|
||||
public new SkinnableDrawable JudgementBody => base.JudgementBody;
|
||||
public new SkinnableDrawable? JudgementBody => base.JudgementBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,9 +161,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
pressed = value;
|
||||
if (value)
|
||||
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager().CurrentState, OsuAction.LeftButton));
|
||||
OnPressed(new KeyBindingPressEvent<OsuAction>(GetContainingInputManager()!.CurrentState, OsuAction.LeftButton));
|
||||
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", () =>
|
||||
{
|
||||
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);
|
||||
Add(slider);
|
||||
slider.HitObject.NodeSamples.Clear();
|
||||
});
|
||||
|
||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -41,22 +42,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
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
|
||||
foreach (var h in osuBeatmap.HitObjects)
|
||||
foreach (var h in hitObjects)
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1);
|
||||
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
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.ThrowIfNegative(startIndex);
|
||||
@ -64,24 +65,24 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
|
||||
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
|
||||
for (int i = endIndex; i >= startIndex; 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;
|
||||
|
||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner)
|
||||
continue;
|
||||
|
||||
double endTime = stackBaseObject.GetEndTime();
|
||||
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
|
||||
double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency;
|
||||
|
||||
if (objectN.StartTime - endTime > stackThreshold)
|
||||
// We are no longer within stacking range of the next object.
|
||||
@ -100,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
if (stackBaseIndex > extendedEndIndex)
|
||||
{
|
||||
extendedEndIndex = stackBaseIndex;
|
||||
if (extendedEndIndex == beatmap.HitObjects.Count - 1)
|
||||
if (extendedEndIndex == hitObjects.Count - 1)
|
||||
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.
|
||||
*/
|
||||
|
||||
OsuHitObject objectI = beatmap.HitObjects[i];
|
||||
OsuHitObject objectI = hitObjects[i];
|
||||
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.
|
||||
* 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)
|
||||
{
|
||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
double endTime = objectN.GetEndTime();
|
||||
@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
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).
|
||||
OsuHitObject objectJ = beatmap.HitObjects[j];
|
||||
OsuHitObject objectJ = hitObjects[j];
|
||||
if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance)
|
||||
objectJ.StackHeight -= offset;
|
||||
}
|
||||
@ -191,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
*/
|
||||
while (--n >= startIndex)
|
||||
{
|
||||
OsuHitObject objectN = beatmap.HitObjects[n];
|
||||
OsuHitObject objectN = hitObjects[n];
|
||||
if (objectN is Spinner) continue;
|
||||
|
||||
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))
|
||||
continue;
|
||||
@ -220,11 +221,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
double startTime = currHitObject.GetEndTime();
|
||||
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;
|
||||
|
||||
// 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
|
||||
// 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++;
|
||||
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.
|
||||
sliderStack++;
|
||||
beatmap.HitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = beatmap.HitObjects[j].StartTime;
|
||||
hitObjects[j].StackHeight -= sliderStack;
|
||||
startTime = hitObjects[j].StartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
@ -23,12 +26,32 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private partial class OsuEditorPlayfield : OsuPlayfield
|
||||
{
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
|
||||
protected override GameplayCursorContainer? CreateCursor() => null;
|
||||
|
||||
public OsuEditorPlayfield()
|
||||
{
|
||||
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.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@ -65,6 +66,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[Cached(typeof(IDistanceSnapProvider))]
|
||||
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||
|
||||
[Cached]
|
||||
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
||||
|
||||
[Cached]
|
||||
protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup();
|
||||
|
||||
@ -80,10 +84,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
LayerBelowRuleset.AddRange(new Drawable[]
|
||||
{
|
||||
distanceSnapGridContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
@ -99,14 +99,38 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// we may be entering the screen with a selection already active
|
||||
updateDistanceSnapGrid();
|
||||
|
||||
updatePositionSnapGrid();
|
||||
|
||||
RightToolbox.AddRange(new EditorToolboxGroup[]
|
||||
{
|
||||
new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, },
|
||||
OsuGridToolboxGroup,
|
||||
new TransformToolboxGroup
|
||||
{
|
||||
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
|
||||
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
|
||||
},
|
||||
FreehandlSliderToolboxGroup
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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()
|
||||
=> new OsuBlueprintContainer(this);
|
||||
|
||||
@ -147,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private readonly Cached distanceSnapGridCache = new Cached();
|
||||
private double? lastDistanceSnapGridTime;
|
||||
|
||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
||||
private PositionSnapGrid positionSnapGrid;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@ -205,9 +229,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private void updateState()
|
||||
{
|
||||
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
|
||||
CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;
|
||||
CanRotatePlayfieldOrigin.Value = selectedMovableObjects.Any();
|
||||
CanRotateAroundSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0;
|
||||
CanRotateAroundPlayfieldOrigin.Value = selectedMovableObjects.Any();
|
||||
}
|
||||
|
||||
private OsuHitObject[]? objectsInRotation;
|
||||
|
@ -24,6 +24,16 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class OsuSelectionScaleHandler : SelectionScaleHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether scaling anchored by the center of the playfield can currently be performed.
|
||||
/// </summary>
|
||||
public Bindable<bool> CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// Whether a single slider is currently selected, which results in a different scaling behaviour.
|
||||
/// </summary>
|
||||
public Bindable<bool> IsScalingSlider { get; private set; } = new BindableBool();
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
@ -53,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
CanScaleX.Value = quad.Width > 0;
|
||||
CanScaleY.Value = quad.Height > 0;
|
||||
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
|
||||
CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any();
|
||||
IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider;
|
||||
}
|
||||
|
||||
private Dictionary<OsuHitObject, OriginalHitObjectState>? objectsInScale;
|
||||
@ -67,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
|
||||
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
|
||||
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position))
|
||||
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
|
||||
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
|
||||
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
|
||||
}
|
||||
@ -92,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
else
|
||||
{
|
||||
scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale);
|
||||
scale = ClampScaleToPlayfieldBounds(scale, actualOrigin);
|
||||
|
||||
foreach (var (ho, originalState) in objectsInScale)
|
||||
{
|
||||
@ -158,27 +170,36 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// <summary>
|
||||
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
|
||||
/// </summary>
|
||||
/// <param name="selectionQuad">The quad surrounding the hitobjects</param>
|
||||
/// <param name="origin">The origin from which the scale operation is performed</param>
|
||||
/// <param name="scale">The scale to be clamped</param>
|
||||
/// <returns>The clamped scale vector</returns>
|
||||
private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale)
|
||||
public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null)
|
||||
{
|
||||
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
|
||||
if (objectsInScale == null)
|
||||
return scale;
|
||||
|
||||
var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin);
|
||||
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin);
|
||||
var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin);
|
||||
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin);
|
||||
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
|
||||
|
||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0))
|
||||
scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
|
||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0))
|
||||
scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
|
||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0))
|
||||
scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
|
||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0))
|
||||
scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
|
||||
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
|
||||
origin = slider.Position;
|
||||
|
||||
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
|
||||
var selectionQuad = OriginalSurroundingQuad.Value;
|
||||
|
||||
var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
|
||||
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin);
|
||||
var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin);
|
||||
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin);
|
||||
|
||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0))
|
||||
scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
|
||||
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0))
|
||||
scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
|
||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0))
|
||||
scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
|
||||
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0))
|
||||
scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
|
||||
|
||||
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
|
||||
}
|
||||
|
@ -78,11 +78,15 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScheduleAfterChildren(() => angleInput.TakeFocus());
|
||||
ScheduleAfterChildren(() =>
|
||||
{
|
||||
angleInput.TakeFocus();
|
||||
angleInput.SelectAll();
|
||||
});
|
||||
angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue });
|
||||
rotationOrigin.Items.First().Select();
|
||||
|
||||
rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e =>
|
||||
rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e =>
|
||||
{
|
||||
selectionCentreButton.Selected.Disabled = !e.NewValue;
|
||||
}, true);
|
||||
|
212
osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
Normal file
212
osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
Normal file
@ -0,0 +1,212 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class PreciseScalePopover : OsuPopover
|
||||
{
|
||||
private readonly OsuSelectionScaleHandler scaleHandler;
|
||||
|
||||
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
|
||||
|
||||
private SliderWithTextBoxInput<float> scaleInput = null!;
|
||||
private BindableNumber<float> scaleInputBindable = null!;
|
||||
private EditorRadioButtonCollection scaleOrigin = null!;
|
||||
|
||||
private RadioButton playfieldCentreButton = null!;
|
||||
private RadioButton selectionCentreButton = null!;
|
||||
|
||||
private OsuCheckbox xCheckBox = null!;
|
||||
private OsuCheckbox yCheckBox = null!;
|
||||
|
||||
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler)
|
||||
{
|
||||
this.scaleHandler = scaleHandler;
|
||||
|
||||
AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
scaleInput = new SliderWithTextBoxInput<float>("Scale:")
|
||||
{
|
||||
Current = scaleInputBindable = new BindableNumber<float>
|
||||
{
|
||||
MinValue = 0.5f,
|
||||
MaxValue = 2,
|
||||
Precision = 0.001f,
|
||||
Value = 1,
|
||||
Default = 1,
|
||||
},
|
||||
Instantaneous = true
|
||||
},
|
||||
scaleOrigin = new EditorRadioButtonCollection
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Items = new[]
|
||||
{
|
||||
playfieldCentreButton = new RadioButton("Playfield centre",
|
||||
() => setOrigin(ScaleOrigin.PlayfieldCentre),
|
||||
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
|
||||
selectionCentreButton = new RadioButton("Selection centre",
|
||||
() => setOrigin(ScaleOrigin.SelectionCentre),
|
||||
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(4),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
xCheckBox = new OsuCheckbox(false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
LabelText = "X-axis",
|
||||
Current = { Value = true },
|
||||
},
|
||||
yCheckBox = new OsuCheckbox(false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
LabelText = "Y-axis",
|
||||
Current = { Value = true },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
playfieldCentreButton.Selected.DisabledChanged += isDisabled =>
|
||||
{
|
||||
playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty;
|
||||
};
|
||||
selectionCentreButton.Selected.DisabledChanged += isDisabled =>
|
||||
{
|
||||
selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty;
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ScheduleAfterChildren(() =>
|
||||
{
|
||||
scaleInput.TakeFocus();
|
||||
scaleInput.SelectAll();
|
||||
});
|
||||
scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue });
|
||||
|
||||
xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value));
|
||||
yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue));
|
||||
|
||||
selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value);
|
||||
playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled;
|
||||
|
||||
scaleOrigin.Items.First(b => !b.Selected.Disabled).Select();
|
||||
|
||||
scaleInfo.BindValueChanged(scale =>
|
||||
{
|
||||
var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
|
||||
scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
|
||||
});
|
||||
}
|
||||
|
||||
private void updateAxisCheckBoxesEnabled()
|
||||
{
|
||||
if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre)
|
||||
{
|
||||
toggleAxisAvailable(xCheckBox.Current, true);
|
||||
toggleAxisAvailable(yCheckBox.Current, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value);
|
||||
toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleAxisAvailable(Bindable<bool> axisBindable, bool available)
|
||||
{
|
||||
// enable the bindable to allow setting the value
|
||||
axisBindable.Disabled = false;
|
||||
// restore the presumed default value given the axis's new availability state
|
||||
axisBindable.Value = available;
|
||||
axisBindable.Disabled = !available;
|
||||
}
|
||||
|
||||
private void updateMaxScale()
|
||||
{
|
||||
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
|
||||
return;
|
||||
|
||||
const float max_scale = 10;
|
||||
var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value));
|
||||
|
||||
if (!scaleInfo.Value.XAxis)
|
||||
scale.X = max_scale;
|
||||
if (!scaleInfo.Value.YAxis)
|
||||
scale.Y = max_scale;
|
||||
|
||||
scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y));
|
||||
}
|
||||
|
||||
private void setOrigin(ScaleOrigin origin)
|
||||
{
|
||||
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
|
||||
updateMaxScale();
|
||||
updateAxisCheckBoxesEnabled();
|
||||
}
|
||||
|
||||
private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
|
||||
|
||||
private void setAxis(bool x, bool y)
|
||||
{
|
||||
scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y };
|
||||
updateMaxScale();
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
scaleHandler.Begin();
|
||||
updateMaxScale();
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
if (IsLoaded) scaleHandler.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScaleOrigin
|
||||
{
|
||||
PlayfieldCentre,
|
||||
SelectionCentre
|
||||
}
|
||||
|
||||
public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis);
|
||||
}
|
@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
private void updateBeatmap()
|
||||
{
|
||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||
Beatmap.UpdateAllHitObjects();
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private readonly Bindable<bool> canRotate = new BindableBool();
|
||||
private readonly AggregateBindable<bool> canRotate = new AggregateBindable<bool>((x, y) => x || y);
|
||||
private readonly AggregateBindable<bool> canScale = new AggregateBindable<bool>((x, y) => x || y);
|
||||
|
||||
private EditorToolButton rotateButton = null!;
|
||||
|
||||
private Bindable<bool> canRotatePlayfieldOrigin = null!;
|
||||
private Bindable<bool> canRotateSelectionOrigin = null!;
|
||||
private EditorToolButton scaleButton = null!;
|
||||
|
||||
public SelectionRotationHandler RotationHandler { get; init; } = null!;
|
||||
public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!;
|
||||
|
||||
public TransformToolboxGroup()
|
||||
: base("transform")
|
||||
@ -45,7 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
rotateButton = new EditorToolButton("Rotate",
|
||||
() => new SpriteIcon { Icon = FontAwesome.Solid.Undo },
|
||||
() => new PreciseRotationPopover(RotationHandler)),
|
||||
// TODO: scale
|
||||
scaleButton = new EditorToolButton("Scale",
|
||||
() => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt },
|
||||
() => new PreciseScalePopover(ScaleHandler))
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -54,21 +56,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// aggregate two values into canRotate
|
||||
canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy();
|
||||
canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate());
|
||||
canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin);
|
||||
canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin);
|
||||
|
||||
canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy();
|
||||
canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate());
|
||||
|
||||
void updateCanRotateAggregate()
|
||||
{
|
||||
canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value;
|
||||
}
|
||||
canScale.AddSource(ScaleHandler.CanScaleX);
|
||||
canScale.AddSource(ScaleHandler.CanScaleY);
|
||||
canScale.AddSource(ScaleHandler.CanScaleFromPlayfieldOrigin);
|
||||
|
||||
// bindings to `Enabled` on the buttons are decoupled on purpose
|
||||
// due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set.
|
||||
canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true);
|
||||
canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true);
|
||||
canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
@ -82,6 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
rotateButton.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
case GlobalAction.EditorToggleScaleControl:
|
||||
{
|
||||
scaleButton.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -67,8 +67,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
// Generate the replay frames the cursor should follow
|
||||
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast<OsuReplayFrame>().ToList();
|
||||
|
||||
drawableRuleset.UseResumeOverlay = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,9 +47,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Hide judgment displays and follow points as they won't make any sense.
|
||||
// Hide follow points as they won't make any sense.
|
||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Hide judgment displays and follow points as they won't make any sense.
|
||||
// Hide follow points as they won't make any sense.
|
||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||
}
|
||||
|
||||
|
@ -38,9 +38,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Hide judgment displays and follow points as they won't make any sense.
|
||||
// Hide follow points as they won't make any sense.
|
||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
@ -14,10 +12,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableOsuJudgement : DrawableJudgement
|
||||
{
|
||||
internal SkinnableLighting Lighting { get; private set; }
|
||||
internal SkinnableLighting Lighting { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private bool positionTransferred;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -39,10 +39,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Lighting.ResetAnimation();
|
||||
Lighting.SetColourFrom(JudgedObject, Result);
|
||||
|
||||
if (JudgedObject?.HitObject is OsuHitObject osuObject)
|
||||
positionTransferred = false;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse)
|
||||
{
|
||||
Position = osuObject.StackedEndPosition;
|
||||
Scale = new Vector2(osuObject.Scale);
|
||||
Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!);
|
||||
Scale = new Vector2(osuObject.HitObject.Scale);
|
||||
|
||||
positionTransferred = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
@ -63,22 +61,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.ApplyTransformsAt(time, false);
|
||||
}
|
||||
|
||||
private Vector2? lastPosition;
|
||||
|
||||
public void UpdateProgress(double completionProgress)
|
||||
{
|
||||
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
|
||||
Slider slider = drawableSlider.HitObject;
|
||||
Position = slider.CurvePositionAt(completionProgress);
|
||||
|
||||
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
|
||||
|
||||
bool rewinding = (Clock as IGameplayClock)?.IsRewinding == true;
|
||||
//0.1 / slider.Path.Distance is the additional progress needed to ensure the diff length is 0.1
|
||||
var diff = slider.CurvePositionAt(completionProgress) - slider.CurvePositionAt(Math.Min(1, completionProgress + 0.1 / slider.Path.Distance));
|
||||
|
||||
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
|
||||
// Needed for when near completion, or in case of a very short slider.
|
||||
if (diff.LengthFast < 0.01f)
|
||||
return;
|
||||
|
||||
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0);
|
||||
lastPosition = Position;
|
||||
ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,6 +260,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
protected void UpdateNestedSamples()
|
||||
{
|
||||
this.PopulateNodeSamples();
|
||||
|
||||
// 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");
|
||||
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -45,7 +46,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { AlignWithStoryboard = true };
|
||||
|
||||
protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay();
|
||||
protected override ResumeOverlay CreateResumeOverlay()
|
||||
{
|
||||
if (Mods.Any(m => m is OsuModAutopilot))
|
||||
return new DelayedResumeOverlay { Scale = new Vector2(0.65f) };
|
||||
|
||||
return new OsuResumeOverlay();
|
||||
}
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
||||
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
[Cached]
|
||||
public partial class OsuPlayfield : Playfield
|
||||
{
|
||||
private readonly Container borderContainer;
|
||||
private readonly PlayfieldBorder playfieldBorder;
|
||||
private readonly ProxyContainer approachCircles;
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
@ -54,7 +56,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
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 },
|
||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||
@ -151,6 +157,14 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
||||
RegisterPool<SpinnerTick, DrawableSpinnerTick>(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);
|
||||
|
@ -85,6 +85,42 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
||||
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]
|
||||
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,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(100, 1600),
|
||||
},
|
||||
|
@ -53,6 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
|
||||
public void SetStrongState(bool state)
|
||||
{
|
||||
if (SelectedItems.OfType<Hit>().All(h => h.IsStrong == state))
|
||||
return;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
if (!(h is Hit taikoHit)) return;
|
||||
@ -67,6 +70,9 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
|
||||
public void SetRimState(bool state)
|
||||
{
|
||||
if (SelectedItems.OfType<Hit>().All(h => h.Type == (state ? HitType.Rim : HitType.Centre)))
|
||||
return;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
if (h is Hit taikoHit)
|
||||
|
@ -5,13 +5,34 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
|
||||
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 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>
|
||||
/// A list of keys which can result in hits for this HitObject.
|
||||
/// </summary>
|
||||
public TaikoAction[] HitActions { get; private set; }
|
||||
public TaikoAction[] HitActions { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the player must alternate centre and rim hits.
|
||||
/// </summary>
|
||||
public bool MustAlternate { get; internal set; } = true;
|
||||
|
||||
public DrawableSwell()
|
||||
: this(null)
|
||||
{
|
||||
@ -292,7 +297,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre;
|
||||
|
||||
// Ensure alternating centre and rim hits
|
||||
if (lastWasCentre == isCentre)
|
||||
if (lastWasCentre == isCentre && MustAlternate)
|
||||
return false;
|
||||
|
||||
// If we've already successfully judged a tick this frame, do not judge more.
|
||||
|
@ -33,6 +33,7 @@ using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
@ -126,17 +127,20 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)]
|
||||
public void TestLegacyBeatmapReplayOffsetsDecode(int beatmapVersion, bool offsetApplied)
|
||||
{
|
||||
const double first_frame_time = 48;
|
||||
const double second_frame_time = 65;
|
||||
const double first_frame_time = 31;
|
||||
const double second_frame_time = 48;
|
||||
const double third_frame_time = 65;
|
||||
|
||||
var decoder = new TestLegacyScoreDecoder(beatmapVersion);
|
||||
|
||||
using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
|
||||
{
|
||||
var score = decoder.Parse(resourceStream);
|
||||
int offset = offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0;
|
||||
|
||||
Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0)));
|
||||
Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0)));
|
||||
Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + offset));
|
||||
Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + offset));
|
||||
Assert.That(score.Replay.Frames[2].Time, Is.EqualTo(third_frame_time + offset));
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,6 +181,94 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(second_frame_time));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNegativeFrameSkipped()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
ScoreInfo = scoreInfo,
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2()),
|
||||
new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE),
|
||||
new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2),
|
||||
new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(1000));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(2000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FirstTwoFramesSwappedIfInWrongOrder()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
ScoreInfo = scoreInfo,
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(100, new Vector2()),
|
||||
new OsuReplayFrame(50, OsuPlayfield.BASE_SIZE / 2),
|
||||
new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(100));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(1000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FirstTwoFramesPulledTowardThirdIfTheyAreAfterIt()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
ScoreInfo = scoreInfo,
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2()),
|
||||
new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2),
|
||||
new OsuReplayFrame(-1500, OsuPlayfield.BASE_SIZE),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap);
|
||||
|
||||
Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(-1500));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(-1500));
|
||||
Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(-1500));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCultureInvariance()
|
||||
{
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Chat
|
||||
return true;
|
||||
|
||||
case ChatAckRequest ack:
|
||||
ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToList() });
|
||||
ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToArray() });
|
||||
silencedUserIds.Clear();
|
||||
return true;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -29,7 +28,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(0, 649)
|
||||
}
|
||||
@ -52,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1_200 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(100, 751)
|
||||
}
|
||||
@ -75,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1_298 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(200, 850)
|
||||
}
|
||||
@ -98,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1200 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(1398, 2300)
|
||||
}
|
||||
@ -121,7 +120,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 1100 },
|
||||
new HitCircle { StartTime = 1500 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(0, 652)
|
||||
}
|
||||
@ -145,7 +144,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 1_297 },
|
||||
new HitCircle { StartTime = 1_298 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(200, 850)
|
||||
}
|
||||
@ -168,7 +167,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 1_300 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(200, 850)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
new HitCircle { StartTime = 0 },
|
||||
new HitCircle { StartTime = 40_000 }
|
||||
},
|
||||
Breaks = new List<BreakPeriod>
|
||||
Breaks =
|
||||
{
|
||||
new BreakPeriod(10_000, 21_000)
|
||||
}
|
||||
|
300
osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Normal file
300
osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs
Normal file
@ -0,0 +1,300 @@
|
||||
// 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.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 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
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",
|
||||
// Covers "Argon" performance points counter
|
||||
"Archives/modified-argon-20240305.osk",
|
||||
// Covers default rank display
|
||||
"Archives/modified-default-20230809.osk",
|
||||
// Covers legacy rank display
|
||||
"Archives/modified-classic-20230809.osk"
|
||||
};
|
||||
|
||||
/// <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,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,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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
this.getTargetContainer = getTargetContainer;
|
||||
|
||||
CanRotateSelectionOrigin.Value = true;
|
||||
CanRotateAroundSelectionOrigin.Value = true;
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
|
@ -4,10 +4,12 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
@ -15,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[Test]
|
||||
public void TestSelectedObjects()
|
||||
{
|
||||
|
@ -13,9 +13,12 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets;
|
||||
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.Objects;
|
||||
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.Timing;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@ -79,10 +82,10 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPopoverHasFocus()
|
||||
public void TestPopoverHasNoFocus()
|
||||
{
|
||||
clickSamplePiece(0);
|
||||
samplePopoverHasFocus();
|
||||
samplePopoverHasNoFocus();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -226,6 +229,84 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
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]
|
||||
public void TestHotkeysMultipleSelectionWithSameSampleBank()
|
||||
{
|
||||
@ -329,13 +410,21 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
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 slider = popover?.ChildrenOfType<IndeterminateSliderWithTextBoxInput<int>>().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}", () =>
|
||||
@ -372,7 +461,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private void dismissPopover()
|
||||
{
|
||||
AddStep("unfocus textbox", () => InputManager.Key(Key.Escape));
|
||||
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
|
||||
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);
|
||||
});
|
||||
|
||||
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", () =>
|
||||
{
|
||||
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
|
||||
@ -401,6 +495,26 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
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)}", () =>
|
||||
{
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||
@ -412,5 +526,41 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||
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
|
||||
{
|
||||
public partial class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene
|
||||
public partial class TestScenePositionSnapGrid : OsuManualInputManagerTestScene
|
||||
{
|
||||
private Container content;
|
||||
protected override Container<Drawable> Content => content;
|
||||
@ -33,28 +33,34 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(10),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static readonly object[][] test_cases =
|
||||
{
|
||||
new object[] { new Vector2(0, 0), new Vector2(10, 10) },
|
||||
new object[] { new Vector2(240, 180), new Vector2(10, 15) },
|
||||
new object[] { new Vector2(160, 120), new Vector2(30, 20) },
|
||||
new object[] { new Vector2(480, 360), new Vector2(100, 100) },
|
||||
new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f },
|
||||
new object[] { new Vector2(240, 180), new Vector2(10, 15), 10f },
|
||||
new object[] { new Vector2(160, 120), new Vector2(30, 20), -10f },
|
||||
new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestRectangularGrid(Vector2 position, Vector2 spacing)
|
||||
public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotation)
|
||||
{
|
||||
RectangularPositionSnapGrid grid = null;
|
||||
|
||||
AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position)
|
||||
AddStep("create grid", () =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Spacing = spacing
|
||||
Child = grid = new RectangularPositionSnapGrid
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
grid.StartPosition.Value = position;
|
||||
grid.Spacing.Value = spacing;
|
||||
grid.GridLineRotation.Value = rotation;
|
||||
});
|
||||
|
||||
AddStep("add snapping cursor", () => Add(new SnappingCursorContainer
|
||||
@ -86,7 +92,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
|
||||
updatePosition(GetContainingInputManager()!.CurrentState.Mouse.Position);
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@ -19,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneBeatmapOffsetControl : OsuTestScene
|
||||
{
|
||||
private BeatmapOffsetControl offsetControl;
|
||||
private BeatmapOffsetControl offsetControl = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
@ -137,5 +135,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||
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.IO;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -28,14 +31,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private DrawableStoryboard? storyboard;
|
||||
|
||||
[Cached]
|
||||
private GameplayState testGameplayState = TestGameplayState.Create(new OsuRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestStoryboard()
|
||||
{
|
||||
AddStep("Restart", restart);
|
||||
AddToggleStep("Passing", passing =>
|
||||
{
|
||||
if (storyboard != null) storyboard.Passing = passing;
|
||||
});
|
||||
AddToggleStep("Toggle passing state", passing => testGameplayState.HealthProcessor.Health.Value = passing ? 1 : 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -109,7 +112,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
storyboardContainer.Clock = new FramedClock(Beatmap.Value.Track);
|
||||
|
||||
storyboard = toLoad.CreateDrawable(SelectedMods.Value);
|
||||
storyboard.Passing = false;
|
||||
|
||||
storyboardContainer.Add(storyboard);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -15,8 +16,14 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
private OnlineMenuBanner onlineMenuBanner => Game.ChildrenOfType<OnlineMenuBanner>().Single();
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineMenuBanner()
|
||||
public void TestOnlineMenuBannerTrusted()
|
||||
{
|
||||
AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent
|
||||
{
|
||||
@ -25,13 +32,51 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
new APIMenuImage
|
||||
{
|
||||
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||
Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023",
|
||||
Url = $@"{API.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023",
|
||||
}
|
||||
}
|
||||
});
|
||||
AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddStep("enter menu", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>().FirstOrDefault()?.IsLoaded, () => Is.True);
|
||||
|
||||
AddStep("click banner", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(onlineMenuBanner);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
// Might not catch every occurrence due to async nature, but works in manual testing and saves annoying test setup.
|
||||
AddAssert("no dialog", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineMenuBannerUntrustedDomain()
|
||||
{
|
||||
AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent
|
||||
{
|
||||
Images = new[]
|
||||
{
|
||||
new APIMenuImage
|
||||
{
|
||||
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
|
||||
Url = @"https://google.com",
|
||||
}
|
||||
}
|
||||
});
|
||||
AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
AddStep("enter menu", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>().FirstOrDefault()?.IsLoaded, () => Is.True);
|
||||
|
||||
AddStep("click banner", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(onlineMenuBanner);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for dialog", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
|
||||
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
||||
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||
|
||||
AddUntilStep("mod select contents loaded",
|
||||
() => 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);
|
||||
|
||||
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
|
||||
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||
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));
|
||||
|
||||
|
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.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -15,7 +16,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public TestSceneBackButton()
|
||||
{
|
||||
BackButton button;
|
||||
BackButton.Receptor receptor = new BackButton.Receptor();
|
||||
ScreenFooter.BackReceptor receptor = new ScreenFooter.BackReceptor();
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
|
@ -2,194 +2,93 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Overlays;
|
||||
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.SelectV2.Footer;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
private ScreenFooterButtonRandom randomButton = null!;
|
||||
private ScreenFooterButtonMods modsButton = null!;
|
||||
|
||||
private bool nextRandomCalled;
|
||||
private bool previousRandomCalled;
|
||||
|
||||
private DummyOverlay overlay = null!;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
private ScreenFooter screenFooter = null!;
|
||||
private TestModSelectOverlay overlay = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
nextRandomCalled = false;
|
||||
previousRandomCalled = false;
|
||||
|
||||
ScreenFooter footer;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
overlay = new TestModSelectOverlay
|
||||
{
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Bottom = ScreenFooter.HEIGHT
|
||||
}
|
||||
},
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = footer = new ScreenFooter(),
|
||||
Child = screenFooter = new ScreenFooter(),
|
||||
},
|
||||
overlay = new DummyOverlay()
|
||||
};
|
||||
|
||||
footer.AddButton(modsButton = new ScreenFooterButtonMods { Current = SelectedMods }, overlay);
|
||||
footer.AddButton(randomButton = new ScreenFooterButtonRandom
|
||||
screenFooter.SetButtons(new ScreenFooterButton[]
|
||||
{
|
||||
NextRandom = () => nextRandomCalled = true,
|
||||
PreviousRandom = () => previousRandomCalled = true
|
||||
new ScreenFooterButtonMods(overlay) { Current = SelectedMods },
|
||||
new ScreenFooterButtonRandom(),
|
||||
new ScreenFooterButtonOptions(),
|
||||
});
|
||||
footer.AddButton(new ScreenFooterButtonOptions());
|
||||
|
||||
overlay.Hide();
|
||||
});
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)));
|
||||
AddStep("show footer", () => screenFooter.Show());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transition when moving from a screen with no buttons to a screen with buttons.
|
||||
/// </summary>
|
||||
[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]
|
||||
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();
|
||||
|
||||
optionsButton.Enabled.Value = true;
|
||||
optionsButton.TriggerClick();
|
||||
});
|
||||
new ScreenFooterButton { Text = "One", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestState()
|
||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||
{
|
||||
AddToggleStep("set options enabled state", state => this.ChildrenOfType<ScreenFooterButton>().Last().Enabled.Value = state);
|
||||
}
|
||||
protected override bool ShowPresets => true;
|
||||
|
||||
[Test]
|
||||
public void TestFooterRandom()
|
||||
{
|
||||
AddStep("press F2", () => InputManager.Key(Key.F2));
|
||||
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooterRandomViaMouse()
|
||||
{
|
||||
AddStep("click button", () =>
|
||||
public TestModSelectOverlay()
|
||||
: base(OverlayColourScheme.Aquamarine)
|
||||
{
|
||||
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.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
@ -26,12 +27,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
public TestSceneScreenFooterButtonMods()
|
||||
{
|
||||
Add(footerButtonMods = new TestScreenFooterButtonMods
|
||||
Add(footerButtonMods = new TestScreenFooterButtonMods(new TestModSelectOverlay())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
X = -100,
|
||||
Action = () => { },
|
||||
X = -100,
|
||||
});
|
||||
}
|
||||
|
||||
@ -112,9 +113,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
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
|
||||
{
|
||||
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
||||
|
||||
public TestScreenFooterButtonMods(ModSelectOverlay overlay)
|
||||
: base(overlay)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,15 +51,16 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
|
||||
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,
|
||||
Origin = Anchor.Centre,
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Depth = float.MaxValue
|
||||
});
|
||||
|
||||
grid.Spacing.Value = new Vector2(GRID_SPACING);
|
||||
|
||||
LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
|
||||
updateMessage();
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||
editorInfo.Selected.ValueChanged += selection =>
|
||||
{
|
||||
// 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).
|
||||
// Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best.
|
||||
|
@ -123,7 +123,12 @@ namespace osu.Game.Tournament.Screens.MapPool
|
||||
|
||||
private void beatmapChanged(ValueChangedEvent<TournamentBeatmap?> beatmap)
|
||||
{
|
||||
if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2)
|
||||
if (CurrentMatch.Value?.Round.Value == null)
|
||||
return;
|
||||
|
||||
int totalBansRequired = CurrentMatch.Value.Round.Value.BanCount.Value * 2;
|
||||
|
||||
if (CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < totalBansRequired)
|
||||
return;
|
||||
|
||||
// if bans have already been placed, beatmap changes result in a selection being made automatically
|
||||
|
@ -1,10 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using ManagedBass.Fx;
|
||||
using osu.Framework.Audio.Mixing;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Audio.Effects
|
||||
@ -26,8 +24,6 @@ namespace osu.Game.Audio.Effects
|
||||
private readonly BQFParameters filter;
|
||||
private readonly BQFType type;
|
||||
|
||||
private readonly Cached filterApplication = new Cached();
|
||||
|
||||
private int cutoff;
|
||||
|
||||
/// <summary>
|
||||
@ -42,7 +38,7 @@ namespace osu.Game.Audio.Effects
|
||||
return;
|
||||
|
||||
cutoff = value;
|
||||
filterApplication.Invalidate();
|
||||
updateFilter();
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,18 +60,9 @@ namespace osu.Game.Audio.Effects
|
||||
fQ = 0.7f
|
||||
};
|
||||
|
||||
Cutoff = getInitialCutoff(type);
|
||||
}
|
||||
cutoff = getInitialCutoff(type);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!filterApplication.IsValid)
|
||||
{
|
||||
updateFilter(cutoff);
|
||||
filterApplication.Validate();
|
||||
}
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
private int getInitialCutoff(BQFType type)
|
||||
@ -93,13 +80,13 @@ namespace osu.Game.Audio.Effects
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFilter(int newValue)
|
||||
private void updateFilter()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case BQFType.LowPass:
|
||||
// Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
|
||||
if (newValue >= MAX_LOWPASS_CUTOFF)
|
||||
if (Cutoff >= MAX_LOWPASS_CUTOFF)
|
||||
{
|
||||
ensureDetached();
|
||||
return;
|
||||
@ -109,7 +96,7 @@ namespace osu.Game.Audio.Effects
|
||||
|
||||
// Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
|
||||
case BQFType.HighPass:
|
||||
if (newValue <= 1)
|
||||
if (Cutoff <= 1)
|
||||
{
|
||||
ensureDetached();
|
||||
return;
|
||||
@ -120,17 +107,8 @@ namespace osu.Game.Audio.Effects
|
||||
|
||||
ensureAttached();
|
||||
|
||||
int filterIndex = mixer.Effects.IndexOf(filter);
|
||||
|
||||
if (filterIndex < 0) return;
|
||||
|
||||
if (mixer.Effects[filterIndex] is BQFParameters existingFilter)
|
||||
{
|
||||
existingFilter.fCenter = newValue;
|
||||
|
||||
// required to update effect with new parameters.
|
||||
mixer.Effects[filterIndex] = existingFilter;
|
||||
}
|
||||
filter.fCenter = Cutoff;
|
||||
mixer.UpdateEffect(filter);
|
||||
}
|
||||
|
||||
private void ensureAttached()
|
||||
@ -138,8 +116,7 @@ namespace osu.Game.Audio.Effects
|
||||
if (IsAttached)
|
||||
return;
|
||||
|
||||
Debug.Assert(!mixer.Effects.Contains(filter));
|
||||
mixer.Effects.Add(filter);
|
||||
mixer.AddEffect(filter);
|
||||
IsAttached = true;
|
||||
}
|
||||
|
||||
@ -148,8 +125,7 @@ namespace osu.Game.Audio.Effects
|
||||
if (!IsAttached)
|
||||
return;
|
||||
|
||||
Debug.Assert(mixer.Effects.Contains(filter));
|
||||
mixer.Effects.Remove(filter);
|
||||
mixer.RemoveEffect(filter);
|
||||
IsAttached = false;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();
|
||||
|
||||
public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
|
||||
public BindableList<BreakPeriod> Breaks { get; set; } = new BindableList<BreakPeriod>();
|
||||
|
||||
public List<string> UnhandledEventLines { get; set; } = new List<string>();
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Utils;
|
||||
@ -9,6 +10,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IDeepCloneable<ControlPoint>, IEquatable<ControlPoint>, IControlPoint
|
||||
{
|
||||
[JsonIgnore]
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Utils;
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
[Serializable]
|
||||
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
public class ControlPointInfo : IDeepCloneable<ControlPointInfo>
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
@ -124,12 +125,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
miscFillFlowContainer.Show();
|
||||
|
||||
double rate = 1;
|
||||
|
||||
if (displayedContent.Mods != null)
|
||||
{
|
||||
foreach (var mod in displayedContent.Mods.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
}
|
||||
rate = ModUtils.CalculateRateWithMods(displayedContent.Mods);
|
||||
|
||||
double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate;
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -40,7 +41,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// The breaks in this beatmap.
|
||||
/// </summary>
|
||||
List<BreakPeriod> Breaks { get; }
|
||||
BindableList<BreakPeriod> Breaks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All lines from the [Events] section which aren't handled in the encoding process yet.
|
||||
|
@ -1,26 +1,44 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
public class BreakPeriod
|
||||
public class BreakPeriod : IEquatable<BreakPeriod>
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum gap between the start of the break and the previous object.
|
||||
/// </summary>
|
||||
public const double GAP_BEFORE_BREAK = 200;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum gap between the end of the break and the next object.
|
||||
/// Based on osu! preempt time at AR=10.
|
||||
/// See also: https://github.com/ppy/osu/issues/14330#issuecomment-1002158551
|
||||
/// </summary>
|
||||
public const double GAP_AFTER_BREAK = 450;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum duration required for a break to have any effect.
|
||||
/// </summary>
|
||||
public const double MIN_BREAK_DURATION = 650;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum required duration of a gap between two objects such that a break can be placed between them.
|
||||
/// </summary>
|
||||
public const double MIN_GAP_DURATION = GAP_BEFORE_BREAK + MIN_BREAK_DURATION + GAP_AFTER_BREAK;
|
||||
|
||||
/// <summary>
|
||||
/// The break start time.
|
||||
/// </summary>
|
||||
public double StartTime;
|
||||
public double StartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The break end time.
|
||||
/// </summary>
|
||||
public double EndTime;
|
||||
public double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The break duration.
|
||||
@ -49,5 +67,14 @@ namespace osu.Game.Beatmaps.Timing
|
||||
/// <param name="time">The time to check in milliseconds.</param>
|
||||
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
|
||||
public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION;
|
||||
|
||||
public bool Intersects(BreakPeriod other) => StartTime <= other.EndTime && EndTime >= other.StartTime;
|
||||
|
||||
public virtual bool Equals(BreakPeriod? other) =>
|
||||
other != null
|
||||
&& StartTime == other.StartTime
|
||||
&& EndTime == other.EndTime;
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(StartTime, EndTime);
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +208,9 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
|
||||
SetDefault<UserStatus?>(OsuSetting.UserOnlineStatus, null);
|
||||
|
||||
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
|
||||
SetDefault(OsuSetting.EditorTimelineShowTicks, true);
|
||||
}
|
||||
|
||||
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
||||
@ -439,5 +442,7 @@ namespace osu.Game.Configuration
|
||||
UserOnlineStatus,
|
||||
MultiplayerRoomFilter,
|
||||
HideCountryFlags,
|
||||
EditorTimelineShowTimingChanges,
|
||||
EditorTimelineShowTicks,
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
inputManager = GetContainingInputManager()!;
|
||||
showDuringTouch = config.GetBindable<bool>(OsuSetting.GameplayCursorDuringTouch);
|
||||
}
|
||||
|
||||
|
@ -7,19 +7,18 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens.Footer;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
// todo: remove this once all screens migrate to display the new game footer and back button.
|
||||
public partial class BackButton : VisibilityContainer
|
||||
{
|
||||
public Action Action;
|
||||
|
||||
private readonly TwoLayerButton button;
|
||||
|
||||
public BackButton(Receptor receptor = null)
|
||||
public BackButton(ScreenFooter.BackReceptor receptor = null)
|
||||
{
|
||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||
|
||||
@ -35,7 +34,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
if (receptor == null)
|
||||
{
|
||||
// if a receptor wasn't provided, create our own locally.
|
||||
Add(receptor = new Receptor());
|
||||
Add(receptor = new ScreenFooter.BackReceptor());
|
||||
}
|
||||
|
||||
receptor.OnBackPressed = () => button.TriggerClick();
|
||||
@ -59,29 +58,5 @@ namespace osu.Game.Graphics.UserInterface
|
||||
button.MoveToX(-TwoLayerButton.SIZE_EXTENDED.X / 2, 400, Easing.OutQuint);
|
||||
button.FadeOut(400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public partial class Receptor : Drawable, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public Action OnBackPressed;
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
OnBackPressed?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,9 +26,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
// Right mouse button is a special case where we allow actioning without dismissing the menu.
|
||||
// This is achieved by not calling `Clicked` (as done by the base implementation in OnClick).
|
||||
if (IsActionable && e.Button == MouseButton.Right)
|
||||
{
|
||||
Item.Action.Value?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private partial class ToggleTextContainer : TextContainer
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
if (!allowImmediateFocus)
|
||||
return;
|
||||
|
||||
Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(this));
|
||||
Scheduler.Add(() => GetContainingFocusManager()!.ChangeFocus(this));
|
||||
}
|
||||
|
||||
public new void KillFocus() => base.KillFocus();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user