1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 01:02:55 +08:00

Merge remote-tracking branch 'upstream/master' into discord-rich-presence

This commit is contained in:
Lucas A 2019-07-02 21:50:33 +02:00
commit 4e1cbadcbd
56 changed files with 678 additions and 318 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

@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
private readonly ShakeContainer shakeContainer; private readonly ShakeContainer shakeContainer;
// Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects.
public override bool HandlePositionalInput => true;
protected DrawableOsuHitObject(OsuHitObject hitObject) protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {

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

@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{ {
public override string Description => @"Beats fade out before you hit them!"; public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
public override bool HasImplementation => false;
} }
} }

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

@ -3,19 +3,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Chat.Selection;
using osu.Game.Overlays.Chat.Tabs; using osu.Game.Overlays.Chat.Tabs;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
[Description("Testing chat api and overlay")] public class TestSceneChatOverlay : ManualInputManagerTestScene
public class TestSceneChatOverlay : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
@ -28,17 +32,126 @@ namespace osu.Game.Tests.Visual.Online
typeof(TabCloseButton) typeof(TabCloseButton)
}; };
private TestChatOverlay chatOverlay;
private ChannelManager channelManager;
private readonly Channel channel1 = new Channel(new User()) { Name = "test1" };
private readonly Channel channel2 = new Channel(new User()) { Name = "test2" };
[SetUp]
public void Setup()
{
Schedule(() =>
{
ChannelManagerContainer container;
Child = container = new ChannelManagerContainer(new List<Channel> { channel1, channel2 })
{
RelativeSizeAxes = Axes.Both,
};
chatOverlay = container.ChatOverlay;
channelManager = container.ChannelManager;
});
}
[Test]
public void TestHideOverlay()
{
AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
AddStep("Close chat overlay", () => chatOverlay.Hide());
AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden);
AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
[Test]
public void TestSelectingChannelClosesSelector()
{
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
[Test]
public void TestCloseChannelWhileSelectorClosed()
{
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Join channel 2", () => channelManager.JoinChannel(channel2));
AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
AddStep("Close channel 2", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
}
private void clickDrawable(Drawable d)
{
InputManager.MoveMouseTo(d);
InputManager.Click(MouseButton.Left);
}
private class ChannelManagerContainer : Container
{
public TestChatOverlay ChatOverlay { get; private set; }
[Cached] [Cached]
private readonly ChannelManager channelManager = new ChannelManager(); public ChannelManager ChannelManager { get; } = new ChannelManager();
private readonly List<Channel> channels;
public ChannelManagerContainer(List<Channel> channels)
{
this.channels = channels;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] ((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, };
ChatOverlay.Show();
}
}
private class TestChatOverlay : ChatOverlay
{ {
channelManager, public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value;
new ChatOverlay { State = { Value = Visibility.Visible } }
}; public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay;
protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl();
public IReadOnlyDictionary<Channel, TabItem<Channel>> TabMap => ((TestTabControl)ChannelTabControl).TabMap;
}
private class TestTabControl : ChannelTabControl
{
protected override TabItem<Channel> CreateTabItem(Channel value) => new TestChannelTabItem(value);
public new IReadOnlyDictionary<Channel, TabItem<Channel>> TabMap => base.TabMap;
}
private class TestChannelTabItem : PrivateChannelTabItem
{
public TestChannelTabItem(Channel channel)
: base(channel)
{
}
public new ClickableContainer CloseButton => base.CloseButton;
} }
} }
} }

View File

@ -30,9 +30,9 @@ namespace osu.Game.Tests
trackStore = audioManager.GetTrackStore(reader); trackStore = audioManager.GetTrackStore(reader);
} }
public override void Dispose() protected override void Dispose(bool isDisposing)
{ {
base.Dispose(); base.Dispose(isDisposing);
stream?.Dispose(); stream?.Dispose();
reader?.Dispose(); reader?.Dispose();
trackStore?.Dispose(); trackStore?.Dispose();

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 public IEnumerable<string> LookupNames => new[] { sampleName };
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
yield return $"{Bank}-{Name}"; public int Volume { get; set; } = 100;
}
}
public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
} }
} }

View File

@ -13,6 +13,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Threading; using osu.Framework.Threading;
@ -159,6 +160,8 @@ namespace osu.Game.Beatmaps
/// <param name="beatmap">The beatmap difficulty to restore.</param> /// <param name="beatmap">The beatmap difficulty to restore.</param>
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
/// <summary> /// <summary>
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/> /// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
/// </summary> /// </summary>
@ -173,12 +176,18 @@ namespace osu.Game.Beatmaps
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap; return DefaultBeatmap;
var cached = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
if (cached != null)
return cached;
if (beatmapInfo.Metadata == null) if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager); WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager);
previous?.TransferTo(working); previous?.TransferTo(working);
workingCache.Add(working);
return working; return working;
} }

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

@ -11,7 +11,9 @@ using osu.Framework.IO.File;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Statistics;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -31,6 +33,8 @@ namespace osu.Game.Beatmaps
protected AudioManager AudioManager { get; } protected AudioManager AudioManager { get; }
private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s");
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
{ {
AudioManager = audioManager; AudioManager = audioManager;
@ -38,24 +42,13 @@ namespace osu.Game.Beatmaps
BeatmapSetInfo = beatmapInfo.BeatmapSet; BeatmapSetInfo = beatmapInfo.BeatmapSet;
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
beatmap = new RecyclableLazy<IBeatmap>(() =>
{
var b = GetBeatmap() ?? new Beatmap();
// The original beatmap version needs to be preserved as the database doesn't contain it
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
b.BeatmapInfo = BeatmapInfo;
return b;
});
track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack()); track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack());
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid); background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
waveform = new RecyclableLazy<Waveform>(GetWaveform); waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard); storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
skin = new RecyclableLazy<Skin>(GetSkin); skin = new RecyclableLazy<Skin>(GetSkin);
total_count.Value++;
} }
protected virtual Track GetVirtualTrack() protected virtual Track GetVirtualTrack()
@ -153,10 +146,40 @@ namespace osu.Game.Beatmaps
public override string ToString() => BeatmapInfo.ToString(); public override string ToString() => BeatmapInfo.ToString();
public bool BeatmapLoaded => beatmap.IsResultAvailable; public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
public IBeatmap Beatmap => beatmap.Value;
public Task<IBeatmap> LoadBeatmapAsync() => (beatmapLoadTask ?? (beatmapLoadTask = Task.Factory.StartNew(() =>
{
// Todo: Handle cancellation during beatmap parsing
var b = GetBeatmap() ?? new Beatmap();
// The original beatmap version needs to be preserved as the database doesn't contain it
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
b.BeatmapInfo = BeatmapInfo;
return b;
}, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)));
public IBeatmap Beatmap
{
get
{
try
{
return LoadBeatmapAsync().Result;
}
catch (TaskCanceledException)
{
return null;
}
}
}
private readonly CancellationTokenSource beatmapCancellation = new CancellationTokenSource();
protected abstract IBeatmap GetBeatmap(); protected abstract IBeatmap GetBeatmap();
private readonly RecyclableLazy<IBeatmap> beatmap; private Task<IBeatmap> beatmapLoadTask;
public bool BackgroundLoaded => background.IsResultAvailable; public bool BackgroundLoaded => background.IsResultAvailable;
public Texture Background => background.Value; public Texture Background => background.Value;
@ -195,20 +218,46 @@ namespace osu.Game.Beatmaps
other.track = track; other.track = track;
} }
public virtual void Dispose()
{
background.Recycle();
waveform.Recycle();
storyboard.Recycle();
skin.Recycle();
}
/// <summary> /// <summary>
/// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any). /// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any).
/// Accessing track again will load a fresh instance. /// Accessing track again will load a fresh instance.
/// </summary> /// </summary>
public virtual void RecycleTrack() => track.Recycle(); public virtual void RecycleTrack() => track.Recycle();
#region Disposal
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool isDisposed;
protected virtual void Dispose(bool isDisposing)
{
if (isDisposed)
return;
isDisposed = true;
// recycling logic is not here for the time being, as components which use
// retrieved objects from WorkingBeatmap may not hold a reference to the WorkingBeatmap itself.
// this should be fine as each retrieved component do have their own finalizers.
// cancelling the beatmap load is safe for now since the retrieval is a synchronous
// operation. if we add an async retrieval method this may need to be reconsidered.
beatmapCancellation.Cancel();
total_count.Value--;
}
~WorkingBeatmap()
{
Dispose(false);
}
#endregion
public class RecyclableLazy<T> public class RecyclableLazy<T>
{ {
private Lazy<T> lazy; private Lazy<T> lazy;

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Statistics;
namespace osu.Game.Database namespace osu.Game.Database
{ {
@ -31,11 +32,20 @@ namespace osu.Game.Database
recycleThreadContexts(); recycleThreadContexts();
} }
private static readonly GlobalStatistic<int> reads = GlobalStatistics.Get<int>("Database", "Get (Read)");
private static readonly GlobalStatistic<int> writes = GlobalStatistics.Get<int>("Database", "Get (Write)");
private static readonly GlobalStatistic<int> commits = GlobalStatistics.Get<int>("Database", "Commits");
private static readonly GlobalStatistic<int> rollbacks = GlobalStatistics.Get<int>("Database", "Rollbacks");
/// <summary> /// <summary>
/// Get a context for the current thread for read-only usage. /// Get a context for the current thread for read-only usage.
/// If a <see cref="DatabaseWriteUsage"/> is in progress, the existing write-safe context will be returned. /// If a <see cref="DatabaseWriteUsage"/> is in progress, the existing write-safe context will be returned.
/// </summary> /// </summary>
public OsuDbContext Get() => threadContexts.Value; public OsuDbContext Get()
{
reads.Value++;
return threadContexts.Value;
}
/// <summary> /// <summary>
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
@ -45,6 +55,7 @@ namespace osu.Game.Database
/// <returns>A usage containing a usable context.</returns> /// <returns>A usage containing a usable context.</returns>
public DatabaseWriteUsage GetForWrite(bool withTransaction = true) public DatabaseWriteUsage GetForWrite(bool withTransaction = true)
{ {
writes.Value++;
Monitor.Enter(writeLock); Monitor.Enter(writeLock);
OsuDbContext context; OsuDbContext context;
@ -90,9 +101,15 @@ namespace osu.Game.Database
if (usages == 0) if (usages == 0)
{ {
if (currentWriteDidError) if (currentWriteDidError)
{
rollbacks.Value++;
currentWriteTransaction?.Rollback(); currentWriteTransaction?.Rollback();
}
else else
{
commits.Value++;
currentWriteTransaction?.Commit(); currentWriteTransaction?.Commit();
}
if (currentWriteDidWrite || currentWriteDidError) if (currentWriteDidWrite || currentWriteDidError)
{ {

View File

@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.IO; using osu.Game.IO;
@ -34,6 +35,8 @@ namespace osu.Game.Database
private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory()); private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory());
private static readonly GlobalStatistic<int> contexts = GlobalStatistics.Get<int>("Database", "Contexts");
static OsuDbContext() static OsuDbContext()
{ {
// required to initialise native SQLite libraries on some platforms. // required to initialise native SQLite libraries on some platforms.
@ -76,6 +79,8 @@ namespace osu.Game.Database
connection.Close(); connection.Close();
throw; throw;
} }
contexts.Value++;
} }
~OsuDbContext() ~OsuDbContext()
@ -85,6 +90,20 @@ namespace osu.Game.Database
Dispose(); Dispose();
} }
private bool isDisposed;
public override void Dispose()
{
if (isDisposed) return;
isDisposed = true;
base.Dispose();
contexts.Value--;
GC.SuppressFinalize(this);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);

View File

@ -6,23 +6,28 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Transforms;
using osuTK;
namespace osu.Game.Graphics.Backgrounds namespace osu.Game.Graphics.Backgrounds
{ {
public class Background : BufferedContainer /// <summary>
/// A background which offers blurring via a <see cref="BufferedContainer"/> on demand.
/// </summary>
public class Background : CompositeDrawable
{ {
public Sprite Sprite; public Sprite Sprite;
private readonly string textureName; private readonly string textureName;
private BufferedContainer bufferedContainer;
public Background(string textureName = @"") public Background(string textureName = @"")
{ {
CacheDrawnFrameBuffer = true;
this.textureName = textureName; this.textureName = textureName;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Add(Sprite = new Sprite AddInternal(Sprite = new Sprite
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -37,5 +42,28 @@ namespace osu.Game.Graphics.Backgrounds
if (!string.IsNullOrEmpty(textureName)) if (!string.IsNullOrEmpty(textureName))
Sprite.Texture = textures.Get(textureName); Sprite.Texture = textures.Get(textureName);
} }
public Vector2 BlurSigma => bufferedContainer?.BlurSigma ?? Vector2.Zero;
/// <summary>
/// Smoothly adjusts <see cref="IBufferedContainer.BlurSigma"/> over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public void BlurTo(Vector2 newBlurSigma, double duration = 0, Easing easing = Easing.None)
{
if (bufferedContainer == null)
{
RemoveInternal(Sprite);
AddInternal(bufferedContainer = new BufferedContainer
{
CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both,
Child = Sprite
});
}
bufferedContainer.BlurTo(newBlurSigma, duration, easing);
}
} }
} }

View File

@ -295,6 +295,10 @@ namespace osu.Game
var nextBeatmap = beatmap.NewValue; var nextBeatmap = beatmap.NewValue;
if (nextBeatmap?.Track != null) if (nextBeatmap?.Track != null)
nextBeatmap.Track.Completed += currentTrackCompleted; nextBeatmap.Track.Completed += currentTrackCompleted;
beatmap.OldValue?.Dispose();
nextBeatmap?.LoadBeatmapAsync();
} }
private void currentTrackCompleted() private void currentTrackCompleted()

View File

@ -154,6 +154,7 @@ namespace osu.Game
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Medium"));
runMigrations(); runMigrations();

View File

@ -32,6 +32,8 @@ namespace osu.Game.Overlays.Chat.Selection
private readonly SearchTextBox search; private readonly SearchTextBox search;
private readonly SearchContainer<ChannelSection> sectionsFlow; private readonly SearchContainer<ChannelSection> sectionsFlow;
protected override bool DimMainContent => false;
public Action<Channel> OnRequestJoin; public Action<Channel> OnRequestJoin;
public Action<Channel> OnRequestLeave; public Action<Channel> OnRequestLeave;

View File

@ -49,6 +49,8 @@ namespace osu.Game.Overlays.Chat.Tabs
// performTabSort might've made selectorTab's position wonky, fix it // performTabSort might've made selectorTab's position wonky, fix it
TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
((ChannelTabItem)item).OnRequestClose += tabCloseRequested;
base.AddTabItem(item, addToDropdown); base.AddTabItem(item, addToDropdown);
} }
@ -57,10 +59,10 @@ namespace osu.Game.Overlays.Chat.Tabs
switch (value.Type) switch (value.Type)
{ {
default: default:
return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; return new ChannelTabItem(value);
case ChannelType.PM: case ChannelType.PM:
return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; return new PrivateChannelTabItem(value);
} }
} }

