1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-29 10:12:56 +08:00

Merge branch 'master' into custom-ipc-location

This commit is contained in:
Shivam 2020-05-20 22:31:42 +02:00 committed by GitHub
commit 451275496f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 633 additions and 262 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.511.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.518.0" />
</ItemGroup>
</Project>

View File

@ -4,12 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
namespace osu.Game.Rulesets.Catch.Difficulty
{
@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
mods = Score.Mods;
fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
ticksHit = Score?.GetCount100() ?? 0;
tinyTicksHit = Score?.GetCount50() ?? 0;
tinyTicksMissed = Score?.GetCountKatu() ?? 0;
misses = Score.Statistics[HitResult.Miss];
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Longer maps are worth more
double lengthBonus =
0.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) +
(numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f);
0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
// Longer maps are worth more
value *= lengthBonus;
@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (Attributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
float approachRate = (float)Attributes.ApproachRate;
float approachRateFactor = 1.0f;
if (approachRate > 9.0f)
approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
if (approachRate > 10.0f)
approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total
else if (approachRate < 8.0f)
approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
double approachRate = Attributes.ApproachRate;
double approachRateFactor = 1.0;
if (approachRate > 9.0)
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
if (approachRate > 10.0)
approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total
else if (approachRate < 8.0)
approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8
value *= approachRateFactor;
@ -80,10 +80,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
// Hiddens gives almost nothing on max approach rate, and more the lower it is
if (approachRate <= 10.0f)
value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10
else if (approachRate > 10.0f)
value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11
if (approachRate <= 10.0)
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
else if (approachRate > 10.0)
value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
}
if (mods.Any(m => m is ModFlashlight))
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return value;
}
private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
private int totalComboHits() => misses + ticksHit + fruitsHit;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
countPerfect = Score.Statistics[HitResult.Perfect];
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countOk = Score.Statistics[HitResult.Ok];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect);
countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
countGood = Score.Statistics.GetOrDefault(HitResult.Good);
countOk = Score.Statistics.GetOrDefault(HitResult.Ok);
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
if (mods.Any(m => !m.Ranked))
return 0;

View File

@ -7,5 +7,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
public class ManiaHitWindows : HitWindows
{
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
case HitResult.Great:
case HitResult.Good:
case HitResult.Ok:
case HitResult.Meh:
case HitResult.Miss:
return true;
}
return false;
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@ -45,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
countGood = Score.Statistics.GetOrDefault(HitResult.Good);
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@ -180,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
else
betterAccuracyPercentage = 0;
@ -203,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return accuracyValue;
}
private double totalHits => countGreat + countGood + countMeh + countMiss;
private double totalSuccessfulHits => countGreat + countGood + countMeh;
private int totalHits => countGreat + countGood + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countGood + countMeh;
}
}

View File

@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[NonParallelizable]
[TestCase("basic")]
[TestCase("slider-generating-drumroll")]
[TestCase("sample-to-type-conversions")]
public void Test(string name) => base.Test(name);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
public struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
/// A sane value to account for osu!stable using ints everywhere.
/// </summary>
private const float conversion_lenience = 2;

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
/// <summary>
/// Taiko has some interesting rules for legacy mappings.
/// </summary>
[HeadlessTest]
public class TestSceneSampleOutput : PlayerTestScene
{
public TestSceneSampleOutput()
: base(new TaikoRuleset())
{
}
public override void SetUpSteps()
{
base.SetUpSteps();
AddAssert("has correct samples", () =>
{
var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType<DrawableHit>().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name)));
var expected = new[]
{
string.Empty,
string.Empty,
string.Empty,
string.Empty,
HitSampleInfo.HIT_FINISH,
HitSampleInfo.HIT_WHISTLE,
HitSampleInfo.HIT_WHISTLE,
HitSampleInfo.HIT_WHISTLE,
};
return names.SequenceEqual(expected);
});
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions");
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
countGreat = Score.Statistics[HitResult.Great];
countGood = Score.Statistics[HitResult.Good];
countMeh = Score.Statistics[HitResult.Meh];
countMiss = Score.Statistics[HitResult.Miss];
countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
countGood = Score.Statistics.GetOrDefault(HitResult.Good);
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))

View File

