mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 20:53:00 +08:00
Merge branch 'master' into more-timeline-toggles
This commit is contained in:
commit
7957773d58
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.930.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1001.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -9,7 +9,7 @@ using osu.Framework.Android;
|
|||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
||||||
public class OsuGameActivity : AndroidGameActivity
|
public class OsuGameActivity : AndroidGameActivity
|
||||||
{
|
{
|
||||||
protected override Framework.Game CreateGame() => new OsuGameAndroid();
|
protected override Framework.Game CreateGame() => new OsuGameAndroid();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
public class TestSceneHoldNote : ManiaHitObjectTestScene
|
public class TestSceneHoldNote : ManiaHitObjectTestScene
|
||||||
{
|
{
|
||||||
public TestSceneHoldNote()
|
[Test]
|
||||||
|
public void TestHoldNote()
|
||||||
{
|
{
|
||||||
AddToggleStep("toggle hitting", v =>
|
AddToggleStep("toggle hitting", v =>
|
||||||
{
|
{
|
||||||
|
@ -28,25 +28,33 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneNotes : OsuTestScene
|
public class TestSceneNotes : OsuTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[Test]
|
||||||
private void load()
|
public void TestVariousNotes()
|
||||||
{
|
{
|
||||||
Child = new FillFlowContainer
|
DrawableNote note1 = null;
|
||||||
|
DrawableNote note2 = null;
|
||||||
|
DrawableHoldNote holdNote1 = null;
|
||||||
|
DrawableHoldNote holdNote2 = null;
|
||||||
|
|
||||||
|
AddStep("create notes", () =>
|
||||||
{
|
{
|
||||||
Clock = new FramedClock(new ManualClock()),
|
Child = new FillFlowContainer
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(20),
|
|
||||||
Children = new[]
|
|
||||||
{
|
{
|
||||||
createNoteDisplay(ScrollingDirection.Down, 1, out var note1),
|
Clock = new FramedClock(new ManualClock()),
|
||||||
createNoteDisplay(ScrollingDirection.Up, 2, out var note2),
|
Anchor = Anchor.Centre,
|
||||||
createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1),
|
Origin = Anchor.Centre,
|
||||||
createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2),
|
AutoSizeAxes = Axes.Both,
|
||||||
}
|
Direction = FillDirection.Horizontal,
|
||||||
};
|
Spacing = new Vector2(20),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
createNoteDisplay(ScrollingDirection.Down, 1, out note1),
|
||||||
|
createNoteDisplay(ScrollingDirection.Up, 2, out note2),
|
||||||
|
createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1),
|
||||||
|
createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
|
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
|
||||||
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));
|
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));
|
||||||
|
@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
private int depthIndex;
|
private int depthIndex;
|
||||||
|
|
||||||
public TestSceneHitCircle()
|
[Test]
|
||||||
|
public void TestVariousHitCircles()
|
||||||
{
|
{
|
||||||
AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
|
AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
|
||||||
AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));
|
AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));
|
||||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
private int depthIndex;
|
private int depthIndex;
|
||||||
|
|
||||||
public TestSceneSlider()
|
[Test]
|
||||||
|
public void TestVariousSliders()
|
||||||
{
|
{
|
||||||
AddStep("Big Single", () => SetContents(() => testSimpleBig()));
|
AddStep("Big Single", () => SetContents(() => testSimpleBig()));
|
||||||
AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));
|
AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));
|
||||||
@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(239, 176),
|
Position = new Vector2(239, 176),
|
||||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
{
|
{
|
||||||
@ -185,22 +186,26 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5);
|
private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5);
|
||||||
|
|
||||||
private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
|
private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 0.5);
|
||||||
|
|
||||||
private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
|
private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
|
||||||
|
|
||||||
private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
|
private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 15);
|
||||||
|
|
||||||
private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
|
private const double time_offset = 1500;
|
||||||
|
|
||||||
|
private const float max_length = 200;
|
||||||
|
|
||||||
|
private Drawable createSlider(float circleSize = 2, float distance = max_length, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
|
||||||
{
|
{
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(-(distance / 2), 0),
|
Position = new Vector2(0, -(distance / 2)),
|
||||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(distance, 0),
|
new Vector2(0, distance),
|
||||||
}, distance),
|
}, distance),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
StackHeight = stackHeight
|
StackHeight = stackHeight
|
||||||
@ -213,14 +218,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(-200, 0),
|
Position = new Vector2(-max_length / 2, 0),
|
||||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(200, 200),
|
new Vector2(max_length / 2, max_length / 2),
|
||||||
new Vector2(400, 0)
|
new Vector2(max_length, 0)
|
||||||
}, 600),
|
}, max_length * 1.5f),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -233,16 +238,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(-200, 0),
|
Position = new Vector2(-max_length / 2, 0),
|
||||||
Path = new SliderPath(PathType.Linear, new[]
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(150, 75),
|
new Vector2(max_length * 0.375f, max_length * 0.18f),
|
||||||
new Vector2(200, 0),
|
new Vector2(max_length / 2, 0),
|
||||||
new Vector2(300, -200),
|
new Vector2(max_length * 0.75f, -max_length / 2),
|
||||||
new Vector2(400, 0),
|
new Vector2(max_length * 0.95f, 0),
|
||||||
new Vector2(430, 0)
|
new Vector2(max_length, 0)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
};
|
};
|
||||||
@ -256,15 +261,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(-200, 0),
|
Position = new Vector2(-max_length / 2, 0),
|
||||||
Path = new SliderPath(PathType.Bezier, new[]
|
Path = new SliderPath(PathType.Bezier, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(150, 75),
|
new Vector2(max_length * 0.375f, max_length * 0.18f),
|
||||||
new Vector2(200, 100),
|
new Vector2(max_length / 2, max_length / 4),
|
||||||
new Vector2(300, -200),
|
new Vector2(max_length * 0.75f, -max_length / 2),
|
||||||
new Vector2(430, 0)
|
new Vector2(max_length, 0)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
};
|
};
|
||||||
@ -278,16 +283,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(0, 0),
|
Position = new Vector2(0, 0),
|
||||||
Path = new SliderPath(PathType.Linear, new[]
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(-200, 0),
|
new Vector2(-max_length / 2, 0),
|
||||||
new Vector2(0, 0),
|
new Vector2(0, 0),
|
||||||
new Vector2(0, -200),
|
new Vector2(0, -max_length / 2),
|
||||||
new Vector2(-200, -200),
|
new Vector2(-max_length / 2, -max_length / 2),
|
||||||
new Vector2(0, -200)
|
new Vector2(0, -max_length / 2)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
};
|
};
|
||||||
@ -305,14 +310,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
var slider = new Slider
|
var slider = new Slider
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000,
|
StartTime = Time.Current + time_offset,
|
||||||
Position = new Vector2(-100, 0),
|
Position = new Vector2(-max_length / 4, 0),
|
||||||
Path = new SliderPath(PathType.Catmull, new[]
|
Path = new SliderPath(PathType.Catmull, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(50, -50),
|
new Vector2(max_length * 0.125f, max_length * 0.125f),
|
||||||
new Vector2(150, 50),
|
new Vector2(max_length * 0.375f, max_length * 0.125f),
|
||||||
new Vector2(200, 0)
|
new Vector2(max_length / 2, 0)
|
||||||
}),
|
}),
|
||||||
RepeatCount = repeats,
|
RepeatCount = repeats,
|
||||||
NodeSamples = repeatSamples
|
NodeSamples = repeatSamples
|
||||||
|
@ -110,6 +110,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void StopAllSamples()
|
||||||
|
{
|
||||||
|
base.StopAllSamples();
|
||||||
|
slidingSample?.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
|
private void updateSlidingSample(ValueChangedEvent<bool> tracking)
|
||||||
{
|
{
|
||||||
if (tracking.NewValue)
|
if (tracking.NewValue)
|
||||||
|
@ -124,6 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void StopAllSamples()
|
||||||
|
{
|
||||||
|
base.StopAllSamples();
|
||||||
|
spinningSample?.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||||
@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore textures)
|
private void load(TextureStore textures, DrawableHitObject drawableHitObject)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Texture = textures.Get(@"Gameplay/osu/disc"),
|
Texture = textures.Get(@"Gameplay/osu/disc"),
|
||||||
},
|
},
|
||||||
new TrianglesPiece
|
new TrianglesPiece((int)drawableHitObject.HitObject.StartTime)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
private Spinner spinner;
|
private Spinner spinner;
|
||||||
|
|
||||||
|
private const float initial_scale = 1.3f;
|
||||||
private const float idle_alpha = 0.2f;
|
private const float idle_alpha = 0.2f;
|
||||||
private const float tracking_alpha = 0.4f;
|
private const float tracking_alpha = 0.4f;
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
// we are slightly bigger than our parent, to clip the top and bottom of the circle
|
// we are slightly bigger than our parent, to clip the top and bottom of the circle
|
||||||
// this should probably be revisited when scaled spinners are a thing.
|
// this should probably be revisited when scaled spinners are a thing.
|
||||||
Scale = new Vector2(1.3f);
|
Scale = new Vector2(initial_scale);
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
@ -93,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
|
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -115,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime));
|
fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
const float initial_scale = 0.2f;
|
const float initial_fill_scale = 0.2f;
|
||||||
float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress;
|
float targetScale = initial_fill_scale + (1 - initial_fill_scale) * drawableSpinner.Progress;
|
||||||
|
|
||||||
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
|
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
|
||||||
mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation;
|
mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation;
|
||||||
@ -124,41 +127,57 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
centre.ScaleTo(0);
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
mainContainer.ScaleTo(0);
|
return;
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
||||||
{
|
{
|
||||||
// constant ambient rotation to give the spinner "spinning" character.
|
this.ScaleTo(initial_scale);
|
||||||
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
this.RotateTo(0);
|
||||||
|
|
||||||
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
|
||||||
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
||||||
{
|
{
|
||||||
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
|
// constant ambient rotation to give the spinner "spinning" character.
|
||||||
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out);
|
||||||
|
this.RotateTo(mainContainer.Rotation + 180, 320);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ArmedState.Miss:
|
||||||
|
this.ScaleTo(initial_scale * 0.8f, 320, Easing.In);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0);
|
||||||
|
mainContainer.ScaleTo(0);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
||||||
updateComplete(state == ArmedState.Hit, 0);
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
||||||
|
updateComplete(state == ArmedState.Hit, 0);
|
||||||
using (BeginDelayedSequence(spinner.Duration, true))
|
|
||||||
{
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case ArmedState.Hit:
|
|
||||||
this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
|
|
||||||
this.RotateTo(mainContainer.Rotation + 180, 320);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArmedState.Miss:
|
|
||||||
this.ScaleTo(Scale * 0.8f, 320, Easing.In);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateComplete(bool complete, double duration)
|
private void updateComplete(bool complete, double duration)
|
||||||
|
@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
protected override bool CreateNewTriangles => false;
|
protected override bool CreateNewTriangles => false;
|
||||||
protected override float SpawnRatio => 0.5f;
|
protected override float SpawnRatio => 0.5f;
|
||||||
|
|
||||||
public TrianglesPiece()
|
public TrianglesPiece(int? seed = null)
|
||||||
|
: base(seed)
|
||||||
{
|
{
|
||||||
TriangleScale = 1.2f;
|
TriangleScale = 1.2f;
|
||||||
HideAlphaDiscrepancies = false;
|
HideAlphaDiscrepancies = false;
|
||||||
|
@ -70,20 +70,30 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
this.FadeOut();
|
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
var spinner = (Spinner)drawableSpinner.HitObject;
|
var spinner = (Spinner)drawableSpinner.HitObject;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
||||||
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||||
|
|
||||||
fixedMiddle.FadeColour(Color4.White);
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime, true))
|
{
|
||||||
fixedMiddle.FadeColour(Color4.Red, spinner.Duration);
|
fixedMiddle.FadeColour(Color4.White);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt, true))
|
||||||
|
fixedMiddle.FadeColour(Color4.Red, spinner.Duration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -24,12 +24,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
private Sprite metreSprite;
|
private Sprite metreSprite;
|
||||||
private Container metre;
|
private Container metre;
|
||||||
|
|
||||||
|
private bool spinnerBlink;
|
||||||
|
|
||||||
private const float sprite_scale = 1 / 1.6f;
|
private const float sprite_scale = 1 / 1.6f;
|
||||||
private const float final_metre_height = 692 * sprite_scale;
|
private const float final_metre_height = 692 * sprite_scale;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource source, DrawableHitObject drawableObject)
|
private void load(ISkinSource source, DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
|
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
|
||||||
|
|
||||||
drawableSpinner = (DrawableSpinner)drawableObject;
|
drawableSpinner = (DrawableSpinner)drawableObject;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -84,14 +88,20 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
this.FadeOut();
|
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
var spinner = drawableSpinner.HitObject;
|
var spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
|
||||||
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||||
}
|
}
|
||||||
@ -116,12 +126,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
|
|
||||||
private float getMetreHeight(float progress)
|
private float getMetreHeight(float progress)
|
||||||
{
|
{
|
||||||
progress = Math.Min(99, progress * 100);
|
progress *= 100;
|
||||||
|
|
||||||
|
// the spinner should still blink at 100% progress.
|
||||||
|
if (spinnerBlink)
|
||||||
|
progress = Math.Min(99, progress);
|
||||||
|
|
||||||
int barCount = (int)progress / 10;
|
int barCount = (int)progress / 10;
|
||||||
|
|
||||||
// todo: add SpinnerNoBlink support
|
if (spinnerBlink && RNG.NextBool(((int)progress % 10) / 10f))
|
||||||
if (RNG.NextBool(((int)progress % 10) / 10f))
|
|
||||||
barCount++;
|
barCount++;
|
||||||
|
|
||||||
return (float)barCount / total_bars * final_metre_height;
|
return (float)barCount / total_bars * final_metre_height;
|
||||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
CursorRotate,
|
CursorRotate,
|
||||||
HitCircleOverlayAboveNumber,
|
HitCircleOverlayAboveNumber,
|
||||||
HitCircleOverlayAboveNumer, // Some old skins will have this typo
|
HitCircleOverlayAboveNumer, // Some old skins will have this typo
|
||||||
SpinnerFrequencyModulate
|
SpinnerFrequencyModulate,
|
||||||
|
SpinnerNoBlink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
private readonly Random rng = new Random(1337);
|
private readonly Random rng = new Random(1337);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[Test]
|
||||||
private void load()
|
public void TestVariousHits()
|
||||||
{
|
{
|
||||||
AddStep("Hit", () => addHitJudgement(false));
|
AddStep("Hit", () => addHitJudgement(false));
|
||||||
AddStep("Strong hit", () => addStrongHitJudgement(false));
|
AddStep("Strong hit", () => addStrongHitJudgement(false));
|
||||||
|
@ -12,6 +12,14 @@ namespace osu.Game.Tests.Editing
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class EditorChangeHandlerTest
|
public class EditorChangeHandlerTest
|
||||||
{
|
{
|
||||||
|
private int stateChangedFired;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
stateChangedFired = 0;
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSaveRestoreState()
|
public void TestSaveRestoreState()
|
||||||
{
|
{
|
||||||
@ -23,6 +31,8 @@ namespace osu.Game.Tests.Editing
|
|||||||
addArbitraryChange(beatmap);
|
addArbitraryChange(beatmap);
|
||||||
handler.SaveState();
|
handler.SaveState();
|
||||||
|
|
||||||
|
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.True);
|
Assert.That(handler.CanUndo.Value, Is.True);
|
||||||
Assert.That(handler.CanRedo.Value, Is.False);
|
Assert.That(handler.CanRedo.Value, Is.False);
|
||||||
|
|
||||||
@ -30,6 +40,8 @@ namespace osu.Game.Tests.Editing
|
|||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.False);
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
Assert.That(handler.CanRedo.Value, Is.True);
|
Assert.That(handler.CanRedo.Value, Is.True);
|
||||||
|
|
||||||
|
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -45,6 +57,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
|
|
||||||
Assert.That(handler.CanUndo.Value, Is.True);
|
Assert.That(handler.CanUndo.Value, Is.True);
|
||||||
Assert.That(handler.CanRedo.Value, Is.False);
|
Assert.That(handler.CanRedo.Value, Is.False);
|
||||||
|
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||||
|
|
||||||
string hash = handler.CurrentStateHash;
|
string hash = handler.CurrentStateHash;
|
||||||
|
|
||||||
@ -52,6 +65,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
handler.SaveState();
|
handler.SaveState();
|
||||||
|
|
||||||
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
|
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
|
||||||
|
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||||
|
|
||||||
handler.RestoreState(-1);
|
handler.RestoreState(-1);
|
||||||
|
|
||||||
@ -60,6 +74,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
// we should only be able to restore once even though we saved twice.
|
// we should only be able to restore once even though we saved twice.
|
||||||
Assert.That(handler.CanUndo.Value, Is.False);
|
Assert.That(handler.CanUndo.Value, Is.False);
|
||||||
Assert.That(handler.CanRedo.Value, Is.True);
|
Assert.That(handler.CanRedo.Value, Is.True);
|
||||||
|
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -71,6 +86,8 @@ namespace osu.Game.Tests.Editing
|
|||||||
|
|
||||||
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
|
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
|
||||||
{
|
{
|
||||||
|
Assert.That(stateChangedFired, Is.EqualTo(i));
|
||||||
|
|
||||||
addArbitraryChange(beatmap);
|
addArbitraryChange(beatmap);
|
||||||
handler.SaveState();
|
handler.SaveState();
|
||||||
}
|
}
|
||||||
@ -114,7 +131,10 @@ namespace osu.Game.Tests.Editing
|
|||||||
{
|
{
|
||||||
var beatmap = new EditorBeatmap(new Beatmap());
|
var beatmap = new EditorBeatmap(new Beatmap());
|
||||||
|
|
||||||
return (new EditorChangeHandler(beatmap), beatmap);
|
var changeHandler = new EditorChangeHandler(beatmap);
|
||||||
|
|
||||||
|
changeHandler.OnStateChange += () => stateChangedFired++;
|
||||||
|
return (changeHandler, beatmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addArbitraryChange(EditorBeatmap beatmap)
|
private void addArbitraryChange(EditorBeatmap beatmap)
|
||||||
|
@ -139,6 +139,22 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveGroupAlsoRemovedControlPoints()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(1000, true);
|
||||||
|
|
||||||
|
group.Add(new SampleControlPoint());
|
||||||
|
|
||||||
|
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
cpi.RemoveGroup(group);
|
||||||
|
|
||||||
|
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAddControlPointToGroup()
|
public void TestAddControlPointToGroup()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
// 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.Graphics.Audio;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneEditorSamplePlayback : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSlidingSampleStopsOnSeek()
|
||||||
|
{
|
||||||
|
DrawableSlider slider = null;
|
||||||
|
DrawableSample[] samples = null;
|
||||||
|
|
||||||
|
AddStep("get first slider", () =>
|
||||||
|
{
|
||||||
|
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
|
||||||
|
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start playback", () => EditorClock.Start());
|
||||||
|
|
||||||
|
AddUntilStep("wait for slider sliding then seek", () =>
|
||||||
|
{
|
||||||
|
if (!slider.Tracking.Value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!samples.Any(s => s.Playing))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EditorClock.Seek(20000);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
{
|
{
|
||||||
base.Bindable = new Bindable<string>();
|
base.Bindable = new Bindable<string>();
|
||||||
|
|
||||||
((OsuTextBox)Control).OnCommit = (sender, newText) =>
|
((OsuTextBox)Control).OnCommit += (sender, newText) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -158,6 +158,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
|
|
||||||
public void RemoveGroup(ControlPointGroup group)
|
public void RemoveGroup(ControlPointGroup group)
|
||||||
{
|
{
|
||||||
|
foreach (var item in group.ControlPoints.ToArray())
|
||||||
|
group.Remove(item);
|
||||||
|
|
||||||
group.ItemAdded -= groupItemAdded;
|
group.ItemAdded -= groupItemAdded;
|
||||||
group.ItemRemoved -= groupItemRemoved;
|
group.ItemRemoved -= groupItemRemoved;
|
||||||
|
|
||||||
|
@ -86,13 +86,24 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float Velocity = 1;
|
public float Velocity = 1;
|
||||||
|
|
||||||
|
private readonly Random stableRandom;
|
||||||
|
|
||||||
|
private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle());
|
||||||
|
|
||||||
private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
|
private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
|
||||||
|
|
||||||
private IShader shader;
|
private IShader shader;
|
||||||
private readonly Texture texture;
|
private readonly Texture texture;
|
||||||
|
|
||||||
public Triangles()
|
/// <summary>
|
||||||
|
/// Construct a new triangle visualisation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seed">An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time.</param>
|
||||||
|
public Triangles(int? seed = null)
|
||||||
{
|
{
|
||||||
|
if (seed != null)
|
||||||
|
stableRandom = new Random(seed.Value);
|
||||||
|
|
||||||
texture = Texture.WhitePixel;
|
texture = Texture.WhitePixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,8 +186,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
{
|
{
|
||||||
TriangleParticle particle = CreateTriangle();
|
TriangleParticle particle = CreateTriangle();
|
||||||
|
|
||||||
particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1);
|
particle.Position = new Vector2(nextRandom(), randomY ? nextRandom() : 1);
|
||||||
particle.ColourShade = RNG.NextSingle();
|
particle.ColourShade = nextRandom();
|
||||||
particle.Colour = CreateTriangleShade(particle.ColourShade);
|
particle.Colour = CreateTriangleShade(particle.ColourShade);
|
||||||
|
|
||||||
return particle;
|
return particle;
|
||||||
@ -191,8 +202,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
const float std_dev = 0.16f;
|
const float std_dev = 0.16f;
|
||||||
const float mean = 0.5f;
|
const float mean = 0.5f;
|
||||||
|
|
||||||
float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats
|
float u1 = 1 - nextRandom(); //uniform(0,1] random floats
|
||||||
float u2 = 1 - RNG.NextSingle();
|
float u2 = 1 - nextRandom();
|
||||||
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1)
|
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1)
|
||||||
var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2)
|
var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2)
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
Rank = Rank,
|
Rank = Rank,
|
||||||
Ruleset = ruleset,
|
Ruleset = ruleset,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
|
IsLegacyScore = true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Statistics != null)
|
if (Statistics != null)
|
||||||
|
@ -59,12 +59,13 @@ namespace osu.Game.Online.Chat
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = textbox_height,
|
Height = textbox_height,
|
||||||
PlaceholderText = "type your message",
|
PlaceholderText = "type your message",
|
||||||
OnCommit = postMessage,
|
|
||||||
ReleaseFocusOnCommit = false,
|
ReleaseFocusOnCommit = false,
|
||||||
HoldFocus = true,
|
HoldFocus = true,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
textbox.OnCommit += postMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel.BindValueChanged(channelChanged);
|
Channel.BindValueChanged(channelChanged);
|
||||||
|
@ -146,7 +146,6 @@ namespace osu.Game.Overlays
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 1,
|
Height = 1,
|
||||||
PlaceholderText = "type your message",
|
PlaceholderText = "type your message",
|
||||||
OnCommit = postMessage,
|
|
||||||
ReleaseFocusOnCommit = false,
|
ReleaseFocusOnCommit = false,
|
||||||
HoldFocus = true,
|
HoldFocus = true,
|
||||||
}
|
}
|
||||||
@ -186,6 +185,8 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
textbox.OnCommit += postMessage;
|
||||||
|
|
||||||
ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
|
ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
|
||||||
ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
|
ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
|
||||||
ChannelSelectionOverlay.State.ValueChanged += state =>
|
ChannelSelectionOverlay.State.ValueChanged += state =>
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
filter.Search.OnCommit = (sender, newText) =>
|
filter.Search.OnCommit += (sender, newText) =>
|
||||||
{
|
{
|
||||||
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
|
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
|
||||||
|
|
||||||
|
@ -236,7 +236,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
PlaceholderText = "password",
|
PlaceholderText = "password",
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
TabbableContentContainer = this,
|
TabbableContentContainer = this,
|
||||||
OnCommit = (sender, newText) => performLogin()
|
|
||||||
},
|
},
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
@ -276,6 +275,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
password.OnCommit += (sender, newText) => performLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool AcceptsFocus => true;
|
public override bool AcceptsFocus => true;
|
||||||
|
@ -40,17 +40,28 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Playfield.DisplayJudgements.Value = false;
|
Playfield.DisplayJudgements.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
beatmap.HitObjectAdded += addHitObject;
|
beatmap.HitObjectAdded += addHitObject;
|
||||||
beatmap.HitObjectUpdated += updateReplay;
|
|
||||||
beatmap.HitObjectRemoved += removeHitObject;
|
beatmap.HitObjectRemoved += removeHitObject;
|
||||||
|
|
||||||
|
if (changeHandler != null)
|
||||||
|
{
|
||||||
|
// for now only regenerate replay on a finalised state change, not HitObjectUpdated.
|
||||||
|
changeHandler.OnStateChange += updateReplay;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
beatmap.HitObjectUpdated += _ => updateReplay();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateReplay(HitObject obj = null) =>
|
private void updateReplay() => drawableRuleset.RegenerateAutoplay();
|
||||||
drawableRuleset.RegenerateAutoplay();
|
|
||||||
|
|
||||||
private void addHitObject(HitObject hitObject)
|
private void addHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
@ -58,8 +69,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
drawableRuleset.Playfield.Add(drawableObject);
|
drawableRuleset.Playfield.Add(drawableObject);
|
||||||
drawableRuleset.Playfield.PostProcess();
|
drawableRuleset.Playfield.PostProcess();
|
||||||
|
|
||||||
updateReplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeHitObject(HitObject hitObject)
|
private void removeHitObject(HitObject hitObject)
|
||||||
@ -68,8 +77,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
drawableRuleset.Playfield.Remove(drawableObject);
|
drawableRuleset.Playfield.Remove(drawableObject);
|
||||||
drawableRuleset.Playfield.PostProcess();
|
drawableRuleset.Playfield.PostProcess();
|
||||||
|
|
||||||
drawableRuleset.RegenerateAutoplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool PropagatePositionalInputSubTree => false;
|
public override bool PropagatePositionalInputSubTree => false;
|
||||||
|
@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
return -DEFAULT_MAX_HEALTH_INCREASE;
|
return -DEFAULT_MAX_HEALTH_INCREASE;
|
||||||
|
|
||||||
case HitResult.Meh:
|
case HitResult.Meh:
|
||||||
return -DEFAULT_MAX_HEALTH_INCREASE * 0.05;
|
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
|
||||||
|
|
||||||
case HitResult.Ok:
|
case HitResult.Ok:
|
||||||
return -DEFAULT_MAX_HEALTH_INCREASE * 0.01;
|
return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
|
||||||
|
|
||||||
case HitResult.Good:
|
case HitResult.Good:
|
||||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.5;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||||
|
|
||||||
case HitResult.Great:
|
case HitResult.Great:
|
||||||
return DEFAULT_MAX_HEALTH_INCREASE;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
|
||||||
|
|
||||||
case HitResult.Perfect:
|
case HitResult.Perfect:
|
||||||
return DEFAULT_MAX_HEALTH_INCREASE * 1.05;
|
return DEFAULT_MAX_HEALTH_INCREASE;
|
||||||
|
|
||||||
case HitResult.SmallBonus:
|
case HitResult.SmallBonus:
|
||||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||||
|
@ -51,12 +51,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
|
public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="JudgementResult"/> has been applied by this <see cref="DrawableHitObject"/> or a nested <see cref="DrawableHitObject"/>.
|
/// Invoked by this or a nested <see cref="DrawableHitObject"/> after a <see cref="JudgementResult"/> has been applied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DrawableHitObject, JudgementResult> OnNewResult;
|
public event Action<DrawableHitObject, JudgementResult> OnNewResult;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="JudgementResult"/> is being reverted by this <see cref="DrawableHitObject"/> or a nested <see cref="DrawableHitObject"/>.
|
/// Invoked by this or a nested <see cref="DrawableHitObject"/> prior to a <see cref="JudgementResult"/> being reverted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
#region State / Transform Management
|
#region State / Transform Management
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bind to apply a custom state which can override the default implementation.
|
/// Invoked by this or a nested <see cref="DrawableHitObject"/> to apply a custom state that can override the default implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
|
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
|
||||||
|
|
||||||
@ -384,6 +384,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops playback of all samples. Automatically called when <see cref="DrawableHitObject{TObject}"/>'s lifetime has been exceeded.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void StopAllSamples() => Samples?.Stop();
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -452,6 +457,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
foreach (var nested in NestedHitObjects)
|
foreach (var nested in NestedHitObjects)
|
||||||
nested.OnKilled();
|
nested.OnKilled();
|
||||||
|
|
||||||
|
StopAllSamples();
|
||||||
|
|
||||||
UpdateResult(false);
|
UpdateResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,6 +469,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param>
|
/// <param name="application">The callback that applies changes to the <see cref="JudgementResult"/>.</param>
|
||||||
protected void ApplyResult(Action<JudgementResult> application)
|
protected void ApplyResult(Action<JudgementResult> application)
|
||||||
{
|
{
|
||||||
|
if (Result.HasResult)
|
||||||
|
throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result.");
|
||||||
|
|
||||||
application?.Invoke(Result);
|
application?.Invoke(Result);
|
||||||
|
|
||||||
if (!Result.HasResult)
|
if (!Result.HasResult)
|
||||||
|
@ -185,6 +185,34 @@ namespace osu.Game.Scoring
|
|||||||
[JsonProperty("position")]
|
[JsonProperty("position")]
|
||||||
public int? Position { get; set; }
|
public int? Position { get; set; }
|
||||||
|
|
||||||
|
private bool isLegacyScore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
[NotMapped]
|
||||||
|
public bool IsLegacyScore
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isLegacyScore)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// The above check will catch legacy online scores that have an appropriate UserString + UserId.
|
||||||
|
// For non-online scores such as those imported in, a heuristic is used based on the following table:
|
||||||
|
//
|
||||||
|
// Mode | UserString | UserId
|
||||||
|
// --------------- | ---------- | ---------
|
||||||
|
// stable | <username> | 1
|
||||||
|
// lazer | <username> | <userid>
|
||||||
|
// lazer (offline) | Guest | 1
|
||||||
|
|
||||||
|
return ID > 0 && UserID == 1 && UserString != "Guest";
|
||||||
|
}
|
||||||
|
set => isLegacyScore = value;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay()
|
public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay()
|
||||||
{
|
{
|
||||||
foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
foreach (var key in OrderAttributeUtils.GetValuesInOrder<HitResult>())
|
||||||
|
@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -149,23 +150,38 @@ namespace osu.Game.Scoring
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int? beatmapMaxCombo = score.Beatmap.MaxCombo;
|
int beatmapMaxCombo;
|
||||||
|
|
||||||
if (beatmapMaxCombo == null)
|
if (score.IsLegacyScore)
|
||||||
{
|
{
|
||||||
if (score.Beatmap.ID == 0 || difficulties == null)
|
// This score is guaranteed to be an osu!stable score.
|
||||||
|
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
|
||||||
|
if (score.Beatmap.MaxCombo == null)
|
||||||
{
|
{
|
||||||
// We don't have enough information (max combo) to compute the score, so let's use the provided score.
|
if (score.Beatmap.ID == 0 || difficulties == null)
|
||||||
Value = score.TotalScore;
|
{
|
||||||
|
// We don't have enough information (max combo) to compute the score, so use the provided score.
|
||||||
|
Value = score.TotalScore;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||||
|
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
|
||||||
|
difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can compute the max combo locally after the async beatmap difficulty computation.
|
beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
|
||||||
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
|
|
||||||
difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
updateScore(beatmapMaxCombo.Value);
|
{
|
||||||
|
// This score is guaranteed to be an osu!lazer score.
|
||||||
|
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
|
||||||
|
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScore(beatmapMaxCombo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateScore(int beatmapMaxCombo)
|
private void updateScore(int beatmapMaxCombo)
|
||||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
public readonly Bindable<bool> CanUndo = new Bindable<bool>();
|
public readonly Bindable<bool> CanUndo = new Bindable<bool>();
|
||||||
public readonly Bindable<bool> CanRedo = new Bindable<bool>();
|
public readonly Bindable<bool> CanRedo = new Bindable<bool>();
|
||||||
|
|
||||||
|
public event Action OnStateChange;
|
||||||
|
|
||||||
private readonly LegacyEditorBeatmapPatcher patcher;
|
private readonly LegacyEditorBeatmapPatcher patcher;
|
||||||
private readonly List<byte[]> savedStates = new List<byte[]>();
|
private readonly List<byte[]> savedStates = new List<byte[]>();
|
||||||
|
|
||||||
@ -109,6 +111,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
savedStates.Add(newState);
|
savedStates.Add(newState);
|
||||||
|
|
||||||
currentState = savedStates.Count - 1;
|
currentState = savedStates.Count - 1;
|
||||||
|
|
||||||
|
OnStateChange?.Invoke();
|
||||||
updateBindables();
|
updateBindables();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,6 +140,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
isRestoring = false;
|
isRestoring = false;
|
||||||
|
|
||||||
|
OnStateChange?.Invoke();
|
||||||
updateBindables();
|
updateBindables();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -10,6 +11,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IEditorChangeHandler
|
public interface IEditorChangeHandler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fired whenever a state change occurs.
|
||||||
|
/// </summary>
|
||||||
|
event Action OnStateChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begins a bulk state change event. <see cref="EndChange"/> should be invoked soon after.
|
/// Begins a bulk state change event. <see cref="EndChange"/> should be invoked soon after.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||||
{
|
{
|
||||||
|
new GroupSection(),
|
||||||
new TimingSection(),
|
new TimingSection(),
|
||||||
new DifficultySection(),
|
new DifficultySection(),
|
||||||
new SampleSection(),
|
new SampleSection(),
|
||||||
|
@ -2,27 +2,23 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
internal class DifficultySection : Section<DifficultyControlPoint>
|
internal class DifficultySection : Section<DifficultyControlPoint>
|
||||||
{
|
{
|
||||||
private SettingsSlider<double> multiplier;
|
private SliderWithTextBoxInput<double> multiplierSlider;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Flow.AddRange(new[]
|
Flow.AddRange(new[]
|
||||||
{
|
{
|
||||||
multiplier = new SettingsSlider<double>
|
multiplierSlider = new SliderWithTextBoxInput<double>("Speed Multiplier")
|
||||||
{
|
{
|
||||||
LabelText = "Speed Multiplier",
|
Current = new DifficultyControlPoint().SpeedMultiplierBindable
|
||||||
Bindable = new DifficultyControlPoint().SpeedMultiplierBindable,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -31,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
if (point.NewValue != null)
|
if (point.NewValue != null)
|
||||||
{
|
{
|
||||||
multiplier.Bindable = point.NewValue.SpeedMultiplierBindable;
|
multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
119
osu.Game/Screens/Edit/Timing/GroupSection.cs
Normal file
119
osu.Game/Screens/Edit/Timing/GroupSection.cs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class GroupSection : CompositeDrawable
|
||||||
|
{
|
||||||
|
private LabelledTextBox textBox;
|
||||||
|
|
||||||
|
private TriangleButton button;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected Bindable<ControlPointGroup> SelectedGroup { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Padding = new MarginPadding(10);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
textBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = "Time"
|
||||||
|
},
|
||||||
|
button = new TriangleButton
|
||||||
|
{
|
||||||
|
Text = "Use current time",
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Action = () => changeSelectedGroupTime(clock.CurrentTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
textBox.OnCommit += (sender, isNew) =>
|
||||||
|
{
|
||||||
|
if (!isNew)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (double.TryParse(sender.Text, out var newTime))
|
||||||
|
{
|
||||||
|
changeSelectedGroupTime(newTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedGroup.TriggerChange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectedGroup.BindValueChanged(group =>
|
||||||
|
{
|
||||||
|
if (group.NewValue == null)
|
||||||
|
{
|
||||||
|
textBox.Text = string.Empty;
|
||||||
|
|
||||||
|
textBox.Current.Disabled = true;
|
||||||
|
button.Enabled.Value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
textBox.Current.Disabled = false;
|
||||||
|
button.Enabled.Value = true;
|
||||||
|
|
||||||
|
textBox.Text = $"{group.NewValue.Time:n0}";
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeSelectedGroupTime(in double time)
|
||||||
|
{
|
||||||
|
if (SelectedGroup.Value == null || time == SelectedGroup.Value.Time)
|
||||||
|
return;
|
||||||
|
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
|
var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray();
|
||||||
|
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
|
||||||
|
|
||||||
|
foreach (var cp in currentGroupItems)
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp);
|
||||||
|
|
||||||
|
SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time);
|
||||||
|
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,18 +2,17 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
internal class SampleSection : Section<SampleControlPoint>
|
internal class SampleSection : Section<SampleControlPoint>
|
||||||
{
|
{
|
||||||
private LabelledTextBox bank;
|
private LabelledTextBox bank;
|
||||||
private SettingsSlider<int> volume;
|
private SliderWithTextBoxInput<int> volume;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -24,10 +23,9 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
Label = "Bank Name",
|
Label = "Bank Name",
|
||||||
},
|
},
|
||||||
volume = new SettingsSlider<int>
|
volume = new SliderWithTextBoxInput<int>("Volume")
|
||||||
{
|
{
|
||||||
Bindable = new SampleControlPoint().SampleVolumeBindable,
|
Current = new SampleControlPoint().SampleVolumeBindable,
|
||||||
LabelText = "Volume",
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -37,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
if (point.NewValue != null)
|
if (point.NewValue != null)
|
||||||
{
|
{
|
||||||
bank.Current = point.NewValue.SampleBankBindable;
|
bank.Current = point.NewValue.SampleBankBindable;
|
||||||
volume.Bindable = point.NewValue.SampleVolumeBindable;
|
volume.Current = point.NewValue.SampleVolumeBindable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
77
osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
Normal file
77
osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T>
|
||||||
|
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||||
|
{
|
||||||
|
private readonly SettingsSlider<T> slider;
|
||||||
|
|
||||||
|
public SliderWithTextBoxInput(string labelText)
|
||||||
|
{
|
||||||
|
LabelledTextBox textbox;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
textbox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = labelText,
|
||||||
|
},
|
||||||
|
slider = new SettingsSlider<T>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
textbox.OnCommit += (t, isNew) =>
|
||||||
|
{
|
||||||
|
if (!isNew) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
slider.Bindable.Parse(t.Text);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// TriggerChange below will restore the previous text value on failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is run regardless of parsing success as the parsed number may not actually trigger a change
|
||||||
|
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
|
||||||
|
Current.TriggerChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
Current.BindValueChanged(val =>
|
||||||
|
{
|
||||||
|
textbox.Text = val.NewValue.ToString();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<T> Current
|
||||||
|
{
|
||||||
|
get => slider.Bindable;
|
||||||
|
set => slider.Bindable = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,18 +65,19 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
if (!isNew) return;
|
if (!isNew) return;
|
||||||
|
|
||||||
if (double.TryParse(Current.Value, out double doubleVal))
|
try
|
||||||
{
|
{
|
||||||
try
|
if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0)
|
||||||
{
|
|
||||||
beatLengthBindable.Value = beatLengthToBpm(doubleVal);
|
beatLengthBindable.Value = beatLengthToBpm(doubleVal);
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// will restore the previous text value on failure.
|
|
||||||
beatLengthBindable.TriggerChange();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// TriggerChange below will restore the previous text value on failure.
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is run regardless of parsing success as the parsed number may not actually trigger a change
|
||||||
|
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
|
||||||
|
beatLengthBindable.TriggerChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
beatLengthBindable.BindValueChanged(val =>
|
beatLengthBindable.BindValueChanged(val =>
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Skinning
|
|||||||
ComboPrefix,
|
ComboPrefix,
|
||||||
ComboOverlap,
|
ComboOverlap,
|
||||||
AnimationFramerate,
|
AnimationFramerate,
|
||||||
LayeredHitSounds,
|
LayeredHitSounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,14 @@ namespace osu.Game.Skinning
|
|||||||
// it's not easy to know if a sample has finished playing (to end).
|
// it's not easy to know if a sample has finished playing (to end).
|
||||||
// to keep things simple only resume playing looping samples.
|
// to keep things simple only resume playing looping samples.
|
||||||
else if (Looping)
|
else if (Looping)
|
||||||
base.Play();
|
{
|
||||||
|
// schedule so we don't start playing a sample which is no longer alive.
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (RequestedPlaying)
|
||||||
|
base.Play();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.930.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1001.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.6" />
|
<PackageReference Include="Sentry" Version="2.1.6" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.930.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1001.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
@ -80,11 +80,11 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.930.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1001.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2020.213.0" ExcludeAssets="all" />
|
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2020.923.0" ExcludeAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user