1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 08:27:49 +08:00

Fix skinning support for combobreak

This commit is contained in:
Dean Herbert 2019-06-30 21:58:30 +09:00
parent bd54b34538
commit 60ea3d4e1a
29 changed files with 222 additions and 165 deletions

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
var tickSamples = Samples.Select(s => new SampleInfo var tickSamples = Samples.Select(s => new HitSampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
Name = @"slidertick", Name = @"slidertick",
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance => Path.Distance; public double Distance => Path.Distance;
public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<HitSampleInfo>> NodeSamples { get; set; } = new List<List<HitSampleInfo>>();
public double? LegacyLastTickOffset { get; set; } public double? LegacyLastTickOffset { get; set; }
} }

View File

@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private List<SampleInfo> sampleInfoListAt(double time) private List<HitSampleInfo> sampleInfoListAt(double time)
{ {
var curveData = HitObject as IHasCurve; var curveData = HitObject as IHasCurve;

View File

@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break; break;
} }
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH; bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
@ -443,7 +443,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
noteCount = 0; noteCount = 0;
noteCount = Math.Min(TotalColumns - 1, noteCount); noteCount = Math.Min(TotalColumns - 1, noteCount);
bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == HitSampleInfo.HIT_WHISTLE || s.Name == HitSampleInfo.HIT_FINISH || s.Name == HitSampleInfo.HIT_CLAP);
var rowPattern = new Pattern(); var rowPattern = new Pattern();
@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private List<SampleInfo> sampleInfoListAt(double time) private List<HitSampleInfo> sampleInfoListAt(double time)
{ {
var curveData = HitObject as IHasCurve; var curveData = HitObject as IHasCurve;

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
switch (TotalColumns) switch (TotalColumns)
{ {
case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: case 8 when HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000:
addToPattern(pattern, 0, generateHold); addToPattern(pattern, 0, generateHold);
break; break;
@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}; };
if (hold.Head.Samples == null) if (hold.Head.Samples == null)
hold.Head.Samples = new List<SampleInfo>(); hold.Head.Samples = new List<HitSampleInfo>();
hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); hold.Head.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
hold.Tail.Samples = HitObject.Samples; hold.Tail.Samples = HitObject.Samples;

View File

@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (!convertType.HasFlag(PatternType.KeepSingle)) if (!convertType.HasFlag(PatternType.KeepSingle))
{ {
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8) if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror; convertType |= PatternType.Mirror;
else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) else if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP))
convertType |= PatternType.Gathered; convertType |= PatternType.Gathered;
} }
} }
@ -263,7 +263,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <summary> /// <summary>
/// Whether this hit object can generate a note in the special column. /// Whether this hit object can generate a note in the special column.
/// </summary> /// </summary>
private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH); private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
/// <summary> /// <summary>
/// Generates a random pattern. /// Generates a random pattern.
@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break; break;
} }
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP))
p2 = 1; p2 = 1;
return GetRandomNoteCount(p2, p3, p4, p5); return GetRandomNoteCount(p2, p3, p4, p5);

View File

@ -248,9 +248,9 @@ namespace osu.Game.Rulesets.Osu.Tests
private void createCatmull(int repeats = 0) private void createCatmull(int repeats = 0)
{ {
var repeatSamples = new List<List<SampleInfo>>(); var repeatSamples = new List<List<HitSampleInfo>>();
for (int i = 0; i < repeats; i++) for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>()); repeatSamples.Add(new List<HitSampleInfo>());
var slider = new Slider var slider = new Slider
{ {
@ -270,11 +270,11 @@ namespace osu.Game.Rulesets.Osu.Tests
addSlider(slider, 3, 1); addSlider(slider, 3, 1);
} }
private List<List<SampleInfo>> createEmptySamples(int repeats) private List<List<HitSampleInfo>> createEmptySamples(int repeats)
{ {
var repeatSamples = new List<List<SampleInfo>>(); var repeatSamples = new List<List<HitSampleInfo>>();
for (int i = 0; i < repeats; i++) for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>()); repeatSamples.Add(new List<HitSampleInfo>());
return repeatSamples; return repeatSamples;
} }

View File

@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
internal float LazyTravelDistance; internal float LazyTravelDistance;
public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<HitSampleInfo>> NodeSamples { get; set; } = new List<List<HitSampleInfo>>();
private int repeatCount; private int repeatCount;
@ -157,12 +157,12 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var e in foreach (var e in
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{ {
var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL) var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List<SampleInfo>(); var sampleList = new List<HitSampleInfo>();
if (firstSample != null) if (firstSample != null)
sampleList.Add(new SampleInfo sampleList.Add(new HitSampleInfo
{ {
Bank = firstSample.Bank, Bank = firstSample.Bank,
Volume = firstSample.Volume, Volume = firstSample.Volume,
@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Objects
} }
} }
private List<SampleInfo> getNodeSamples(int nodeIndex) => private List<HitSampleInfo> getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement(); public override Judgement CreateJudgement() => new OsuJudgement();

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{ {
typeof(InputDrum), typeof(InputDrum),
typeof(DrumSampleMapping), typeof(DrumSampleMapping),
typeof(SampleInfo), typeof(HitSampleInfo),
typeof(SampleControlPoint) typeof(SampleControlPoint)
}; };

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
foreach (var s in samplePoints) foreach (var s in samplePoints)
{ {
var centre = s.GetSampleInfo(); var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP); var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
// todo: this is ugly // todo: this is ugly
centre.Namespace = "taiko"; centre.Namespace = "taiko";
@ -43,9 +43,9 @@ namespace osu.Game.Rulesets.Taiko.Audio
} }
} }
private SkinnableSound addSound(SampleInfo sampleInfo) private SkinnableSound addSound(HitSampleInfo hitSampleInfo)
{ {
var drawable = new SkinnableSound(sampleInfo); var drawable = new SkinnableSound(hitSampleInfo);
Sounds.Add(drawable); Sounds.Add(drawable);
return drawable; return drawable;
} }

View File

@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var curveData = obj as IHasCurve; var curveData = obj as IHasCurve;
// Old osu! used hit sounding to determine various hit type information // Old osu! used hit sounding to determine various hit type information
List<SampleInfo> samples = obj.Samples; List<HitSampleInfo> samples = obj.Samples;
bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
if (distanceData != null) if (distanceData != null)
{ {
@ -117,15 +117,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{ {
List<List<SampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<List<SampleInfo>>(new[] { samples }); List<List<HitSampleInfo>> allSamples = curveData != null ? curveData.NodeSamples : new List<List<HitSampleInfo>>(new[] { samples });
int i = 0; int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{ {
List<SampleInfo> currentSamples = allSamples[i]; List<HitSampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
if (isRim) if (isRim)
{ {
@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
} }
else else
{ {
bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
if (isRim) if (isRim)
{ {

View File

@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
// Normal and clap samples are handled by the drum // Normal and clap samples are handled by the drum
protected override IEnumerable<SampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP); protected override IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
protected override string SampleNamespace => "Taiko"; protected override string SampleNamespace => "Taiko";

View File

@ -354,14 +354,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData); Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position); Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, hitObjects[0].StartTime); Assert.AreEqual(956, hitObjects[0].StartTime);
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = hitObjects[1] as IHasPosition; positionData = hitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData); Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position); Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, hitObjects[1].StartTime); Assert.AreEqual(1285, hitObjects[1].StartTime);
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
} }
} }
@ -384,7 +384,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
} }
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
} }
[Test] [Test]
@ -402,7 +402,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
} }
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
} }
[Test] [Test]
@ -422,7 +422,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
} }
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
} }
[Test] [Test]
@ -438,34 +438,34 @@ namespace osu.Game.Tests.Beatmaps.Formats
var slider1 = (ConvertSlider)hitObjects[0]; var slider1 = (ConvertSlider)hitObjects[0];
Assert.AreEqual(1, slider1.NodeSamples[0].Count); Assert.AreEqual(1, slider1.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
Assert.AreEqual(1, slider1.NodeSamples[1].Count); Assert.AreEqual(1, slider1.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
Assert.AreEqual(1, slider1.NodeSamples[2].Count); Assert.AreEqual(1, slider1.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
var slider2 = (ConvertSlider)hitObjects[1]; var slider2 = (ConvertSlider)hitObjects[1];
Assert.AreEqual(2, slider2.NodeSamples[0].Count); Assert.AreEqual(2, slider2.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name); Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[1].Count); Assert.AreEqual(2, slider2.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name); Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[2].Count); Assert.AreEqual(2, slider2.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name); Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
var slider3 = (ConvertSlider)hitObjects[2]; var slider3 = (ConvertSlider)hitObjects[2];
Assert.AreEqual(2, slider3.NodeSamples[0].Count); Assert.AreEqual(2, slider3.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name); Assert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
Assert.AreEqual(1, slider3.NodeSamples[1].Count); Assert.AreEqual(1, slider3.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
Assert.AreEqual(2, slider3.NodeSamples[2].Count); Assert.AreEqual(2, slider3.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name); Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name); Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
} }
} }

View File

@ -101,14 +101,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData); Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position); Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = beatmap.HitObjects[1] as IHasPosition; positionData = beatmap.HitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData); Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position); Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
} }
[TestCase(normal)] [TestCase(normal)]

View File

@ -0,0 +1,70 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
namespace osu.Game.Audio
{
/// <summary>
/// Describes a gameplay hit sample.
/// </summary>
[Serializable]
public class HitSampleInfo : ISampleInfo
{
public const string HIT_WHISTLE = @"hitwhistle";
public const string HIT_FINISH = @"hitfinish";
public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap";
/// <summary>
/// An optional ruleset namespace.
/// </summary>
public string Namespace;
/// <summary>
/// The bank to load the sample from.
/// </summary>
public string Bank;
/// <summary>
/// The name of the sample to load.
/// </summary>
public string Name;
/// <summary>
/// An optional suffix to provide priority lookup. Falls back to non-suffixed <see cref="Name"/>.
/// </summary>
public string Suffix;
/// <summary>
/// The sample volume.
/// </summary>
public int Volume { get; set; }
/// <summary>
/// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
/// </summary>
public virtual IEnumerable<string> LookupNames
{
get
{
if (!string.IsNullOrEmpty(Namespace))
{
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
yield return $"{Namespace}/{Bank}-{Name}";
}
// check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
yield return $"{Bank}-{Name}";
}
}
public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone();
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
namespace osu.Game.Audio
{
public interface ISampleInfo
{
/// <summary>
/// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
/// </summary>
IEnumerable<string> LookupNames { get; }
int Volume { get; }
}
}

View File

@ -1,67 +1,24 @@
// 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;
namespace osu.Game.Audio namespace osu.Game.Audio
{ {
[Serializable] /// <summary>
public class SampleInfo /// Describes a gameplay sample.
/// </summary>
public class SampleInfo : ISampleInfo
{ {
public const string HIT_WHISTLE = @"hitwhistle"; private readonly string sampleName;
public const string HIT_FINISH = @"hitfinish";
public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap";
/// <summary> public SampleInfo(string sampleName)
/// An optional ruleset namespace.
/// </summary>
public string Namespace;
/// <summary>
/// The bank to load the sample from.
/// </summary>
public string Bank;
/// <summary>
/// The name of the sample to load.
/// </summary>
public string Name;
/// <summary>
/// An optional suffix to provide priority lookup. Falls back to non-suffixed <see cref="Name"/>.
/// </summary>
public string Suffix;
/// <summary>
/// The sample volume.
/// </summary>
public int Volume;
/// <summary>
/// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
/// </summary>
public virtual IEnumerable<string> LookupNames
{ {
get this.sampleName = sampleName;
{
if (!string.IsNullOrEmpty(Namespace))
{
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
yield return $"{Namespace}/{Bank}-{Name}";
}
// check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
yield return $"{Bank}-{Name}";
}
} }
public SampleInfo Clone() => (SampleInfo)MemberwiseClone(); public IEnumerable<string> LookupNames => new[] { sampleName };
public int Volume { get; set; } = 100;
} }
} }

View File

@ -24,8 +24,8 @@ namespace osu.Game.Beatmaps.ControlPoints
/// Create a SampleInfo based on the sample settings in this control point. /// Create a SampleInfo based on the sample settings in this control point.
/// </summary> /// </summary>
/// <param name="sampleName">The name of the same.</param> /// <param name="sampleName">The name of the same.</param>
/// <returns>A populated <see cref="SampleInfo"/>.</returns> /// <returns>A populated <see cref="HitSampleInfo"/>.</returns>
public SampleInfo GetSampleInfo(string sampleName = SampleInfo.HIT_NORMAL) => new SampleInfo public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo
{ {
Bank = SampleBank, Bank = SampleBank,
Name = sampleName, Name = sampleName,
@ -33,15 +33,15 @@ namespace osu.Game.Beatmaps.ControlPoints
}; };
/// <summary> /// <summary>
/// Applies <see cref="SampleBank"/> and <see cref="SampleVolume"/> to a <see cref="SampleInfo"/> if necessary, returning the modified <see cref="SampleInfo"/>. /// Applies <see cref="SampleBank"/> and <see cref="SampleVolume"/> to a <see cref="HitSampleInfo"/> if necessary, returning the modified <see cref="HitSampleInfo"/>.
/// </summary> /// </summary>
/// <param name="sampleInfo">The <see cref="SampleInfo"/>. This will not be modified.</param> /// <param name="hitSampleInfo">The <see cref="HitSampleInfo"/>. This will not be modified.</param>
/// <returns>The modified <see cref="SampleInfo"/>. This does not share a reference with <paramref name="sampleInfo"/>.</returns> /// <returns>The modified <see cref="HitSampleInfo"/>. This does not share a reference with <paramref name="hitSampleInfo"/>.</returns>
public virtual SampleInfo ApplyTo(SampleInfo sampleInfo) public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
{ {
var newSampleInfo = sampleInfo.Clone(); var newSampleInfo = hitSampleInfo.Clone();
newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank; newSampleInfo.Bank = hitSampleInfo.Bank ?? SampleBank;
newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume; newSampleInfo.Volume = hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume;
return newSampleInfo; return newSampleInfo;
} }

View File

@ -193,9 +193,9 @@ namespace osu.Game.Beatmaps.Formats
{ {
public int CustomSampleBank; public int CustomSampleBank;
public override SampleInfo ApplyTo(SampleInfo sampleInfo) public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
{ {
var baseInfo = base.ApplyTo(sampleInfo); var baseInfo = base.ApplyTo(hitSampleInfo);
if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 1) if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 1)
baseInfo.Suffix = CustomSampleBank.ToString(); baseInfo.Suffix = CustomSampleBank.ToString();

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected SkinnableSound Samples; protected SkinnableSound Samples;
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples; protected virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>(); private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
public IEnumerable<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty<DrawableHitObject>(); public IEnumerable<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty<DrawableHitObject>();
@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
var samples = GetSamples().ToArray(); var samples = GetSamples().ToArray();
if (samples.Any()) if (samples.Length > 0)
{ {
if (HitObject.SampleControlPoint == null) if (HitObject.SampleControlPoint == null)
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Objects
/// </summary> /// </summary>
public virtual double StartTime { get; set; } public virtual double StartTime { get; set; }
private List<SampleInfo> samples; private List<HitSampleInfo> samples;
/// <summary> /// <summary>
/// The samples to be played when this hit object is hit. /// The samples to be played when this hit object is hit.
@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Objects
/// and can be treated as the default samples for the hit object. /// and can be treated as the default samples for the hit object.
/// </para> /// </para>
/// </summary> /// </summary>
public List<SampleInfo> Samples public List<HitSampleInfo> Samples
{ {
get => samples ?? (samples = new List<SampleInfo>()); get => samples ?? (samples = new List<HitSampleInfo>());
set => samples = value; set => samples = value;
} }

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
}; };
} }
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> nodeSamples) protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<HitSampleInfo>> nodeSamples)
{ {
newCombo |= forceNewCombo; newCombo |= forceNewCombo;
comboOffset += extraComboOffset; comboOffset += extraComboOffset;

View File

@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
// Generate the final per-node samples // Generate the final per-node samples
var nodeSamples = new List<List<SampleInfo>>(nodes); var nodeSamples = new List<List<HitSampleInfo>>(nodes);
for (int i = 0; i < nodes; i++) for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
@ -291,7 +291,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="repeatCount">The slider repeat count.</param> /// <param name="repeatCount">The slider repeat count.</param>
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param> /// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
/// <returns>The hit object.</returns> /// <returns>The hit object.</returns>
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> nodeSamples); protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<HitSampleInfo>> nodeSamples);
/// <summary> /// <summary>
/// Creates a legacy Spinner-type hit object. /// Creates a legacy Spinner-type hit object.
@ -312,14 +312,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="endTime">The hold end time.</param> /// <param name="endTime">The hold end time.</param>
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime);
private List<SampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) private List<HitSampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{ {
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
if (!string.IsNullOrEmpty(bankInfo.Filename)) if (!string.IsNullOrEmpty(bankInfo.Filename))
{ {
return new List<SampleInfo> return new List<HitSampleInfo>
{ {
new FileSampleInfo new FileHitSampleInfo
{ {
Filename = bankInfo.Filename, Filename = bankInfo.Filename,
Volume = bankInfo.Volume Volume = bankInfo.Volume
@ -327,12 +327,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
}; };
} }
var soundTypes = new List<SampleInfo> var soundTypes = new List<HitSampleInfo>
{ {
new LegacySampleInfo new LegacyHitSampleInfo
{ {
Bank = bankInfo.Normal, Bank = bankInfo.Normal,
Name = SampleInfo.HIT_NORMAL, Name = HitSampleInfo.HIT_NORMAL,
Volume = bankInfo.Volume, Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank CustomSampleBank = bankInfo.CustomSampleBank
} }
@ -340,10 +340,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (type.HasFlag(LegacySoundType.Finish)) if (type.HasFlag(LegacySoundType.Finish))
{ {
soundTypes.Add(new LegacySampleInfo soundTypes.Add(new LegacyHitSampleInfo
{ {
Bank = bankInfo.Add, Bank = bankInfo.Add,
Name = SampleInfo.HIT_FINISH, Name = HitSampleInfo.HIT_FINISH,
Volume = bankInfo.Volume, Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank CustomSampleBank = bankInfo.CustomSampleBank
}); });
@ -351,10 +351,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (type.HasFlag(LegacySoundType.Whistle)) if (type.HasFlag(LegacySoundType.Whistle))
{ {
soundTypes.Add(new LegacySampleInfo soundTypes.Add(new LegacyHitSampleInfo
{ {
Bank = bankInfo.Add, Bank = bankInfo.Add,
Name = SampleInfo.HIT_WHISTLE, Name = HitSampleInfo.HIT_WHISTLE,
Volume = bankInfo.Volume, Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank CustomSampleBank = bankInfo.CustomSampleBank
}); });
@ -362,10 +362,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (type.HasFlag(LegacySoundType.Clap)) if (type.HasFlag(LegacySoundType.Clap))
{ {
soundTypes.Add(new LegacySampleInfo soundTypes.Add(new LegacyHitSampleInfo
{ {
Bank = bankInfo.Add, Bank = bankInfo.Add,
Name = SampleInfo.HIT_CLAP, Name = HitSampleInfo.HIT_CLAP,
Volume = bankInfo.Volume, Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank CustomSampleBank = bankInfo.CustomSampleBank
}); });
@ -387,7 +387,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
} }
private class LegacySampleInfo : SampleInfo private class LegacyHitSampleInfo : HitSampleInfo
{ {
public int CustomSampleBank public int CustomSampleBank
{ {
@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
} }
private class FileSampleInfo : SampleInfo private class FileHitSampleInfo : HitSampleInfo
{ {
public string Filename; public string Filename;

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
public double Distance => Path.Distance; public double Distance => Path.Distance;
public List<List<SampleInfo>> NodeSamples { get; set; } public List<List<HitSampleInfo>> NodeSamples { get; set; }
public int RepeatCount { get; set; } public int RepeatCount { get; set; }
public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
}; };
} }
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> nodeSamples) protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<HitSampleInfo>> nodeSamples)
{ {
return new ConvertSlider return new ConvertSlider
{ {

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
}; };
} }
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> nodeSamples) protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<HitSampleInfo>> nodeSamples)
{ {
newCombo |= forceNewCombo; newCombo |= forceNewCombo;
comboOffset += extraComboOffset; comboOffset += extraComboOffset;

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
return new ConvertHit(); return new ConvertHit();
} }
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> nodeSamples) protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<HitSampleInfo>> nodeSamples)
{ {
return new ConvertSlider return new ConvertSlider
{ {

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types
/// n-1: The last repeat.<br /> /// n-1: The last repeat.<br />
/// n: The last node. /// n: The last node.
/// </summary> /// </summary>
List<List<SampleInfo>> NodeSamples { get; } List<List<HitSampleInfo>> NodeSamples { get; }
} }
public static class HasRepeatsExtensions public static class HasRepeatsExtensions

View File

@ -2,10 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
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.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -13,23 +12,31 @@ namespace osu.Game.Screens.Play
{ {
public class ComboEffects : CompositeDrawable public class ComboEffects : CompositeDrawable
{ {
private SampleChannel sampleComboBreak; private readonly ScoreProcessor processor;
private SkinnableSound comboBreakSample;
public ComboEffects(ScoreProcessor processor) public ComboEffects(ScoreProcessor processor)
{ {
processor.Combo.BindValueChanged(onComboChange); this.processor = processor;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("combobreak"));
}
protected override void LoadComplete()
{
base.LoadComplete();
processor.Combo.BindValueChanged(onComboChange, true);
} }
private void onComboChange(ValueChangedEvent<int> combo) private void onComboChange(ValueChangedEvent<int> combo)
{ {
if (combo.NewValue == 0 && combo.OldValue > 20) if (combo.NewValue == 0 && combo.OldValue > 20)
sampleComboBreak?.Play(); comboBreakSample?.Play();
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, AudioManager audio)
{
sampleComboBreak = skin.GetSample(@"Gameplay/combobreak") ?? audio.Samples.Get(@"Gameplay/combobreak");
} }
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
@ -13,14 +14,19 @@ namespace osu.Game.Skinning
{ {
public class SkinnableSound : SkinReloadableDrawable public class SkinnableSound : SkinReloadableDrawable
{ {
private readonly SampleInfo[] samples; private readonly ISampleInfo[] hitSamples;
private SampleChannel[] channels; private SampleChannel[] channels;
private AudioManager audio; private AudioManager audio;
public SkinnableSound(params SampleInfo[] samples) public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
{ {
this.samples = samples; this.hitSamples = hitSamples.ToArray();
}
public SkinnableSound(ISampleInfo hitSamples)
{
this.hitSamples = new[] { hitSamples };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -35,7 +41,7 @@ namespace osu.Game.Skinning
protected override void SkinChanged(ISkinSource skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
channels = samples.Select(s => channels = hitSamples.Select(s =>
{ {
var ch = loadChannel(s, skin.GetSample); var ch = loadChannel(s, skin.GetSample);
if (ch == null && allowFallback) if (ch == null && allowFallback)
@ -44,7 +50,7 @@ namespace osu.Game.Skinning
}).Where(c => c != null).ToArray(); }).Where(c => c != null).ToArray();
} }
private SampleChannel loadChannel(SampleInfo info, Func<string, SampleChannel> getSampleFunction) private SampleChannel loadChannel(ISampleInfo info, Func<string, SampleChannel> getSampleFunction)
{ {
foreach (var lookup in info.LookupNames) foreach (var lookup in info.LookupNames)
{ {