@ -49,10 +49,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit)
: new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
protected override IEnumerable<HitSampleInfo> GetSamples()
public override IEnumerable<HitSampleInfo> GetSamples()
{
// normal and claps are always handled by the drum (see DrumSampleMapping).
var samples = HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
// in addition, whistles are excluded as they are an alternative rim marker.
var samples = HitObject.Samples.Where(s =>
s.Name != HitSampleInfo.HIT_NORMAL
&& s.Name != HitSampleInfo.HIT_CLAP
&& s.Name != HitSampleInfo.HIT_WHISTLE);
if (HitObject.Type == HitType.Rim && HitObject.IsStrong)
{

View File

@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
// Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
protected override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
public override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
protected abstract SkinnableDrawable CreateMainPiece();

View File

@ -0,0 +1,116 @@
{
"Mappings": [
{
"StartTime": 110.0,
"Objects": [
{
"StartTime": 110.0,
"EndTime": 110.0,
"IsRim": false,
"IsCentre": true,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": false
}
]
},
{
"StartTime": 538.0,
"Objects": [
{
"StartTime": 538.0,
"EndTime": 538.0,
"IsRim": true,
"IsCentre": false,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": false
}
]
},
{
"StartTime": 967.0,
"Objects": [
{
"StartTime": 967.0,
"EndTime": 967.0,
"IsRim": true,
"IsCentre": false,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": false
}
]
},
{
"StartTime": 1395.0,
"Objects": [
{
"StartTime": 1395.0,
"EndTime": 1395.0,
"IsRim": true,
"IsCentre": false,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": false
}
]
},
{
"StartTime": 1824.0,
"Objects": [
{
"StartTime": 1824.0,
"EndTime": 1824.0,
"IsRim": false,
"IsCentre": true,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": true
}
]
},
{
"StartTime": 2252.0,
"Objects": [
{
"StartTime": 2252.0,
"EndTime": 2252.0,
"IsRim": true,
"IsCentre": false,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": true
}
]
},
{
"StartTime": 2681.0,
"Objects": [
{
"StartTime": 2681.0,
"EndTime": 2681.0,
"IsRim": true,
"IsCentre": false,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": true
}
]
},
{
"StartTime": 3110.0,
"Objects": [
{
"StartTime": 3110.0,
"EndTime": 3110.0,
"IsRim": true,
"IsCentre": false,
"IsDrumRoll": false,
"IsSwell": false,
"IsStrong": true
}
]
}
]
}

View File

@ -0,0 +1,62 @@
osu file format v14
[General]
AudioFilename: audio.mp3
AudioLeadIn: 0
PreviewTime: -1
Countdown: 0
SampleSet: Normal
StackLeniency: 0.5
Mode: 1
LetterboxInBreaks: 0
WidescreenStoryboard: 1
[Editor]
Bookmarks: 110,13824,54967,82395,109824
DistanceSpacing: 0.1
BeatDivisor: 4
GridSize: 32
TimelineZoom: 3.099999
[Metadata]
Title:test
TitleUnicode:test
Artist:sample conversion
ArtistUnicode:sample conversion
Creator:banchobot
Version:sample test
Source:
Tags:
BeatmapID:0
BeatmapSetID:-1
[Difficulty]
HPDrainRate:6
CircleSize:2
OverallDifficulty:6
ApproachRate:7
SliderMultiplier:1.4
SliderTickRate:4
[Events]
//Background and Video events
//Break Periods
//Storyboard Layer 0 (Background)
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Layer 4 (Overlay)
//Storyboard Sound Samples
[TimingPoints]
110,428.571428571429,4,1,0,100,1,0
[HitObjects]
256,192,110,5,0,0:0:0:0:
256,192,538,1,8,0:0:0:0:
256,192,967,1,2,0:0:0:0:
256,192,1395,1,10,0:0:0:0:
256,192,1824,1,4,0:0:0:0:
256,192,2252,1,12,0:0:0:0:
256,192,2681,1,6,0:0:0:0:
256,192,3110,1,14,0:0:0:0:

View File

@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var storyboard = decoder.Decode(stream);
Assert.IsTrue(storyboard.HasDrawable);
Assert.AreEqual(5, storyboard.Layers.Count());
Assert.AreEqual(6, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
Assert.IsNotNull(background);
@ -56,6 +56,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(foreground.VisibleWhenPassing);
Assert.AreEqual("Foreground", foreground.Name);
StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue);
Assert.IsNotNull(overlay);
Assert.IsEmpty(overlay.Elements);
Assert.IsTrue(overlay.VisibleWhenFailing);
Assert.IsTrue(overlay.VisibleWhenPassing);
Assert.AreEqual("Overlay", overlay.Name);
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));

View File

@ -156,8 +156,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>();
// ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
manager.ItemAdded.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
var imported = await LoadOszIntoOsu(osu);

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneSkipOverlay : OsuManualInputManagerTestScene
{
private SkipOverlay skip;
private TestSkipOverlay skip;
private int requestCount;
private double increment;
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
skip = new SkipOverlay(skip_time)
skip = new TestSkipOverlay(skip_time)
{
RequestSkip = () =>
{
@ -56,19 +56,19 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestFadeOnIdle()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
}
[Test]
public void TestClickableAfterFade()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0);
AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
@ -105,13 +105,25 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("wait for overlay disappear", () => !skip.IsPresent);
AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent);
AddAssert("ensure button didn't disappear", () => skip.FadingContent.Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);
}
private void checkRequestCount(int expected) =>
AddAssert($"request count is {expected}", () => requestCount == expected);
private class TestSkipOverlay : SkipOverlay
{
public TestSkipOverlay(double startTime)
: base(startTime)
{
}
public Drawable OverlayContent => InternalChild;
public Drawable FadingContent => (OverlayContent as Container)?.Child;
}
}
}

