mirror of
https://github.com/ppy/osu.git
synced 2025-01-07 22:22:59 +08:00
Merge branch 'master' into caught-object-refactor
This commit is contained in:
commit
aa24890aff
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1201.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1203.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
@ -37,39 +38,50 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
|
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
|
||||||
SetProperties(new DrawableFruit(new Fruit
|
new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
|
||||||
{
|
{
|
||||||
IndexInBeatmap = indexInBeatmap,
|
IndexInBeatmap = indexInBeatmap,
|
||||||
HyperDashBindable = { Value = hyperdash }
|
HyperDashBindable = { Value = hyperdash }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
private Drawable createDrawableBanana() =>
|
private Drawable createDrawableBanana() =>
|
||||||
SetProperties(new DrawableBanana(new Banana()));
|
new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
|
||||||
|
|
||||||
private Drawable createDrawableDroplet(bool hyperdash = false) =>
|
private Drawable createDrawableDroplet(bool hyperdash = false) =>
|
||||||
SetProperties(new DrawableDroplet(new Droplet
|
new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
|
||||||
{
|
{
|
||||||
HyperDashBindable = { Value = hyperdash }
|
HyperDashBindable = { Value = hyperdash }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet()));
|
private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d)
|
public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
public readonly ManualClock ManualClock;
|
||||||
|
|
||||||
|
public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
ManualClock = new ManualClock();
|
||||||
|
Clock = new FramedClock(ManualClock);
|
||||||
|
|
||||||
var hitObject = d.HitObject;
|
var hitObject = d.HitObject;
|
||||||
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 });
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
hitObject.StartTime = 1000000000000;
|
|
||||||
hitObject.Scale = 1.5f;
|
hitObject.Scale = 1.5f;
|
||||||
|
hitObject.StartTime = 500;
|
||||||
|
|
||||||
d.Anchor = Anchor.Centre;
|
d.Anchor = Anchor.Centre;
|
||||||
d.RelativePositionAxes = Axes.None;
|
|
||||||
d.Position = Vector2.Zero;
|
|
||||||
d.HitObjectApplied += _ =>
|
d.HitObjectApplied += _ =>
|
||||||
{
|
{
|
||||||
d.LifetimeStart = double.NegativeInfinity;
|
d.LifetimeStart = double.NegativeInfinity;
|
||||||
d.LifetimeEnd = double.PositiveInfinity;
|
d.LifetimeEnd = double.PositiveInfinity;
|
||||||
};
|
};
|
||||||
return d;
|
|
||||||
|
InternalChild = d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
96
osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
Normal file
96
osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
Normal file
@ -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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneFruitRandomness : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly TestDrawableFruit drawableFruit;
|
||||||
|
private readonly TestDrawableBanana drawableBanana;
|
||||||
|
|
||||||
|
public TestSceneFruitRandomness()
|
||||||
|
{
|
||||||
|
drawableFruit = new TestDrawableFruit(new Fruit());
|
||||||
|
drawableBanana = new TestDrawableBanana(new Banana());
|
||||||
|
|
||||||
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
|
||||||
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
|
||||||
|
|
||||||
|
AddSliderStep("start time", 500, 600, 0, x =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitRandomness()
|
||||||
|
{
|
||||||
|
// Use values such that the banana colour changes (2/3 of the integers are okay)
|
||||||
|
const int initial_start_time = 500;
|
||||||
|
const int another_start_time = 501;
|
||||||
|
|
||||||
|
float fruitRotation = 0;
|
||||||
|
float bananaRotation = 0;
|
||||||
|
float bananaScale = 0;
|
||||||
|
Color4 bananaColour = new Color4();
|
||||||
|
|
||||||
|
AddStep("Initialize start time", () =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
|
||||||
|
fruitRotation = drawableFruit.InnerRotation;
|
||||||
|
bananaRotation = drawableBanana.InnerRotation;
|
||||||
|
bananaScale = drawableBanana.InnerScale;
|
||||||
|
bananaColour = drawableBanana.AccentColour.Value;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change start time", () =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
|
||||||
|
AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
|
||||||
|
AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
|
||||||
|
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
|
||||||
|
|
||||||
|
AddStep("reset start time", () =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("rotation and scale restored", () =>
|
||||||
|
drawableFruit.InnerRotation == fruitRotation &&
|
||||||
|
drawableBanana.InnerRotation == bananaRotation &&
|
||||||
|
drawableBanana.InnerScale == bananaScale &&
|
||||||
|
drawableBanana.AccentColour.Value == bananaColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableFruit : DrawableFruit
|
||||||
|
{
|
||||||
|
public float InnerRotation => ScaleContainer.Rotation;
|
||||||
|
|
||||||
|
public TestDrawableFruit(Fruit h)
|
||||||
|
: base(h)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableBanana : DrawableBanana
|
||||||
|
{
|
||||||
|
public float InnerRotation => ScaleContainer.Rotation;
|
||||||
|
public float InnerScale => ScaleContainer.Scale.X;
|
||||||
|
|
||||||
|
public TestDrawableBanana(Banana h)
|
||||||
|
: base(h)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
AddStep("fruit changes visual and hyper", () => SetContents(() => SetProperties(new DrawableFruit(new Fruit
|
AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
|
||||||
{
|
{
|
||||||
IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
|
IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
|
||||||
HyperDashBindable = { BindTarget = hyperDash },
|
HyperDashBindable = { BindTarget = hyperDash },
|
||||||
}))));
|
}))));
|
||||||
|
|
||||||
AddStep("droplet changes hyper", () => SetContents(() => SetProperties(new DrawableDroplet(new Droplet
|
AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
|
||||||
{
|
{
|
||||||
HyperDashBindable = { BindTarget = hyperDash },
|
HyperDashBindable = { BindTarget = hyperDash },
|
||||||
}))));
|
}))));
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -31,17 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
Samples = samples;
|
Samples = samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4? colour;
|
|
||||||
|
|
||||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours)
|
|
||||||
{
|
|
||||||
// override any external colour changes with banananana
|
// override any external colour changes with banananana
|
||||||
return colour ??= getBananaColour();
|
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => getBananaColour();
|
||||||
}
|
|
||||||
|
|
||||||
private Color4 getBananaColour()
|
private Color4 getBananaColour()
|
||||||
{
|
{
|
||||||
switch (RNG.Next(0, 3))
|
switch (StatelessRNG.NextInt(3, RandomSeed))
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
return new Color4(255, 240, 0, 255);
|
return new Color4(255, 240, 0, 255);
|
||||||
|
@ -97,6 +97,12 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
set => ScaleBindable.Value = value;
|
set => ScaleBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The seed value used for visual randomness such as fruit rotation.
|
||||||
|
/// The value is <see cref="HitObject.StartTime"/> truncated to an integer.
|
||||||
|
/// </summary>
|
||||||
|
public int RandomSeed => (int)StartTime;
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -21,6 +20,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// start time affects the random seed which is used to determine the banana colour
|
||||||
|
StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
@ -28,14 +35,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
const float end_scale = 0.6f;
|
const float end_scale = 0.6f;
|
||||||
const float random_scale_range = 1.6f;
|
const float random_scale_range = 1.6f;
|
||||||
|
|
||||||
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle()))
|
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
||||||
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
||||||
|
|
||||||
ScaleContainer.RotateTo(getRandomAngle())
|
ScaleContainer.RotateTo(getRandomAngle(1))
|
||||||
.Then()
|
.Then()
|
||||||
.RotateTo(getRandomAngle(), HitObject.TimePreempt);
|
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
||||||
|
|
||||||
float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
|
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PlaySamples()
|
public override void PlaySamples()
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -18,12 +19,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
|
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
||||||
|
|
||||||
protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
|
protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft;
|
Anchor = Anchor.BottomLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a random number in range [0,1) based on seed <see cref="RandomSeed"/>.
|
||||||
|
/// </summary>
|
||||||
|
public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed, series);
|
||||||
|
|
||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
// roughly matches osu-stable
|
// roughly matches osu-stable
|
||||||
float startRotation = RNG.NextSingle() * 20;
|
float startRotation = RandomSingle(1) * 20;
|
||||||
double duration = HitObject.TimePreempt + 2000;
|
double duration = HitObject.TimePreempt + 2000;
|
||||||
|
|
||||||
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
||||||
|
@ -5,7 +5,7 @@ using System;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
|
|
||||||
|
|
||||||
IndexInBeatmap.BindValueChanged(change =>
|
IndexInBeatmap.BindValueChanged(change =>
|
||||||
{
|
{
|
||||||
VisualRepresentation.Value = GetVisualRepresentation(change.NewValue);
|
VisualRepresentation.Value = GetVisualRepresentation(change.NewValue);
|
||||||
@ -41,6 +39,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
HyperDash.BindValueChanged(_ => updatePiece(), true);
|
HyperDash.BindValueChanged(_ => updatePiece(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
|
ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePiece()
|
private void updatePiece()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScaleContainer.Child = new SkinnableDrawable(
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
@ -17,15 +20,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
|
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModHidden(),
|
Mod = new TestOsuModHidden(),
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
PassCondition = checkSomeHit
|
PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
|
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModHidden(),
|
Mod = new TestOsuModHidden(),
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
@ -54,13 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PassCondition = checkSomeHit
|
PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2)
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
|
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModHidden(),
|
Mod = new TestOsuModHidden(),
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
@ -89,12 +92,41 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2)
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWithSliderReuse() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TestOsuModHidden(),
|
||||||
|
Autoplay = true,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 4000,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
PassCondition = checkSomeHit
|
PassCondition = checkSomeHit
|
||||||
});
|
});
|
||||||
|
|
||||||
private bool checkSomeHit()
|
private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4;
|
||||||
|
|
||||||
|
private bool objectWithIncreasedVisibilityHasIndex(int index)
|
||||||
|
=> Player.Mods.Value.OfType<TestOsuModHidden>().Single().FirstObject == Player.ChildrenOfType<GameplayBeatmap>().Single().HitObjects[index];
|
||||||
|
|
||||||
|
private class TestOsuModHidden : OsuModHidden
|
||||||
{
|
{
|
||||||
return Player.ScoreProcessor.JudgedHits >= 4;
|
public new HitObject FirstObject => base.FirstObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -23,26 +24,22 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
private const double fade_in_duration_multiplier = 0.4;
|
private const double fade_in_duration_multiplier = 0.4;
|
||||||
private const double fade_out_duration_multiplier = 0.3;
|
private const double fade_out_duration_multiplier = 0.3;
|
||||||
|
|
||||||
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner);
|
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
|
||||||
|
|
||||||
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public override void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
foreach (var d in drawables)
|
base.ApplyToBeatmap(beatmap);
|
||||||
d.HitObjectApplied += applyFadeInAdjustment;
|
|
||||||
|
|
||||||
base.ApplyToDrawableHitObjects(drawables);
|
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||||
}
|
applyFadeInAdjustment(obj);
|
||||||
|
|
||||||
private void applyFadeInAdjustment(DrawableHitObject hitObject)
|
static void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||||
{
|
{
|
||||||
if (!(hitObject is DrawableOsuHitObject d))
|
osuObject.TimeFadeIn = osuObject.TimePreempt * fade_in_duration_multiplier;
|
||||||
return;
|
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||||
|
|
||||||
d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier;
|
|
||||||
|
|
||||||
foreach (var nested in d.NestedHitObjects)
|
|
||||||
applyFadeInAdjustment(nested);
|
applyFadeInAdjustment(nested);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
@ -56,37 +53,27 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
applyState(hitObject, false);
|
applyState(hitObject, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyState(DrawableHitObject drawable, bool increaseVisibility)
|
private void applyState(DrawableHitObject drawableObject, bool increaseVisibility)
|
||||||
{
|
{
|
||||||
if (!(drawable is DrawableOsuHitObject d))
|
if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var h = d.HitObject;
|
OsuHitObject hitObject = drawableOsuObject.HitObject;
|
||||||
|
|
||||||
var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn;
|
(double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject);
|
||||||
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
|
|
||||||
|
|
||||||
// new duration from completed fade in to end (before fading out)
|
switch (drawableObject)
|
||||||
var longFadeDuration = h.GetEndTime() - fadeOutStartTime;
|
|
||||||
|
|
||||||
switch (drawable)
|
|
||||||
{
|
{
|
||||||
case DrawableSliderTail sliderTail:
|
case DrawableSliderTail _:
|
||||||
// use stored values from head circle to achieve same fade sequence.
|
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
var tailFadeOutParameters = getFadeOutParametersFromSliderHead(h);
|
drawableObject.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(tailFadeOutParameters.startTime, true))
|
|
||||||
sliderTail.FadeOut(tailFadeOutParameters.duration);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderRepeat sliderRepeat:
|
case DrawableSliderRepeat sliderRepeat:
|
||||||
// use stored values from head circle to achieve same fade sequence.
|
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
var repeatFadeOutParameters = getFadeOutParametersFromSliderHead(h);
|
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(repeatFadeOutParameters.startTime, true))
|
|
||||||
// only apply to circle piece – reverse arrow is not affected by hidden.
|
// only apply to circle piece – reverse arrow is not affected by hidden.
|
||||||
sliderRepeat.CirclePiece.FadeOut(repeatFadeOutParameters.duration);
|
sliderRepeat.CirclePiece.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -101,29 +88,23 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// we don't want to see the approach circle
|
// we don't want to see the approach circle
|
||||||
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true))
|
||||||
circle.ApproachCircle.Hide();
|
circle.ApproachCircle.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fade out immediately after fade in.
|
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
|
fadeTarget.FadeOut(fadeOut.duration);
|
||||||
fadeTarget.FadeOut(fadeOutDuration);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case DrawableSlider slider:
|
||||||
associateNestedSliderCirclesWithHead(slider.HitObject);
|
using (slider.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
|
slider.Body.FadeOut(fadeOut.duration, Easing.Out);
|
||||||
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
|
|
||||||
slider.Body.FadeOut(longFadeDuration, Easing.Out);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderTick sliderTick:
|
case DrawableSliderTick sliderTick:
|
||||||
// slider ticks fade out over up to one second
|
using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
|
sliderTick.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true))
|
|
||||||
sliderTick.FadeOut(tickFadeOutDuration);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -131,30 +112,55 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
// hide elements we don't care about.
|
// hide elements we don't care about.
|
||||||
// todo: hide background
|
// todo: hide background
|
||||||
|
|
||||||
using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
|
using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
spinner.FadeOut(fadeOutDuration);
|
spinner.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<HitObject, SliderHeadCircle> correspondingSliderHeadForObject = new Dictionary<HitObject, SliderHeadCircle>();
|
private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject)
|
||||||
|
|
||||||
private void associateNestedSliderCirclesWithHead(Slider slider)
|
|
||||||
{
|
{
|
||||||
var sliderHead = slider.NestedHitObjects.Single(obj => obj is SliderHeadCircle);
|
switch (drawableObject)
|
||||||
|
|
||||||
foreach (var nested in slider.NestedHitObjects)
|
|
||||||
{
|
{
|
||||||
if ((nested is SliderRepeat || nested is SliderEndCircle) && !correspondingSliderHeadForObject.ContainsKey(nested))
|
case DrawableSliderTail tail:
|
||||||
correspondingSliderHeadForObject[nested] = (SliderHeadCircle)sliderHead;
|
// Use the same fade sequence as the slider head.
|
||||||
}
|
Debug.Assert(tail.Slider != null);
|
||||||
|
return getParameters(tail.Slider.HeadCircle);
|
||||||
|
|
||||||
|
case DrawableSliderRepeat repeat:
|
||||||
|
// Use the same fade sequence as the slider head.
|
||||||
|
Debug.Assert(repeat.Slider != null);
|
||||||
|
return getParameters(repeat.Slider.HeadCircle);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return getParameters(drawableObject.HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (double startTime, double duration) getFadeOutParametersFromSliderHead(OsuHitObject h)
|
static (double startTime, double duration) getParameters(OsuHitObject hitObject)
|
||||||
{
|
{
|
||||||
var sliderHead = correspondingSliderHeadForObject[h];
|
var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn;
|
||||||
return (sliderHead.StartTime - sliderHead.TimePreempt + sliderHead.TimeFadeIn, sliderHead.TimePreempt * fade_out_duration_multiplier);
|
var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier;
|
||||||
|
|
||||||
|
// new duration from completed fade in to end (before fading out)
|
||||||
|
var longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime;
|
||||||
|
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Slider _:
|
||||||
|
return (fadeOutStartTime, longFadeDuration);
|
||||||
|
|
||||||
|
case SliderTick _:
|
||||||
|
var tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
|
||||||
|
return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration);
|
||||||
|
|
||||||
|
case Spinner _:
|
||||||
|
return (fadeOutStartTime + longFadeDuration, fadeOutDuration);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (fadeOutStartTime, fadeOutDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private Container<DrawableSliderTail> tailContainer;
|
private Container<DrawableSliderTail> tailContainer;
|
||||||
private Container<DrawableSliderTick> tickContainer;
|
private Container<DrawableSliderTick> tickContainer;
|
||||||
private Container<DrawableSliderRepeat> repeatContainer;
|
private Container<DrawableSliderRepeat> repeatContainer;
|
||||||
private Container<PausableSkinnableSound> samplesContainer;
|
private PausableSkinnableSound slidingSample;
|
||||||
|
|
||||||
public DrawableSlider()
|
public DrawableSlider()
|
||||||
: this(null)
|
: this(null)
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Alpha = 0
|
Alpha = 0
|
||||||
},
|
},
|
||||||
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||||
samplesContainer = new Container<PausableSkinnableSound> { RelativeSizeAxes = Axes.Both }
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||||
};
|
};
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
@ -100,27 +101,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
base.OnFree();
|
base.OnFree();
|
||||||
|
|
||||||
PathVersion.UnbindFrom(HitObject.Path.Version);
|
PathVersion.UnbindFrom(HitObject.Path.Version);
|
||||||
}
|
|
||||||
|
|
||||||
private PausableSkinnableSound slidingSample;
|
slidingSample.Samples = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadSamples()
|
protected override void LoadSamples()
|
||||||
{
|
{
|
||||||
base.LoadSamples();
|
base.LoadSamples();
|
||||||
|
|
||||||
samplesContainer.Clear();
|
|
||||||
slidingSample = null;
|
|
||||||
|
|
||||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||||
|
|
||||||
if (firstSample != null)
|
if (firstSample != null)
|
||||||
{
|
{
|
||||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
|
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
|
||||||
|
|
||||||
samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone)
|
slidingSample.Samples = new ISampleInfo[] { clone };
|
||||||
{
|
|
||||||
Looping = true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,23 +2,25 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSliderHead : DrawableHitCircle
|
public class DrawableSliderHead : DrawableHitCircle
|
||||||
{
|
{
|
||||||
|
[CanBeNull]
|
||||||
|
public Slider Slider => DrawableSlider?.HitObject;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
||||||
|
|
||||||
private DrawableSlider drawableSlider;
|
|
||||||
|
|
||||||
private Slider slider => drawableSlider?.HitObject;
|
|
||||||
|
|
||||||
public DrawableSliderHead()
|
public DrawableSliderHead()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -39,30 +41,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
|
|
||||||
pathVersion.UnbindFrom(drawableSlider.PathVersion);
|
pathVersion.UnbindFrom(DrawableSlider.PathVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnParentReceived(parent);
|
base.OnApply();
|
||||||
|
|
||||||
drawableSlider = (DrawableSlider)parent;
|
pathVersion.BindTo(DrawableSlider.PathVersion);
|
||||||
|
|
||||||
pathVersion.BindTo(drawableSlider.PathVersion);
|
OnShake = DrawableSlider.Shake;
|
||||||
|
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
||||||
OnShake = drawableSlider.Shake;
|
|
||||||
CheckHittable = (d, t) => drawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
Debug.Assert(Slider != null);
|
||||||
|
|
||||||
|
double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1);
|
||||||
|
|
||||||
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
||||||
if (!IsHit)
|
if (!IsHit)
|
||||||
Position = slider.CurvePositionAt(completionProgress);
|
Position = Slider.CurvePositionAt(completionProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<double> OnShake;
|
public Action<double> OnShake;
|
||||||
@ -71,8 +73,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private void updatePosition()
|
private void updatePosition()
|
||||||
{
|
{
|
||||||
if (slider != null)
|
if (Slider != null)
|
||||||
Position = HitObject.Position - slider.Position;
|
Position = HitObject.Position - Slider.Position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public Slider Slider => DrawableSlider?.HitObject;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
private double animDuration;
|
private double animDuration;
|
||||||
|
|
||||||
public Drawable CirclePiece { get; private set; }
|
public Drawable CirclePiece { get; private set; }
|
||||||
@ -26,8 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
private DrawableSlider drawableSlider;
|
|
||||||
|
|
||||||
public DrawableSliderRepeat()
|
public DrawableSliderRepeat()
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
@ -60,19 +64,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnParentReceived(parent);
|
base.OnApply();
|
||||||
|
|
||||||
drawableSlider = (DrawableSlider)parent;
|
Position = HitObject.Position - DrawableSlider.Position;
|
||||||
|
|
||||||
Position = HitObject.Position - drawableSlider.Position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (HitObject.StartTime <= Time.Current)
|
if (HitObject.StartTime <= Time.Current)
|
||||||
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
ApplyResult(r => r.Type = DrawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
@ -114,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (IsHit) return;
|
if (IsHit) return;
|
||||||
|
|
||||||
bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0;
|
bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0;
|
||||||
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
List<Vector2> curve = ((PlaySliderBody)DrawableSlider.Body.Drawable).CurrentCurve;
|
||||||
|
|
||||||
Position = isRepeatAtEnd ? end : start;
|
Position = isRepeatAtEnd ? end : start;
|
||||||
|
|
||||||
|
@ -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.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -15,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public Slider Slider => DrawableSlider?.HitObject;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
|
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
private SkinnableDrawable scaleContainer;
|
private SkinnableDrawable scaleContainer;
|
||||||
|
|
||||||
public DrawableSliderTick()
|
public DrawableSliderTick()
|
||||||
@ -62,11 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnParentReceived(parent);
|
base.OnApply();
|
||||||
|
|
||||||
Position = HitObject.Position - ((DrawableSlider)parent).HitObject.Position;
|
Position = HitObject.Position - DrawableSlider.HitObject.Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private Container<DrawableSpinnerTick> ticks;
|
private Container<DrawableSpinnerTick> ticks;
|
||||||
private SpinnerBonusDisplay bonusDisplay;
|
private SpinnerBonusDisplay bonusDisplay;
|
||||||
private Container<PausableSkinnableSound> samplesContainer;
|
private PausableSkinnableSound spinningSample;
|
||||||
|
|
||||||
private Bindable<bool> isSpinning;
|
private Bindable<bool> isSpinning;
|
||||||
private bool spinnerFrequencyModulate;
|
private bool spinnerFrequencyModulate;
|
||||||
@ -81,7 +82,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Y = -120,
|
Y = -120,
|
||||||
},
|
},
|
||||||
samplesContainer = new Container<PausableSkinnableSound> { RelativeSizeAxes = Axes.Both }
|
spinningSample = new PausableSkinnableSound
|
||||||
|
{
|
||||||
|
Volume = { Value = 0 },
|
||||||
|
Looping = true,
|
||||||
|
Frequency = { Value = spinning_sample_initial_frequency }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
PositionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
||||||
@ -95,29 +101,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
isSpinning.BindValueChanged(updateSpinningSample);
|
isSpinning.BindValueChanged(updateSpinningSample);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PausableSkinnableSound spinningSample;
|
|
||||||
private const float spinning_sample_initial_frequency = 1.0f;
|
private const float spinning_sample_initial_frequency = 1.0f;
|
||||||
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
spinningSample.Samples = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadSamples()
|
protected override void LoadSamples()
|
||||||
{
|
{
|
||||||
base.LoadSamples();
|
base.LoadSamples();
|
||||||
|
|
||||||
samplesContainer.Clear();
|
|
||||||
spinningSample = null;
|
|
||||||
|
|
||||||
var firstSample = HitObject.Samples.FirstOrDefault();
|
var firstSample = HitObject.Samples.FirstOrDefault();
|
||||||
|
|
||||||
if (firstSample != null)
|
if (firstSample != null)
|
||||||
{
|
{
|
||||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin");
|
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin");
|
||||||
|
|
||||||
samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone)
|
spinningSample.Samples = new ISampleInfo[] { clone };
|
||||||
{
|
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
||||||
Volume = { Value = 0 },
|
|
||||||
Looping = true,
|
|
||||||
Frequency = { Value = spinning_sample_initial_frequency }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSpinnerTick : DrawableOsuHitObject
|
public class DrawableSpinnerTick : DrawableOsuHitObject
|
||||||
{
|
{
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
|
protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
|
||||||
|
|
||||||
public DrawableSpinnerTick()
|
public DrawableSpinnerTick()
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
@ -19,15 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableSpinner drawableSpinner;
|
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
|
||||||
{
|
|
||||||
base.OnParentReceived(parent);
|
|
||||||
drawableSpinner = (DrawableSpinner)parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double MaximumJudgementOffset => drawableSpinner.HitObject.Duration;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply a judgement result.
|
/// Apply a judgement result.
|
||||||
|
78
osu.Game.Tests/Audio/SampleInfoEqualityTest.cs
Normal file
78
osu.Game.Tests/Audio/SampleInfoEqualityTest.cs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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.Audio;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Audio
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SampleInfoEqualityTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSameSingleSamplesAreEqual()
|
||||||
|
{
|
||||||
|
var first = new SampleInfo("sample");
|
||||||
|
var second = new SampleInfo("sample");
|
||||||
|
|
||||||
|
assertEquality(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifferentSingleSamplesAreNotEqual()
|
||||||
|
{
|
||||||
|
var first = new SampleInfo("first");
|
||||||
|
var second = new SampleInfo("second");
|
||||||
|
|
||||||
|
assertNonEquality(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifferentCountSampleSetsAreNotEqual()
|
||||||
|
{
|
||||||
|
var first = new SampleInfo("sample", "extra");
|
||||||
|
var second = new SampleInfo("sample");
|
||||||
|
|
||||||
|
assertNonEquality(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifferentSampleSetsOfSameCountAreNotEqual()
|
||||||
|
{
|
||||||
|
var first = new SampleInfo("first", "common");
|
||||||
|
var second = new SampleInfo("common", "second");
|
||||||
|
|
||||||
|
assertNonEquality(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSameOrderSameSampleSetsAreEqual()
|
||||||
|
{
|
||||||
|
var first = new SampleInfo("first", "second");
|
||||||
|
var second = new SampleInfo("first", "second");
|
||||||
|
|
||||||
|
assertEquality(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifferentOrderSameSampleSetsAreEqual()
|
||||||
|
{
|
||||||
|
var first = new SampleInfo("first", "second");
|
||||||
|
var second = new SampleInfo("second", "first");
|
||||||
|
|
||||||
|
assertEquality(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEquality(SampleInfo first, SampleInfo second)
|
||||||
|
{
|
||||||
|
Assert.That(first.Equals(second), Is.True);
|
||||||
|
Assert.That(first.GetHashCode(), Is.EqualTo(second.GetHashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNonEquality(SampleInfo first, SampleInfo second)
|
||||||
|
{
|
||||||
|
Assert.That(first.Equals(second), Is.False);
|
||||||
|
Assert.That(first.GetHashCode(), Is.Not.EqualTo(second.GetHashCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Drawing;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Tournament.Screens.Ladder.Components;
|
using osu.Game.Tournament.Screens.Ladder.Components;
|
||||||
using SixLabors.Primitives;
|
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Models
|
namespace osu.Game.Tournament.Models
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -16,7 +17,6 @@ using osu.Game.Tournament.Screens.Ladder;
|
|||||||
using osu.Game.Tournament.Screens.Ladder.Components;
|
using osu.Game.Tournament.Screens.Ladder.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using SixLabors.Primitives;
|
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Editors
|
namespace osu.Game.Tournament.Screens.Editors
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -13,7 +14,6 @@ using osu.Game.Tournament.Models;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using SixLabors.Primitives;
|
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Ladder.Components
|
namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||||
{
|
{
|
||||||
|
@ -1,24 +1,41 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace osu.Game.Audio
|
namespace osu.Game.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a gameplay sample.
|
/// Describes a gameplay sample.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SampleInfo : ISampleInfo
|
public class SampleInfo : ISampleInfo, IEquatable<SampleInfo>
|
||||||
{
|
{
|
||||||
private readonly string[] sampleNames;
|
private readonly string[] sampleNames;
|
||||||
|
|
||||||
public SampleInfo(params string[] sampleNames)
|
public SampleInfo(params string[] sampleNames)
|
||||||
{
|
{
|
||||||
this.sampleNames = sampleNames;
|
this.sampleNames = sampleNames;
|
||||||
|
Array.Sort(sampleNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> LookupNames => sampleNames;
|
public IEnumerable<string> LookupNames => sampleNames;
|
||||||
|
|
||||||
public int Volume { get; } = 100;
|
public int Volume { get; } = 100;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(
|
||||||
|
StructuralComparisons.StructuralEqualityComparer.GetHashCode(sampleNames),
|
||||||
|
Volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(SampleInfo other)
|
||||||
|
=> other != null && sampleNames.SequenceEqual(other.sampleNames);
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is SampleInfo other && Equals(other);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,13 +116,13 @@ namespace osu.Game.Graphics
|
|||||||
switch (screenshotFormat.Value)
|
switch (screenshotFormat.Value)
|
||||||
{
|
{
|
||||||
case ScreenshotFormat.Png:
|
case ScreenshotFormat.Png:
|
||||||
image.SaveAsPng(stream);
|
await image.SaveAsPngAsync(stream);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ScreenshotFormat.Jpg:
|
case ScreenshotFormat.Jpg:
|
||||||
const int jpeg_quality = 92;
|
const int jpeg_quality = 92;
|
||||||
|
|
||||||
image.SaveAsJpeg(stream, new JpegEncoder { Quality = jpeg_quality });
|
await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -75,6 +75,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
StarDifficulty = starDifficulty,
|
StarDifficulty = starDifficulty,
|
||||||
OnlineBeatmapID = OnlineBeatmapID,
|
OnlineBeatmapID = OnlineBeatmapID,
|
||||||
Version = version,
|
Version = version,
|
||||||
|
// this is actually an incorrect mapping (Length is calculated as drain length in lazer's import process, see BeatmapManager.calculateLength).
|
||||||
Length = TimeSpan.FromSeconds(length).TotalMilliseconds,
|
Length = TimeSpan.FromSeconds(length).TotalMilliseconds,
|
||||||
Status = Status,
|
Status = Status,
|
||||||
BeatmapSet = set,
|
BeatmapSet = set,
|
||||||
|
16
osu.Game/Online/Multiplayer/PlaylistExtensions.cs
Normal file
16
osu.Game/Online/Multiplayer/PlaylistExtensions.cs
Normal file
@ -0,0 +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.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Humanizer;
|
||||||
|
using Humanizer.Localisation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer
|
||||||
|
{
|
||||||
|
public static class PlaylistExtensions
|
||||||
|
{
|
||||||
|
public static string GetTotalDuration(this BindableList<PlaylistItem> playlist) =>
|
||||||
|
playlist.Select(p => p.Beatmap.Value.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2);
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -43,6 +42,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public HitObject HitObject { get; private set; }
|
public HitObject HitObject { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The parenting <see cref="DrawableHitObject"/>, if any.
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
protected internal DrawableHitObject ParentHitObject { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour used for various elements of this DrawableHitObject.
|
/// The colour used for various elements of this DrawableHitObject.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -150,8 +155,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||||
|
|
||||||
private Container<PausableSkinnableSound> samplesContainer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the initialization logic in <see cref="Playfield" /> has applied.
|
/// Whether the initialization logic in <see cref="Playfield" /> has applied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -175,7 +178,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds);
|
config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds);
|
||||||
|
|
||||||
// Explicit non-virtual function call.
|
// Explicit non-virtual function call.
|
||||||
base.AddInternal(samplesContainer = new Container<PausableSkinnableSound> { RelativeSizeAxes = Axes.Both });
|
base.AddInternal(Samples = new PausableSkinnableSound());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadAsyncComplete()
|
protected override void LoadAsyncComplete()
|
||||||
@ -230,12 +233,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
foreach (var h in HitObject.NestedHitObjects)
|
foreach (var h in HitObject.NestedHitObjects)
|
||||||
{
|
{
|
||||||
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h);
|
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this);
|
||||||
var drawableNested = pooledDrawableNested
|
var drawableNested = pooledDrawableNested
|
||||||
?? CreateNestedHitObject(h)
|
?? CreateNestedHitObject(h)
|
||||||
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");
|
||||||
|
|
||||||
// Invoke the event only if this nested object is just created by `CreateNestedHitObject`.
|
// Only invoke the event for non-pooled DHOs, otherwise the event will be fired by the playfield.
|
||||||
if (pooledDrawableNested == null)
|
if (pooledDrawableNested == null)
|
||||||
OnNestedDrawableCreated?.Invoke(drawableNested);
|
OnNestedDrawableCreated?.Invoke(drawableNested);
|
||||||
|
|
||||||
@ -243,10 +246,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
drawableNested.OnRevertResult += onRevertResult;
|
drawableNested.OnRevertResult += onRevertResult;
|
||||||
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
||||||
|
|
||||||
|
// This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation().
|
||||||
|
// Must be done before the nested DHO is added to occur before the nested Apply()!
|
||||||
|
drawableNested.ParentHitObject = this;
|
||||||
|
|
||||||
nestedHitObjects.Value.Add(drawableNested);
|
nestedHitObjects.Value.Add(drawableNested);
|
||||||
AddNestedHitObject(drawableNested);
|
AddNestedHitObject(drawableNested);
|
||||||
|
|
||||||
drawableNested.OnParentReceived(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StartTimeBindable.BindTo(HitObject.StartTimeBindable);
|
StartTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||||
@ -297,6 +302,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
||||||
samplesBindable.CollectionChanged -= onSamplesChanged;
|
samplesBindable.CollectionChanged -= onSamplesChanged;
|
||||||
|
|
||||||
|
// Release the samples for other hitobjects to use.
|
||||||
|
Samples.Samples = null;
|
||||||
|
|
||||||
if (nestedHitObjects.IsValueCreated)
|
if (nestedHitObjects.IsValueCreated)
|
||||||
{
|
{
|
||||||
foreach (var obj in nestedHitObjects.Value)
|
foreach (var obj in nestedHitObjects.Value)
|
||||||
@ -315,6 +323,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
OnFree();
|
OnFree();
|
||||||
|
|
||||||
HitObject = null;
|
HitObject = null;
|
||||||
|
ParentHitObject = null;
|
||||||
Result = null;
|
Result = null;
|
||||||
lifetimeEntry = null;
|
lifetimeEntry = null;
|
||||||
|
|
||||||
@ -348,22 +357,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when this <see cref="DrawableHitObject"/> receives a new parenting <see cref="DrawableHitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parent">The parenting <see cref="DrawableHitObject"/>.</param>
|
|
||||||
protected virtual void OnParentReceived(DrawableHitObject parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked by the base <see cref="DrawableHitObject"/> to populate samples, once on initial load and potentially again on any change to the samples collection.
|
/// Invoked by the base <see cref="DrawableHitObject"/> to populate samples, once on initial load and potentially again on any change to the samples collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void LoadSamples()
|
protected virtual void LoadSamples()
|
||||||
{
|
{
|
||||||
samplesContainer.Clear();
|
|
||||||
Samples = null;
|
|
||||||
|
|
||||||
var samples = GetSamples().ToArray();
|
var samples = GetSamples().ToArray();
|
||||||
|
|
||||||
if (samples.Length <= 0)
|
if (samples.Length <= 0)
|
||||||
@ -375,7 +373,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
samplesContainer.Add(Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))));
|
Samples.Samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
||||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
Debug.Assert(!drawableMap.ContainsKey(entry));
|
Debug.Assert(!drawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject);
|
var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
||||||
if (drawable == null)
|
if (drawable == null)
|
||||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||||
|
|
||||||
|
@ -13,8 +13,9 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
/// Attempts to retrieve the poolable <see cref="DrawableHitObject"/> representation of a <see cref="HitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
/// <param name="hitObject">The <see cref="HitObject"/> to retrieve the <see cref="DrawableHitObject"/> representation of.</param>
|
||||||
|
/// <param name="parent">The parenting <see cref="DrawableHitObject"/>, if any.</param>
|
||||||
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
/// <returns>The <see cref="DrawableHitObject"/> representing <see cref="HitObject"/>, or <c>null</c> if no poolable representation exists.</returns>
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject);
|
DrawableHitObject GetPooledDrawableRepresentation([NotNull] HitObject hitObject, [CanBeNull] DrawableHitObject parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,20 +8,24 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
[Cached(typeof(IPooledHitObjectProvider))]
|
[Cached(typeof(IPooledHitObjectProvider))]
|
||||||
public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider
|
[Cached(typeof(IPooledSampleProvider))]
|
||||||
|
public abstract class Playfield : CompositeDrawable, IPooledHitObjectProvider, IPooledSampleProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
|
||||||
@ -81,6 +85,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableBool DisplayJudgements = new BindableBool(true);
|
public readonly BindableBool DisplayJudgements = new BindableBool(true);
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IReadOnlyList<Mod> mods { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ISampleStore sampleStore { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="Playfield"/>.
|
/// Creates a new <see cref="Playfield"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -97,9 +107,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private IReadOnlyList<Mod> mods { get; set; }
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -323,7 +330,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
AddInternal(pool);
|
AddInternal(pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject)
|
DrawableHitObject IPooledHitObjectProvider.GetPooledDrawableRepresentation(HitObject hitObject, DrawableHitObject parent)
|
||||||
{
|
{
|
||||||
var lookupType = hitObject.GetType();
|
var lookupType = hitObject.GetType();
|
||||||
|
|
||||||
@ -359,10 +366,34 @@ namespace osu.Game.Rulesets.UI
|
|||||||
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
|
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
|
||||||
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);
|
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);
|
||||||
|
|
||||||
|
dho.ParentHitObject = parent;
|
||||||
dho.Apply(hitObject, entry);
|
dho.Apply(hitObject, entry);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<ISampleInfo, DrawablePool<PoolableSkinnableSample>> samplePools = new Dictionary<ISampleInfo, DrawablePool<PoolableSkinnableSample>>();
|
||||||
|
|
||||||
|
public PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo)
|
||||||
|
{
|
||||||
|
if (!samplePools.TryGetValue(sampleInfo, out var existingPool))
|
||||||
|
AddInternal(samplePools[sampleInfo] = existingPool = new DrawableSamplePool(sampleInfo, 1));
|
||||||
|
|
||||||
|
return existingPool.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DrawableSamplePool : DrawablePool<PoolableSkinnableSample>
|
||||||
|
{
|
||||||
|
private readonly ISampleInfo sampleInfo;
|
||||||
|
|
||||||
|
public DrawableSamplePool(ISampleInfo sampleInfo, int initialSize, int? maximumSize = null)
|
||||||
|
: base(initialSize, maximumSize)
|
||||||
|
{
|
||||||
|
this.sampleInfo = sampleInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override PoolableSkinnableSample CreateNewDrawable() => base.CreateNewDrawable().With(d => d.Apply(sampleInfo));
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Editor logic
|
#region Editor logic
|
||||||
|
22
osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs
Normal file
22
osu.Game/Screens/Multi/Components/OverlinedPlaylistHeader.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Multi.Components
|
||||||
|
{
|
||||||
|
public class OverlinedPlaylistHeader : OverlinedHeader
|
||||||
|
{
|
||||||
|
public OverlinedPlaylistHeader()
|
||||||
|
: base("Playlist")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -67,7 +67,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Drawable[] { new OverlinedHeader("Playlist"), },
|
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new DrawableRoomPlaylist(false, false)
|
new DrawableRoomPlaylist(false, false)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -69,6 +70,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
|||||||
private OsuSpriteText typeLabel;
|
private OsuSpriteText typeLabel;
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer;
|
||||||
private DrawableRoomPlaylist playlist;
|
private DrawableRoomPlaylist playlist;
|
||||||
|
private OsuSpriteText playlistLength;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IRoomManager manager { get; set; }
|
private IRoomManager manager { get; set; }
|
||||||
@ -229,6 +231,15 @@ namespace osu.Game.Screens.Multi.Match.Components
|
|||||||
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
|
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
|
{
|
||||||
|
playlistLength = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Vertical = 5 },
|
||||||
|
Colour = colours.Yellow,
|
||||||
|
Font = OsuFont.GetFont(size: 12),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new PurpleTriangleButton
|
new PurpleTriangleButton
|
||||||
{
|
{
|
||||||
@ -243,6 +254,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
|||||||
{
|
{
|
||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -315,6 +327,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
|||||||
Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue, true);
|
Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue, true);
|
||||||
|
|
||||||
playlist.Items.BindTo(Playlist);
|
playlist.Items.BindTo(Playlist);
|
||||||
|
Playlist.BindCollectionChanged(onPlaylistChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -324,6 +337,9 @@ namespace osu.Game.Screens.Multi.Match.Components
|
|||||||
ApplyButton.Enabled.Value = hasValidSettings;
|
ApplyButton.Enabled.Value = hasValidSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||||
|
playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}";
|
||||||
|
|
||||||
private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0;
|
private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0;
|
||||||
|
|
||||||
private void apply()
|
private void apply()
|
||||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Screens.Multi.Match
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[] { new OverlinedHeader("Playlist"), },
|
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new DrawableRoomPlaylistWithResults
|
new DrawableRoomPlaylistWithResults
|
||||||
|
@ -914,6 +914,9 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
// size is determined by the carousel itself, due to not all content necessarily being loaded.
|
// size is determined by the carousel itself, due to not all content necessarily being loaded.
|
||||||
ScrollContent.AutoSizeAxes = Axes.None;
|
ScrollContent.AutoSizeAxes = Axes.None;
|
||||||
|
|
||||||
|
// the scroll container may get pushed off-screen by global screen changes, but we still want panels to display outside of the bounds.
|
||||||
|
Masking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910)
|
// ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910)
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
protected override CarouselItem GetNextToSelect()
|
protected override CarouselItem GetNextToSelect()
|
||||||
{
|
{
|
||||||
if (LastSelected == null)
|
if (LastSelected == null || LastSelected.Filtered.Value)
|
||||||
{
|
{
|
||||||
if (GetRecommendedBeatmap?.Invoke(Children.OfType<CarouselBeatmap>().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended)
|
if (GetRecommendedBeatmap?.Invoke(Children.OfType<CarouselBeatmap>().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended)
|
||||||
return Children.OfType<CarouselBeatmap>().First(b => b.Beatmap == recommended);
|
return Children.OfType<CarouselBeatmap>().First(b => b.Beatmap == recommended);
|
||||||
|
22
osu.Game/Skinning/IPooledSampleProvider.cs
Normal file
22
osu.Game/Skinning/IPooledSampleProvider.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides pooled samples to be used by <see cref="SkinnableSound"/>s.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IPooledSampleProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="PoolableSkinnableSample"/> from a pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleInfo">The <see cref="SampleInfo"/> describing the sample to retrieve.</param>
|
||||||
|
/// <returns>The <see cref="PoolableSkinnableSample"/>.</returns>
|
||||||
|
[CanBeNull]
|
||||||
|
PoolableSkinnableSample GetPooledSample(ISampleInfo sampleInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -14,13 +15,17 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
protected bool RequestedPlaying { get; private set; }
|
protected bool RequestedPlaying { get; private set; }
|
||||||
|
|
||||||
public PausableSkinnableSound(ISampleInfo hitSamples)
|
public PausableSkinnableSound()
|
||||||
: base(hitSamples)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public PausableSkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
public PausableSkinnableSound([NotNull] IEnumerable<ISampleInfo> samples)
|
||||||
: base(hitSamples)
|
: base(samples)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PausableSkinnableSound([NotNull] ISampleInfo sample)
|
||||||
|
: base(sample)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
168
osu.Game/Skinning/PoolableSkinnableSample.cs
Normal file
168
osu.Game/Skinning/PoolableSkinnableSample.cs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A sample corresponding to an <see cref="ISampleInfo"/> that supports being pooled and responding to skin changes.
|
||||||
|
/// </summary>
|
||||||
|
public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The currently-loaded <see cref="DrawableSample"/>.
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
public DrawableSample Sample { get; private set; }
|
||||||
|
|
||||||
|
private readonly AudioContainer<DrawableSample> sampleContainer;
|
||||||
|
private ISampleInfo sampleInfo;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ISampleStore sampleStore { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="PoolableSkinnableSample"/> with no applied <see cref="ISampleInfo"/>.
|
||||||
|
/// An <see cref="ISampleInfo"/> can be applied later via <see cref="Apply"/>.
|
||||||
|
/// </summary>
|
||||||
|
public PoolableSkinnableSample()
|
||||||
|
{
|
||||||
|
InternalChild = sampleContainer = new AudioContainer<DrawableSample> { RelativeSizeAxes = Axes.Both };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="PoolableSkinnableSample"/> with an applied <see cref="ISampleInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleInfo">The <see cref="ISampleInfo"/> to attach.</param>
|
||||||
|
public PoolableSkinnableSample(ISampleInfo sampleInfo)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
Apply(sampleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies an <see cref="ISampleInfo"/> that describes the sample to retrieve.
|
||||||
|
/// Only one <see cref="ISampleInfo"/> can ever be applied to a <see cref="PoolableSkinnableSample"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sampleInfo">The <see cref="ISampleInfo"/> to apply.</param>
|
||||||
|
/// <exception cref="InvalidOperationException">If an <see cref="ISampleInfo"/> has already been applied to this <see cref="PoolableSkinnableSample"/>.</exception>
|
||||||
|
public void Apply(ISampleInfo sampleInfo)
|
||||||
|
{
|
||||||
|
if (this.sampleInfo != null)
|
||||||
|
throw new InvalidOperationException($"A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s.");
|
||||||
|
|
||||||
|
this.sampleInfo = sampleInfo;
|
||||||
|
|
||||||
|
Volume.Value = sampleInfo.Volume / 100.0;
|
||||||
|
|
||||||
|
if (LoadState >= LoadState.Ready)
|
||||||
|
updateSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
updateSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSample()
|
||||||
|
{
|
||||||
|
if (sampleInfo == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool wasPlaying = Playing;
|
||||||
|
|
||||||
|
sampleContainer.Clear();
|
||||||
|
Sample = null;
|
||||||
|
|
||||||
|
var ch = CurrentSkin.GetSample(sampleInfo);
|
||||||
|
|
||||||
|
if (ch == null && AllowDefaultFallback)
|
||||||
|
{
|
||||||
|
foreach (var lookup in sampleInfo.LookupNames)
|
||||||
|
{
|
||||||
|
if ((ch = sampleStore.Get(lookup)) != null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sampleContainer.Add(Sample = new DrawableSample(ch) { Looping = Looping });
|
||||||
|
|
||||||
|
// Start playback internally for the new sample if the previous one was playing beforehand.
|
||||||
|
if (wasPlaying)
|
||||||
|
Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays the sample.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="restart">Whether to play the sample from the beginning.</param>
|
||||||
|
public void Play(bool restart = true) => Sample?.Play(restart);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the sample.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop() => Sample?.Stop();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the sample is currently playing.
|
||||||
|
/// </summary>
|
||||||
|
public bool Playing => Sample?.Playing ?? false;
|
||||||
|
|
||||||
|
private bool looping;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the sample should loop on completion.
|
||||||
|
/// </summary>
|
||||||
|
public bool Looping
|
||||||
|
{
|
||||||
|
get => looping;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
looping = value;
|
||||||
|
|
||||||
|
if (Sample != null)
|
||||||
|
Sample.Looping = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Re-expose AudioContainer
|
||||||
|
|
||||||
|
public BindableNumber<double> Volume => sampleContainer.Volume;
|
||||||
|
|
||||||
|
public BindableNumber<double> Balance => sampleContainer.Balance;
|
||||||
|
|
||||||
|
public BindableNumber<double> Frequency => sampleContainer.Frequency;
|
||||||
|
|
||||||
|
public BindableNumber<double> Tempo => sampleContainer.Tempo;
|
||||||
|
|
||||||
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
|
public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type);
|
||||||
|
|
||||||
|
public IBindable<double> AggregateVolume => sampleContainer.AggregateVolume;
|
||||||
|
|
||||||
|
public IBindable<double> AggregateBalance => sampleContainer.AggregateBalance;
|
||||||
|
|
||||||
|
public IBindable<double> AggregateFrequency => sampleContainer.AggregateFrequency;
|
||||||
|
|
||||||
|
public IBindable<double> AggregateTempo => sampleContainer.AggregateTempo;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ namespace osu.Game.Skinning
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
|
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin);
|
protected bool AllowDefaultFallback => allowFallback == null || allowFallback.Invoke(CurrentSkin);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="SkinReloadableDrawable"/>
|
/// Create a new <see cref="SkinReloadableDrawable"/>
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private void skinChanged()
|
private void skinChanged()
|
||||||
{
|
{
|
||||||
SkinChanged(CurrentSkin, allowDefaultFallback);
|
SkinChanged(CurrentSkin, AllowDefaultFallback);
|
||||||
OnSkinChanged?.Invoke();
|
OnSkinChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A sound consisting of one or more samples to be played.
|
||||||
|
/// </summary>
|
||||||
public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent
|
public class SkinnableSound : SkinReloadableDrawable, IAdjustableAudioComponent
|
||||||
{
|
{
|
||||||
private readonly ISampleInfo[] hitSamples;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private ISampleStore samples { get; set; }
|
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
@ -34,21 +35,74 @@ namespace osu.Game.Skinning
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected bool PlayWhenZeroVolume => Looping;
|
protected bool PlayWhenZeroVolume => Looping;
|
||||||
|
|
||||||
protected readonly AudioContainer<DrawableSample> SamplesContainer;
|
/// <summary>
|
||||||
|
/// All raw <see cref="DrawableSamples"/>s contained in this <see cref="SkinnableSound"/>.
|
||||||
|
/// </summary>
|
||||||
|
[NotNull, ItemNotNull]
|
||||||
|
protected IEnumerable<DrawableSample> DrawableSamples => samplesContainer.Select(c => c.Sample).Where(s => s != null);
|
||||||
|
|
||||||
public SkinnableSound(ISampleInfo hitSamples)
|
private readonly AudioContainer<PoolableSkinnableSample> samplesContainer;
|
||||||
: this(new[] { hitSamples })
|
|
||||||
|
[Resolved]
|
||||||
|
private ISampleStore sampleStore { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IPooledSampleProvider samplePool { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SkinnableSound"/>.
|
||||||
|
/// </summary>
|
||||||
|
public SkinnableSound()
|
||||||
|
{
|
||||||
|
InternalChild = samplesContainer = new AudioContainer<PoolableSkinnableSample>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SkinnableSound"/> with some initial samples.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="samples">The initial samples.</param>
|
||||||
|
public SkinnableSound([NotNull] IEnumerable<ISampleInfo> samples)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
this.samples = samples.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SkinnableSound"/> with an initial sample.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sample">The initial sample.</param>
|
||||||
|
public SkinnableSound([NotNull] ISampleInfo sample)
|
||||||
|
: this(new[] { sample })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
private ISampleInfo[] samples = Array.Empty<ISampleInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The samples that should be played.
|
||||||
|
/// </summary>
|
||||||
|
public ISampleInfo[] Samples
|
||||||
{
|
{
|
||||||
this.hitSamples = hitSamples.ToArray();
|
get => samples;
|
||||||
InternalChild = SamplesContainer = new AudioContainer<DrawableSample>();
|
set
|
||||||
|
{
|
||||||
|
value ??= Array.Empty<ISampleInfo>();
|
||||||
|
|
||||||
|
if (samples == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
samples = value;
|
||||||
|
|
||||||
|
if (LoadState >= LoadState.Ready)
|
||||||
|
updateSamples();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool looping;
|
private bool looping;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the samples should loop on completion.
|
||||||
|
/// </summary>
|
||||||
public bool Looping
|
public bool Looping
|
||||||
{
|
{
|
||||||
get => looping;
|
get => looping;
|
||||||
@ -58,77 +112,80 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
looping = value;
|
looping = value;
|
||||||
|
|
||||||
SamplesContainer.ForEach(c => c.Looping = looping);
|
samplesContainer.ForEach(c => c.Looping = looping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays the samples.
|
||||||
|
/// </summary>
|
||||||
public virtual void Play()
|
public virtual void Play()
|
||||||
{
|
{
|
||||||
SamplesContainer.ForEach(c =>
|
samplesContainer.ForEach(c =>
|
||||||
{
|
{
|
||||||
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
|
if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0)
|
||||||
c.Play();
|
c.Play();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops the samples.
|
||||||
|
/// </summary>
|
||||||
public virtual void Stop()
|
public virtual void Stop()
|
||||||
{
|
{
|
||||||
SamplesContainer.ForEach(c => c.Stop());
|
samplesContainer.ForEach(c => c.Stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
updateSamples();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSamples()
|
||||||
{
|
{
|
||||||
bool wasPlaying = IsPlaying;
|
bool wasPlaying = IsPlaying;
|
||||||
|
|
||||||
var channels = hitSamples.Select(s =>
|
// Remove all pooled samples (return them to the pool), and dispose the rest.
|
||||||
{
|
samplesContainer.RemoveAll(s => s.IsInPool);
|
||||||
var ch = skin.GetSample(s);
|
samplesContainer.Clear();
|
||||||
|
|
||||||
if (ch == null && allowFallback)
|
foreach (var s in samples)
|
||||||
{
|
{
|
||||||
foreach (var lookup in s.LookupNames)
|
var sample = samplePool?.GetPooledSample(s) ?? new PoolableSkinnableSample(s);
|
||||||
{
|
sample.Looping = Looping;
|
||||||
if ((ch = samples.Get(lookup)) != null)
|
sample.Volume.Value = s.Volume / 100.0;
|
||||||
break;
|
|
||||||
}
|
samplesContainer.Add(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ch != null)
|
|
||||||
{
|
|
||||||
ch.Looping = looping;
|
|
||||||
ch.Volume.Value = s.Volume / 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch;
|
|
||||||
}).Where(c => c != null);
|
|
||||||
|
|
||||||
SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
|
||||||
|
|
||||||
// Start playback internally for the new samples if the previous ones were playing beforehand.
|
|
||||||
if (wasPlaying)
|
if (wasPlaying)
|
||||||
Play();
|
Play();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Re-expose AudioContainer
|
#region Re-expose AudioContainer
|
||||||
|
|
||||||
public BindableNumber<double> Volume => SamplesContainer.Volume;
|
public BindableNumber<double> Volume => samplesContainer.Volume;
|
||||||
|
|
||||||
public BindableNumber<double> Balance => SamplesContainer.Balance;
|
public BindableNumber<double> Balance => samplesContainer.Balance;
|
||||||
|
|
||||||
public BindableNumber<double> Frequency => SamplesContainer.Frequency;
|
public BindableNumber<double> Frequency => samplesContainer.Frequency;
|
||||||
|
|
||||||
public BindableNumber<double> Tempo => SamplesContainer.Tempo;
|
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
||||||
|
|
||||||
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||||
=> SamplesContainer.AddAdjustment(type, adjustBindable);
|
=> samplesContainer.AddAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
||||||
=> SamplesContainer.RemoveAdjustment(type, adjustBindable);
|
=> samplesContainer.RemoveAdjustment(type, adjustBindable);
|
||||||
|
|
||||||
public void RemoveAllAdjustments(AdjustableProperty type)
|
public void RemoveAllAdjustments(AdjustableProperty type)
|
||||||
=> SamplesContainer.RemoveAllAdjustments(type);
|
=> samplesContainer.RemoveAllAdjustments(type);
|
||||||
|
|
||||||
public bool IsPlaying => SamplesContainer.Any(s => s.Playing);
|
/// <summary>
|
||||||
|
/// Whether any samples are currently playing.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPlaying => samplesContainer.Any(s => s.Playing);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
foreach (var mod in mods.Value.OfType<IApplicableToSample>())
|
||||||
{
|
{
|
||||||
foreach (var sample in SamplesContainer)
|
foreach (var sample in DrawableSamples)
|
||||||
mod.ApplyToSample(sample);
|
mod.ApplyToSample(sample);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
|
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
|
||||||
BeatmapInfo.BeatmapSet.Files = new List<BeatmapSetFileInfo>();
|
BeatmapInfo.BeatmapSet.Files = new List<BeatmapSetFileInfo>();
|
||||||
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
|
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
|
||||||
|
BeatmapInfo.Length = 75000;
|
||||||
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
|
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
{
|
{
|
||||||
Status = BeatmapSetOnlineStatus.Ranked,
|
Status = BeatmapSetOnlineStatus.Ranked,
|
||||||
|
@ -26,7 +26,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.1201.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1203.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.8" />
|
<PackageReference Include="Sentry" Version="2.1.8" />
|
||||||
<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.1201.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1203.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -88,7 +88,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.1201.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1203.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" />
|
||||||
|
Loading…
Reference in New Issue
Block a user