mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge branch 'master' into generic-math
This commit is contained in:
commit
221af74f95
128
osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs
Normal file
128
osu.Game.Tests/Editing/Checks/CheckHitsoundsFormatTest.cs
Normal file
@ -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.IO;
|
||||
using System.Linq;
|
||||
using ManagedBass;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Audio;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckHitsoundsFormatTest
|
||||
{
|
||||
private CheckHitsoundsFormat check = null!;
|
||||
|
||||
private IBeatmap beatmap = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckHitsoundsFormat();
|
||||
beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Files = { CheckTestHelpers.CreateMockFile("wav") }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 0 = No output device. This still allows decoding.
|
||||
if (!Bass.Init(0) && Bass.LastError != Errors.Already)
|
||||
throw new AudioException("Could not initialize Bass.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMp3Audio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckHitsoundsFormat.IssueTemplateIncorrectFormat);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOggAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.ogg"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWavAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/hitsound-delay.wav"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWebmAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.webm"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckHitsoundsFormat.IssueTemplateFormatUnsupported);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotAnAudioFile()
|
||||
{
|
||||
beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Files = { CheckTestHelpers.CreateMockFile("png") }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using (var resourceStream = TestResources.OpenResource("Textures/test-image.png"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorruptAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckHitsoundsFormat.IssueTemplateFormatUnsupported);
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||
}
|
||||
}
|
||||
}
|
112
osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs
Normal file
112
osu.Game.Tests/Editing/Checks/CheckSongFormatTest.cs
Normal file
@ -0,0 +1,112 @@
|
||||
// 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.Linq;
|
||||
using ManagedBass;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Audio;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class CheckSongFormatTest
|
||||
{
|
||||
private CheckSongFormat check = null!;
|
||||
|
||||
private IBeatmap beatmap = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new CheckSongFormat();
|
||||
beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
Files = { CheckTestHelpers.CreateMockFile("mp3") }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 0 = No output device. This still allows decoding.
|
||||
if (!Bass.Init(0) && Bass.LastError != Errors.Already)
|
||||
throw new AudioException("Could not initialize Bass.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMp3Audio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample-cut.mp3"))
|
||||
{
|
||||
beatmap.Metadata.AudioFile = "abc123.mp3";
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOggAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.ogg"))
|
||||
{
|
||||
beatmap.Metadata.AudioFile = "abc123.mp3";
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWavAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/hitsound-delay.wav"))
|
||||
{
|
||||
beatmap.Metadata.AudioFile = "abc123.mp3";
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckSongFormat.IssueTemplateIncorrectFormat);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWebmAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.webm"))
|
||||
{
|
||||
beatmap.Metadata.AudioFile = "abc123.mp3";
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckSongFormat.IssueTemplateFormatUnsupported);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorruptAudio()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
|
||||
{
|
||||
beatmap.Metadata.AudioFile = "abc123.mp3";
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckSongFormat.IssueTemplateFormatUnsupported);
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||
}
|
||||
}
|
||||
}
|
@ -95,18 +95,6 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorruptAudioFile()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/corrupt.wav"))
|
||||
{
|
||||
var issues = check.Run(getContext(resourceStream)).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.Single().Template is CheckTooShortAudioFiles.IssueTemplateBadFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
|
235
osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs
Normal file
235
osu.Game.Tests/NonVisual/TestSceneTimedDifficultyCalculation.cs
Normal file
@ -0,0 +1,235 @@
|
||||
// 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 System.Threading;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneTimedDifficultyCalculation
|
||||
{
|
||||
[Test]
|
||||
public void TestAttributesGeneratedForAllNonSkippedObjects()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new TestHitObject { StartTime = 1 },
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 2,
|
||||
Nested = 1
|
||||
},
|
||||
new TestHitObject { StartTime = 3 },
|
||||
}
|
||||
};
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(4));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||
assertEquals(attribs[1], beatmap.HitObjects[0], beatmap.HitObjects[1]);
|
||||
assertEquals(attribs[2], beatmap.HitObjects[0], beatmap.HitObjects[1]); // From the nested object.
|
||||
assertEquals(attribs[3], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttributesNotGeneratedForSkippedObjects()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
// The first object is usually skipped in all implementations
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 1,
|
||||
Skip = true
|
||||
},
|
||||
// An intermediate skipped object.
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 2,
|
||||
Skip = true
|
||||
},
|
||||
new TestHitObject { StartTime = 3 },
|
||||
}
|
||||
};
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(1));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNestedObjectOnlyAddsParentOnce()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 1,
|
||||
Skip = true,
|
||||
Nested = 2
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(2));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0]);
|
||||
assertEquals(attribs[1], beatmap.HitObjects[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkippedLastObjectAddedInLastIteration()
|
||||
{
|
||||
var beatmap = new Beatmap<TestHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new TestHitObject { StartTime = 1 },
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 2,
|
||||
Skip = true
|
||||
},
|
||||
new TestHitObject
|
||||
{
|
||||
StartTime = 3,
|
||||
Skip = true
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
List<TimedDifficultyAttributes> attribs = new TestDifficultyCalculator(new TestWorkingBeatmap(beatmap)).CalculateTimed();
|
||||
|
||||
Assert.That(attribs.Count, Is.EqualTo(1));
|
||||
assertEquals(attribs[0], beatmap.HitObjects[0], beatmap.HitObjects[1], beatmap.HitObjects[2]);
|
||||
}
|
||||
|
||||
private void assertEquals(TimedDifficultyAttributes attribs, params HitObject[] expected)
|
||||
{
|
||||
Assert.That(((TestDifficultyAttributes)attribs.Attributes).Objects, Is.EquivalentTo(expected));
|
||||
}
|
||||
|
||||
private class TestHitObject : HitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to skip generating a difficulty representation for this object.
|
||||
/// </summary>
|
||||
public bool Skip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to generate nested difficulty representations for this object, and if so, how many.
|
||||
/// </summary>
|
||||
public int Nested { get; set; }
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
for (int i = 0; i < Nested; i++)
|
||||
AddNested(new TestHitObject { StartTime = StartTime + 0.1 * i });
|
||||
}
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Enumerable.Empty<Mod>();
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => throw new NotImplementedException();
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PassThroughBeatmapConverter(beatmap);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TestDifficultyCalculator(beatmap);
|
||||
|
||||
public override string Description => string.Empty;
|
||||
public override string ShortName => string.Empty;
|
||||
|
||||
private class PassThroughBeatmapConverter : IBeatmapConverter
|
||||
{
|
||||
public event Action<HitObject, IEnumerable<HitObject>>? ObjectConverted
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public IBeatmap Beatmap { get; }
|
||||
|
||||
public PassThroughBeatmapConverter(IBeatmap beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
}
|
||||
|
||||
public bool CanConvert() => true;
|
||||
|
||||
public IBeatmap Convert(CancellationToken cancellationToken = default) => Beatmap;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public TestDifficultyCalculator(IWorkingBeatmap beatmap)
|
||||
: base(new TestRuleset().RulesetInfo, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
=> new TestDifficultyAttributes { Objects = beatmap.HitObjects.ToArray() };
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
foreach (var obj in beatmap.HitObjects.OfType<TestHitObject>())
|
||||
{
|
||||
if (!obj.Skip)
|
||||
objects.Add(new DifficultyHitObject(obj, obj, clockRate, objects, objects.Count));
|
||||
|
||||
foreach (var nested in obj.NestedHitObjects)
|
||||
objects.Add(new DifficultyHitObject(nested, nested, clockRate, objects, objects.Count));
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new PassThroughSkill(mods) };
|
||||
|
||||
private class PassThroughSkill : Skill
|
||||
{
|
||||
public PassThroughSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Process(DifficultyHitObject current)
|
||||
{
|
||||
}
|
||||
|
||||
public override double DifficultyValue() => 1;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public HitObject[] Objects = Array.Empty<HitObject>();
|
||||
}
|
||||
}
|
||||
}
|
BIN
osu.Game.Tests/Resources/Samples/test-sample.ogg
Normal file
BIN
osu.Game.Tests/Resources/Samples/test-sample.ogg
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Samples/test-sample.webm
Normal file
BIN
osu.Game.Tests/Resources/Samples/test-sample.webm
Normal file
Binary file not shown.
@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
BeatmapInfo = beatmap.Beatmaps.First(),
|
||||
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo,
|
||||
User = new GuestUser(),
|
||||
}).Value;
|
||||
})!.Value;
|
||||
});
|
||||
|
||||
AddAssert($"import {i} succeeded", () => imported != null);
|
||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Files = { new RealmNamedFileUsage(new RealmFile { Hash = $"{i}" }, string.Empty) }
|
||||
};
|
||||
|
||||
importedScores.Add(scoreManager.Import(score).Value);
|
||||
importedScores.Add(scoreManager.Import(score)!.Value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -291,14 +291,17 @@ namespace osu.Game.Database
|
||||
{
|
||||
var score = scoreManager.Query(s => s.ID == id);
|
||||
|
||||
scoreManager.PopulateMaximumStatistics(score);
|
||||
|
||||
// Can't use async overload because we're not on the update thread.
|
||||
// ReSharper disable once MethodHasAsyncOverload
|
||||
realmAccess.Write(r =>
|
||||
if (score != null)
|
||||
{
|
||||
r.Find<ScoreInfo>(id)!.MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics);
|
||||
});
|
||||
scoreManager.PopulateMaximumStatistics(score);
|
||||
|
||||
// Can't use async overload because we're not on the update thread.
|
||||
// ReSharper disable once MethodHasAsyncOverload
|
||||
realmAccess.Write(r =>
|
||||
{
|
||||
r.Find<ScoreInfo>(id)!.MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics);
|
||||
});
|
||||
}
|
||||
|
||||
++processedCount;
|
||||
}
|
||||
|
@ -706,26 +706,9 @@ namespace osu.Game
|
||||
{
|
||||
Logger.Log($"Beginning {nameof(PresentScore)} with score {score}");
|
||||
|
||||
// The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database
|
||||
// to ensure all the required data for presenting a replay are present.
|
||||
ScoreInfo databasedScoreInfo = null;
|
||||
var databasedScore = ScoreManager.GetScore(score);
|
||||
|
||||
if (score.OnlineID > 0)
|
||||
databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID);
|
||||
|
||||
if (score.LegacyOnlineID > 0)
|
||||
databasedScoreInfo ??= ScoreManager.Query(s => s.LegacyOnlineID == score.LegacyOnlineID);
|
||||
|
||||
if (score is ScoreInfo scoreInfo)
|
||||
databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == scoreInfo.Hash);
|
||||
|
||||
if (databasedScoreInfo == null)
|
||||
{
|
||||
Logger.Log("The requested score could not be found locally.", LoggingTarget.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var databasedScore = ScoreManager.GetScore(databasedScoreInfo);
|
||||
if (databasedScore == null) return;
|
||||
|
||||
if (databasedScore.Replay == null)
|
||||
{
|
||||
@ -733,7 +716,7 @@ namespace osu.Game
|
||||
return;
|
||||
}
|
||||
|
||||
var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.BeatmapInfo.ID);
|
||||
var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScore.ScoreInfo.BeatmapInfo.ID);
|
||||
|
||||
if (databasedBeatmap == null)
|
||||
{
|
||||
|
@ -69,6 +69,7 @@ namespace osu.Game.Overlays.Mods
|
||||
private Task? latestLoadTask;
|
||||
private ModPanel[]? latestLoadedPanels;
|
||||
internal bool ItemsLoaded => latestLoadTask?.IsCompleted == true && allPanelsLoaded;
|
||||
private bool? wasPresent;
|
||||
|
||||
private bool allPanelsLoaded
|
||||
{
|
||||
@ -192,6 +193,15 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// we override `IsPresent` to include the scheduler's pending task state to make async loads work correctly when columns are masked away
|
||||
// (see description of https://github.com/ppy/osu/pull/19783).
|
||||
// however, because of that we must also ensure that we signal correct invalidations (https://github.com/ppy/osu-framework/issues/5129).
|
||||
// failing to do so causes columns to be stuck in "present" mode despite actually not being present themselves.
|
||||
// this works because `Update()` will always run after a scheduler update, which is what causes the presence state change responsible for the failure.
|
||||
if (wasPresent != null && wasPresent != IsPresent)
|
||||
Invalidate(Invalidation.Presence);
|
||||
wasPresent = IsPresent;
|
||||
|
||||
if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay)
|
||||
{
|
||||
if (pendingSelectionOperations.TryDequeue(out var dequeuedAction))
|
||||
|
@ -107,18 +107,28 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
|
||||
var skills = CreateSkills(Beatmap, playableMods, clockRate);
|
||||
var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap);
|
||||
var difficultyObjects = getDifficultyHitObjects().ToArray();
|
||||
|
||||
foreach (var hitObject in getDifficultyHitObjects())
|
||||
foreach (var obj in difficultyObjects)
|
||||
{
|
||||
progressiveBeatmap.HitObjects.Add(hitObject.BaseObject);
|
||||
// Implementations expect the progressive beatmap to only contain top-level objects from the original beatmap.
|
||||
// At the same time, we also need to consider the possibility DHOs may not be generated for any given object,
|
||||
// so we'll add all remaining objects up to the current point in time to the progressive beatmap.
|
||||
for (int i = progressiveBeatmap.HitObjects.Count; i < Beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
if (obj != difficultyObjects[^1] && Beatmap.HitObjects[i].StartTime > obj.BaseObject.StartTime)
|
||||
break;
|
||||
|
||||
progressiveBeatmap.HitObjects.Add(Beatmap.HitObjects[i]);
|
||||
}
|
||||
|
||||
foreach (var skill in skills)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
skill.Process(hitObject);
|
||||
skill.Process(obj);
|
||||
}
|
||||
|
||||
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
||||
attribs.Add(new TimedDifficultyAttributes(obj.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
||||
}
|
||||
|
||||
return attribs;
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Edit
|
||||
new CheckTooShortAudioFiles(),
|
||||
new CheckAudioInVideo(),
|
||||
new CheckDelayedHitsounds(),
|
||||
new CheckSongFormat(),
|
||||
new CheckHitsoundsFormat(),
|
||||
|
||||
// Files
|
||||
new CheckZeroByteFiles(),
|
||||
|
86
osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs
Normal file
86
osu.Game/Rulesets/Edit/Checks/CheckHitsoundsFormat.cs
Normal file
@ -0,0 +1,86 @@
|
||||
// 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 System.IO;
|
||||
using ManagedBass;
|
||||
using osu.Framework.Audio.Callbacks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckHitsoundsFormat : ICheck
|
||||
{
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Checks for hitsound formats.");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateFormatUnsupported(this),
|
||||
new IssueTemplateIncorrectFormat(this),
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
|
||||
var audioFile = beatmapSet?.GetFile(context.Beatmap.Metadata.AudioFile);
|
||||
|
||||
if (beatmapSet == null) yield break;
|
||||
|
||||
foreach (var file in beatmapSet.Files)
|
||||
{
|
||||
if (audioFile != null && ReferenceEquals(file.File, audioFile.File)) continue;
|
||||
|
||||
using (Stream data = context.WorkingBeatmap.GetStream(file.File.GetStoragePath()))
|
||||
{
|
||||
if (data == null)
|
||||
continue;
|
||||
|
||||
if (!AudioCheckUtils.HasAudioExtension(file.Filename) || !probablyHasAudioData(data))
|
||||
continue;
|
||||
|
||||
var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data));
|
||||
int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode, fileCallbacks.Callbacks, fileCallbacks.Handle);
|
||||
|
||||
// If the format is not supported by BASS
|
||||
if (decodeStream == 0)
|
||||
{
|
||||
yield return new IssueTemplateFormatUnsupported(this).Create(file.Filename);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var audioInfo = Bass.ChannelGetInfo(decodeStream);
|
||||
|
||||
if ((audioInfo.ChannelType & ChannelType.Wave) == 0 && audioInfo.ChannelType != ChannelType.OGG)
|
||||
yield return new IssueTemplateIncorrectFormat(this).Create(file.Filename);
|
||||
|
||||
Bass.StreamFree(decodeStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool probablyHasAudioData(Stream data) => data.Length > 100;
|
||||
|
||||
public class IssueTemplateFormatUnsupported : IssueTemplate
|
||||
{
|
||||
public IssueTemplateFormatUnsupported(ICheck check)
|
||||
: base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format. Use wav or ogg for hitsounds.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string file) => new Issue(this, file);
|
||||
}
|
||||
|
||||
public class IssueTemplateIncorrectFormat : IssueTemplate
|
||||
{
|
||||
public IssueTemplateIncorrectFormat(ICheck check)
|
||||
: base(check, IssueType.Problem, "\"{0}\" is using a incorrect format. Use wav or ogg for hitsounds.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string file) => new Issue(this, file);
|
||||
}
|
||||
}
|
||||
}
|
83
osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs
Normal file
83
osu.Game/Rulesets/Edit/Checks/CheckSongFormat.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 System.IO;
|
||||
using System.Linq;
|
||||
using ManagedBass;
|
||||
using osu.Framework.Audio.Callbacks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public class CheckSongFormat : ICheck
|
||||
{
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Checks for song formats.");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateFormatUnsupported(this),
|
||||
new IssueTemplateIncorrectFormat(this),
|
||||
};
|
||||
|
||||
private IEnumerable<ChannelType> allowedFormats => new[]
|
||||
{
|
||||
ChannelType.MP3,
|
||||
ChannelType.OGG,
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
|
||||
var audioFile = beatmapSet?.GetFile(context.Beatmap.Metadata.AudioFile);
|
||||
|
||||
if (beatmapSet == null) yield break;
|
||||
if (audioFile == null) yield break;
|
||||
|
||||
using (Stream data = context.WorkingBeatmap.GetStream(audioFile.File.GetStoragePath()))
|
||||
{
|
||||
if (data == null || data.Length <= 0) yield break;
|
||||
|
||||
var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data));
|
||||
int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode, fileCallbacks.Callbacks, fileCallbacks.Handle);
|
||||
|
||||
// If the format is not supported by BASS
|
||||
if (decodeStream == 0)
|
||||
{
|
||||
yield return new IssueTemplateFormatUnsupported(this).Create(audioFile.Filename);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
var audioInfo = Bass.ChannelGetInfo(decodeStream);
|
||||
|
||||
if (!allowedFormats.Contains(audioInfo.ChannelType))
|
||||
yield return new IssueTemplateIncorrectFormat(this).Create(audioFile.Filename);
|
||||
|
||||
Bass.StreamFree(decodeStream);
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateFormatUnsupported : IssueTemplate
|
||||
{
|
||||
public IssueTemplateFormatUnsupported(ICheck check)
|
||||
: base(check, IssueType.Problem, "\"{0}\" may be corrupt or using a unsupported audio format. Use mp3 or ogg for the song's audio.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string file) => new Issue(this, file);
|
||||
}
|
||||
|
||||
public class IssueTemplateIncorrectFormat : IssueTemplate
|
||||
{
|
||||
public IssueTemplateIncorrectFormat(ICheck check)
|
||||
: base(check, IssueType.Problem, "\"{0}\" is using a incorrect format. Use mp3 or ogg for the song's audio.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string file) => new Issue(this, file);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,14 +13,12 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
public class CheckTooShortAudioFiles : ICheck
|
||||
{
|
||||
private const int ms_threshold = 25;
|
||||
private const int min_bytes_threshold = 100;
|
||||
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Audio, "Too short audio files");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateTooShort(this),
|
||||
new IssueTemplateBadFormat(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
@ -39,15 +37,7 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
var fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data));
|
||||
int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Prescan, fileCallbacks.Callbacks, fileCallbacks.Handle);
|
||||
|
||||
if (decodeStream == 0)
|
||||
{
|
||||
// If the file is not likely to be properly parsed by Bass, we don't produce Error issues about it.
|
||||
// Image files and audio files devoid of audio data both fail, for example, but neither would be issues in this check.
|
||||
if (AudioCheckUtils.HasAudioExtension(file.Filename) && probablyHasAudioData(data))
|
||||
yield return new IssueTemplateBadFormat(this).Create(file.Filename);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (decodeStream == 0) continue;
|
||||
|
||||
long length = Bass.ChannelGetLength(decodeStream);
|
||||
double ms = Bass.ChannelBytes2Seconds(decodeStream, length) * 1000;
|
||||
@ -60,8 +50,6 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
}
|
||||
}
|
||||
|
||||
private bool probablyHasAudioData(Stream data) => data.Length > min_bytes_threshold;
|
||||
|
||||
public class IssueTemplateTooShort : IssueTemplate
|
||||
{
|
||||
public IssueTemplateTooShort(ICheck check)
|
||||
@ -71,15 +59,5 @@ namespace osu.Game.Rulesets.Edit.Checks
|
||||
|
||||
public Issue Create(string filename, double ms) => new Issue(this, filename, ms, ms_threshold);
|
||||
}
|
||||
|
||||
public class IssueTemplateBadFormat : IssueTemplate
|
||||
{
|
||||
public IssueTemplateBadFormat(ICheck check)
|
||||
: base(check, IssueType.Error, "Could not check whether \"{0}\" is too short (code \"{1}\").")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string filename) => new Issue(this, filename, Bass.LastError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -10,8 +8,8 @@ using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
@ -28,7 +26,7 @@ namespace osu.Game.Scoring
|
||||
public class ScoreManager : ModelManager<ScoreInfo>, IModelImporter<ScoreInfo>
|
||||
{
|
||||
private readonly Func<BeatmapManager> beatmaps;
|
||||
private readonly OsuConfigManager configManager;
|
||||
private readonly OsuConfigManager? configManager;
|
||||
private readonly ScoreImporter scoreImporter;
|
||||
private readonly LegacyScoreExporter scoreExporter;
|
||||
|
||||
@ -43,7 +41,7 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
|
||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api,
|
||||
OsuConfigManager configManager = null)
|
||||
OsuConfigManager? configManager = null)
|
||||
: base(storage, realm)
|
||||
{
|
||||
this.beatmaps = beatmaps;
|
||||
@ -60,18 +58,54 @@ namespace osu.Game.Scoring
|
||||
};
|
||||
}
|
||||
|
||||
public Score GetScore(ScoreInfo score) => scoreImporter.GetScore(score);
|
||||
/// <summary>
|
||||
/// Retrieve a <see cref="Score"/> from a given <see cref="IScoreInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="scoreInfo">The <see cref="IScoreInfo"/> to convert.</param>
|
||||
/// <returns>The <see cref="Score"/>. Null if the score cannot be found in the database.</returns>
|
||||
/// <remarks>
|
||||
/// The <see cref="IScoreInfo"/> is re-retrieved from the database to ensure all the required data
|
||||
/// for retrieving a replay are present (may have missing properties if it was retrieved from online data).
|
||||
/// </remarks>
|
||||
public Score? GetScore(IScoreInfo scoreInfo)
|
||||
{
|
||||
ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo);
|
||||
|
||||
return databasedScoreInfo == null ? null : scoreImporter.GetScore(databasedScoreInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="ScoreInfo"/>s.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public ScoreInfo Query(Expression<Func<ScoreInfo, bool>> query)
|
||||
public ScoreInfo? Query(Expression<Func<ScoreInfo, bool>> query)
|
||||
{
|
||||
return Realm.Run(r => r.All<ScoreInfo>().FirstOrDefault(query)?.Detach());
|
||||
}
|
||||
|
||||
private ScoreInfo? getDatabasedScoreInfo(IScoreInfo originalScoreInfo)
|
||||
{
|
||||
ScoreInfo? databasedScoreInfo = null;
|
||||
|
||||
if (originalScoreInfo.OnlineID > 0)
|
||||
databasedScoreInfo = Query(s => s.OnlineID == originalScoreInfo.OnlineID);
|
||||
|
||||
if (originalScoreInfo.LegacyOnlineID > 0)
|
||||
databasedScoreInfo ??= Query(s => s.LegacyOnlineID == originalScoreInfo.LegacyOnlineID);
|
||||
|
||||
if (originalScoreInfo is ScoreInfo scoreInfo)
|
||||
databasedScoreInfo ??= Query(s => s.Hash == scoreInfo.Hash);
|
||||
|
||||
if (databasedScoreInfo == null)
|
||||
{
|
||||
Logger.Log("The requested score could not be found locally.", LoggingTarget.Information);
|
||||
return null;
|
||||
}
|
||||
|
||||
return databasedScoreInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bindable that represents the total score of a <see cref="ScoreInfo"/>.
|
||||
/// </summary>
|
||||
@ -80,7 +114,7 @@ namespace osu.Game.Scoring
|
||||
/// </remarks>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||
/// <returns>The bindable containing the total score.</returns>
|
||||
public Bindable<long> GetBindableTotalScore([NotNull] ScoreInfo score) => new TotalScoreBindable(score, configManager);
|
||||
public Bindable<long> GetBindableTotalScore(ScoreInfo score) => new TotalScoreBindable(score, configManager);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
|
||||
@ -90,7 +124,7 @@ namespace osu.Game.Scoring
|
||||
/// </remarks>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||
/// <returns>The bindable containing the formatted total score string.</returns>
|
||||
public Bindable<string> GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
|
||||
public Bindable<string> GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
|
||||
|
||||
/// <summary>
|
||||
/// Provides the total score of a <see cref="ScoreInfo"/>. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
|
||||
@ -104,7 +138,7 @@ namespace osu.Game.Scoring
|
||||
/// </summary>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
|
||||
/// <param name="configManager">The config.</param>
|
||||
public TotalScoreBindable(ScoreInfo score, OsuConfigManager configManager)
|
||||
public TotalScoreBindable(ScoreInfo score, OsuConfigManager? configManager)
|
||||
{
|
||||
configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode);
|
||||
scoringMode.BindValueChanged(mode => Value = score.GetDisplayScore(mode.NewValue), true);
|
||||
@ -126,7 +160,7 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete([CanBeNull] Expression<Func<ScoreInfo, bool>> filter = null, bool silent = false)
|
||||
public void Delete(Expression<Func<ScoreInfo, bool>>? filter = null, bool silent = false)
|
||||
{
|
||||
Realm.Run(r =>
|
||||
{
|
||||
@ -163,11 +197,25 @@ namespace osu.Game.Scoring
|
||||
|
||||
public Task<IEnumerable<Live<ScoreInfo>>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) => scoreImporter.Import(notification, tasks);
|
||||
|
||||
public Task Export(ScoreInfo score) => scoreExporter.ExportAsync(score.ToLive(Realm));
|
||||
/// <summary>
|
||||
/// Export a replay from a given <see cref="IScoreInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="scoreInfo">The <see cref="IScoreInfo"/> to export.</param>
|
||||
/// <returns>The <see cref="Task"/>. Return <see cref="Task.CompletedTask"/> if the score cannot be found in the database.</returns>
|
||||
/// <remarks>
|
||||
/// The <see cref="IScoreInfo"/> is re-retrieved from the database to ensure all the required data
|
||||
/// for exporting a replay are present (may have missing properties if it was retrieved from online data).
|
||||
/// </remarks>
|
||||
public Task Export(ScoreInfo scoreInfo)
|
||||
{
|
||||
ScoreInfo? databasedScoreInfo = getDatabasedScoreInfo(scoreInfo);
|
||||
|
||||
public Task<Live<ScoreInfo>> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original);
|
||||
return databasedScoreInfo == null ? Task.CompletedTask : scoreExporter.ExportAsync(databasedScoreInfo.ToLive(Realm));
|
||||
}
|
||||
|
||||
public Live<ScoreInfo> Import(ScoreInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
||||
public Task<Live<ScoreInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original);
|
||||
|
||||
public Live<ScoreInfo>? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
||||
scoreImporter.ImportModel(item, archive, parameters, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@ -182,7 +230,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
#region Implementation of IPresentImports<ScoreInfo>
|
||||
|
||||
public Action<IEnumerable<Live<ScoreInfo>>> PresentImport
|
||||
public Action<IEnumerable<Live<ScoreInfo>>>? PresentImport
|
||||
{
|
||||
set => scoreImporter.PresentImport = value;
|
||||
}
|
||||
|
@ -101,6 +101,11 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
public IBindable<bool> ShowingOverlayComponents = new Bindable<bool>();
|
||||
|
||||
// Should match PlayerLoader for consistency. Cached here for the rare case we push a Player
|
||||
// without the loading screen (one such usage is the skin editor's scene library).
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
@ -1200,6 +1205,7 @@ namespace osu.Game.Screens.Play
|
||||
var importableScore = score.ScoreInfo.DeepClone();
|
||||
|
||||
var imported = scoreManager.Import(importableScore, replayReader);
|
||||
Debug.Assert(imported != null);
|
||||
|
||||
imported.PerformRead(s =>
|
||||
{
|
||||
|
@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (state.NewValue != DownloadState.LocallyAvailable) return;
|
||||
|
||||
scoreManager.Export(importedScore);
|
||||
if (importedScore != null) scoreManager.Export(importedScore);
|
||||
|
||||
this.state.ValueChanged -= exportWhenReady;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user