View File

@ -69,6 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
roomManager.CreateRequested = r =>
{
@ -89,6 +90,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("setup", () =>
{
Room.Name.Value = "Test Room";
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
fail = true;
roomManager.CreateRequested = _ => !fail;
});

View File

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Lists;
@ -38,12 +39,16 @@ namespace osu.Game.Beatmaps
/// <summary>
/// Fired when a single difficulty has been hidden.
/// </summary>
public event Action<BeatmapInfo> BeatmapHidden;
public IBindable<WeakReference<BeatmapInfo>> BeatmapHidden => beatmapHidden;
private readonly Bindable<WeakReference<BeatmapInfo>> beatmapHidden = new Bindable<WeakReference<BeatmapInfo>>();
/// <summary>
/// Fired when a single difficulty has been restored.
/// </summary>
public event Action<BeatmapInfo> BeatmapRestored;
public IBindable<WeakReference<BeatmapInfo>> BeatmapRestored => beatmapRestored;
private readonly Bindable<WeakReference<BeatmapInfo>> beatmapRestored = new Bindable<WeakReference<BeatmapInfo>>();
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
@ -74,8 +79,8 @@ namespace osu.Game.Beatmaps
DefaultBeatmap = defaultBeatmap;
beatmaps = (BeatmapStore)ModelStore;
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference<BeatmapInfo>(b);
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
exportStorage = storage.GetStorageForDirectory("exports");

View File

@ -9,6 +9,7 @@ namespace osu.Game.Beatmaps.Legacy
Fail = 1,
Pass = 2,
Foreground = 3,
Video = 4
Overlay = 4,
Video = 5
}
}

View File

@ -11,6 +11,7 @@ using Humanizer;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using osu.Framework;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging;
@ -56,13 +57,17 @@ namespace osu.Game.Database
/// Fired when a new <typeparamref name="TModel"/> becomes available in the database.
/// This is not guaranteed to run on the update thread.
/// </summary>
public event Action<TModel> ItemAdded;
public IBindable<WeakReference<TModel>> ItemAdded => itemAdded;
private readonly Bindable<WeakReference<TModel>> itemAdded = new Bindable<WeakReference<TModel>>();
/// <summary>
/// Fired when a <typeparamref name="TModel"/> is removed from the database.
/// This is not guaranteed to run on the update thread.
/// </summary>
public event Action<TModel> ItemRemoved;
public IBindable<WeakReference<TModel>> ItemRemoved => itemRemoved;
private readonly Bindable<WeakReference<TModel>> itemRemoved = new Bindable<WeakReference<TModel>>();
public virtual string[] HandledExtensions => new[] { ".zip" };
@ -82,8 +87,8 @@ namespace osu.Game.Database
ContextFactory = contextFactory;
ModelStore = modelStore;
ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item));
ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference<TModel>(item));
ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference<TModel>(item));
Files = new FileStore(contextFactory, storage);

View File

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Bindables;
namespace osu.Game.Database
{
@ -23,9 +24,13 @@ namespace osu.Game.Database
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete, IEquatable<TModel>
where TFileModel : class, INamedFileInfo, new()
{
public event Action<ArchiveDownloadRequest<TModel>> DownloadBegan;
public IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadBegan => downloadBegan;
public event Action<ArchiveDownloadRequest<TModel>> DownloadFailed;
private readonly Bindable<WeakReference<ArchiveDownloadRequest<TModel>>> downloadBegan = new Bindable<WeakReference<ArchiveDownloadRequest<TModel>>>();
public IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadFailed => downloadFailed;
private readonly Bindable<WeakReference<ArchiveDownloadRequest<TModel>>> downloadFailed = new Bindable<WeakReference<ArchiveDownloadRequest<TModel>>>();
private readonly IAPIProvider api;
@ -81,7 +86,7 @@ namespace osu.Game.Database
// for now a failed import will be marked as a failed download for simplicity.
if (!imported.Any())
DownloadFailed?.Invoke(request);
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning);
@ -100,14 +105,14 @@ namespace osu.Game.Database
api.PerformAsync(request);
DownloadBegan?.Invoke(request);
downloadBegan.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
return true;
void triggerFailure(Exception error)
{
currentDownloads.Remove(request);
DownloadFailed?.Invoke(request);
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
notification.State = ProgressNotificationState.Cancelled;

View File

@ -1,8 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Online.API;
using System;
using osu.Game.Online.API;
using osu.Framework.Bindables;
namespace osu.Game.Database
{
@ -17,13 +18,13 @@ namespace osu.Game.Database
/// Fired when a <typeparamref name="TModel"/> download begins.
/// This is NOT run on the update thread and should be scheduled.
/// </summary>
event Action<ArchiveDownloadRequest<TModel>> DownloadBegan;
IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadBegan { get; }
/// <summary>
/// Fired when a <typeparamref name="TModel"/> download is interrupted, either due to user cancellation or failure.
/// This is NOT run on the update thread and should be scheduled.
/// </summary>
event Action<ArchiveDownloadRequest<TModel>> DownloadFailed;
IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadFailed { get; }
/// <summary>
/// Checks whether a given <typeparamref name="TModel"/> is already available in the local store.

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
namespace osu.Game.Database
{
@ -9,11 +10,11 @@ namespace osu.Game.Database
/// Represents a model manager that publishes events when <typeparamref name="TModel"/>s are added or removed.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
public interface IModelManager<out TModel>
public interface IModelManager<TModel>
where TModel : class
{
event Action<TModel> ItemAdded;
IBindable<WeakReference<TModel>> ItemAdded { get; }
event Action<TModel> ItemRemoved;
IBindable<WeakReference<TModel>> ItemRemoved { get; }
}
}

View File

@ -189,23 +189,18 @@ namespace osu.Game.Graphics.Containers
headerBackgroundContainer.Height = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0);
headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0;
T bestMatch = null;
float minDiff = float.MaxValue;
float scrollOffset = FixedHeader?.LayoutSize.Y ?? 0;
Func<T, float> diff = section => scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset;
foreach (var section in Children)
if (scrollContainer.IsScrolledToEnd())
{
float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset);
if (diff < minDiff)
{
minDiff = diff;
bestMatch = section;
}
SelectedSection.Value = Children.LastOrDefault();
}
else
{
SelectedSection.Value = Children.TakeWhile(section => diff(section) <= 0).LastOrDefault()
?? Children.FirstOrDefault();
}
if (bestMatch != null)
SelectedSection.Value = bestMatch;
}
}

View File

@ -34,6 +34,11 @@ namespace osu.Game.Online
Model.Value = model;
}
private IBindable<WeakReference<TModel>> managerAdded;
private IBindable<WeakReference<TModel>> managerRemoved;
private IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> managerDownloadBegan;
private IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> managerDownloadFailed;
[BackgroundDependencyLoader(true)]
private void load()
{
@ -47,23 +52,39 @@ namespace osu.Game.Online
attachDownload(manager.GetExistingDownload(modelInfo.NewValue));
}, true);
manager.DownloadBegan += downloadBegan;
manager.DownloadFailed += downloadFailed;
manager.ItemAdded += itemAdded;
manager.ItemRemoved += itemRemoved;
managerDownloadBegan = manager.DownloadBegan.GetBoundCopy();
managerDownloadBegan.BindValueChanged(downloadBegan);
managerDownloadFailed = manager.DownloadFailed.GetBoundCopy();
managerDownloadFailed.BindValueChanged(downloadFailed);
managerAdded = manager.ItemAdded.GetBoundCopy();
managerAdded.BindValueChanged(itemAdded);
managerRemoved = manager.ItemRemoved.GetBoundCopy();
managerRemoved.BindValueChanged(itemRemoved);
}
private void downloadBegan(ArchiveDownloadRequest<TModel> request) => Schedule(() =>
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<TModel>>> weakRequest)
{
if (request.Model.Equals(Model.Value))
attachDownload(request);
});
if (weakRequest.NewValue.TryGetTarget(out var request))
{
Schedule(() =>
{
if (request.Model.Equals(Model.Value))
attachDownload(request);
});
}
}
private void downloadFailed(ArchiveDownloadRequest<TModel> request) => Schedule(() =>
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<TModel>>> weakRequest)
{
if (request.Model.Equals(Model.Value))
attachDownload(null);
});
if (weakRequest.NewValue.TryGetTarget(out var request))
{
Schedule(() =>
{
if (request.Model.Equals(Model.Value))
attachDownload(null);
});
}
}
private ArchiveDownloadRequest<TModel> attachedRequest;
@ -107,9 +128,17 @@ namespace osu.Game.Online
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
private void itemAdded(TModel s) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
private void itemAdded(ValueChangedEvent<WeakReference<TModel>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var item))
setDownloadStateFromManager(item, DownloadState.LocallyAvailable);
}
private void itemRemoved(TModel s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);
private void itemRemoved(ValueChangedEvent<WeakReference<TModel>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var item))
setDownloadStateFromManager(item, DownloadState.NotDownloaded);
}
private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() =>
{
@ -125,14 +154,6 @@ namespace osu.Game.Online
{
base.Dispose(isDisposing);
if (manager != null)
{
manager.DownloadBegan -= downloadBegan;
manager.DownloadFailed -= downloadFailed;
manager.ItemAdded -= itemAdded;
manager.ItemRemoved -= itemRemoved;
}
State.UnbindAll();
attachDownload(null);

View File

@ -186,8 +186,17 @@ namespace osu.Game
return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList();
}
BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true);
BeatmapManager.ItemAdded += i => ScoreManager.Undelete(getBeatmapScores(i), true);
BeatmapManager.ItemRemoved.BindValueChanged(i =>
{
if (i.NewValue.TryGetTarget(out var item))
ScoreManager.Delete(getBeatmapScores(item), true);
});
BeatmapManager.ItemAdded.BindValueChanged(i =>
{
if (i.NewValue.TryGetTarget(out var item))
ScoreManager.Undelete(getBeatmapScores(item), true);
});
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));

View File

@ -60,11 +60,16 @@ namespace osu.Game.Overlays
[Resolved(canBeNull: true)]
private OnScreenDisplay onScreenDisplay { get; set; }
private IBindable<WeakReference<BeatmapSetInfo>> managerAdded;
private IBindable<WeakReference<BeatmapSetInfo>> managerRemoved;
[BackgroundDependencyLoader]
private void load()
{
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
managerAdded = beatmaps.ItemAdded.GetBoundCopy();
managerAdded.BindValueChanged(beatmapAdded);
managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
managerRemoved.BindValueChanged(beatmapRemoved);
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal).OrderBy(_ => RNG.Next()));
}
@ -93,16 +98,28 @@ namespace osu.Game.Overlays
/// </summary>
public bool IsPlaying => current?.Track.IsRunning ?? false;
private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() =>
private void beatmapAdded(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
if (!beatmapSets.Contains(set))
beatmapSets.Add(set);
});
if (weakSet.NewValue.TryGetTarget(out var set))
{
Schedule(() =>
{
if (!beatmapSets.Contains(set))
beatmapSets.Add(set);
});
}
}
private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() =>
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
beatmapSets.RemoveAll(s => s.ID == set.ID);
});
if (weakSet.NewValue.TryGetTarget(out var set))
{
Schedule(() =>
{
beatmapSets.RemoveAll(s => s.ID == set.ID);
});
}
}
private ScheduledDelegate seekDelegate;
@ -299,17 +316,6 @@ namespace osu.Game.Overlays
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmaps != null)
{
beatmaps.ItemAdded -= handleBeatmapAdded;
beatmaps.ItemRemoved -= handleBeatmapRemoved;
}
}
public bool OnPressed(GlobalAction action)
{
if (beatmap.Disabled)

View File

@ -1,6 +1,7 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -30,6 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections
[Resolved]
private SkinManager skins { get; set; }
private IBindable<WeakReference<SkinInfo>> managerAdded;
private IBindable<WeakReference<SkinInfo>> managerRemoved;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
@ -66,8 +70,11 @@ namespace osu.Game.Overlays.Settings.Sections
},
};
skins.ItemAdded += itemAdded;
skins.ItemRemoved += itemRemoved;
managerAdded = skins.ItemAdded.GetBoundCopy();
managerAdded.BindValueChanged(itemAdded);
managerRemoved = skins.ItemRemoved.GetBoundCopy();
managerRemoved.BindValueChanged(itemRemoved);
config.BindWith(OsuSetting.Skin, configBindable);
@ -82,19 +89,16 @@ namespace osu.Game.Overlays.Settings.Sections
dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID);
}
private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray());
private void itemAdded(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray());
protected override void Dispose(bool isDisposing)
private void itemAdded(ValueChangedEvent<WeakReference<SkinInfo>> weakItem)
{
base.Dispose(isDisposing);
if (weakItem.NewValue.TryGetTarget(out var item))
Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(item).ToArray());
}
if (skins != null)
{
skins.ItemAdded -= itemAdded;
skins.ItemRemoved -= itemRemoved;
}
private void itemRemoved(ValueChangedEvent<WeakReference<SkinInfo>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var item))
Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray());
}
private class SizeSlider : OsuSliderBar<float>

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected SkinnableSound Samples { get; private set; }
protected virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
public virtual IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples;
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList<DrawableHitObject>)Array.Empty<DrawableHitObject>();

View File

@ -133,7 +133,6 @@ namespace osu.Game.Screens.Multi.Match.Components
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
OnCommit = (sender, text) => apply(),
},
},
new Section("Duration")
@ -196,7 +195,6 @@ namespace osu.Game.Screens.Multi.Match.Components
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
OnCommit = (sender, text) => apply()
},
},
new Section("Password (optional)")
@ -207,7 +205,6 @@ namespace osu.Game.Screens.Multi.Match.Components
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
OnCommit = (sender, text) => apply()
},
},
},
@ -331,6 +328,9 @@ namespace osu.Game.Screens.Multi.Match.Components
private void apply()
{
if (!ApplyButton.Enabled.Value)
return;
hideError();
RoomName.Value = NameField.Text;

View File

@ -32,11 +32,16 @@ namespace osu.Game.Screens.Multi.Match.Components
Text = "Start";
}
private IBindable<WeakReference<BeatmapSetInfo>> managerAdded;
private IBindable<WeakReference<BeatmapSetInfo>> managerRemoved;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
beatmaps.ItemAdded += beatmapAdded;
beatmaps.ItemRemoved += beatmapRemoved;
managerAdded = beatmaps.ItemAdded.GetBoundCopy();
managerAdded.BindValueChanged(beatmapAdded);
managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
managerRemoved.BindValueChanged(beatmapRemoved);
SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true);
@ -56,24 +61,30 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null;
}
private void beatmapAdded(BeatmapSetInfo model)
private void beatmapAdded(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
if (weakSet.NewValue.TryGetTarget(out var set))
{
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
Schedule(() => hasBeatmap = true);
if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
Schedule(() => hasBeatmap = true);
}
}
private void beatmapRemoved(BeatmapSetInfo model)
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
if (weakSet.NewValue.TryGetTarget(out var set))
{
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
Schedule(() => hasBeatmap = false);
if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
Schedule(() => hasBeatmap = false);
}
}
protected override void Update()
@ -95,16 +106,5 @@ namespace osu.Game.Screens.Multi.Match.Components
Enabled.Value = hasBeatmap && hasEnoughTime;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmaps != null)
{
beatmaps.ItemAdded -= beatmapAdded;
beatmaps.ItemRemoved -= beatmapRemoved;
}
}
}
}

View File

@ -50,6 +50,8 @@ namespace osu.Game.Screens.Multi.Match
private LeaderboardChatDisplay leaderboardChatDisplay;
private MatchSettingsOverlay settingsOverlay;
private IBindable<WeakReference<BeatmapSetInfo>> managerAdded;
public MatchSubScreen(Room room)
{
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
@ -181,7 +183,8 @@ namespace osu.Game.Screens.Multi.Match
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
SelectedItem.Value = playlist.FirstOrDefault();
beatmapManager.ItemAdded += beatmapAdded;
managerAdded = beatmapManager.ItemAdded.GetBoundCopy();
managerAdded.BindValueChanged(beatmapAdded);
}
public override bool OnExiting(IScreen next)
@ -214,13 +217,16 @@ namespace osu.Game.Screens.Multi.Match
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}
private void beatmapAdded(BeatmapSetInfo model) => Schedule(() =>
private void beatmapAdded(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;
Schedule(() =>
{
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;
updateWorkingBeatmap();
});
updateWorkingBeatmap();
});
}
private void onStart()
{
@ -235,13 +241,5 @@ namespace osu.Game.Screens.Multi.Match
break;
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmapManager != null)
beatmapManager.ItemAdded -= beatmapAdded;
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
@ -13,6 +14,8 @@ namespace osu.Game.Screens.Play
/// </summary>
public class DimmableStoryboard : UserDimContainer
{
public Container OverlayLayerContainer { get; private set; }
private readonly Storyboard storyboard;
private DrawableStoryboard drawableStoryboard;
@ -24,6 +27,8 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load()
{
Add(OverlayLayerContainer = new Container());
initializeStoryboard(false);
}
@ -46,9 +51,15 @@ namespace osu.Game.Screens.Play
drawableStoryboard = storyboard.CreateDrawable();
if (async)
LoadComponentAsync(drawableStoryboard, Add);
LoadComponentAsync(drawableStoryboard, onStoryboardCreated);
else
Add(drawableStoryboard);
onStoryboardCreated(drawableStoryboard);
}
private void onStoryboardCreated(DrawableStoryboard storyboard)
{
Add(storyboard);
OverlayLayerContainer.Add(storyboard.OverlayLayer.CreateProxy());
}
}
}

View File

@ -83,6 +83,8 @@ namespace osu.Game.Screens.Play
private BreakTracker breakTracker;
private SkipOverlay skipOverlay;
protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; }
@ -189,6 +191,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.ShowHud.Value = false;
HUDOverlay.ShowHud.Disabled = true;
BreakOverlay.Hide();
skipOverlay.Hide();
}
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@ -264,6 +267,7 @@ namespace osu.Game.Screens.Play
{
target.AddRange(new[]
{
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Clock = DrawableRuleset.FrameStableClock,
@ -290,7 +294,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new SkipOverlay(DrawableRuleset.GameplayStartTime)
skipOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
{
RequestSkip = GameplayClockContainer.Skip
},

View File

@ -24,13 +24,14 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Screens.Play
{
public class SkipOverlay : VisibilityContainer, IKeyBindingHandler<GlobalAction>
public class SkipOverlay : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{
private readonly double startTime;
public Action RequestSkip;
private Button button;
private ButtonContainer buttonContainer;
private Box remainingTimeBox;
private FadeContainer fadeContainer;
@ -61,9 +62,10 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours)
{
Children = new Drawable[]
InternalChild = buttonContainer = new ButtonContainer
{
fadeContainer = new FadeContainer
RelativeSizeAxes = Axes.Both,
Child = fadeContainer = new FadeContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@ -104,14 +106,8 @@ namespace osu.Game.Screens.Play
button.Action = () => RequestSkip?.Invoke();
displayTime = gameplayClock.CurrentTime;
Show();
}
protected override void PopIn() => this.FadeIn(fade_time);
protected override void PopOut() => this.FadeOut(fade_time);
protected override void Update()
{
base.Update();
@ -121,13 +117,14 @@ namespace osu.Game.Screens.Play
remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
button.Enabled.Value = progress > 0;
State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden;
buttonContainer.State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden;
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!e.HasAnyButtonPressed)
fadeContainer.Show();
return base.OnMouseMove(e);
}
@ -214,6 +211,13 @@ namespace osu.Game.Screens.Play
public override void Show() => State = Visibility.Visible;
}
private class ButtonContainer : VisibilityContainer
{
protected override void PopIn() => this.FadeIn(fade_time);
protected override void PopOut() => this.FadeOut(fade_time);
}
private class Button : OsuClickableContainer
{
private Color4 colourNormal;

View File

@ -131,6 +131,11 @@ namespace osu.Game.Screens.Select
private CarouselRoot root;
private IBindable<WeakReference<BeatmapSetInfo>> itemAdded;
private IBindable<WeakReference<BeatmapSetInfo>> itemRemoved;
private IBindable<WeakReference<BeatmapInfo>> itemHidden;
private IBindable<WeakReference<BeatmapInfo>> itemRestored;
public BeatmapCarousel()
{
root = new CarouselRoot(this);
@ -161,10 +166,14 @@ namespace osu.Game.Screens.Select
RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue;
RightClickScrollingEnabled.TriggerChange();
beatmaps.ItemAdded += beatmapAdded;
beatmaps.ItemRemoved += beatmapRemoved;
beatmaps.BeatmapHidden += beatmapHidden;
beatmaps.BeatmapRestored += beatmapRestored;
itemAdded = beatmaps.ItemAdded.GetBoundCopy();
itemAdded.BindValueChanged(beatmapAdded);
itemRemoved = beatmaps.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(beatmapRemoved);
itemHidden = beatmaps.BeatmapHidden.GetBoundCopy();
itemHidden.BindValueChanged(beatmapHidden);
itemRestored = beatmaps.BeatmapRestored.GetBoundCopy();
itemRestored.BindValueChanged(beatmapRestored);
loadBeatmapSets(GetLoadableBeatmaps());
}
@ -562,26 +571,34 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
if (beatmaps != null)
{
beatmaps.ItemAdded -= beatmapAdded;
beatmaps.ItemRemoved -= beatmapRemoved;
beatmaps.BeatmapHidden -= beatmapHidden;
beatmaps.BeatmapRestored -= beatmapRestored;
}
// aggressively dispose "off-screen" items to reduce GC pressure.
foreach (var i in Items)
i.Dispose();
}
private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item);
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var item))
RemoveBeatmapSet(item);
}
private void beatmapAdded(BeatmapSetInfo item) => UpdateBeatmapSet(item);
private void beatmapAdded(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var item))
UpdateBeatmapSet(item);
}
private void beatmapRestored(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void beatmapRestored(ValueChangedEvent<WeakReference<BeatmapInfo>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var b))
UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
}
private void beatmapHidden(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void beatmapHidden(ValueChangedEvent<WeakReference<BeatmapInfo>> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var b))
UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
}
private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet)
{

View File

@ -1,6 +1,7 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -27,6 +28,9 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved]
private IAPIProvider api { get; set; }
private IBindable<WeakReference<ScoreInfo>> itemAdded;
private IBindable<WeakReference<ScoreInfo>> itemRemoved;
public TopLocalRank(BeatmapInfo beatmap)
: base(null)
{
@ -36,17 +40,24 @@ namespace osu.Game.Screens.Select.Carousel
[BackgroundDependencyLoader]
private void load()
{
scores.ItemAdded += scoreChanged;
scores.ItemRemoved += scoreChanged;
itemAdded = scores.ItemAdded.GetBoundCopy();
itemAdded.BindValueChanged(scoreChanged);
itemRemoved = scores.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(scoreChanged);
ruleset.ValueChanged += _ => fetchAndLoadTopScore();
fetchAndLoadTopScore();
}
private void scoreChanged(ScoreInfo score)
private void scoreChanged(ValueChangedEvent<WeakReference<ScoreInfo>> weakScore)
{
if (score.BeatmapInfoID == beatmap.ID)
fetchAndLoadTopScore();
if (weakScore.NewValue.TryGetTarget(out var score))
{
if (score.BeatmapInfoID == beatmap.ID)
fetchAndLoadTopScore();
}
}
private ScheduledDelegate scheduledRankUpdate;
@ -75,16 +86,5 @@ namespace osu.Game.Screens.Select.Carousel
.OrderByDescending(s => s.TotalScore)
.FirstOrDefault();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (scores != null)
{
scores.ItemAdded -= scoreChanged;
scores.ItemRemoved -= scoreChanged;
}
}
}
}

View File

@ -60,6 +60,8 @@ namespace osu.Game.Screens.Select.Leaderboards
private UserTopScoreContainer topScoreContainer;
private IBindable<WeakReference<ScoreInfo>> itemRemoved;
/// <summary>
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
/// </summary>
@ -104,7 +106,8 @@ namespace osu.Game.Screens.Select.Leaderboards
ScoreSelected = s => ScoreSelected?.Invoke(s)
});
scoreManager.ItemRemoved += onScoreRemoved;
itemRemoved = scoreManager.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(onScoreRemoved);
}
protected override void Reset()
@ -113,7 +116,7 @@ namespace osu.Game.Screens.Select.Leaderboards
TopScore = null;
}
private void onScoreRemoved(ScoreInfo score) => Schedule(RefreshScores);
private void onScoreRemoved(ValueChangedEvent<WeakReference<ScoreInfo>> score) => Schedule(RefreshScores);
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
@ -190,13 +193,5 @@ namespace osu.Game.Screens.Select.Leaderboards
{
Action = () => ScoreSelected?.Invoke(model)
};
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (scoreManager != null)
scoreManager.ItemRemoved -= onScoreRemoved;
}
}
}

View File

@ -43,12 +43,15 @@ namespace osu.Game.Skinning
this.audio = audio;
this.legacyDefaultResources = legacyDefaultResources;
ItemRemoved += removedInfo =>
ItemRemoved.BindValueChanged(weakRemovedInfo =>
{
// check the removed skin is not the current user choice. if it is, switch back to default.
if (removedInfo.ID == CurrentSkinInfo.Value.ID)
CurrentSkinInfo.Value = SkinInfo.Default;
};
if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo))
{
// check the removed skin is not the current user choice. if it is, switch back to default.
if (removedInfo.ID == CurrentSkinInfo.Value.ID)
CurrentSkinInfo.Value = SkinInfo.Default;
}
});
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
CurrentSkin.ValueChanged += skin =>

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using System.Threading;
using osuTK;
using osu.Framework.Allocation;
@ -72,6 +73,8 @@ namespace osu.Game.Storyboards.Drawables
}
}
public DrawableStoryboardLayer OverlayLayer => Children.Single(layer => layer.Name == "Overlay");
private void updateLayerVisibility()
{
foreach (var layer in Children)

View File

@ -19,19 +19,26 @@ namespace osu.Game.Storyboards
public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0);
/// <summary>
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
/// </summary>
private int minimumLayerDepth;
public Storyboard()
{
layers.Add("Video", new StoryboardLayer("Video", 4, false));
layers.Add("Background", new StoryboardLayer("Background", 3));
layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, });
layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, });
layers.Add("Foreground", new StoryboardLayer("Foreground", 0));
layers.Add("Foreground", new StoryboardLayer("Foreground", minimumLayerDepth = 0));
layers.Add("Overlay", new StoryboardLayer("Overlay", int.MinValue));
}
public StoryboardLayer GetLayer(string name)
{
if (!layers.TryGetValue(name, out var layer))
layers[name] = layer = new StoryboardLayer(name, layers.Values.Min(l => l.Depth) - 1);
layers[name] = layer = new StoryboardLayer(name, --minimumLayerDepth);
return layer;
}

View File

@ -33,6 +33,6 @@ namespace osu.Game.Storyboards
}
public DrawableStoryboardLayer CreateDrawable()
=> new DrawableStoryboardLayer(this) { Depth = Depth, };
=> new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name };
}
}

View File

@ -99,10 +99,7 @@ namespace osu.Game.Tests.Beatmaps
private ConvertResult convert(string name, Mod[] mods)
{
var beatmap = getBeatmap(name);
var rulesetInstance = CreateRuleset();
beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
var beatmap = GetBeatmap(name);
var converterResult = new Dictionary<HitObject, IEnumerable<HitObject>>();
@ -115,7 +112,7 @@ namespace osu.Game.Tests.Beatmaps
}
};
working.GetPlayableBeatmap(rulesetInstance.RulesetInfo, mods);
working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods);
return new ConvertResult
{
@ -143,14 +140,19 @@ namespace osu.Game.Tests.Beatmaps
}
}
private IBeatmap getBeatmap(string name)
public IBeatmap GetBeatmap(string name)
{
using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoder = Decoder.GetDecoder<Beatmap>(stream);
((LegacyBeatmapDecoder)decoder).ApplyOffsets = false;
return decoder.Decode(stream);
var beatmap = decoder.Decode(stream);
var rulesetInstance = CreateRuleset();
beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
return beatmap;
}
}

View File

@ -24,7 +24,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.511.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.518.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
<PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.25.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.511.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.518.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
@ -80,7 +80,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.511.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.518.0" />
<PackageReference Include="SharpCompress" Version="0.25.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />