mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 20:53:00 +08:00
Merge branch 'master' into results-screen-condensed-panel
This commit is contained in:
commit
d5ea076427
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.511.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.518.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
|
||||
|
||||
public bool AllowFail => false;
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
private OsuInputManager inputManager;
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
@ -12,11 +13,30 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
public class TestSceneTaikoScroller : TaikoSkinnableTestScene
|
||||
{
|
||||
private readonly ManualClock clock = new ManualClock();
|
||||
|
||||
private bool reversed;
|
||||
|
||||
public TestSceneTaikoScroller()
|
||||
{
|
||||
AddStep("Load scroller", () => SetContents(() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())));
|
||||
AddStep("Load scroller", () => SetContents(() =>
|
||||
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
Clock = new FramedClock(clock),
|
||||
Height = 0.4f,
|
||||
}));
|
||||
|
||||
AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value =
|
||||
new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss }));
|
||||
|
||||
AddToggleStep("toggle playback direction", reversed => this.reversed = reversed);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
clock.CurrentTime += (reversed ? -1 : 1) * Clock.ElapsedFrameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
49
osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
Normal file
49
osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
Normal 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");
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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:
|
@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
{
|
||||
public class LegacyTaikoScroller : CompositeDrawable
|
||||
{
|
||||
public Bindable<JudgementResult> LastResult = new Bindable<JudgementResult>();
|
||||
|
||||
public LegacyTaikoScroller()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -50,37 +52,38 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
}, true);
|
||||
}
|
||||
|
||||
public Bindable<JudgementResult> LastResult = new Bindable<JudgementResult>();
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
while (true)
|
||||
// store X before checking wide enough so if we perform layout there is no positional discrepancy.
|
||||
float currentX = (InternalChildren?.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f;
|
||||
|
||||
// ensure we have enough sprites
|
||||
if (!InternalChildren.Any()
|
||||
|| InternalChildren.First().ScreenSpaceDrawQuad.Width * InternalChildren.Count < ScreenSpaceDrawQuad.Width * 2)
|
||||
AddInternal(new ScrollerSprite { Passing = passing });
|
||||
|
||||
var first = InternalChildren.First();
|
||||
var last = InternalChildren.Last();
|
||||
|
||||
foreach (var sprite in InternalChildren)
|
||||
{
|
||||
float? additiveX = null;
|
||||
// add the x coordinates and perform re-layout on all sprites as spacing may change with gameplay scale.
|
||||
sprite.X = currentX;
|
||||
currentX += sprite.DrawWidth;
|
||||
}
|
||||
|
||||
foreach (var sprite in InternalChildren)
|
||||
{
|
||||
// add the x coordinates and perform re-layout on all sprites as spacing may change with gameplay scale.
|
||||
sprite.X = additiveX ??= sprite.X - (float)Time.Elapsed * 0.1f;
|
||||
if (first.ScreenSpaceDrawQuad.TopLeft.X >= ScreenSpaceDrawQuad.TopLeft.X)
|
||||
{
|
||||
foreach (var internalChild in InternalChildren)
|
||||
internalChild.X -= first.DrawWidth;
|
||||
}
|
||||
|
||||
additiveX += sprite.DrawWidth - 1;
|
||||
|
||||
if (sprite.X + sprite.DrawWidth < 0)
|
||||
sprite.Expire();
|
||||
}
|
||||
|
||||
var last = InternalChildren.LastOrDefault();
|
||||
|
||||
// only break from this loop once we have saturated horizontal space completely.
|
||||
if (last != null && last.ScreenSpaceDrawQuad.TopRight.X >= ScreenSpaceDrawQuad.TopRight.X)
|
||||
break;
|
||||
|
||||
AddInternal(new ScrollerSprite
|
||||
{
|
||||
Passing = passing
|
||||
});
|
||||
if (last.ScreenSpaceDrawQuad.TopRight.X <= ScreenSpaceDrawQuad.TopRight.X)
|
||||
{
|
||||
foreach (var internalChild in InternalChildren)
|
||||
internalChild.X += first.DrawWidth;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
|
||||
|
||||
AddInternal(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MaxValue
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Audio;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
@ -145,6 +146,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
centreHit.Colour = colours.Pink;
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private GameplayClock gameplayClock { get; set; }
|
||||
|
||||
public bool OnPressed(TaikoAction action)
|
||||
{
|
||||
Drawable target = null;
|
||||
@ -157,14 +161,16 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
target = centreHit;
|
||||
back = centre;
|
||||
|
||||
drumSample.Centre?.Play();
|
||||
if (gameplayClock?.IsSeeking != true)
|
||||
drumSample.Centre?.Play();
|
||||
}
|
||||
else if (action == RimAction)
|
||||
{
|
||||
target = rimHit;
|
||||
back = rim;
|
||||
|
||||
drumSample.Rim?.Play();
|
||||
if (gameplayClock?.IsSeeking != true)
|
||||
drumSample.Rim?.Play();
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
|
@ -13,18 +13,16 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
||||
private const float default_aspect = 16f / 9f;
|
||||
|
||||
public TaikoPlayfieldAdjustmentContainer()
|
||||
{
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.CentreLeft;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
float aspectAdjust = Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
|
||||
Size = new Vector2(1, default_relative_height * aspectAdjust);
|
||||
|
||||
// Position the taiko playfield exactly one playfield from the top of the screen.
|
||||
RelativePositionAxes = Axes.Y;
|
||||
Y = Size.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
||||
|
73
osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
Normal file
73
osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
Normal file
@ -0,0 +1,73 @@
|
||||
// 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;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public class BarLineGeneratorTest
|
||||
{
|
||||
[Test]
|
||||
public void TestRoundingErrorCompensation()
|
||||
{
|
||||
// The aim of this test is to make sure bar line generation compensates for floating-point errors.
|
||||
// The premise of the test is that we have a single timing point that should result in bar lines
|
||||
// that start at a time point that is a whole number every seventh beat.
|
||||
|
||||
// The fact it's every seventh beat is important - it's a number indivisible by 2, which makes
|
||||
// it susceptible to rounding inaccuracies. In fact this was originally spotted in cases of maps
|
||||
// that met exactly this criteria.
|
||||
|
||||
const int beat_length_numerator = 2000;
|
||||
const int beat_length_denominator = 7;
|
||||
const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
|
||||
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitObject { StartTime = 0 },
|
||||
new HitObject { StartTime = 120_000 }
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo()
|
||||
};
|
||||
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint
|
||||
{
|
||||
BeatLength = (double)beat_length_numerator / beat_length_denominator,
|
||||
TimeSignature = signature
|
||||
});
|
||||
|
||||
var barLines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
||||
|
||||
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
|
||||
{
|
||||
var barLine = barLines[i * beat_length_denominator];
|
||||
var expectedTime = beat_length_numerator * (int)signature * i;
|
||||
|
||||
// every seventh bar's start time should be at least greater than the whole number we expect.
|
||||
// It cannot be less, as that can affect overlapping scroll algorithms
|
||||
// (the previous timing point might be chosen incorrectly if this is not the case)
|
||||
Assert.GreaterOrEqual(barLine.StartTime, expectedTime);
|
||||
|
||||
// on the other side, make sure we don't stray too far from the expected time either.
|
||||
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
|
||||
|
||||
// check major/minor lines for good measure too
|
||||
Assert.AreEqual(i % (int)signature == 0, barLine.Major);
|
||||
}
|
||||
}
|
||||
|
||||
private class BarLine : IBarLine
|
||||
{
|
||||
public double StartTime { get; set; }
|
||||
public bool Major { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -211,7 +211,61 @@ namespace osu.Game.Tests.NonVisual
|
||||
var osu = loadOsu(host);
|
||||
|
||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||
Assert.Throws<InvalidOperationException>(() => osu.Migrate(customPath));
|
||||
Assert.Throws<ArgumentException>(() => osu.Migrate(customPath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationToNestedTargetFails()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToNestedTargetFails)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||
|
||||
string subFolder = Path.Combine(customPath, "sub");
|
||||
|
||||
if (Directory.Exists(subFolder))
|
||||
Directory.Delete(subFolder, true);
|
||||
|
||||
Directory.CreateDirectory(subFolder);
|
||||
|
||||
Assert.Throws<ArgumentException>(() => osu.Migrate(subFolder));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationToSeeminglyNestedTarget()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||
|
||||
string seeminglySubFolder = customPath + "sub";
|
||||
|
||||
if (Directory.Exists(seeminglySubFolder))
|
||||
Directory.Delete(seeminglySubFolder, true);
|
||||
|
||||
Directory.CreateDirectory(seeminglySubFolder);
|
||||
|
||||
osu.Migrate(seeminglySubFolder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -29,8 +29,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
|
||||
[Test]
|
||||
public void TestDisplayStartTime()
|
||||
{
|
||||
// Sequential scroll algorithm approximates the start time
|
||||
// This should be fixed in the future
|
||||
// easy cases - time range adjusted for velocity fits within control point duration
|
||||
Assert.AreEqual(2500, algorithm.GetDisplayStartTime(5000, 0, 2500, 1)); // 5000 - (2500 / 1)
|
||||
Assert.AreEqual(13750, algorithm.GetDisplayStartTime(15000, 0, 2500, 1)); // 15000 - (2500 / 2)
|
||||
Assert.AreEqual(20000, algorithm.GetDisplayStartTime(25000, 0, 2500, 1)); // 25000 - (2500 / 0.5)
|
||||
|
||||
// hard case - time range adjusted for velocity exceeds control point duration
|
||||
|
||||
// 1st multiplier point takes 10000 / 2500 = 4 scroll lengths
|
||||
// 2nd multiplier point takes 10000 / (2500 / 2) = 8 scroll lengths
|
||||
// 3rd multiplier point takes 2500 / (2500 * 2) = 0.5 scroll lengths up to hitobject start
|
||||
|
||||
// absolute position of the hitobject = 1000 * (4 + 8 + 0.5) = 12500
|
||||
// minus one scroll length allowance = 12500 - 1000 = 11500 = 11.5 [scroll lengths]
|
||||
// therefore the start time lies within the second multiplier point (because 11.5 < 4 + 8)
|
||||
// its exact time position is = 10000 + 7.5 * (2500 / 2) = 19375
|
||||
Assert.AreEqual(19375, algorithm.GetDisplayStartTime(22500, 0, 2500, 1000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||
public new bool AllowFail => base.AllowFail;
|
||||
|
||||
public bool AllowFail => base.CheckModsAllowFailure();
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Timing;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
@ -77,19 +78,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
};
|
||||
|
||||
setUpHitObjects();
|
||||
hitObjectSpawnDelegate?.Cancel();
|
||||
});
|
||||
|
||||
private void setUpHitObjects()
|
||||
private void setUpHitObjects() => AddStep("set up hit objects", () =>
|
||||
{
|
||||
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
|
||||
|
||||
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
|
||||
addHitObject(Time.Current + i);
|
||||
|
||||
hitObjectSpawnDelegate?.Cancel();
|
||||
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
|
||||
}
|
||||
});
|
||||
|
||||
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
||||
{
|
||||
@ -101,6 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestScrollAlgorithms()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
||||
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
||||
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||
@ -113,6 +115,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestConstantScrollLifetime()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
|
||||
// scroll container time range must be less than the rate of spawning hitobjects
|
||||
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
|
||||
@ -122,14 +126,40 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestSequentialScrollLifetime()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSlowSequentialScroll()
|
||||
{
|
||||
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
|
||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range));
|
||||
AddStep("add control points", () => addControlPoints(
|
||||
new List<MultiplierControlPoint>
|
||||
{
|
||||
new MultiplierControlPoint { Velocity = 0.1 }
|
||||
},
|
||||
Time.Current + time_range));
|
||||
|
||||
// All of the hit objects added below should be immediately visible on screen
|
||||
AddStep("add hit objects", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
addHitObject(Time.Current + time_range * (2 + 0.1 * i));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOverlappingScrollLifetime()
|
||||
{
|
||||
setUpHitObjects();
|
||||
|
||||
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
|
||||
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
|
||||
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
|
||||
@ -221,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
|
||||
{
|
||||
public TestDrawableControlPoint(ScrollingDirection direction, double time)
|
||||
: base(new HitObject { StartTime = time })
|
||||
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
@ -252,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
||||
{
|
||||
public TestDrawableHitObject(double time)
|
||||
: base(new HitObject { StartTime = time })
|
||||
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
|
||||
{
|
||||
Origin = Anchor.Custom;
|
||||
OriginPosition = new Vector2(75 / 4.0f);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
|
36
osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs
Normal file
36
osu.Game.Tests/Visual/Settings/TestSceneMigrationScreens.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// 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.IO;
|
||||
using System.Threading;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
public class TestSceneMigrationScreens : ScreenTestScene
|
||||
{
|
||||
public TestSceneMigrationScreens()
|
||||
{
|
||||
AddStep("Push screen", () => Stack.Push(new TestMigrationSelectScreen()));
|
||||
}
|
||||
|
||||
private class TestMigrationSelectScreen : MigrationSelectScreen
|
||||
{
|
||||
protected override void BeginMigration(DirectoryInfo target) => this.Push(new TestMigrationRunScreen());
|
||||
|
||||
private class TestMigrationRunScreen : MigrationRunScreen
|
||||
{
|
||||
protected override void PerformMigration()
|
||||
{
|
||||
Thread.Sleep(3000);
|
||||
}
|
||||
|
||||
public TestMigrationRunScreen()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Tests.Screens
|
||||
{
|
||||
var match = CreateSampleMatch();
|
||||
|
||||
Add(new SeedingEditorScreen(match.Team1.Value)
|
||||
Add(new SeedingEditorScreen(match.Team1.Value, new TeamEditorScreen())
|
||||
{
|
||||
Width = 0.85f // create room for control panel
|
||||
});
|
||||
|
@ -19,25 +19,29 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament.Screens.Editors
|
||||
{
|
||||
public class SeedingEditorScreen : TournamentEditorScreen<SeedingEditorScreen.SeeingResultRow, SeedingResult>
|
||||
public class SeedingEditorScreen : TournamentEditorScreen<SeedingEditorScreen.SeedingResultRow, SeedingResult>
|
||||
{
|
||||
private readonly TournamentTeam team;
|
||||
|
||||
protected override BindableList<SeedingResult> Storage => team.SeedingResults;
|
||||
|
||||
public SeedingEditorScreen(TournamentTeam team)
|
||||
[Resolved(canBeNull: true)]
|
||||
private TournamentSceneManager sceneManager { get; set; }
|
||||
|
||||
public SeedingEditorScreen(TournamentTeam team, TournamentScreen parentScreen)
|
||||
: base(parentScreen)
|
||||
{
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
public class SeeingResultRow : CompositeDrawable, IModelBacked<SeedingResult>
|
||||
public class SeedingResultRow : CompositeDrawable, IModelBacked<SeedingResult>
|
||||
{
|
||||
public SeedingResult Model { get; }
|
||||
|
||||
[Resolved]
|
||||
private LadderInfo ladderInfo { get; set; }
|
||||
|
||||
public SeeingResultRow(TournamentTeam team, SeedingResult round)
|
||||
public SeedingResultRow(TournamentTeam team, SeedingResult round)
|
||||
{
|
||||
Model = round;
|
||||
|
||||
@ -281,6 +285,6 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
}
|
||||
}
|
||||
|
||||
protected override SeeingResultRow CreateDrawable(SeedingResult model) => new SeeingResultRow(team, model);
|
||||
protected override SeedingResultRow CreateDrawable(SeedingResult model) => new SeedingResultRow(team, model);
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
});
|
||||
}
|
||||
|
||||
protected override TeamRow CreateDrawable(TournamentTeam model) => new TeamRow(model);
|
||||
protected override TeamRow CreateDrawable(TournamentTeam model) => new TeamRow(model, this);
|
||||
|
||||
private void addAllCountries()
|
||||
{
|
||||
@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
[Resolved]
|
||||
private LadderInfo ladderInfo { get; set; }
|
||||
|
||||
public TeamRow(TournamentTeam team)
|
||||
public TeamRow(TournamentTeam team, TournamentScreen parent)
|
||||
{
|
||||
Model = team;
|
||||
|
||||
@ -154,7 +154,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Text = "Edit seeding results",
|
||||
Action = () =>
|
||||
{
|
||||
sceneManager?.SetScreen(new SeedingEditorScreen(team));
|
||||
sceneManager?.SetScreen(new SeedingEditorScreen(team, parent));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays.Settings;
|
||||
@ -25,8 +26,19 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
|
||||
private FillFlowContainer<TDrawable> flow;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private TournamentSceneManager sceneManager { get; set; }
|
||||
|
||||
protected ControlPanel ControlPanel;
|
||||
|
||||
private readonly TournamentScreen parentScreen;
|
||||
private BackButton backButton;
|
||||
|
||||
protected TournamentEditorScreen(TournamentScreen parentScreen = null)
|
||||
{
|
||||
this.parentScreen = parentScreen;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -47,7 +59,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20)
|
||||
Spacing = new Vector2(20),
|
||||
},
|
||||
},
|
||||
ControlPanel = new ControlPanel
|
||||
@ -70,6 +82,19 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
}
|
||||
});
|
||||
|
||||
if (parentScreen != null)
|
||||
{
|
||||
AddInternal(backButton = new BackButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
State = { Value = Visibility.Visible },
|
||||
Action = () => sceneManager?.SetScreen(parentScreen.GetType())
|
||||
});
|
||||
|
||||
flow.Padding = new MarginPadding { Bottom = backButton.Height * 2 };
|
||||
}
|
||||
|
||||
Storage.CollectionChanged += (_, args) =>
|
||||
{
|
||||
switch (args.Action)
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Screens.MapPool
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new TourneyVideo("gameplay")
|
||||
new TourneyVideo("mappool")
|
||||
{
|
||||
Loop = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -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");
|
||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
Fail = 1,
|
||||
Pass = 2,
|
||||
Foreground = 3,
|
||||
Video = 4
|
||||
Overlay = 4,
|
||||
Video = 5
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
23
osu.Game/Extensions/WebRequestExtensions.cs
Normal file
23
osu.Game/Extensions/WebRequestExtensions.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// 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.IO.Network;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Online.API.Requests;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
{
|
||||
public static class WebRequestExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Add a pagination cursor to the web request in the format required by osu-web.
|
||||
/// </summary>
|
||||
public static void AddCursor(this WebRequest webRequest, Cursor cursor)
|
||||
{
|
||||
cursor?.Properties.ForEach(x =>
|
||||
{
|
||||
webRequest.AddParameter("cursor[" + x.Key + "]", x.Value.ToString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private readonly TwoLayerButton button;
|
||||
|
||||
public BackButton(Receptor receptor)
|
||||
public BackButton(Receptor receptor = null)
|
||||
{
|
||||
receptor.OnBackPressed = () => button.Click();
|
||||
|
||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||
|
||||
Child = button = new TwoLayerButton
|
||||
@ -30,6 +28,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Icon = OsuIcon.LeftCircle,
|
||||
Action = () => Action?.Invoke()
|
||||
};
|
||||
|
||||
if (receptor == null)
|
||||
{
|
||||
// if a receptor wasn't provided, create our own locally.
|
||||
Add(receptor = new Receptor());
|
||||
}
|
||||
|
||||
receptor.OnBackPressed = () => button.Click();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -28,11 +28,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private GameHost host { get; set; }
|
||||
|
||||
[Cached]
|
||||
private readonly Bindable<DirectoryInfo> currentDirectory = new Bindable<DirectoryInfo>();
|
||||
public readonly Bindable<DirectoryInfo> CurrentDirectory = new Bindable<DirectoryInfo>();
|
||||
|
||||
public DirectorySelector(string initialPath = null)
|
||||
{
|
||||
currentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
|
||||
CurrentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -40,19 +40,25 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
Padding = new MarginPadding(10);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = new GridContainer
|
||||
{
|
||||
new FillFlowContainer
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
new Dimension(GridSizeMode.Absolute, 50),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new CurrentDirectoryDisplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -65,10 +71,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
currentDirectory.BindValueChanged(updateDisplay, true);
|
||||
CurrentDirectory.BindValueChanged(updateDisplay, true);
|
||||
}
|
||||
|
||||
private void updateDisplay(ValueChangedEvent<DirectoryInfo> directory)
|
||||
@ -86,9 +92,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
else
|
||||
{
|
||||
directoryFlow.Add(new ParentDirectoryPiece(currentDirectory.Value.Parent));
|
||||
directoryFlow.Add(new ParentDirectoryPiece(CurrentDirectory.Value.Parent));
|
||||
|
||||
foreach (var dir in currentDirectory.Value.GetDirectories().OrderBy(d => d.Name))
|
||||
foreach (var dir in CurrentDirectory.Value.GetDirectories().OrderBy(d => d.Name))
|
||||
{
|
||||
if ((dir.Attributes & FileAttributes.Hidden) == 0)
|
||||
directoryFlow.Add(new DirectoryPiece(dir));
|
||||
@ -97,8 +103,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
currentDirectory.Value = directory.OldValue;
|
||||
|
||||
CurrentDirectory.Value = directory.OldValue;
|
||||
this.FlashColour(Color4.Red, 300);
|
||||
}
|
||||
}
|
||||
|
@ -48,11 +48,21 @@ namespace osu.Game.IO
|
||||
var source = new DirectoryInfo(GetFullPath("."));
|
||||
var destination = new DirectoryInfo(newLocation);
|
||||
|
||||
// using Uri is the easiest way to check equality and contains (https://stackoverflow.com/a/7710620)
|
||||
var sourceUri = new Uri(source.FullName + Path.DirectorySeparatorChar);
|
||||
var destinationUri = new Uri(destination.FullName + Path.DirectorySeparatorChar);
|
||||
|
||||
if (sourceUri == destinationUri)
|
||||
throw new ArgumentException("Destination provided is already the current location", nameof(newLocation));
|
||||
|
||||
if (sourceUri.IsBaseOf(destinationUri))
|
||||
throw new ArgumentException("Destination provided is inside the source", nameof(newLocation));
|
||||
|
||||
// ensure the new location has no files present, else hard abort
|
||||
if (destination.Exists)
|
||||
{
|
||||
if (destination.GetFiles().Length > 0 || destination.GetDirectories().Length > 0)
|
||||
throw new InvalidOperationException("Migration destination already has files present");
|
||||
throw new ArgumentException("Destination provided already has files or directories present", nameof(newLocation));
|
||||
|
||||
deleteRecursive(destination);
|
||||
}
|
||||
@ -74,7 +84,7 @@ namespace osu.Game.IO
|
||||
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
||||
continue;
|
||||
|
||||
fi.Delete();
|
||||
attemptOperation(() => fi.Delete());
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo dir in target.GetDirectories())
|
||||
@ -82,8 +92,11 @@ namespace osu.Game.IO
|
||||
if (topLevelExcludes && IGNORE_DIRECTORIES.Contains(dir.Name))
|
||||
continue;
|
||||
|
||||
dir.Delete(true);
|
||||
attemptOperation(() => dir.Delete(true));
|
||||
}
|
||||
|
||||
if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0)
|
||||
attemptOperation(target.Delete);
|
||||
}
|
||||
|
||||
private static void copyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true)
|
||||
@ -96,7 +109,7 @@ namespace osu.Game.IO
|
||||
if (topLevelExcludes && IGNORE_FILES.Contains(fi.Name))
|
||||
continue;
|
||||
|
||||
attemptCopy(fi, Path.Combine(destination.FullName, fi.Name));
|
||||
attemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true));
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo dir in source.GetDirectories())
|
||||
@ -108,24 +121,27 @@ namespace osu.Game.IO
|
||||
}
|
||||
}
|
||||
|
||||
private static void attemptCopy(System.IO.FileInfo fileInfo, string destination)
|
||||
/// <summary>
|
||||
/// Attempt an IO operation multiple times and only throw if none of the attempts succeed.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
/// <param name="attempts">The number of attempts (250ms wait between each).</param>
|
||||
private static void attemptOperation(Action action, int attempts = 10)
|
||||
{
|
||||
int tries = 5;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
fileInfo.CopyTo(destination, true);
|
||||
action();
|
||||
return;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (tries-- == 0)
|
||||
if (attempts-- == 0)
|
||||
throw;
|
||||
}
|
||||
|
||||
Thread.Sleep(50);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
osu.Game/Online/API/Requests/Cursor.cs
Normal file
20
osu.Game/Online/API/Requests/Cursor.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
|
||||
/// </summary>
|
||||
public class Cursor
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> Properties;
|
||||
}
|
||||
}
|
@ -7,10 +7,7 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public abstract class ResponseWithCursor
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
|
||||
/// </summary>
|
||||
[JsonProperty("cursor")]
|
||||
public dynamic CursorJson;
|
||||
public Cursor Cursor;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Rulesets;
|
||||
@ -10,29 +11,31 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
|
||||
{
|
||||
public SearchCategory SearchCategory { get; set; }
|
||||
public SearchCategory SearchCategory { get; }
|
||||
|
||||
public SortCriteria SortCriteria { get; set; }
|
||||
public SortCriteria SortCriteria { get; }
|
||||
|
||||
public SortDirection SortDirection { get; set; }
|
||||
public SortDirection SortDirection { get; }
|
||||
|
||||
public SearchGenre Genre { get; set; }
|
||||
public SearchGenre Genre { get; }
|
||||
|
||||
public SearchLanguage Language { get; set; }
|
||||
public SearchLanguage Language { get; }
|
||||
|
||||
private readonly string query;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly Cursor cursor;
|
||||
|
||||
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset)
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending)
|
||||
{
|
||||
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
||||
this.ruleset = ruleset;
|
||||
this.cursor = cursor;
|
||||
|
||||
SearchCategory = SearchCategory.Any;
|
||||
SortCriteria = SortCriteria.Ranked;
|
||||
SortDirection = SortDirection.Descending;
|
||||
SearchCategory = searchCategory;
|
||||
SortCriteria = sortCriteria;
|
||||
SortDirection = sortDirection;
|
||||
Genre = SearchGenre.Any;
|
||||
Language = SearchLanguage.Any;
|
||||
}
|
||||
@ -55,6 +58,8 @@ namespace osu.Game.Online.API.Requests
|
||||
|
||||
req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
||||
|
||||
req.AddCursor(cursor);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -22,25 +22,46 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingFilterControl : CompositeDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when a search finishes. Contains only new items in the case of pagination.
|
||||
/// </summary>
|
||||
public Action<List<BeatmapSetInfo>> SearchFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when search criteria change.
|
||||
/// </summary>
|
||||
public Action SearchStarted;
|
||||
|
||||
/// <summary>
|
||||
/// True when pagination has reached the end of available results.
|
||||
/// </summary>
|
||||
private bool noMoreResults;
|
||||
|
||||
/// <summary>
|
||||
/// The current page fetched of results (zero index).
|
||||
/// </summary>
|
||||
public int CurrentPage { get; private set; }
|
||||
|
||||
private readonly BeatmapListingSearchControl searchControl;
|
||||
private readonly BeatmapListingSortTabControl sortControl;
|
||||
private readonly Box sortControlBackground;
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
private SearchBeatmapSetsResponse lastResponse;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private readonly BeatmapListingSearchControl searchControl;
|
||||
private readonly BeatmapListingSortTabControl sortControl;
|
||||
private readonly Box sortControlBackground;
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
public BeatmapListingFilterControl()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -114,51 +135,84 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
||||
}
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
public void TakeFocus() => searchControl.TakeFocus();
|
||||
|
||||
/// <summary>
|
||||
/// Fetch the next page of results. May result in a no-op if a fetch is already in progress, or if there are no results left.
|
||||
/// </summary>
|
||||
public void FetchNextPage()
|
||||
{
|
||||
// there may be no results left.
|
||||
if (noMoreResults)
|
||||
return;
|
||||
|
||||
// there may already be an active request.
|
||||
if (getSetsRequest != null)
|
||||
return;
|
||||
|
||||
if (lastResponse != null)
|
||||
CurrentPage++;
|
||||
|
||||
performRequest();
|
||||
}
|
||||
|
||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||
{
|
||||
SearchStarted?.Invoke();
|
||||
|
||||
getSetsRequest?.Cancel();
|
||||
resetSearch();
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
||||
queryChangedDebounce = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
resetSearch();
|
||||
FetchNextPage();
|
||||
}, queryTextChanged ? 500 : 100);
|
||||
}
|
||||
|
||||
private void updateSearch()
|
||||
private void performRequest()
|
||||
{
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value)
|
||||
{
|
||||
SearchCategory = searchControl.Category.Value,
|
||||
SortCriteria = sortControl.Current.Value,
|
||||
SortDirection = sortControl.SortDirection.Value,
|
||||
Genre = searchControl.Genre.Value,
|
||||
Language = searchControl.Language.Value
|
||||
};
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(
|
||||
searchControl.Query.Value,
|
||||
searchControl.Ruleset.Value,
|
||||
lastResponse?.Cursor,
|
||||
searchControl.Category.Value,
|
||||
sortControl.Current.Value,
|
||||
sortControl.SortDirection.Value);
|
||||
|
||||
getSetsRequest.Success += response => Schedule(() => onSearchFinished(response));
|
||||
getSetsRequest.Success += response =>
|
||||
{
|
||||
var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList();
|
||||
|
||||
if (sets.Count == 0)
|
||||
noMoreResults = true;
|
||||
|
||||
lastResponse = response;
|
||||
getSetsRequest = null;
|
||||
|
||||
SearchFinished?.Invoke(sets);
|
||||
};
|
||||
|
||||
api.Queue(getSetsRequest);
|
||||
}
|
||||
|
||||
private void onSearchFinished(SearchBeatmapSetsResponse response)
|
||||
private void resetSearch()
|
||||
{
|
||||
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
||||
noMoreResults = false;
|
||||
CurrentPage = 0;
|
||||
|
||||
searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First();
|
||||
lastResponse = null;
|
||||
|
||||
SearchFinished?.Invoke(beatmaps);
|
||||
getSetsRequest?.Cancel();
|
||||
getSetsRequest = null;
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
queryChangedDebounce?.Cancel();
|
||||
resetSearch();
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
public void TakeFocus() => searchControl.TakeFocus();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -30,6 +32,10 @@ namespace osu.Game.Overlays
|
||||
private Drawable currentContent;
|
||||
private LoadingLayer loadingLayer;
|
||||
private Container panelTarget;
|
||||
private FillFlowContainer<BeatmapPanel> foundContent;
|
||||
private NotFoundDrawable notFoundContent;
|
||||
|
||||
private OverlayScrollContainer resultScrollContainer;
|
||||
|
||||
public BeatmapListingOverlay()
|
||||
: base(OverlayColourScheme.Blue)
|
||||
@ -48,7 +54,7 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background6
|
||||
},
|
||||
new OverlayScrollContainer
|
||||
resultScrollContainer = new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
@ -80,9 +86,14 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 20 }
|
||||
},
|
||||
loadingLayer = new LoadingLayer(panelTarget)
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
foundContent = new FillFlowContainer<BeatmapPanel>(),
|
||||
notFoundContent = new NotFoundDrawable(),
|
||||
loadingLayer = new LoadingLayer(panelTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -110,34 +121,53 @@ namespace osu.Game.Overlays
|
||||
loadingLayer.Show();
|
||||
}
|
||||
|
||||
private Task panelLoadDelegate;
|
||||
|
||||
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
|
||||
{
|
||||
if (!beatmaps.Any())
|
||||
var newPanels = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
|
||||
{
|
||||
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
return;
|
||||
}
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
});
|
||||
|
||||
var newPanels = new FillFlowContainer<BeatmapPanel>
|
||||
if (filterControl.CurrentPage == 0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(10),
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Vertical = 15 },
|
||||
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
|
||||
//No matches case
|
||||
if (!newPanels.Any())
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
})
|
||||
};
|
||||
LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
return;
|
||||
}
|
||||
|
||||
LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
// spawn new children with the contained so we only clear old content at the last moment.
|
||||
var content = new FillFlowContainer<BeatmapPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(10),
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Vertical = 15 },
|
||||
ChildrenEnumerable = newPanels
|
||||
};
|
||||
|
||||
panelLoadDelegate = LoadComponentAsync(foundContent = content, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
panelLoadDelegate = LoadComponentsAsync(newPanels, loaded =>
|
||||
{
|
||||
lastFetchDisplayedTime = Time.Current;
|
||||
foundContent.AddRange(loaded);
|
||||
loaded.ForEach(p => p.FadeIn(200, Easing.OutQuint));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addContentToPlaceholder(Drawable content)
|
||||
{
|
||||
loadingLayer.Hide();
|
||||
lastFetchDisplayedTime = Time.Current;
|
||||
|
||||
var lastContent = currentContent;
|
||||
|
||||
@ -149,11 +179,14 @@ namespace osu.Game.Overlays
|
||||
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
|
||||
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
|
||||
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
|
||||
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
|
||||
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => panelTarget.Remove(lastContent));
|
||||
}
|
||||
|
||||
panelTarget.Add(currentContent = content);
|
||||
currentContent.FadeIn(200, Easing.OutQuint);
|
||||
if (!content.IsAlive)
|
||||
panelTarget.Add(content);
|
||||
content.FadeIn(200, Easing.OutQuint);
|
||||
|
||||
currentContent = content;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@ -203,5 +236,23 @@ namespace osu.Game.Overlays
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private const double time_between_fetches = 500;
|
||||
|
||||
private double lastFetchDisplayedTime;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
const int pagination_scroll_distance = 500;
|
||||
|
||||
bool shouldShowMore = panelLoadDelegate?.IsCompleted != false
|
||||
&& Time.Current - lastFetchDisplayedTime > time_between_fetches
|
||||
&& (resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance));
|
||||
|
||||
if (shouldShowMore)
|
||||
filterControl.FetchNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -4,7 +4,9 @@
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
@ -12,8 +14,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
protected override string Header => "Updates";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(Storage storage, OsuConfigManager config)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(Storage storage, OsuConfigManager config, OsuGame game)
|
||||
{
|
||||
Add(new SettingsEnumDropdown<ReleaseStream>
|
||||
{
|
||||
@ -28,6 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
Text = "Open osu! folder",
|
||||
Action = storage.OpenInNativeExplorer,
|
||||
});
|
||||
|
||||
Add(new SettingsButton
|
||||
{
|
||||
Text = "Change folder location...",
|
||||
Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen()))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
protected override string Header => "Mouse";
|
||||
|
||||
private readonly BindableBool rawInputToggle = new BindableBool();
|
||||
private Bindable<double> sensitivityBindable = new BindableDouble();
|
||||
private Bindable<string> ignoredInputHandler;
|
||||
private SensitivitySetting sensitivity;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager osuConfig, FrameworkConfigManager config)
|
||||
{
|
||||
var configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
|
||||
|
||||
// use local bindable to avoid changing enabled state of game host's bindable.
|
||||
sensitivityBindable = configSensitivity.GetUnboundCopy();
|
||||
configSensitivity.BindValueChanged(val => sensitivityBindable.Value = val.NewValue);
|
||||
sensitivityBindable.BindValueChanged(val => configSensitivity.Value = val.NewValue);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
@ -30,10 +37,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
LabelText = "Raw input",
|
||||
Bindable = rawInputToggle
|
||||
},
|
||||
sensitivity = new SensitivitySetting
|
||||
new SensitivitySetting
|
||||
{
|
||||
LabelText = "Cursor sensitivity",
|
||||
Bindable = config.GetBindable<double>(FrameworkSetting.CursorSensitivity)
|
||||
Bindable = sensitivityBindable
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@ -60,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
|
||||
{
|
||||
rawInputToggle.Disabled = true;
|
||||
sensitivity.Bindable.Disabled = true;
|
||||
sensitivityBindable.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -78,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
{
|
||||
bool raw = !handler.NewValue.Contains("Raw");
|
||||
rawInputToggle.Value = raw;
|
||||
sensitivity.Bindable.Disabled = !raw;
|
||||
sensitivityBindable.Disabled = !raw;
|
||||
};
|
||||
|
||||
ignoredInputHandler.TriggerChange();
|
||||
|
@ -0,0 +1,115 @@
|
||||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
public class MigrationRunScreen : OsuScreen
|
||||
{
|
||||
private readonly DirectoryInfo destination;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
public override bool AllowBackButton => false;
|
||||
|
||||
public override bool AllowExternalScreenChange => false;
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool HideOverlaysOnEnter => true;
|
||||
|
||||
private Task migrationTask;
|
||||
|
||||
public MigrationRunScreen(DirectoryInfo destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Migration in progress",
|
||||
Font = OsuFont.Default.With(size: 40)
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "This could take a few minutes depending on the speed of your disk(s).",
|
||||
Font = OsuFont.Default.With(size: 30)
|
||||
},
|
||||
new LoadingSpinner(true)
|
||||
{
|
||||
State = { Value = Visibility.Visible }
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Please avoid interacting with the game!",
|
||||
Font = OsuFont.Default.With(size: 30)
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Beatmap.Value = Beatmap.Default;
|
||||
|
||||
migrationTask = Task.Run(PerformMigration)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
Logger.Log($"Error during migration: {t.Exception?.Message}", level: LogLevel.Error);
|
||||
|
||||
Schedule(this.Exit);
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void PerformMigration() => game?.Migrate(destination.FullName);
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
this.FadeOut().Delay(250).Then().FadeIn(250);
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
// block until migration is finished
|
||||
if (migrationTask?.IsCompleted == false)
|
||||
return true;
|
||||
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
// 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.IO;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Screens;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
public class MigrationSelectScreen : OsuScreen
|
||||
{
|
||||
private DirectorySelector directorySelector;
|
||||
|
||||
public override bool AllowExternalScreenChange => false;
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool HideOverlaysOnEnter => true;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame game, Storage storage, OsuColour colours)
|
||||
{
|
||||
game?.Toolbar.Hide();
|
||||
|
||||
// begin selection in the parent directory of the current storage location
|
||||
var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.5f, 0.8f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.GreySeafoamDark,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Relative, 0.8f),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Please select a new location",
|
||||
Font = OsuFont.Default.With(size: 40)
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
directorySelector = new DirectorySelector(initialPath)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new TriangleButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 300,
|
||||
Text = "Begin folder migration",
|
||||
Action = start
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
{
|
||||
base.OnSuspending(next);
|
||||
|
||||
this.FadeOut(250);
|
||||
}
|
||||
|
||||
private void start()
|
||||
{
|
||||
var target = directorySelector.CurrentDirectory.Value;
|
||||
|
||||
try
|
||||
{
|
||||
if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0)
|
||||
target = target.CreateSubdirectory("osu-lazer");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
ValidForResume = false;
|
||||
BeginMigration(target);
|
||||
}
|
||||
|
||||
protected virtual void BeginMigration(DirectoryInfo target) => this.Push(new MigrationRunScreen(target));
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -46,6 +46,13 @@ namespace osu.Game.Overlays
|
||||
Width = 300,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
||||
},
|
||||
muteButton = new MuteButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding(10),
|
||||
Current = { BindTarget = IsMuted }
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
@ -56,19 +63,11 @@ namespace osu.Game.Overlays
|
||||
Margin = new MarginPadding { Left = offset },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker)
|
||||
{
|
||||
Margin = new MarginPadding { Top = 100 + MuteButton.HEIGHT } // to counter the mute button and re-center the volume meters
|
||||
},
|
||||
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker),
|
||||
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
|
||||
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
||||
muteButton = new MuteButton
|
||||
{
|
||||
Margin = new MarginPadding { Top = 100 },
|
||||
Current = { BindTarget = IsMuted }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
||||
|
@ -257,7 +257,8 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
EditorBeatmap.Add(hitObject);
|
||||
|
||||
adjustableClock.Seek(hitObject.GetEndTime());
|
||||
if (adjustableClock.CurrentTime < hitObject.StartTime)
|
||||
adjustableClock.Seek(hitObject.StartTime);
|
||||
}
|
||||
|
||||
showGridFor(Enumerable.Empty<HitObject>());
|
||||
|
@ -11,10 +11,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// <summary>
|
||||
/// Whether we should allow failing at the current point in time.
|
||||
/// </summary>
|
||||
bool AllowFail { get; }
|
||||
/// <returns>Whether the fail should be allowed to proceed. Return false to block.</returns>
|
||||
bool PerformFail();
|
||||
|
||||
/// <summary>
|
||||
/// Whether we want to restart on fail. Only used if <see cref="AllowFail"/> is true.
|
||||
/// Whether we want to restart on fail. Only used if <see cref="PerformFail"/> returns true.
|
||||
/// </summary>
|
||||
bool RestartOnFail { get; }
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override string Description => "Watch a perfect automated play through the song.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public bool AllowFail => false;
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// <summary>
|
||||
/// We never fail, 'yo.
|
||||
/// </summary>
|
||||
public bool AllowFail => false;
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
|
@ -48,17 +48,14 @@ namespace osu.Game.Rulesets.Mods
|
||||
retries = Retries.Value;
|
||||
}
|
||||
|
||||
public bool AllowFail
|
||||
public bool PerformFail()
|
||||
{
|
||||
get
|
||||
{
|
||||
if (retries == 0) return true;
|
||||
if (retries == 0) return true;
|
||||
|
||||
health.Value = health.MaxValue;
|
||||
retries--;
|
||||
health.Value = health.MaxValue;
|
||||
retries--;
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override bool Ranked => true;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
|
||||
|
||||
public bool AllowFail => true;
|
||||
public bool PerformFail() => true;
|
||||
|
||||
public bool RestartOnFail => true;
|
||||
|
||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||
|
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
@ -46,6 +47,16 @@ namespace osu.Game.Rulesets.Objects
|
||||
|
||||
for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++)
|
||||
{
|
||||
var roundedTime = Math.Round(t, MidpointRounding.AwayFromZero);
|
||||
|
||||
// in the case of some bar lengths, rounding errors can cause t to be slightly less than
|
||||
// the expected whole number value due to floating point inaccuracies.
|
||||
// if this is the case, apply rounding.
|
||||
if (Precision.AlmostEquals(t, roundedTime))
|
||||
{
|
||||
t = roundedTime;
|
||||
}
|
||||
|
||||
BarLines.Add(new TBarLine
|
||||
{
|
||||
StartTime = t,
|
||||
|
@ -11,13 +11,13 @@ using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Drawables
|
||||
@ -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>();
|
||||
@ -96,8 +96,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// </remarks>
|
||||
protected virtual float SamplePlaybackPosition => 0.5f;
|
||||
|
||||
private readonly BindableDouble balanceAdjust = new BindableDouble();
|
||||
|
||||
private BindableList<HitSampleInfo> samplesBindable;
|
||||
private Bindable<double> startTimeBindable;
|
||||
private Bindable<bool> userPositionalHitSounds;
|
||||
@ -173,7 +171,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
}
|
||||
|
||||
Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)));
|
||||
Samples.AddAdjustment(AdjustableProperty.Balance, balanceAdjust);
|
||||
AddInternal(Samples);
|
||||
}
|
||||
|
||||
@ -352,6 +349,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private GameplayClock gameplayClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plays all the hit sounds for this <see cref="DrawableHitObject"/>.
|
||||
/// This is invoked automatically when this <see cref="DrawableHitObject"/> is hit.
|
||||
@ -360,8 +360,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
{
|
||||
const float balance_adjust_amount = 0.4f;
|
||||
|
||||
balanceAdjust.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
|
||||
Samples?.Play();
|
||||
if (Samples != null && gameplayClock?.IsSeeking != true)
|
||||
{
|
||||
Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
|
||||
Samples.Play();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -29,14 +29,16 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
internal bool FrameStablePlayback = true;
|
||||
|
||||
[Cached]
|
||||
public GameplayClock GameplayClock { get; }
|
||||
public GameplayClock GameplayClock => stabilityGameplayClock;
|
||||
|
||||
[Cached(typeof(GameplayClock))]
|
||||
private readonly StabilityGameplayClock stabilityGameplayClock;
|
||||
|
||||
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
||||
stabilityGameplayClock = new StabilityGameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
||||
|
||||
this.gameplayStartTime = gameplayStartTime;
|
||||
}
|
||||
@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
if (clock != null)
|
||||
{
|
||||
parentGameplayClock = clock;
|
||||
stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock;
|
||||
GameplayClock.IsPaused.BindTo(clock.IsPaused);
|
||||
}
|
||||
}
|
||||
@ -187,5 +189,17 @@ namespace osu.Game.Rulesets.UI
|
||||
}
|
||||
|
||||
public ReplayInputHandler ReplayInputHandler { get; set; }
|
||||
|
||||
private class StabilityGameplayClock : GameplayClock
|
||||
{
|
||||
public IFrameBasedClock ParentGameplayClock;
|
||||
|
||||
public StabilityGameplayClock(FramedClock underlyingClock)
|
||||
: base(underlyingClock)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
|
||||
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
|
||||
{
|
||||
double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
|
||||
return adjustedTime - timeRange - 1000;
|
||||
return TimeAt(-(scrollLength + offset), originTime, timeRange, scrollLength);
|
||||
}
|
||||
|
||||
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,11 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public bool IsRunning => underlyingClock.IsRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Whether an ongoing seek operation is active.
|
||||
/// </summary>
|
||||
public virtual bool IsSeeking => false;
|
||||
|
||||
public void ProcessFrame()
|
||||
{
|
||||
// we do not want to process the underlying clock.
|
||||
|
@ -78,10 +78,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
|
||||
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
||||
platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
|
||||
platformOffsetClock = new HardwareCorrectionOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 };
|
||||
|
||||
// the final usable gameplay clock with user-set offsets applied.
|
||||
userOffsetClock = new FramedOffsetClock(platformOffsetClock);
|
||||
userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock);
|
||||
|
||||
// the clock to be exposed via DI to children.
|
||||
GameplayClock = new GameplayClock(userOffsetClock);
|
||||
@ -248,5 +248,16 @@ namespace osu.Game.Screens.Play
|
||||
speedAdjustmentsApplied = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class HardwareCorrectionOffsetClock : FramedOffsetClock
|
||||
{
|
||||
// we always want to apply the same real-time offset, so it should be adjusted by the playback rate to achieve this.
|
||||
public override double CurrentTime => SourceTime + Offset * Rate;
|
||||
|
||||
public HardwareCorrectionOffsetClock(IClock source, bool processSource = true)
|
||||
: base(source, processSource)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
@ -105,7 +107,7 @@ namespace osu.Game.Screens.Play
|
||||
/// Whether failing should be allowed.
|
||||
/// By default, this checks whether all selected mods allow failing.
|
||||
/// </summary>
|
||||
protected virtual bool AllowFail => Mods.Value.OfType<IApplicableFailOverride>().All(m => m.AllowFail);
|
||||
protected virtual bool CheckModsAllowFailure() => Mods.Value.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
|
||||
|
||||
private readonly bool allowPause;
|
||||
private readonly bool showResults;
|
||||
@ -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
|
||||
},
|
||||
@ -485,7 +489,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private bool onFail()
|
||||
{
|
||||
if (!AllowFail)
|
||||
if (!CheckModsAllowFailure())
|
||||
return false;
|
||||
|
||||
HasFailed = true;
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Play
|
||||
private readonly Score score;
|
||||
|
||||
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
||||
protected override bool AllowFail => false;
|
||||
protected override bool CheckModsAllowFailure() => false;
|
||||
|
||||
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
|
||||
: base(allowPause, showResults)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 =>
|
||||
|
@ -4,11 +4,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Audio;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
@ -17,25 +17,32 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
private readonly ISampleInfo[] hitSamples;
|
||||
|
||||
private List<(AdjustableProperty property, BindableDouble bindable)> adjustments;
|
||||
|
||||
private SampleChannel[] channels;
|
||||
|
||||
[Resolved]
|
||||
private ISampleStore samples { get; set; }
|
||||
|
||||
public SkinnableSound(ISampleInfo hitSamples)
|
||||
: this(new[] { hitSamples })
|
||||
{
|
||||
}
|
||||
|
||||
public SkinnableSound(IEnumerable<ISampleInfo> hitSamples)
|
||||
{
|
||||
this.hitSamples = hitSamples.ToArray();
|
||||
}
|
||||
|
||||
public SkinnableSound(ISampleInfo hitSamples)
|
||||
{
|
||||
this.hitSamples = new[] { hitSamples };
|
||||
InternalChild = samplesContainer = new AudioContainer<DrawableSample>();
|
||||
}
|
||||
|
||||
private bool looping;
|
||||
|
||||
private readonly AudioContainer<DrawableSample> samplesContainer;
|
||||
|
||||
public BindableNumber<double> Volume => samplesContainer.Volume;
|
||||
|
||||
public BindableNumber<double> Balance => samplesContainer.Balance;
|
||||
|
||||
public BindableNumber<double> Frequency => samplesContainer.Frequency;
|
||||
|
||||
public BindableNumber<double> Tempo => samplesContainer.Tempo;
|
||||
|
||||
public bool Looping
|
||||
{
|
||||
get => looping;
|
||||
@ -45,33 +52,23 @@ namespace osu.Game.Skinning
|
||||
|
||||
looping = value;
|
||||
|
||||
channels?.ForEach(c => c.Looping = looping);
|
||||
samplesContainer.ForEach(c => c.Looping = looping);
|
||||
}
|
||||
}
|
||||
|
||||
public void Play() => channels?.ForEach(c => c.Play());
|
||||
|
||||
public void Stop() => channels?.ForEach(c => c.Stop());
|
||||
|
||||
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
|
||||
public void Play() => samplesContainer.ForEach(c =>
|
||||
{
|
||||
if (adjustments == null) adjustments = new List<(AdjustableProperty, BindableDouble)>();
|
||||
if (c.AggregateVolume.Value > 0)
|
||||
c.Play();
|
||||
});
|
||||
|
||||
adjustments.Add((type, adjustBindable));
|
||||
channels?.ForEach(c => c.AddAdjustment(type, adjustBindable));
|
||||
}
|
||||
|
||||
public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
|
||||
{
|
||||
adjustments?.Remove((type, adjustBindable));
|
||||
channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable));
|
||||
}
|
||||
public void Stop() => samplesContainer.ForEach(c => c.Stop());
|
||||
|
||||
public override bool IsPresent => Scheduler.HasPendingTasks;
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
channels = hitSamples.Select(s =>
|
||||
var channels = hitSamples.Select(s =>
|
||||
{
|
||||
var ch = skin.GetSample(s);
|
||||
|
||||
@ -88,27 +85,12 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
ch.Looping = looping;
|
||||
ch.Volume.Value = s.Volume / 100.0;
|
||||
|
||||
if (adjustments != null)
|
||||
{
|
||||
foreach (var (property, bindable) in adjustments)
|
||||
ch.AddAdjustment(property, bindable);
|
||||
}
|
||||
}
|
||||
|
||||
return ch;
|
||||
}).Where(c => c != null).ToArray();
|
||||
}
|
||||
}).Where(c => c != null);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (channels != null)
|
||||
{
|
||||
foreach (var c in channels)
|
||||
c.Dispose();
|
||||
}
|
||||
samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -33,6 +33,6 @@ namespace osu.Game.Storyboards
|
||||
}
|
||||
|
||||
public DrawableStoryboardLayer CreateDrawable()
|
||||
=> new DrawableStoryboardLayer(this) { Depth = Depth, };
|
||||
=> new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name };
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool AllowFail => true;
|
||||
protected override bool CheckModsAllowFailure() => true;
|
||||
|
||||
public bool CheckFailed(bool failed)
|
||||
{
|
||||
|
@ -59,12 +59,14 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
protected class ModTestPlayer : TestPlayer
|
||||
{
|
||||
protected override bool AllowFail { get; }
|
||||
private readonly bool allowFail;
|
||||
|
||||
protected override bool CheckModsAllowFailure() => allowFail;
|
||||
|
||||
public ModTestPlayer(bool allowFail)
|
||||
: base(false, false)
|
||||
{
|
||||
AllowFail = allowFail;
|
||||
this.allowFail = allowFail;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,8 @@
|
||||
<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.Game.Resources" Version="2020.427.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" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
|
@ -70,8 +70,8 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.511.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.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. -->
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
@ -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" />
|
||||
|
Loading…
Reference in New Issue
Block a user