View File

@ -45,7 +45,9 @@ namespace osu.Game.Overlays
public const float TAB_AREA_HEIGHT = 50; public const float TAB_AREA_HEIGHT = 50;
private ChannelTabControl channelTabControl; protected ChannelTabControl ChannelTabControl;
protected virtual ChannelTabControl CreateChannelTabControl() => new ChannelTabControl();
private Container chatContainer; private Container chatContainer;
private TabsArea tabsArea; private TabsArea tabsArea;
@ -55,9 +57,10 @@ namespace osu.Game.Overlays
public Bindable<double> ChatHeight { get; set; } public Bindable<double> ChatHeight { get; set; }
private Container channelSelectionContainer; private Container channelSelectionContainer;
private ChannelSelectionOverlay channelSelectionOverlay; protected ChannelSelectionOverlay ChannelSelectionOverlay;
public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (channelSelectionOverlay.State.Value == Visibility.Visible && channelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos)
|| (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos));
public ChatOverlay() public ChatOverlay()
{ {
@ -81,7 +84,7 @@ namespace osu.Game.Overlays
Masking = true, Masking = true,
Children = new[] Children = new[]
{ {
channelSelectionOverlay = new ChannelSelectionOverlay ChannelSelectionOverlay = new ChannelSelectionOverlay
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
@ -154,31 +157,25 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
channelTabControl = new ChannelTabControl ChannelTabControl = CreateChannelTabControl().With(d =>
{ {
Anchor = Anchor.BottomLeft, d.Anchor = Anchor.BottomLeft;
Origin = Anchor.BottomLeft, d.Origin = Anchor.BottomLeft;
RelativeSizeAxes = Axes.Both, d.RelativeSizeAxes = Axes.Both;
OnRequestLeave = channelManager.LeaveChannel d.OnRequestLeave = channelManager.LeaveChannel;
}, }),
} }
}, },
}, },
}, },
}; };
channelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
channelTabControl.ChannelSelectorActive.ValueChanged += active => channelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
channelSelectionOverlay.State.ValueChanged += state => ChannelSelectionOverlay.State.ValueChanged += state =>
{ {
if (state.NewValue == Visibility.Hidden && channelManager.JoinedChannels.Count == 0) // Propagate the visibility state to ChannelSelectorActive
{ ChannelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible;
channelSelectionOverlay.Show();
Hide();
return;
}
channelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible;
if (state.NewValue == Visibility.Visible) if (state.NewValue == Visibility.Visible)
{ {
@ -190,8 +187,8 @@ namespace osu.Game.Overlays
textbox.HoldFocus = true; textbox.HoldFocus = true;
}; };
channelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel);
channelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel;
ChatHeight = config.GetBindable<double>(OsuSetting.ChatDisplayHeight); ChatHeight = config.GetBindable<double>(OsuSetting.ChatDisplayHeight);
ChatHeight.ValueChanged += height => ChatHeight.ValueChanged += height =>
@ -217,11 +214,11 @@ namespace osu.Game.Overlays
channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels;
channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels;
foreach (Channel channel in channelManager.JoinedChannels) foreach (Channel channel in channelManager.JoinedChannels)
channelTabControl.AddChannel(channel); ChannelTabControl.AddChannel(channel);
channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged;
channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged;
channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel = channelManager.CurrentChannel.GetBoundCopy();
currentChannel.BindValueChanged(currentChannelChanged, true); currentChannel.BindValueChanged(currentChannelChanged, true);
@ -236,7 +233,7 @@ namespace osu.Game.Overlays
{ {
textbox.Current.Disabled = true; textbox.Current.Disabled = true;
currentChannelContainer.Clear(false); currentChannelContainer.Clear(false);
channelSelectionOverlay.Show(); ChannelSelectionOverlay.Show();
return; return;
} }
@ -245,8 +242,8 @@ namespace osu.Game.Overlays
textbox.Current.Disabled = e.NewValue.ReadOnly; textbox.Current.Disabled = e.NewValue.ReadOnly;
if (channelTabControl.Current.Value != e.NewValue) if (ChannelTabControl.Current.Value != e.NewValue)
Scheduler.Add(() => channelTabControl.Current.Value = e.NewValue); Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue);
var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); var loaded = loadedChannels.Find(d => d.Channel == e.NewValue);
@ -294,7 +291,7 @@ namespace osu.Game.Overlays
double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y; double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
// If the channel selection screen is shown, mind its minimum height // If the channel selection screen is shown, mind its minimum height
if (channelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) if (ChannelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height)
targetChatHeight = 1f - channel_selection_min_height; targetChatHeight = 1f - channel_selection_min_height;
ChatHeight.Value = targetChatHeight; ChatHeight.Value = targetChatHeight;
@ -311,9 +308,9 @@ namespace osu.Game.Overlays
private void selectTab(int index) private void selectTab(int index)
{ {
var channel = channelTabControl.Items.Skip(index).FirstOrDefault(); var channel = ChannelTabControl.Items.Skip(index).FirstOrDefault();
if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel))
channelTabControl.Current.Value = channel; ChannelTabControl.Current.Value = channel;
} }
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
@ -358,6 +355,7 @@ namespace osu.Game.Overlays
this.FadeIn(transition_length, Easing.OutQuint); this.FadeIn(transition_length, Easing.OutQuint);
textbox.HoldFocus = true; textbox.HoldFocus = true;
base.PopIn(); base.PopIn();
} }
@ -366,7 +364,7 @@ namespace osu.Game.Overlays
this.MoveToY(Height, transition_length, Easing.InSine); this.MoveToY(Height, transition_length, Easing.InSine);
this.FadeOut(transition_length, Easing.InSine); this.FadeOut(transition_length, Easing.InSine);
channelSelectionOverlay.Hide(); ChannelSelectionOverlay.Hide();
textbox.HoldFocus = false; textbox.HoldFocus = false;
base.PopOut(); base.PopOut();
@ -375,20 +373,20 @@ namespace osu.Game.Overlays
private void onChannelAddedToJoinedChannels(IEnumerable<Channel> channels) private void onChannelAddedToJoinedChannels(IEnumerable<Channel> channels)
{ {
foreach (Channel channel in channels) foreach (Channel channel in channels)
channelTabControl.AddChannel(channel); ChannelTabControl.AddChannel(channel);
} }
private void onChannelRemovedFromJoinedChannels(IEnumerable<Channel> channels) private void onChannelRemovedFromJoinedChannels(IEnumerable<Channel> channels)
{ {
foreach (Channel channel in channels) foreach (Channel channel in channels)
{ {
channelTabControl.RemoveChannel(channel); ChannelTabControl.RemoveChannel(channel);
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel));
} }
} }
private void availableChannelsChanged(IEnumerable<Channel> channels) private void availableChannelsChanged(IEnumerable<Channel> channels)
=> channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); => ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {

View File

@ -66,24 +66,64 @@ namespace osu.Game.Overlays
} }
}; };
Header.Tabs.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); Header.Tabs.Current.ValueChanged += _ => queueUpdate();
Filter.Tabs.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); Filter.Tabs.Current.ValueChanged += _ => queueUpdate();
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue);
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdate();
currentQuery.BindTo(Filter.Search.Current);
currentQuery.ValueChanged += query => currentQuery.ValueChanged += query =>
{ {
queryChangedDebounce?.Cancel(); queryChangedDebounce?.Cancel();
if (string.IsNullOrEmpty(query.NewValue)) if (string.IsNullOrEmpty(query.NewValue))
Scheduler.AddOnce(updateSearch); queueUpdate();
else else
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500);
}; };
}
currentQuery.BindTo(Filter.Search.Current); private APIRequest getUsersRequest;
private readonly Bindable<string> currentQuery = new Bindable<string>();
private ScheduledDelegate queryChangedDebounce;
private void queueUpdate() => Scheduler.AddOnce(updateSearch);
private void updateSearch()
{
queryChangedDebounce?.Cancel();
if (!IsLoaded)
return;
Users = null;
clearPanels();
loading.Hide();
getUsersRequest?.Cancel();
if (API?.IsLoggedIn != true)
return;
switch (Header.Tabs.Current.Value)
{
case SocialTab.Friends:
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
friendRequest.Success += updateUsers;
API.Queue(getUsersRequest = friendRequest);
break;
default:
var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += response => updateUsers(response.Select(r => r.User));
API.Queue(getUsersRequest = userRequest);
break;
}
loading.Show();
} }
private void recreatePanels(PanelDisplayStyle displayStyle) private void recreatePanels(PanelDisplayStyle displayStyle)
@ -133,45 +173,6 @@ namespace osu.Game.Overlays
}); });
} }
private APIRequest getUsersRequest;
private readonly Bindable<string> currentQuery = new Bindable<string>();
private ScheduledDelegate queryChangedDebounce;
private void updateSearch()
{
queryChangedDebounce?.Cancel();
if (!IsLoaded)
return;
Users = null;
clearPanels();
loading.Hide();
getUsersRequest?.Cancel();
if (API?.IsLoggedIn != true)
return;
switch (Header.Tabs.Current.Value)
{
case SocialTab.Friends:
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
friendRequest.Success += updateUsers;
API.Queue(getUsersRequest = friendRequest);
break;
default:
var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += response => updateUsers(response.Select(r => r.User));
API.Queue(getUsersRequest = userRequest);
break;
}
loading.Show();
}
private void updateUsers(IEnumerable<User> newUsers) private void updateUsers(IEnumerable<User> newUsers)
{ {
Users = newUsers; Users = newUsers;
@ -193,7 +194,7 @@ namespace osu.Game.Overlays
switch (state) switch (state)
{ {
case APIState.Online: case APIState.Online:
Scheduler.AddOnce(updateSearch); queueUpdate();
break; break;
default: default:

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

@ -18,7 +18,7 @@ namespace osu.Game.Screens.Backgrounds
private Background background; private Background background;
private int currentDisplay; private int currentDisplay;
private const int background_count = 5; private const int background_count = 7;
private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}";

View File

@ -24,11 +24,13 @@ using osu.Game.Screens.Edit.Design;
using osuTK.Input; using osuTK.Input;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework; using osu.Framework;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
public class Editor : OsuScreen public class Editor : OsuScreen, IKeyBindingHandler<GlobalAction>
{ {
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
@ -206,6 +208,20 @@ namespace osu.Game.Screens.Edit
return true; return true;
} }
public bool OnPressed(GlobalAction action)
{
if (action == GlobalAction.Back)
{
// as we don't want to display the back button, manual handling of exit action is required.
this.Exit();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back;
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
Beatmap.Value.Track?.Stop(); Beatmap.Value.Track?.Stop();

View File

@ -86,6 +86,7 @@ namespace osu.Game.Screens.Menu
if (!resuming) if (!resuming)
{ {
Beatmap.Value = introBeatmap; Beatmap.Value = introBeatmap;
introBeatmap = null;
if (menuVoice.Value) if (menuVoice.Value)
welcome.Play(); welcome.Play();
@ -94,7 +95,10 @@ namespace osu.Game.Screens.Menu
{ {
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu.
if (menuMusic.Value) if (menuMusic.Value)
{
track.Start(); track.Start();
track = null;
}
LoadComponentAsync(mainMenu = new MainMenu()); LoadComponentAsync(mainMenu = new MainMenu());

View File

@ -96,13 +96,13 @@ namespace osu.Game.Screens.Menu
var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null;
var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes ?? new float[256]; float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes;
for (int i = 0; i < bars_per_visualiser; i++) for (int i = 0; i < bars_per_visualiser; i++)
{ {
if (track?.IsRunning ?? false) if (track?.IsRunning ?? false)
{ {
float targetAmplitude = temporalAmplitudes[(i + indexOffset) % bars_per_visualiser] * (effect?.KiaiMode == true ? 1 : 0.5f); float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f);
if (targetAmplitude > frequencyAmplitudes[i]) if (targetAmplitude > frequencyAmplitudes[i])
frequencyAmplitudes[i] = targetAmplitude; frequencyAmplitudes[i] = targetAmplitude;
} }
@ -115,7 +115,6 @@ namespace osu.Game.Screens.Menu
} }
indexOffset = (indexOffset + index_change) % bars_per_visualiser; indexOffset = (indexOffset + index_change) % bars_per_visualiser;
Scheduler.AddDelayed(updateAmplitudes, time_between_updates);
} }
private void updateColour() private void updateColour()
@ -131,7 +130,8 @@ namespace osu.Game.Screens.Menu
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
updateAmplitudes();
Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true);
} }
protected override void Update() protected override void Update()

View File

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play
{
public class ComboEffects : CompositeDrawable
{
private readonly ScoreProcessor processor;
private SkinnableSound comboBreakSample;
public ComboEffects(ScoreProcessor processor)
{
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)
{
if (combo.NewValue == 0 && combo.OldValue > 20)
comboBreakSample?.Play();
}
}
}

View File

@ -127,7 +127,11 @@ namespace osu.Game.Screens.Play
Child = new LocalSkinOverrideContainer(working.Skin) Child = new LocalSkinOverrideContainer(working.Skin)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = DrawableRuleset Children = new Drawable[]
{
DrawableRuleset,
new ComboEffects(ScoreProcessor)
}
} }
}, },
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)

View File

@ -11,7 +11,6 @@ using osu.Game.Configuration;
using osuTK.Input; using osuTK.Input;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Caching; using osu.Framework.Caching;
@ -64,24 +63,19 @@ namespace osu.Game.Screens.Select
public IEnumerable<BeatmapSetInfo> BeatmapSets public IEnumerable<BeatmapSetInfo> BeatmapSets
{ {
get => beatmapSets.Select(g => g.BeatmapSet); get => beatmapSets.Select(g => g.BeatmapSet);
set => loadBeatmapSets(() => value); set => loadBeatmapSets(value);
} }
public void LoadBeatmapSetsFromManager(BeatmapManager manager) => loadBeatmapSets(manager.GetAllUsableBeatmapSetsEnumerable); private void loadBeatmapSets(IEnumerable<BeatmapSetInfo> beatmapSets)
private void loadBeatmapSets(Func<IEnumerable<BeatmapSetInfo>> beatmapSets)
{ {
CarouselRoot newRoot = new CarouselRoot(this); CarouselRoot newRoot = new CarouselRoot(this);
Task.Run(() => beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild);
{
beatmapSets().Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild);
newRoot.Filter(activeCriteria); newRoot.Filter(activeCriteria);
// preload drawables as the ctor overhead is quite high currently. // preload drawables as the ctor overhead is quite high currently.
var _ = newRoot.Drawables; var _ = newRoot.Drawables;
}).ContinueWith(_ => Schedule(() =>
{
root = newRoot; root = newRoot;
scrollableContent.Clear(false); scrollableContent.Clear(false);
itemsCache.Invalidate(); itemsCache.Invalidate();
@ -92,7 +86,6 @@ namespace osu.Game.Screens.Select
BeatmapSetsChanged?.Invoke(); BeatmapSetsChanged?.Invoke();
BeatmapSetsLoaded = true; BeatmapSetsLoaded = true;
}); });
}));
} }
private readonly List<float> yPositions = new List<float>(); private readonly List<float> yPositions = new List<float>();
@ -125,13 +118,15 @@ namespace osu.Game.Screens.Select
} }
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuConfigManager config) private void load(OsuConfigManager config, BeatmapManager beatmaps)
{ {
config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm);
config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled);
RightClickScrollingEnabled.ValueChanged += enabled => RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.ValueChanged += enabled => RightMouseScrollbar = enabled.NewValue;
RightClickScrollingEnabled.TriggerChange(); RightClickScrollingEnabled.TriggerChange();
loadBeatmapSets(beatmaps.GetAllUsableBeatmapSetsEnumerable());
} }
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)

View File

@ -244,8 +244,6 @@ namespace osu.Game.Screens.Select
sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand");
SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection");
Carousel.LoadBeatmapSetsFromManager(this.beatmaps);
if (dialogOverlay != null) if (dialogOverlay != null)
{ {
Schedule(() => Schedule(() =>

View File

@ -81,7 +81,10 @@ namespace osu.Game.Skinning
}; };
} }
var texture = GetTexture(componentName); // temporary allowance is given for skins the fact that stable handles non-animatable items such as hitcircles (incorrectly)
// by (incorrectly) displaying the first frame of animation rather than the non-animated version.
// users have used this to "hide" certain elements like hit300.
var texture = GetTexture($"{componentName}-0") ?? GetTexture(componentName);
if (texture == null) if (texture == null)
return null; return null;

View File

@ -23,6 +23,7 @@ namespace osu.Game.Skinning
private readonly Bindable<bool> beatmapHitsounds = new Bindable<bool>(); private readonly Bindable<bool> beatmapHitsounds = new Bindable<bool>();
private readonly ISkin skin; private readonly ISkin skin;
private ISkinSource fallbackSource; private ISkinSource fallbackSource;
public LocalSkinOverrideContainer(ISkin skin) public LocalSkinOverrideContainer(ISkin skin)

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)
{ {

View File

@ -137,9 +137,9 @@ namespace osu.Game.Tests.Visual
track = audio?.Tracks.GetVirtual(length); track = audio?.Tracks.GetVirtual(length);
} }
public override void Dispose() protected override void Dispose(bool isDisposing)
{ {
base.Dispose(); base.Dispose(isDisposing);
store?.Dispose(); store?.Dispose();
} }

View File

@ -14,8 +14,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.609.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.628.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.703.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" /> <PackageReference Include="SharpCompress" Version="0.23.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" />

View File

@ -104,9 +104,9 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.609.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.628.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.703.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.628.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.703.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" /> <PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />