1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 12:33:01 +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();
var tickSamples = Samples.Select(s => new SampleInfo
var tickSamples = Samples.Select(s => new HitSampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects
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; }
}

View File

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

View File

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

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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);
break;
@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
};
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;

View File

@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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;
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;
}
}
@ -263,7 +263,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <summary>
/// Whether this hit object can generate a note in the special column.
/// </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>
/// Generates a random pattern.
@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break;
}
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP))
p2 = 1;
return GetRandomNoteCount(p2, p3, p4, p5);

View File

@ -248,9 +248,9 @@ namespace osu.Game.Rulesets.Osu.Tests
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++)
repeatSamples.Add(new List<SampleInfo>());
repeatSamples.Add(new List<HitSampleInfo>());
var slider = new Slider
{
@ -270,11 +270,11 @@ namespace osu.Game.Rulesets.Osu.Tests
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++)
repeatSamples.Add(new List<SampleInfo>());
repeatSamples.Add(new List<HitSampleInfo>());
return repeatSamples;
}

View File

@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
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)
: base(hitObject)
{

View File

@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
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;
@ -157,12 +157,12 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var e in
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)
var sampleList = new List<SampleInfo>();
var sampleList = new List<HitSampleInfo>();
if (firstSample != null)
sampleList.Add(new SampleInfo
sampleList.Add(new HitSampleInfo
{
Bank = firstSample.Bank,
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;
public override Judgement CreateJudgement() => new OsuJudgement();

View File

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

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
foreach (var s in samplePoints)
{
var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP);
var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
// todo: this is ugly
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);
return drawable;
}

View File

@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var curveData = obj as IHasCurve;
// 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)
{
@ -117,15 +117,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
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;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{
List<SampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH);
List<HitSampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
if (isRim)
{
@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}
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)
{

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 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
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";

View File

@ -354,14 +354,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
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;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
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());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@ -402,7 +402,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
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]
@ -422,7 +422,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
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]
@ -438,34 +438,34 @@ namespace osu.Game.Tests.Beatmaps.Formats
var slider1 = (ConvertSlider)hitObjects[0];
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(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(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
var slider2 = (ConvertSlider)hitObjects[1];
Assert.AreEqual(2, slider2.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[1].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[2].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
var slider3 = (ConvertSlider)hitObjects[2];
Assert.AreEqual(2, slider3.NodeSamples[0].Count);
Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
Assert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
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(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].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.AreEqual(new Vector2(192, 168), positionData.Position);
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;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
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)]

View File

@ -3,19 +3,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Chat.Selection;
using osu.Game.Overlays.Chat.Tabs;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
[Description("Testing chat api and overlay")]
public class TestSceneChatOverlay : OsuTestScene
public class TestSceneChatOverlay : ManualInputManagerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
@ -28,17 +32,126 @@ namespace osu.Game.Tests.Visual.Online
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]
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]
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,
new ChatOverlay { State = { Value = Visibility.Visible } }
};
public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value;
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);
}
public override void Dispose()
protected override void Dispose(bool isDisposing)
{
base.Dispose();
base.Dispose(isDisposing);
stream?.Dispose();
reader?.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.
using System;
using System.Collections.Generic;
namespace osu.Game.Audio
{
[Serializable]
public class SampleInfo
/// <summary>
/// Describes a gameplay sample.
/// </summary>
public class SampleInfo : 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";
private readonly string sampleName;
/// <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;
/// <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
public SampleInfo(string sampleName)
{
get
{
if (!string.IsNullOrEmpty(Namespace))
{
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
yield return $"{Namespace}/{Bank}-{Name}";
this.sampleName = sampleName;
}
// check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
public IEnumerable<string> LookupNames => new[] { sampleName };
yield return $"{Bank}-{Name}";
}
}
public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
public int Volume { get; set; } = 100;
}
}

View File

@ -13,6 +13,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Threading;
@ -159,6 +160,8 @@ namespace osu.Game.Beatmaps
/// <param name="beatmap">The beatmap difficulty to restore.</param>
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
/// <summary>
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
/// </summary>
@ -173,12 +176,18 @@ namespace osu.Game.Beatmaps
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
var cached = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
if (cached != null)
return cached;
if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager);
previous?.TransferTo(working);
workingCache.Add(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.
/// </summary>
/// <param name="sampleName">The name of the same.</param>
/// <returns>A populated <see cref="SampleInfo"/>.</returns>
public SampleInfo GetSampleInfo(string sampleName = SampleInfo.HIT_NORMAL) => new SampleInfo
/// <returns>A populated <see cref="HitSampleInfo"/>.</returns>
public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo
{
Bank = SampleBank,
Name = sampleName,
@ -33,15 +33,15 @@ namespace osu.Game.Beatmaps.ControlPoints
};
/// <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>
/// <param name="sampleInfo">The <see cref="SampleInfo"/>. This will not be modified.</param>
/// <returns>The modified <see cref="SampleInfo"/>. This does not share a reference with <paramref name="sampleInfo"/>.</returns>
public virtual SampleInfo ApplyTo(SampleInfo sampleInfo)
/// <param name="hitSampleInfo">The <see cref="HitSampleInfo"/>. This will not be modified.</param>
/// <returns>The modified <see cref="HitSampleInfo"/>. This does not share a reference with <paramref name="hitSampleInfo"/>.</returns>
public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
{
var newSampleInfo = sampleInfo.Clone();
newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank;
newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume;
var newSampleInfo = hitSampleInfo.Clone();
newSampleInfo.Bank = hitSampleInfo.Bank ?? SampleBank;
newSampleInfo.Volume = hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume;
return newSampleInfo;
}

View File

@ -193,9 +193,9 @@ namespace osu.Game.Beatmaps.Formats
{
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)
baseInfo.Suffix = CustomSampleBank.ToString();

View File

@ -11,7 +11,9 @@ using osu.Framework.IO.File;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Audio;
using osu.Framework.Statistics;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
@ -31,6 +33,8 @@ namespace osu.Game.Beatmaps
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)
{
AudioManager = audioManager;
@ -38,24 +42,13 @@ namespace osu.Game.Beatmaps
BeatmapSetInfo = beatmapInfo.BeatmapSet;
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());
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
skin = new RecyclableLazy<Skin>(GetSkin);
total_count.Value++;
}
protected virtual Track GetVirtualTrack()
@ -153,10 +146,40 @@ namespace osu.Game.Beatmaps
public override string ToString() => BeatmapInfo.ToString();
public bool BeatmapLoaded => beatmap.IsResultAvailable;
public IBeatmap Beatmap => beatmap.Value;
public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
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();
private readonly RecyclableLazy<IBeatmap> beatmap;
private Task<IBeatmap> beatmapLoadTask;
public bool BackgroundLoaded => background.IsResultAvailable;
public Texture Background => background.Value;
@ -195,20 +218,46 @@ namespace osu.Game.Beatmaps
other.track = track;
}
public virtual void Dispose()
{
background.Recycle();
waveform.Recycle();
storyboard.Recycle();
skin.Recycle();
}
/// <summary>
/// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any).
/// Accessing track again will load a fresh instance.
/// </summary>
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>
{
private Lazy<T> lazy;

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Framework.Platform;
using osu.Framework.Statistics;
namespace osu.Game.Database
{
@ -31,11 +32,20 @@ namespace osu.Game.Database
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>
/// 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.
/// </summary>
public OsuDbContext Get() => threadContexts.Value;
public OsuDbContext Get()
{
reads.Value++;
return threadContexts.Value;
}
/// <summary>
/// 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>
public DatabaseWriteUsage GetForWrite(bool withTransaction = true)
{
writes.Value++;
Monitor.Enter(writeLock);
OsuDbContext context;
@ -90,9 +101,15 @@ namespace osu.Game.Database
if (usages == 0)
{
if (currentWriteDidError)
{
rollbacks.Value++;
currentWriteTransaction?.Rollback();
}
else
{
commits.Value++;
currentWriteTransaction?.Commit();
}
if (currentWriteDidWrite || currentWriteDidError)
{

View File

@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
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 GlobalStatistic<int> contexts = GlobalStatistics.Get<int>("Database", "Contexts");
static OsuDbContext()
{
// required to initialise native SQLite libraries on some platforms.
@ -76,6 +79,8 @@ namespace osu.Game.Database
connection.Close();
throw;
}
contexts.Value++;
}
~OsuDbContext()
@ -85,6 +90,20 @@ namespace osu.Game.Database
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)
{
base.OnConfiguring(optionsBuilder);

View File

@ -6,23 +6,28 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Transforms;
using osuTK;
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;
private readonly string textureName;
private BufferedContainer bufferedContainer;
public Background(string textureName = @"")
{
CacheDrawnFrameBuffer = true;
this.textureName = textureName;
RelativeSizeAxes = Axes.Both;
Add(Sprite = new Sprite
AddInternal(Sprite = new Sprite
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
@ -37,5 +42,28 @@ namespace osu.Game.Graphics.Backgrounds
if (!string.IsNullOrEmpty(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;
if (nextBeatmap?.Track != null)
nextBeatmap.Track.Completed += currentTrackCompleted;
beatmap.OldValue?.Dispose();
nextBeatmap?.LoadBeatmapAsync();
}
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-Light"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Medium"));
runMigrations();

View File

@ -32,6 +32,8 @@ namespace osu.Game.Overlays.Chat.Selection
private readonly SearchTextBox search;
private readonly SearchContainer<ChannelSection> sectionsFlow;
protected override bool DimMainContent => false;
public Action<Channel> OnRequestJoin;
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
TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
((ChannelTabItem)item).OnRequestClose += tabCloseRequested;
base.AddTabItem(item, addToDropdown);
}
@ -57,10 +59,10 @@ namespace osu.Game.Overlays.Chat.Tabs
switch (value.Type)
{
default:
return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested };
return new ChannelTabItem(value);
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;
private ChannelTabControl channelTabControl;
protected ChannelTabControl ChannelTabControl;
protected virtual ChannelTabControl CreateChannelTabControl() => new ChannelTabControl();
private Container chatContainer;
private TabsArea tabsArea;
@ -55,9 +57,10 @@ namespace osu.Game.Overlays
public Bindable<double> ChatHeight { get; set; }
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()
{
@ -81,7 +84,7 @@ namespace osu.Game.Overlays
Masking = true,
Children = new[]
{
channelSelectionOverlay = new ChannelSelectionOverlay
ChannelSelectionOverlay = new ChannelSelectionOverlay
{
RelativeSizeAxes = Axes.Both,
},
@ -154,31 +157,25 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
channelTabControl = new ChannelTabControl
ChannelTabControl = CreateChannelTabControl().With(d =>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
OnRequestLeave = channelManager.LeaveChannel
},
d.Anchor = Anchor.BottomLeft;
d.Origin = Anchor.BottomLeft;
d.RelativeSizeAxes = Axes.Both;
d.OnRequestLeave = channelManager.LeaveChannel;
}),
}
},
},
},
};
channelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
channelTabControl.ChannelSelectorActive.ValueChanged += active => channelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
channelSelectionOverlay.State.ValueChanged += state =>
ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue;
ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden;
ChannelSelectionOverlay.State.ValueChanged += state =>
{
if (state.NewValue == Visibility.Hidden && channelManager.JoinedChannels.Count == 0)
{
channelSelectionOverlay.Show();
Hide();
return;
}
channelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible;
// Propagate the visibility state to ChannelSelectorActive
ChannelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible;
if (state.NewValue == Visibility.Visible)
{
@ -190,8 +187,8 @@ namespace osu.Game.Overlays
textbox.HoldFocus = true;
};
channelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel);
channelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel;
ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel);
ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel;
ChatHeight = config.GetBindable<double>(OsuSetting.ChatDisplayHeight);
ChatHeight.ValueChanged += height =>
@ -217,11 +214,11 @@ namespace osu.Game.Overlays
channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels;
channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels;
foreach (Channel channel in channelManager.JoinedChannels)
channelTabControl.AddChannel(channel);
ChannelTabControl.AddChannel(channel);
channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged;
channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged;
channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
currentChannel = channelManager.CurrentChannel.GetBoundCopy();
currentChannel.BindValueChanged(currentChannelChanged, true);
@ -236,7 +233,7 @@ namespace osu.Game.Overlays
{
textbox.Current.Disabled = true;
currentChannelContainer.Clear(false);
channelSelectionOverlay.Show();
ChannelSelectionOverlay.Show();
return;
}
@ -245,8 +242,8 @@ namespace osu.Game.Overlays
textbox.Current.Disabled = e.NewValue.ReadOnly;
if (channelTabControl.Current.Value != e.NewValue)
Scheduler.Add(() => channelTabControl.Current.Value = e.NewValue);
if (ChannelTabControl.Current.Value != e.NewValue)
Scheduler.Add(() => ChannelTabControl.Current.Value = 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;
// 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;
ChatHeight.Value = targetChatHeight;
@ -311,9 +308,9 @@ namespace osu.Game.Overlays
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))
channelTabControl.Current.Value = channel;
ChannelTabControl.Current.Value = channel;
}
protected override bool OnKeyDown(KeyDownEvent e)
@ -358,6 +355,7 @@ namespace osu.Game.Overlays
this.FadeIn(transition_length, Easing.OutQuint);
textbox.HoldFocus = true;
base.PopIn();
}
@ -366,7 +364,7 @@ namespace osu.Game.Overlays
this.MoveToY(Height, transition_length, Easing.InSine);
this.FadeOut(transition_length, Easing.InSine);
channelSelectionOverlay.Hide();
ChannelSelectionOverlay.Hide();
textbox.HoldFocus = false;
base.PopOut();
@ -375,20 +373,20 @@ namespace osu.Game.Overlays
private void onChannelAddedToJoinedChannels(IEnumerable<Channel> channels)
{
foreach (Channel channel in channels)
channelTabControl.AddChannel(channel);
ChannelTabControl.AddChannel(channel);
}
private void onChannelRemovedFromJoinedChannels(IEnumerable<Channel> channels)
{
foreach (Channel channel in channels)
{
channelTabControl.RemoveChannel(channel);
ChannelTabControl.RemoveChannel(channel);
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel));
}
}
private void availableChannelsChanged(IEnumerable<Channel> channels)
=> channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
=> ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels);
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.Dropdown.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch);
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdate();
currentQuery.BindTo(Filter.Search.Current);
currentQuery.ValueChanged += query =>
{
queryChangedDebounce?.Cancel();
if (string.IsNullOrEmpty(query.NewValue))
Scheduler.AddOnce(updateSearch);
queueUpdate();
else
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)
@ -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)
{
Users = newUsers;
@ -193,7 +194,7 @@ namespace osu.Game.Overlays
switch (state)
{
case APIState.Online:
Scheduler.AddOnce(updateSearch);
queueUpdate();
break;
default:

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
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>>();
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();
if (samples.Any())
if (samples.Length > 0)
{
if (HitObject.SampleControlPoint == null)
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>
public virtual double StartTime { get; set; }
private List<SampleInfo> samples;
private List<HitSampleInfo> samples;
/// <summary>
/// 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.
/// </para>
/// </summary>
public List<SampleInfo> Samples
public List<HitSampleInfo> Samples
{
get => samples ?? (samples = new List<SampleInfo>());
get => samples ?? (samples = new List<HitSampleInfo>());
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;
comboOffset += extraComboOffset;

View File

@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
}
// 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++)
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="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>
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>
/// 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>
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
if (!string.IsNullOrEmpty(bankInfo.Filename))
{
return new List<SampleInfo>
return new List<HitSampleInfo>
{
new FileSampleInfo
new FileHitSampleInfo
{
Filename = bankInfo.Filename,
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,
Name = SampleInfo.HIT_NORMAL,
Name = HitSampleInfo.HIT_NORMAL,
Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank
}
@ -340,10 +340,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (type.HasFlag(LegacySoundType.Finish))
{
soundTypes.Add(new LegacySampleInfo
soundTypes.Add(new LegacyHitSampleInfo
{
Bank = bankInfo.Add,
Name = SampleInfo.HIT_FINISH,
Name = HitSampleInfo.HIT_FINISH,
Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank
});
@ -351,10 +351,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (type.HasFlag(LegacySoundType.Whistle))
{
soundTypes.Add(new LegacySampleInfo
soundTypes.Add(new LegacyHitSampleInfo
{
Bank = bankInfo.Add,
Name = SampleInfo.HIT_WHISTLE,
Name = HitSampleInfo.HIT_WHISTLE,
Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank
});
@ -362,10 +362,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (type.HasFlag(LegacySoundType.Clap))
{
soundTypes.Add(new LegacySampleInfo
soundTypes.Add(new LegacyHitSampleInfo
{
Bank = bankInfo.Add,
Name = SampleInfo.HIT_CLAP,
Name = HitSampleInfo.HIT_CLAP,
Volume = bankInfo.Volume,
CustomSampleBank = bankInfo.CustomSampleBank
});
@ -387,7 +387,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
}
private class LegacySampleInfo : SampleInfo
private class LegacyHitSampleInfo : HitSampleInfo
{
public int CustomSampleBank
{
@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
}
}
private class FileSampleInfo : SampleInfo
private class FileHitSampleInfo : HitSampleInfo
{
public string Filename;

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
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 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
{

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;
comboOffset += extraComboOffset;

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
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
{

View File

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

View File

@ -18,7 +18,7 @@ namespace osu.Game.Screens.Backgrounds
private Background background;
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}";

View File

@ -24,11 +24,13 @@ using osu.Game.Screens.Edit.Design;
using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Users;
namespace osu.Game.Screens.Edit
{
public class Editor : OsuScreen
public class Editor : OsuScreen, IKeyBindingHandler<GlobalAction>
{
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
@ -206,6 +208,20 @@ namespace osu.Game.Screens.Edit
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)
{
Beatmap.Value.Track?.Stop();

View File

@ -86,6 +86,7 @@ namespace osu.Game.Screens.Menu
if (!resuming)
{
Beatmap.Value = introBeatmap;
introBeatmap = null;
if (menuVoice.Value)
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.
if (menuMusic.Value)
{
track.Start();
track = null;
}
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 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++)
{
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])
frequencyAmplitudes[i] = targetAmplitude;
}
@ -115,7 +115,6 @@ namespace osu.Game.Screens.Menu
}
indexOffset = (indexOffset + index_change) % bars_per_visualiser;
Scheduler.AddDelayed(updateAmplitudes, time_between_updates);
}
private void updateColour()
@ -131,7 +130,8 @@ namespace osu.Game.Screens.Menu
protected override void LoadComplete()
{
base.LoadComplete();
updateAmplitudes();
Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true);
}
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)
{
RelativeSizeAxes = Axes.Both,
Child = DrawableRuleset
Children = new Drawable[]
{
DrawableRuleset,
new ComboEffects(ScoreProcessor)
}
}
},
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -13,14 +14,19 @@ namespace osu.Game.Skinning
{
public class SkinnableSound : SkinReloadableDrawable
{
private readonly SampleInfo[] samples;
private readonly ISampleInfo[] hitSamples;
private SampleChannel[] channels;
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]
@ -35,7 +41,7 @@ namespace osu.Game.Skinning
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
channels = samples.Select(s =>
channels = hitSamples.Select(s =>
{
var ch = loadChannel(s, skin.GetSample);
if (ch == null && allowFallback)
@ -44,7 +50,7 @@ namespace osu.Game.Skinning
}).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)
{

View File

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

View File

@ -14,8 +14,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.609.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.628.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.703.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" />
<PackageReference Include="NUnit" Version="3.12.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.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.609.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.628.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.628.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.703.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.703.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />