1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 19:27:24 +08:00

Merge branch 'master' into aim-refactor-ppcalc

This commit is contained in:
Dan Balasescu 2021-11-08 01:38:18 +09:00 committed by GitHub
commit 547feaa392
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 641 additions and 668 deletions

View File

@ -14,8 +14,8 @@
"jb"
]
},
"smoogipoo.nvika": {
"version": "1.0.3",
"nvika": {
"version": "2.2.0",
"commands": [
"nvika"
]

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1103.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1106.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -86,20 +86,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
[BackgroundDependencyLoader]
private void load()
{
InternalChild = foregroundBuffer = new BufferedContainer
InternalChild = foregroundBuffer = new BufferedContainer(cachedFrameBuffer: true)
{
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
new Box { RelativeSizeAxes = Axes.Both },
subtractionBuffer = new BufferedContainer
subtractionBuffer = new BufferedContainer(cachedFrameBuffer: true)
{
RelativeSizeAxes = Axes.Both,
// This is needed because we're blending with another object
BackgroundColour = Color4.White.Opacity(0),
CacheDrawnFrameBuffer = true,
// The 'hole' is achieved by subtracting the result of this container with the parent
Blending = new BlendingParameters { AlphaEquation = BlendingEquation.ReverseSubtract },
Child = subtractionLayer = new CircularContainer

View File

@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.531832890435525d, "diffcalc-test")]
[TestCase(1.4644923495008817d, "zero-length-sliders")]
[TestCase(6.6975550434910005d, "diffcalc-test")]
[TestCase(1.4673500058356748d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
[TestCase(8.8067616302940852d, "diffcalc-test")]
[TestCase(1.7763214959309293d, "zero-length-sliders")]
[TestCase(8.938989502378238d, "diffcalc-test")]
[TestCase(1.779323508403831d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime());

View File

@ -54,7 +54,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModRelax))
{
effectiveMissCount += countOk + countMeh;
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
multiplier *= 0.6;
}
@ -248,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int calculateEffectiveMissCount()
{
// guess the number of misses + slider breaks from combo
// Guess the number of misses + slider breaks from combo
double comboBasedMissCount = 0.0;
if (Attributes.SliderCount > 0)
@ -258,7 +260,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
}
// we're clamping misscount because since its derived from combo it can be higher than total hits and that breaks some calculations
// Clamp misscount since it's derived from combo and can be higher than total hits and that breaks some calculations
comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits);
return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double wide_angle_multiplier = 1.5;
private const double acute_angle_multiplier = 2.0;
private const double slider_multiplier = 1.5;
private const double vel_change_multiplier = 0.75;
private const double velocity_change_multiplier = 0.75;
private double currentStrain = 1;
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double wideAngleBonus = 0;
double acuteAngleBonus = 0;
double sliderBonus = 0;
double velChangeBonus = 0;
double velocityChangeBonus = 0;
double aimStrain = currVelocity; // Start strain with regular velocity.
@ -107,26 +107,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
prevVelocity = (osuLastObj.JumpDistance + osuLastObj.TravelDistance) / osuLastObj.StrainTime;
currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime;
// scale with ratio of difference compared to 0.5 * max dist.
// Scale with ratio of difference compared to 0.5 * max dist.
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
// reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity));
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity) // reward for % distance slowed down compared to previous, paying attention to not award overlap
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.JumpDistance, osuLastObj.JumpDistance) / 100)), 2); // do not award overlap
velChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio; // choose larger distance, multiplied by ratio.
// Reward for % distance slowed down compared to previous, paying attention to not award overlap
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
// do not award overlap
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.JumpDistance, osuLastObj.JumpDistance) / 100)), 2);
velChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2); // penalize for rhythm changes.
// Choose the largest bonus, multiplied by ratio.
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
// Penalize for rhythm changes.
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
}
if (osuCurrObj.TravelTime != 0)
{
sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // add some slider rewards
// Reward sliders based on velocity.
sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
}
// Add in acute angle bonus or wide angle bonus + velchange bonus, whichever is larger.
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velChangeBonus * vel_change_multiplier);
aimStrain += sliderBonus * slider_multiplier; // Add in additional slider velocity.
// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier);
// Add in additional slider velocity bonus.
aimStrain += sliderBonus * slider_multiplier;
return aimStrain;
}

View File

@ -136,10 +136,9 @@ namespace osu.Game.Rulesets.Osu.Statistics
}
}
},
bufferedGrid = new BufferedContainer
bufferedGrid = new BufferedContainer(cachedFrameBuffer: true)
{
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
BackgroundColour = Color4Extensions.FromHex("#202624").Opacity(0),
Child = pointGrid = new GridContainer
{

View File

@ -673,6 +673,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null));
// ReSharper disable once HeuristicUnreachableCode
// weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159.
Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier));
Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15)));

View File

@ -846,6 +846,42 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
// TODO: needs to be pulled across to realm implementation when this file is nuked.
[Test]
public void TestSaveRemovesInvalidCharactersFromPath()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
{
try
{
var osu = LoadOsuIntoHost(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
var beatmap = working.Beatmap;
beatmap.BeatmapInfo.Version = "difficulty";
beatmap.BeatmapInfo.Metadata = new BeatmapMetadata
{
Artist = "Artist/With\\Slashes",
Title = "Title",
AuthorString = "mapper",
};
manager.Save(beatmap.BeatmapInfo, working.Beatmap);
Assert.AreEqual("Artist_With_Slashes - Title (mapper) [difficulty].osu", beatmap.BeatmapInfo.Path);
}
finally
{
host.Exit();
}
}
}
[Test]
public void TestCreateNewEmptyBeatmap()
{

View File

@ -35,7 +35,7 @@ namespace osu.Game.Tests.Editing.Checks
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.AudioFile = null;
beatmap.Metadata.AudioFile = string.Empty;
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.BackgroundFile = null;
beatmap.Metadata.BackgroundFile = string.Empty;
var context = getContext(null, System.Array.Empty<byte>());
Assert.That(check.Run(context), Is.Empty);

View File

@ -65,7 +65,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestBackgroundNotSet()
{
beatmap.Metadata.BackgroundFile = null;
beatmap.Metadata.BackgroundFile = string.Empty;
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();

View File

@ -105,6 +105,9 @@ namespace osu.Game.Tests.Mods
testMod.ResetSettingsToDefaults();
Assert.That(testMod.DrainRate.Value, Is.Null);
// ReSharper disable once HeuristicUnreachableCode
// see https://youtrack.jetbrains.com/issue/RIDER-70159.
Assert.That(testMod.OverallDifficulty.Value, Is.Null);
var applied = applyDifficulty(new BeatmapDifficulty

View File

@ -39,8 +39,8 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestCheckNullID()
{
var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved };
var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved };
var ourInfo = new BeatmapSetInfo { Hash = "1" };
var otherInfo = new BeatmapSetInfo { Hash = "2" };
Assert.AreNotEqual(ourInfo, otherInfo);
}

View File

@ -189,7 +189,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
exampleBeatmapInfo.Metadata.ArtistUnicode = null;
exampleBeatmapInfo.Metadata.ArtistUnicode = string.Empty;
var criteria = new FilterCriteria
{

View File

@ -79,8 +79,17 @@ namespace osu.Game.Tests.NonVisual
public List<HitObject> HitObjects;
public override IEnumerable<HitObject> Objects => HitObjects;
public override event Action<JudgementResult> NewResult;
public override event Action<JudgementResult> RevertResult;
public override event Action<JudgementResult> NewResult
{
add => throw new InvalidOperationException();
remove => throw new InvalidOperationException();
}
public override event Action<JudgementResult> RevertResult
{
add => throw new InvalidOperationException();
remove => throw new InvalidOperationException();
}
public override Playfield Playfield { get; }
public override Container Overlays { get; }
@ -95,9 +104,6 @@ namespace osu.Game.Tests.NonVisual
public TestDrawableRuleset()
: base(new OsuRuleset())
{
// won't compile without this.
NewResult?.Invoke(null);
RevertResult?.Invoke(null);
}
public override void SetReplayScore(Score replayScore) => throw new NotImplementedException();

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
@ -67,9 +68,11 @@ namespace osu.Game.Tests.Online
var deserialised = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var converted = (TestModTimeRamp)deserialised?.ToMod(new TestRuleset());
Assert.That(converted?.AdjustPitch.Value, Is.EqualTo(false));
Assert.That(converted?.InitialRate.Value, Is.EqualTo(1.25));
Assert.That(converted?.FinalRate.Value, Is.EqualTo(0.25));
Assert.That(converted, Is.Not.Null);
Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false));
Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25));
Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25));
}
[Test]
@ -121,11 +124,11 @@ namespace osu.Game.Tests.Online
new TestModDifficultyAdjust()
};
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new System.NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty;

View File

@ -168,19 +168,19 @@ namespace osu.Game.Tests.Online
return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
}
protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager manager, IAPIProvider api, GameHost host)
protected override BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> manager, IAPIProvider api, GameHost host)
{
return new TestBeatmapModelDownloader(manager, api, host);
}
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
{
public TestBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider apiProvider, GameHost gameHost)
: base(modelManager, apiProvider, gameHost)
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider, GameHost gameHost)
: base(importer, apiProvider, gameHost)
{
}
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize)
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize)
=> new TestDownloadRequest(set);
}
@ -202,12 +202,12 @@ namespace osu.Game.Tests.Online
}
}
private class TestDownloadRequest : ArchiveDownloadRequest<BeatmapSetInfo>
private class TestDownloadRequest : ArchiveDownloadRequest<IBeatmapSetInfo>
{
public new void SetProgress(float progress) => base.SetProgress(progress);
public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename);
public TestDownloadRequest(BeatmapSetInfo model)
public TestDownloadRequest(IBeatmapSetInfo model)
: base(model)
{
}

View File

@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new List<APIBeatmap>
Beatmaps = new[]
{
new APIBeatmap
{
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = beatmaps,
Beatmaps = beatmaps.ToArray(),
};
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
{
RulesetID = difficulty.rulesetId,
StarRating = difficulty.stars
}).ToList()
}).ToArray()
};
[Test]

View File

@ -5,7 +5,10 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
@ -29,6 +32,9 @@ namespace osu.Game.Tests.Visual.Editing
private ComposeBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();
private ContextMenuContainer contextMenuContainer
=> Editor.ChildrenOfType<ContextMenuContainer>().First();
private void moveMouseToObject(Func<HitObject> targetFunc)
{
AddStep("move mouse to object", () =>
@ -42,6 +48,19 @@ namespace osu.Game.Tests.Visual.Editing
});
}
[Test]
public void TestSelectAndShowContextMenu()
{
var addedObject = new HitCircle { StartTime = 100, Position = new Vector2(100, 100) };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
moveMouseToObject(() => addedObject);
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddUntilStep("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
AddUntilStep("context menu is visible", () => contextMenuContainer.ChildrenOfType<OsuContextMenu>().Single().State == MenuState.Open);
}
[Test]
public void TestNudgeSelection()
{

View File

@ -23,10 +23,10 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set metadata", () =>
{
editorBeatmap.Metadata.Artist = "Example Artist";
editorBeatmap.Metadata.ArtistUnicode = null;
editorBeatmap.Metadata.ArtistUnicode = string.Empty;
editorBeatmap.Metadata.Title = "Example Title";
editorBeatmap.Metadata.TitleUnicode = null;
editorBeatmap.Metadata.TitleUnicode = string.Empty;
});
createSection();
@ -44,10 +44,10 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set metadata", () =>
{
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
editorBeatmap.Metadata.Artist = null;
editorBeatmap.Metadata.Artist = string.Empty;
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
editorBeatmap.Metadata.Title = null;
editorBeatmap.Metadata.Title = string.Empty;
});
createSection();
@ -86,10 +86,10 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set metadata", () =>
{
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
editorBeatmap.Metadata.Artist = null;
editorBeatmap.Metadata.Artist = string.Empty;
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
editorBeatmap.Metadata.Title = null;
editorBeatmap.Metadata.Title = string.Empty;
});
createSection();

View File

@ -235,8 +235,17 @@ namespace osu.Game.Tests.Visual.Gameplay
public override IEnumerable<HitObject> Objects => new[] { new HitCircle { HitWindows = HitWindows } };
public override event Action<JudgementResult> NewResult;
public override event Action<JudgementResult> RevertResult;
public override event Action<JudgementResult> NewResult
{
add => throw new InvalidOperationException();
remove => throw new InvalidOperationException();
}
public override event Action<JudgementResult> RevertResult
{
add => throw new InvalidOperationException();
remove => throw new InvalidOperationException();
}
public override Playfield Playfield { get; }
public override Container Overlays { get; }
@ -251,9 +260,6 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestDrawableRuleset()
: base(new OsuRuleset())
{
// won't compile without this.
NewResult?.Invoke(null);
RevertResult?.Invoke(null);
}
public override void SetReplayScore(Score replayScore) => throw new NotImplementedException();

View File

@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay
ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
})
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
ScreenSpaceToGamefield = pos => recordingManager?.ToLocalSpace(pos) ?? Vector2.Zero,
},
Child = new Container
{
@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
GamefieldToScreenSpace = pos => playbackManager?.ToScreenSpace(pos) ?? Vector2.Zero,
},
Child = new Container
{

View File

@ -67,6 +67,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
}),
createLoungeRoom(new Room
{
Name = { Value = "Multiplayer room" },
Status = { Value = new RoomStatusOpen() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem
{
Beatmap =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarDifficulty = 2.5,
Metadata =
{
Artist = "very very very very very very very very very long artist",
ArtistUnicode = "very very very very very very very very very long artist",
Title = "very very very very very very very very very very very long title",
TitleUnicode = "very very very very very very very very very very very long title",
}
}
}.BeatmapInfo,
}
}
}
}),
createLoungeRoom(new Room
{
Name = { Value = "Playlist room with multiple beatmaps" },
Status = { Value = new RoomStatusPlaying() },

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -223,11 +224,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist()
{
var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally.
var byOnlineId = CreateAPIBeatmap();
byOnlineId.OnlineID = 1337; // Some random ID that does not exist locally.
var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally.
var byChecksum = CreateAPIBeatmap();
byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally.
createPlaylist(byOnlineId, byChecksum);
@ -237,8 +238,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestExplicitBeatmapItem()
{
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
beatmap.BeatmapSet.OnlineInfo.HasExplicitContent = true;
var beatmap = CreateAPIBeatmap();
Debug.Assert(beatmap.BeatmapSet != null);
beatmap.BeatmapSet.HasExplicitContent = true;
createPlaylist(beatmap);
}
@ -310,7 +314,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
}
private void createPlaylist(params BeatmapInfo[] beatmaps)
private void createPlaylist(params IBeatmapInfo[] beatmaps)
{
AddStep("create playlist", () =>
{

View File

@ -1,7 +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.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -37,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
{
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = enabledRulesets.Select(r => new APIBeatmap { RulesetID = r.OnlineID }).ToList()
Beatmaps = enabledRulesets.Select(r => new APIBeatmap { RulesetID = r.OnlineID }).ToArray()
};
});
@ -55,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online
{
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = new List<APIBeatmap>
Beatmaps = new[]
{
new APIBeatmap
{
@ -71,10 +70,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestEmptyBeatmapSet()
{
AddStep("load empty beatmapset", () => selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = new List<APIBeatmap>()
});
AddStep("load empty beatmapset", () => selector.BeatmapSet = new APIBeatmapSet());
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));

View File

@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online
Ratings = Enumerable.Range(0, 11).ToArray(),
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new List<APIBeatmap>
Beatmaps = new[]
{
new APIBeatmap
{
@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Online
var set = getBeatmapSet();
set.Beatmaps = beatmaps;
set.Beatmaps = beatmaps.ToArray();
overlay.ShowBeatmapSet(set);
});
@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Online
});
}
set.Beatmaps = beatmaps;
set.Beatmaps = beatmaps.ToArray();
return set;
}

View File

@ -1,7 +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.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -46,7 +45,7 @@ namespace osu.Game.Tests.Visual.Online
static APIBeatmapSet createSet() => new APIBeatmapSet
{
Beatmaps = new List<APIBeatmap>
Beatmaps = new[]
{
new APIBeatmap
{

View File

@ -9,7 +9,6 @@ using osu.Game.Beatmaps;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Resources;
using osuTK;
@ -110,7 +109,7 @@ namespace osu.Game.Tests.Visual.Online
private IBeatmapSetInfo getDownloadableBeatmapSet()
{
var apiBeatmapSet = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo.OnlineInfo;
var apiBeatmapSet = CreateAPIBeatmapSet();
apiBeatmapSet.HasVideo = true;
apiBeatmapSet.HasStoryboard = true;
@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual.Online
private IBeatmapSetInfo getUndownloadableBeatmapSet()
{
var apiBeatmapSet = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo.OnlineInfo;
var apiBeatmapSet = CreateAPIBeatmapSet();
apiBeatmapSet.Artist = "test";
apiBeatmapSet.Title = "undownloadable";

View File

@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new List<APIBeatmap>
Beatmaps = new[]
{
new APIBeatmap
{
@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Online
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = beatmaps,
Beatmaps = beatmaps.ToArray(),
};
}
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect
@ -32,159 +31,112 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestAllMetrics()
{
AddStep("all metrics", () => details.BeatmapInfo = new BeatmapInfo
AddStep("all metrics", () => details.BeatmapInfo = new APIBeatmap
{
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
}
},
Version = "All Metrics",
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Source = "osu!",
Tags = "this beatmap has all the metrics",
Ratings = Enumerable.Range(0, 11).ToArray(),
},
BaseDifficulty = new BeatmapDifficulty
DifficultyName = "All Metrics",
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
StarRating = 5.3f,
FailTimes = new APIFailTimes
{
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
StarDifficulty = 5.3f,
Ruleset = new OsuRuleset().RulesetInfo,
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
}
[Test]
public void TestAllMetricsExceptSource()
{
AddStep("all except source", () => details.BeatmapInfo = new BeatmapInfo
AddStep("all except source", () => details.BeatmapInfo = new APIBeatmap
{
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
}
},
Version = "All Metrics",
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Tags = "this beatmap has all the metrics",
Ratings = Enumerable.Range(0, 11).ToArray(),
},
BaseDifficulty = new BeatmapDifficulty
DifficultyName = "All Metrics",
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
StarRating = 5.3f,
FailTimes = new APIFailTimes
{
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
StarDifficulty = 5.3f,
Ruleset = new OsuRuleset().RulesetInfo,
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
}
[Test]
public void TestOnlyRatings()
{
AddStep("ratings", () => details.BeatmapInfo = new BeatmapInfo
AddStep("ratings", () => details.BeatmapInfo = new APIBeatmap
{
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
}
},
Version = "Only Ratings",
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
Source = "osu!",
Tags = "this beatmap has ratings metrics but not retries or fails",
},
Ruleset = new OsuRuleset().RulesetInfo,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 6,
DrainRate = 9,
OverallDifficulty = 6,
ApproachRate = 6,
},
StarDifficulty = 4.8f,
DifficultyName = "Only Ratings",
CircleSize = 6,
DrainRate = 9,
OverallDifficulty = 6,
ApproachRate = 6,
StarRating = 4.8f,
});
}
[Test]
public void TestOnlyFailsAndRetries()
{
AddStep("fails retries", () => details.BeatmapInfo = new BeatmapInfo
AddStep("fails retries", () => details.BeatmapInfo = new APIBeatmap
{
Version = "Only Retries and Fails",
Metadata = new BeatmapMetadata
DifficultyName = "Only Retries and Fails",
BeatmapSet = new APIBeatmapSet
{
Source = "osu!",
Tags = "this beatmap has retries and fails but no ratings",
},
BaseDifficulty = new BeatmapDifficulty
CircleSize = 3.7f,
DrainRate = 6,
OverallDifficulty = 6,
ApproachRate = 7,
StarRating = 2.91f,
FailTimes = new APIFailTimes
{
CircleSize = 3.7f,
DrainRate = 6,
OverallDifficulty = 6,
ApproachRate = 7,
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
Ruleset = new OsuRuleset().RulesetInfo,
StarDifficulty = 2.91f,
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
}
[Test]
public void TestNoMetrics()
{
AddStep("no metrics", () => details.BeatmapInfo = new BeatmapInfo
AddStep("no metrics", () => details.BeatmapInfo = new APIBeatmap
{
Version = "No Metrics",
Metadata = new BeatmapMetadata
DifficultyName = "No Metrics",
BeatmapSet = new APIBeatmapSet
{
Source = "osu!",
Tags = "this beatmap has no metrics",
},
Ruleset = new OsuRuleset().RulesetInfo,
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 5,
OverallDifficulty = 5.5f,
ApproachRate = 6.5f,
},
StarDifficulty = 1.97f,
CircleSize = 5,
DrainRate = 5,
OverallDifficulty = 5.5f,
ApproachRate = 6.5f,
StarRating = 1.97f,
});
}
@ -197,10 +149,9 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestOnlineMetrics()
{
AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new BeatmapInfo
AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new APIBeatmap
{
OnlineBeatmapID = 162,
Ruleset = new OsuRuleset().RulesetInfo
OnlineID = 162,
});
AddStep("set online", () => api.SetState(APIState.Online));
AddStep("set offline", () => api.SetState(APIState.Offline));

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
@ -65,10 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface
private void createPaddedComponent(bool hasDescription = false, bool padded = true)
{
LabelledDrawable<Drawable> component = null;
AddStep("create component", () =>
{
LabelledDrawable<Drawable> component;
Child = new Container
{
Anchor = Anchor.Centre,
@ -81,6 +82,8 @@ namespace osu.Game.Tests.Visual.UserInterface
component.Label = "a sample component";
component.Description = hasDescription ? "this text describes the component" : string.Empty;
});
AddAssert($"description {(hasDescription ? "visible" : "hidden")}", () => component.ChildrenOfType<TextFlowContainer>().ElementAt(1).IsPresent == hasDescription);
}
private class PaddedLabelledDrawable : LabelledDrawable<Drawable>

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep("setup cover", () => Child = new UpdateableOnlineBeatmapSetCover(coverType)
{
OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo,
OnlineInfo = CreateAPIBeatmapSet(),
RelativeSizeAxes = Axes.Both,
Masking = true,
});
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("setup covers", () =>
{
BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
var beatmapSet = CreateAPIBeatmapSet();
FillFlowContainer fillFlow;
@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var cover = new UpdateableOnlineBeatmapSetCover(coverType)
{
OnlineInfo = setInfo.OnlineInfo,
OnlineInfo = beatmapSet,
Height = 100,
Masking = true,
};
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover
{
OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo,
OnlineInfo = CreateAPIBeatmapSet(),
RelativeSizeAxes = Axes.Both,
Masking = true,
});
@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover(0)
{
OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg").OnlineInfo,
OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"),
RelativeSizeAxes = Axes.Both,
Masking = true,
Alpha = 0.4f
@ -128,16 +128,13 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1);
AddStep("switch beatmap",
() => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg").OnlineInfo);
() => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg"));
AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType<OnlineBeatmapSetCover>().Except(new[] { initialCover }).Any());
}
private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo
private static APIBeatmapSet createBeatmapWithCover(string coverUrl) => new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
{
Covers = new BeatmapSetOnlineCovers { Cover = coverUrl }
}
Covers = new BeatmapSetOnlineCovers { Cover = coverUrl }
};
private class TestUpdateableOnlineBeatmapSetCover : UpdateableOnlineBeatmapSetCover

View File

@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
[Serializable]
public class BeatmapInfo : IEquatable<BeatmapInfo>, IHasPrimaryKey, IBeatmapInfo, IBeatmapOnlineInfo
public class BeatmapInfo : IEquatable<BeatmapInfo>, IHasPrimaryKey, IBeatmapInfo
{
public int ID { get; set; }
@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps
string IBeatmapInfo.DifficultyName => Version;
[JsonIgnore]
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata();
[JsonIgnore]
IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty;
@ -201,24 +201,5 @@ namespace osu.Game.Beatmaps
double IBeatmapInfo.StarRating => StarDifficulty;
#endregion
#region Implementation of IBeatmapOnlineInfo
[JsonIgnore]
public int CircleCount => OnlineInfo.CircleCount;
[JsonIgnore]
public int SliderCount => OnlineInfo.SliderCount;
[JsonIgnore]
public int PlayCount => OnlineInfo.PlayCount;
[JsonIgnore]
public int PassCount => OnlineInfo.PassCount;
[JsonIgnore]
public APIFailTimes FailTimes => OnlineInfo.FailTimes;
#endregion
}
}

View File

@ -11,14 +11,14 @@ namespace osu.Game.Beatmaps
/// <summary>
/// A user-presentable display title representing this beatmap.
/// </summary>
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{getClosestMetadata(beatmapInfo)} {getVersionString(beatmapInfo)}".Trim();
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata} {getVersionString(beatmapInfo)}".Trim();
/// <summary>
/// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
/// </summary>
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo, bool includeDifficultyName = true, bool includeCreator = true)
{
var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(includeCreator);
var metadata = beatmapInfo.Metadata.GetDisplayTitleRomanisable(includeCreator);
if (includeDifficultyName)
{
@ -32,12 +32,8 @@ namespace osu.Game.Beatmaps
public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[]
{
beatmapInfo.DifficultyName
}.Concat(getClosestMetadata(beatmapInfo).GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
}.Concat(beatmapInfo.Metadata.GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
// temporary helper methods until we figure which metadata should be where.
private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) =>
beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet?.Metadata ?? new BeatmapMetadata();
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps
/// Handles general operations related to global beatmap management.
/// </summary>
[ExcludeFromDynamicCompile]
public class BeatmapManager : IModelDownloader<BeatmapSetInfo>, IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, BeatmapSetFileInfo>, IWorkingBeatmapCache, IDisposable
public class BeatmapManager : IModelDownloader<IBeatmapSetInfo>, IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, BeatmapSetFileInfo>, IModelImporter<BeatmapSetInfo>, IWorkingBeatmapCache, IDisposable
{
private readonly BeatmapModelManager beatmapModelManager;
private readonly BeatmapModelDownloader beatmapModelDownloader;
@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
}
}
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IBeatmapModelManager modelManager, IAPIProvider api, GameHost host)
protected virtual BeatmapModelDownloader CreateBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> modelManager, IAPIProvider api, GameHost host)
{
return new BeatmapModelDownloader(modelManager, api, host);
}
@ -114,7 +114,8 @@ namespace osu.Game.Beatmaps
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) => beatmapModelManager.Save(info, beatmapContent, beatmapSkin);
public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) =>
beatmapModelManager.Save(info, beatmapContent, beatmapSkin);
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
@ -245,33 +246,16 @@ namespace osu.Game.Beatmaps
#region Implementation of IModelDownloader<BeatmapSetInfo>
public IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> DownloadBegan => beatmapModelDownloader.DownloadBegan;
public IBindable<WeakReference<ArchiveDownloadRequest<IBeatmapSetInfo>>> DownloadBegan => beatmapModelDownloader.DownloadBegan;
public IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> DownloadFailed => beatmapModelDownloader.DownloadFailed;
public IBindable<WeakReference<ArchiveDownloadRequest<IBeatmapSetInfo>>> DownloadFailed => beatmapModelDownloader.DownloadFailed;
// Temporary method until this class supports IBeatmapSetInfo or otherwise.
public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false)
{
return beatmapModelDownloader.Download(new BeatmapSetInfo
{
OnlineBeatmapSetID = model.OnlineID,
Metadata = new BeatmapMetadata
{
Title = model.Metadata?.Title,
Artist = model.Metadata?.Artist,
TitleUnicode = model.Metadata?.TitleUnicode,
ArtistUnicode = model.Metadata?.ArtistUnicode,
Author = new User { Username = model.Metadata?.Author },
}
}, minimiseDownloadSize);
}
public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false)
{
return beatmapModelDownloader.Download(model, minimiseDownloadSize);
}
public ArchiveDownloadRequest<BeatmapSetInfo> GetExistingDownload(BeatmapSetInfo model)
public ArchiveDownloadRequest<IBeatmapSetInfo> GetExistingDownload(IBeatmapSetInfo model)
{
return beatmapModelDownloader.GetExistingDownload(model);
}

View File

@ -9,6 +9,8 @@ using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Users;
#nullable enable
namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
@ -17,21 +19,21 @@ namespace osu.Game.Beatmaps
{
public int ID { get; set; }
public string Title { get; set; }
public string Title { get; set; } = string.Empty;
[JsonProperty("title_unicode")]
public string TitleUnicode { get; set; }
public string TitleUnicode { get; set; } = string.Empty;
public string Artist { get; set; }
public string Artist { get; set; } = string.Empty;
[JsonProperty("artist_unicode")]
public string ArtistUnicode { get; set; }
public string ArtistUnicode { get; set; } = string.Empty;
[JsonIgnore]
public List<BeatmapInfo> Beatmaps { get; set; }
public List<BeatmapInfo> Beatmaps { get; set; } = new List<BeatmapInfo>();
[JsonIgnore]
public List<BeatmapSetInfo> BeatmapSets { get; set; }
public List<BeatmapSetInfo> BeatmapSets { get; set; } = new List<BeatmapSetInfo>();
/// <summary>
/// Helper property to deserialize a username to <see cref="User"/>.
@ -55,7 +57,7 @@ namespace osu.Game.Beatmaps
[Column("Author")]
public string AuthorString
{
get => Author?.Username;
get => Author?.Username ?? string.Empty;
set
{
Author ??= new User();
@ -67,22 +69,22 @@ namespace osu.Game.Beatmaps
/// The author of the beatmaps in this set.
/// </summary>
[JsonIgnore]
public User Author;
public User? Author;
public string Source { get; set; }
public string Source { get; set; } = string.Empty;
[JsonProperty(@"tags")]
public string Tags { get; set; }
public string Tags { get; set; } = string.Empty;
/// <summary>
/// The time in milliseconds to begin playing the track for preview purposes.
/// If -1, the track should begin playing at 40% of its length.
/// </summary>
public int PreviewTime { get; set; }
public int PreviewTime { get; set; } = -1;
public string AudioFile { get; set; }
public string AudioFile { get; set; } = string.Empty;
public string BackgroundFile { get; set; }
public string BackgroundFile { get; set; } = string.Empty;
public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other);

View File

@ -8,16 +8,16 @@ using osu.Game.Online.API.Requests;
namespace osu.Game.Beatmaps
{
public class BeatmapModelDownloader : ModelDownloader<BeatmapSetInfo>
public class BeatmapModelDownloader : ModelDownloader<BeatmapSetInfo, IBeatmapSetInfo>
{
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) =>
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
public override ArchiveDownloadRequest<BeatmapSetInfo> GetExistingDownload(BeatmapSetInfo model)
public override ArchiveDownloadRequest<IBeatmapSetInfo> GetExistingDownload(IBeatmapSetInfo model)
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID);
public BeatmapModelDownloader(IBeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
: base(beatmapModelManager, api, host)
public BeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api, GameHost host = null)
: base(beatmapImporter, api, host)
{
}
}

View File

@ -216,7 +216,8 @@ namespace osu.Game.Beatmaps
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
// metadata may have changed; update the path with the standard format.
beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu";
beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu");
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
// update existing or populate new file's filename.

View File

@ -6,10 +6,8 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Beatmaps
{
@ -32,13 +30,11 @@ namespace osu.Game.Beatmaps
public List<BeatmapInfo> Beatmaps { get; set; }
public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
[NotNull]
public List<BeatmapSetFileInfo> Files { get; set; } = new List<BeatmapSetFileInfo>();
// This field is temporary and only used by `APIBeatmapSet.ToBeatmapSet` (soon to be removed) and tests (to be updated to provide APIBeatmapSet instead).
[NotMapped]
public APIBeatmapSet OnlineInfo { get; set; }
/// <summary>
/// The maximum star difficulty of all beatmaps in this set.
/// </summary>
@ -100,80 +96,5 @@ namespace osu.Game.Beatmaps
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;
#endregion
#region Delegation for IBeatmapSetOnlineInfo
[NotMapped]
[JsonIgnore]
public DateTimeOffset Submitted => OnlineInfo.Submitted;
[NotMapped]
[JsonIgnore]
public DateTimeOffset? Ranked => OnlineInfo.Ranked;
[NotMapped]
[JsonIgnore]
public DateTimeOffset? LastUpdated => OnlineInfo.LastUpdated;
[JsonIgnore]
public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
[NotMapped]
[JsonIgnore]
public bool HasExplicitContent => OnlineInfo.HasExplicitContent;
[NotMapped]
[JsonIgnore]
public bool HasVideo => OnlineInfo.HasVideo;
[NotMapped]
[JsonIgnore]
public bool HasStoryboard => OnlineInfo.HasStoryboard;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineCovers Covers => OnlineInfo.Covers;
[NotMapped]
[JsonIgnore]
public string Preview => OnlineInfo.Preview;
[NotMapped]
[JsonIgnore]
public double BPM => OnlineInfo.BPM;
[NotMapped]
[JsonIgnore]
public int PlayCount => OnlineInfo.PlayCount;
[NotMapped]
[JsonIgnore]
public int FavouriteCount => OnlineInfo.FavouriteCount;
[NotMapped]
[JsonIgnore]
public bool HasFavourited => OnlineInfo.HasFavourited;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineAvailability Availability => OnlineInfo.Availability;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineGenre Genre => OnlineInfo.Genre;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineLanguage Language => OnlineInfo.Language;
[NotMapped]
[JsonIgnore]
public int? TrackId => OnlineInfo?.TrackId;
[NotMapped]
[JsonIgnore]
public int[] Ratings => OnlineInfo?.Ratings;
#endregion
}
}

View File

@ -57,12 +57,7 @@ namespace osu.Game.Beatmaps.Drawables
return new OnlineBeatmapSetCover(online, beatmapSetCoverType);
if (model is BeatmapInfo localModel)
{
if (localModel.BeatmapSet?.OnlineInfo != null)
return new OnlineBeatmapSetCover(localModel.BeatmapSet.OnlineInfo, beatmapSetCoverType);
return new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(localModel));
}
return new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap);
}

View File

@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps.Formats
{
writer.WriteLine("[General]");
if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}"));
@ -126,13 +126,13 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[Metadata]");
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.TitleUnicode)) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.ArtistUnicode)) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}"));
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}"));
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps
/// <summary>
/// The metadata representing this beatmap. May be shared between multiple beatmaps.
/// </summary>
IBeatmapMetadataInfo? Metadata { get; }
IBeatmapMetadataInfo Metadata { get; }
/// <summary>
/// The difficulty settings for this beatmap.

View File

@ -149,7 +149,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground()
{
if (Metadata?.BackgroundFile == null)
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
return null;
try
@ -165,7 +165,7 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack()
{
if (Metadata?.AudioFile == null)
if (string.IsNullOrEmpty(Metadata?.AudioFile))
return null;
try
@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps
protected override Waveform GetWaveform()
{
if (Metadata?.AudioFile == null)
if (string.IsNullOrEmpty(Metadata?.AudioFile))
return null;
try

View File

@ -30,7 +30,7 @@ namespace osu.Game.Database
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
public abstract class ArchiveModelManager<TModel, TFileModel> : IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
public abstract class ArchiveModelManager<TModel, TFileModel> : IModelImporter<TModel>, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : class, INamedFileInfo, IHasPrimaryKey, new()
{
@ -466,7 +466,7 @@ namespace osu.Game.Database
if (retrievedItem == null)
throw new ArgumentException(@"Specified model could not be found", nameof(item));
string filename = $"{getValidFilename(item.ToString())}{HandledExtensions.First()}";
string filename = $"{GetValidFilename(item.ToString())}{HandledExtensions.First()}";
using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create))
ExportModelTo(retrievedItem, stream);
@ -913,9 +913,15 @@ namespace osu.Game.Database
return Guid.NewGuid().ToString();
}
private string getValidFilename(string filename)
private readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars()
// Backslash is added to avoid issues when exporting to zip.
// See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143.
.Append('\\')
.ToArray();
protected string GetValidFilename(string filename)
{
foreach (char c in Path.GetInvalidFileNameChars())
foreach (char c in invalidFilenameCharacters)
filename = filename.Replace(c, '_');
return filename;
}

View File

@ -10,35 +10,35 @@ namespace osu.Game.Database
/// <summary>
/// Represents a <see cref="IModelManager{TModel}"/> that can download new models from an external source.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
public interface IModelDownloader<TModel> : IPostNotifications
where TModel : class
/// <typeparam name="T">The item's interface type.</typeparam>
public interface IModelDownloader<T> : IPostNotifications
where T : class
{
/// <summary>
/// Fired when a <typeparamref name="TModel"/> download begins.
/// Fired when a <typeparamref name="T"/> download begins.
/// This is NOT run on the update thread and should be scheduled.
/// </summary>
IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadBegan { get; }
IBindable<WeakReference<ArchiveDownloadRequest<T>>> DownloadBegan { get; }
/// <summary>
/// Fired when a <typeparamref name="TModel"/> download is interrupted, either due to user cancellation or failure.
/// Fired when a <typeparamref name="T"/> download is interrupted, either due to user cancellation or failure.
/// This is NOT run on the update thread and should be scheduled.
/// </summary>
IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadFailed { get; }
IBindable<WeakReference<ArchiveDownloadRequest<T>>> DownloadFailed { get; }
/// <summary>
/// Begin a download for the requested <typeparamref name="TModel"/>.
/// Begin a download for the requested <typeparamref name="T"/>.
/// </summary>
/// <param name="model">The <stypeparamref name="TModel"/> to be downloaded.</param>
/// <param name="item">The <stypeparamref name="T"/> to be downloaded.</param>
/// <param name="minimiseDownloadSize">Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle..</param>
/// <returns>Whether the download was started.</returns>
bool Download(TModel model, bool minimiseDownloadSize);
bool Download(T item, bool minimiseDownloadSize);
/// <summary>
/// Gets an existing <typeparamref name="TModel"/> download request if it exists.
/// Gets an existing <typeparamref name="T"/> download request if it exists.
/// </summary>
/// <param name="model">The <typeparamref name="TModel"/> whose request is wanted.</param>
/// <returns>The <see cref="ArchiveDownloadRequest{TModel}"/> object if it exists, otherwise null.</returns>
ArchiveDownloadRequest<TModel> GetExistingDownload(TModel model);
/// <param name="item">The <typeparamref name="T"/> whose request is wanted.</param>
/// <returns>The <see cref="ArchiveDownloadRequest{T}"/> object if it exists, otherwise null.</returns>
ArchiveDownloadRequest<T> GetExistingDownload(T item);
}
}

View File

@ -14,7 +14,7 @@ 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<TModel> : IModelImporter<TModel>
public interface IModelManager<TModel>
where TModel : class
{
/// <summary>

View File

@ -14,39 +14,40 @@ using osu.Game.Overlays.Notifications;
namespace osu.Game.Database
{
public abstract class ModelDownloader<TModel> : IModelDownloader<TModel>
where TModel : class, IHasPrimaryKey, ISoftDelete, IEquatable<TModel>
public abstract class ModelDownloader<TModel, T> : IModelDownloader<T>
where TModel : class, IHasPrimaryKey, ISoftDelete, IEquatable<TModel>, T
where T : class
{
public Action<Notification> PostNotification { protected get; set; }
public IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadBegan => downloadBegan;
public IBindable<WeakReference<ArchiveDownloadRequest<T>>> DownloadBegan => downloadBegan;
private readonly Bindable<WeakReference<ArchiveDownloadRequest<TModel>>> downloadBegan = new Bindable<WeakReference<ArchiveDownloadRequest<TModel>>>();
private readonly Bindable<WeakReference<ArchiveDownloadRequest<T>>> downloadBegan = new Bindable<WeakReference<ArchiveDownloadRequest<T>>>();
public IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> DownloadFailed => downloadFailed;
public IBindable<WeakReference<ArchiveDownloadRequest<T>>> DownloadFailed => downloadFailed;
private readonly Bindable<WeakReference<ArchiveDownloadRequest<TModel>>> downloadFailed = new Bindable<WeakReference<ArchiveDownloadRequest<TModel>>>();
private readonly Bindable<WeakReference<ArchiveDownloadRequest<T>>> downloadFailed = new Bindable<WeakReference<ArchiveDownloadRequest<T>>>();
private readonly IModelManager<TModel> modelManager;
private readonly IModelImporter<TModel> importer;
private readonly IAPIProvider api;
protected readonly List<ArchiveDownloadRequest<TModel>> CurrentDownloads = new List<ArchiveDownloadRequest<TModel>>();
protected readonly List<ArchiveDownloadRequest<T>> CurrentDownloads = new List<ArchiveDownloadRequest<T>>();
protected ModelDownloader(IModelManager<TModel> modelManager, IAPIProvider api, IIpcHost importHost = null)
protected ModelDownloader(IModelImporter<TModel> importer, IAPIProvider api, IIpcHost importHost = null)
{
this.modelManager = modelManager;
this.importer = importer;
this.api = api;
}
/// <summary>
/// Creates the download request for this <typeparamref name="TModel"/>.
/// Creates the download request for this <typeparamref name="T"/>.
/// </summary>
/// <param name="model">The <typeparamref name="TModel"/> to be downloaded.</param>
/// <param name="model">The <typeparamref name="T"/> to be downloaded.</param>
/// <param name="minimiseDownloadSize">Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.</param>
/// <returns>The request object.</returns>
protected abstract ArchiveDownloadRequest<TModel> CreateDownloadRequest(TModel model, bool minimiseDownloadSize);
protected abstract ArchiveDownloadRequest<T> CreateDownloadRequest(T model, bool minimiseDownloadSize);
public bool Download(TModel model, bool minimiseDownloadSize = false)
public bool Download(T model, bool minimiseDownloadSize = false)
{
if (!canDownload(model)) return false;
@ -68,11 +69,11 @@ namespace osu.Game.Database
Task.Factory.StartNew(async () =>
{
// This gets scheduled back to the update thread, but we want the import to run in the background.
var imported = await modelManager.Import(notification, new ImportTask(filename)).ConfigureAwait(false);
var imported = await importer.Import(notification, new ImportTask(filename)).ConfigureAwait(false);
// for now a failed import will be marked as a failed download for simplicity.
if (!imported.Any())
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<T>>(request);
CurrentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning);
@ -91,25 +92,25 @@ namespace osu.Game.Database
api.PerformAsync(request);
downloadBegan.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
downloadBegan.Value = new WeakReference<ArchiveDownloadRequest<T>>(request);
return true;
void triggerFailure(Exception error)
{
CurrentDownloads.Remove(request);
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<T>>(request);
notification.State = ProgressNotificationState.Cancelled;
if (!(error is OperationCanceledException))
Logger.Error(error, $"{modelManager.HumanisedModelName.Titleize()} download failed!");
Logger.Error(error, $"{importer.HumanisedModelName.Titleize()} download failed!");
}
}
public abstract ArchiveDownloadRequest<TModel> GetExistingDownload(TModel model);
public abstract ArchiveDownloadRequest<T> GetExistingDownload(T model);
private bool canDownload(TModel model) => GetExistingDownload(model) == null && api != null;
private bool canDownload(T model) => GetExistingDownload(model) == null && api != null;
private class DownloadNotification : ProgressNotification
{

View File

@ -58,10 +58,9 @@ namespace osu.Game.Graphics.Backgrounds
{
RemoveInternal(Sprite);
AddInternal(bufferedContainer = new BufferedContainer
AddInternal(bufferedContainer = new BufferedContainer(cachedFrameBuffer: true)
{
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
RedrawOnScale = false,
Child = Sprite
});

View File

@ -69,12 +69,11 @@ namespace osu.Game.Graphics.Sprites
Children = new Drawable[]
{
new BufferedContainer
new BufferedContainer(cachedFrameBuffer: true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(4),
CacheDrawnFrameBuffer = true,
RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,

View File

@ -1,7 +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.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osuTK;
@ -22,15 +24,9 @@ namespace osu.Game.Graphics.UserInterface
Enabled.Value = !isLoading;
if (value)
{
loading.Show();
OnLoadStarted();
}
else
{
loading.Hide();
OnLoadFinished();
}
}
}
@ -44,18 +40,34 @@ namespace osu.Game.Graphics.UserInterface
protected LoadingButton()
{
AddRange(new[]
Add(loading = new LoadingSpinner
{
CreateContent(),
loading = new LoadingSpinner
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(12)
}
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(12),
Depth = -1,
});
}
[BackgroundDependencyLoader]
private void load()
{
Add(CreateContent());
}
protected override void LoadComplete()
{
base.LoadComplete();
loading.State.BindValueChanged(s =>
{
if (s.NewValue == Visibility.Visible)
OnLoadStarted();
else
OnLoadFinished();
}, true);
}
protected override bool OnClick(ClickEvent e)
{
if (!Enabled.Value)

View File

@ -168,7 +168,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
descriptionText.Text = value;
if (value == default)
if (!string.IsNullOrEmpty(value.ToString()))
descriptionText.Show();
else
descriptionText.Hide();

View File

@ -6,11 +6,11 @@ using osu.Game.Beatmaps;
namespace osu.Game.Online.API.Requests
{
public class DownloadBeatmapSetRequest : ArchiveDownloadRequest<BeatmapSetInfo>
public class DownloadBeatmapSetRequest : ArchiveDownloadRequest<IBeatmapSetInfo>
{
private readonly bool noVideo;
public DownloadBeatmapSetRequest(BeatmapSetInfo set, bool noVideo)
public DownloadBeatmapSetRequest(IBeatmapSetInfo set, bool noVideo)
: base(set)
{
this.noVideo = noVideo;
@ -25,6 +25,6 @@ namespace osu.Game.Online.API.Requests
protected override string FileExtension => ".osz";
protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}";
protected override string Target => $@"beatmapsets/{Model.OnlineID}/download{(noVideo ? "?noVideo=1" : "")}";
}
}

View File

@ -5,15 +5,15 @@ using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests
{
public class DownloadReplayRequest : ArchiveDownloadRequest<ScoreInfo>
public class DownloadReplayRequest : ArchiveDownloadRequest<IScoreInfo>
{
public DownloadReplayRequest(ScoreInfo score)
public DownloadReplayRequest(IScoreInfo score)
: base(score)
{
}
protected override string FileExtension => ".osr";
protected override string Target => $@"scores/{Model.Ruleset.ShortName}/{Model.OnlineScoreID}/download";
protected override string Target => $@"scores/{Model.Ruleset.ShortName}/{Model.OnlineID}/download";
}
}

View File

@ -81,34 +81,6 @@ namespace osu.Game.Online.API.Requests.Responses
public double BPM { get; set; }
public virtual BeatmapInfo ToBeatmapInfo(RulesetStore rulesets)
{
var set = BeatmapSet?.ToBeatmapSet(rulesets);
return new BeatmapInfo
{
Metadata = set?.Metadata ?? new BeatmapMetadata(),
Ruleset = rulesets.GetRuleset(RulesetID),
StarDifficulty = StarRating,
OnlineBeatmapID = OnlineID,
Version = DifficultyName,
// this is actually an incorrect mapping (Length is calculated as drain length in lazer's import process, see BeatmapManager.calculateLength).
Length = Length,
Status = Status,
MD5Hash = Checksum,
BeatmapSet = set,
MaxCombo = MaxCombo,
BaseDifficulty = new BeatmapDifficulty
{
DrainRate = DrainRate,
CircleSize = CircleSize,
ApproachRate = ApproachRate,
OverallDifficulty = OverallDifficulty,
},
OnlineInfo = this,
};
}
#region Implementation of IBeatmapInfo
public IBeatmapMetadataInfo Metadata => (BeatmapSet as IBeatmapSetInfo)?.Metadata ?? new BeatmapMetadata();

View File

@ -3,11 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Users;
#nullable enable
@ -119,28 +117,7 @@ namespace osu.Game.Online.API.Requests.Responses
public string Tags { get; set; } = string.Empty;
[JsonProperty(@"beatmaps")]
public IEnumerable<APIBeatmap> Beatmaps { get; set; } = Array.Empty<APIBeatmap>();
public virtual BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{
var beatmapSet = new BeatmapSetInfo
{
OnlineBeatmapSetID = OnlineID,
Metadata = metadata,
Status = Status,
OnlineInfo = this
};
beatmapSet.Beatmaps = Beatmaps.Select(b =>
{
var beatmap = b.ToBeatmapInfo(rulesets);
beatmap.BeatmapSet = beatmapSet;
beatmap.Metadata = beatmapSet.Metadata;
return beatmap;
}).ToList();
return beatmapSet;
}
public APIBeatmap[] Beatmaps { get; set; } = Array.Empty<APIBeatmap>();
private BeatmapMetadata metadata => new BeatmapMetadata
{

View File

@ -98,7 +98,7 @@ namespace osu.Game.Online.API.Requests.Responses
{
TotalScore = TotalScore,
MaxCombo = MaxCombo,
BeatmapInfo = Beatmap?.ToBeatmapInfo(rulesets),
BeatmapInfo = beatmap,
User = User,
Accuracy = Accuracy,
OnlineScoreID = OnlineID,
@ -111,9 +111,6 @@ namespace osu.Game.Online.API.Requests.Responses
Mods = modInstances,
};
if (beatmap != null)
scoreInfo.BeatmapInfo = beatmap;
if (Statistics != null)
{
foreach (var kvp in Statistics)

View File

@ -16,7 +16,7 @@ namespace osu.Game.Online
[Resolved(CanBeNull = true)]
protected BeatmapManager? Manager { get; private set; }
private ArchiveDownloadRequest<BeatmapSetInfo>? attachedRequest;
private ArchiveDownloadRequest<IBeatmapSetInfo>? attachedRequest;
public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem)
: base(trackedItem)
@ -25,8 +25,8 @@ namespace osu.Game.Online
private IBindable<WeakReference<BeatmapSetInfo>>? managerUpdated;
private IBindable<WeakReference<BeatmapSetInfo>>? managerRemoved;
private IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>>? managerDownloadBegan;
private IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>>? managerDownloadFailed;
private IBindable<WeakReference<ArchiveDownloadRequest<IBeatmapSetInfo>>>? managerDownloadBegan;
private IBindable<WeakReference<ArchiveDownloadRequest<IBeatmapSetInfo>>>? managerDownloadFailed;
[BackgroundDependencyLoader(true)]
private void load()
@ -52,7 +52,7 @@ namespace osu.Game.Online
managerRemoved.BindValueChanged(itemRemoved);
}
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> weakRequest)
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<IBeatmapSetInfo>>> weakRequest)
{
if (weakRequest.NewValue.TryGetTarget(out var request))
{
@ -64,7 +64,7 @@ namespace osu.Game.Online
}
}
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> weakRequest)
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<IBeatmapSetInfo>>> weakRequest)
{
if (weakRequest.NewValue.TryGetTarget(out var request))
{
@ -76,7 +76,7 @@ namespace osu.Game.Online
}
}
private void attachDownload(ArchiveDownloadRequest<BeatmapSetInfo>? request)
private void attachDownload(ArchiveDownloadRequest<IBeatmapSetInfo>? request)
{
if (attachedRequest != null)
{

View File

@ -17,6 +17,7 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Rulesets;
@ -644,24 +645,32 @@ namespace osu.Game.Online.Multiplayer
RoomUpdated?.Invoke();
GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() =>
GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(task => Schedule(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
updatePlaylist(settings, set.Result);
APIBeatmapSet beatmapSet = task.Result;
// The incoming response is deserialised without circular reference handling currently.
// Because we require using metadata from this instance, populate the nested beatmaps' sets manually here.
foreach (var b in beatmapSet.Beatmaps)
b.BeatmapSet = beatmapSet;
updatePlaylist(settings, beatmapSet);
}), TaskContinuationOptions.OnlyOnRanToCompletion);
}, cancellationToken);
private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet)
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet beatmapSet)
{
if (Room == null || !Room.Settings.Equals(settings))
return;
Debug.Assert(APIRoom != null);
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
beatmap.MD5Hash = settings.BeatmapChecksum;
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineID == settings.BeatmapID);
beatmap.Checksum = settings.BeatmapChecksum;
var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance();
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
@ -694,12 +703,12 @@ namespace osu.Game.Online.Multiplayer
}
/// <summary>
/// Retrieves a <see cref="BeatmapSetInfo"/> from an online source.
/// Retrieves a <see cref="APIBeatmapSet"/> from an online source.
/// </summary>
/// <param name="beatmapId">The beatmap set ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param>
/// <returns>The <see cref="BeatmapSetInfo"/> retrieval task.</returns>
protected abstract Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default);
/// <returns>The <see cref="APIBeatmapSet"/> retrieval task.</returns>
protected abstract Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default);
/// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.

View File

@ -9,9 +9,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
namespace osu.Game.Online.Multiplayer
@ -148,9 +148,9 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch));
}
protected override Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<BeatmapSetInfo>();
var tcs = new TaskCompletionSource<APIBeatmapSet>();
var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId);
req.Success += res =>
@ -161,7 +161,7 @@ namespace osu.Game.Online.Multiplayer
return;
}
tcs.SetResult(res.ToBeatmapSet(Rulesets));
tcs.SetResult(res);
};
req.Failure += e => tcs.SetException(e);

View File

@ -7,6 +7,7 @@ using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@ -61,7 +62,7 @@ namespace osu.Game.Online.Rooms
[CanBeNull]
public MultiplayerScoresAround ScoresAround { get; set; }
public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem)
public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap)
{
var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance();
@ -70,7 +71,7 @@ namespace osu.Game.Online.Rooms
OnlineScoreID = ID,
TotalScore = TotalScore,
MaxCombo = MaxCombo,
BeatmapInfo = playlistItem.Beatmap.Value,
BeatmapInfo = beatmap,
BeatmapInfoID = playlistItem.BeatmapID,
Ruleset = playlistItem.Ruleset.Value,
RulesetID = playlistItem.RulesetID,

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -52,6 +53,8 @@ namespace osu.Game.Online.Rooms
downloadTracker?.RemoveAndDisposeImmediately();
Debug.Assert(item.NewValue.Beatmap.Value.BeatmapSet != null);
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
AddInternal(downloadTracker);

View File

@ -31,7 +31,7 @@ namespace osu.Game.Online.Rooms
public bool Expired { get; set; }
[JsonIgnore]
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
[JsonIgnore]
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
@ -65,13 +65,13 @@ namespace osu.Game.Online.Rooms
public PlaylistItem()
{
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineBeatmapID ?? 0);
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1);
Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0);
}
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
public void MapObjects(RulesetStore rulesets)
{
Beatmap.Value ??= apiBeatmap.ToBeatmapInfo(rulesets);
Beatmap.Value ??= apiBeatmap;
Ruleset.Value ??= rulesets.GetRuleset(RulesetID);
Ruleset rulesetInstance = Ruleset.Value.CreateInstance();

View File

@ -16,7 +16,7 @@ namespace osu.Game.Online
[Resolved(CanBeNull = true)]
protected ScoreManager? Manager { get; private set; }
private ArchiveDownloadRequest<ScoreInfo>? attachedRequest;
private ArchiveDownloadRequest<IScoreInfo>? attachedRequest;
public ScoreDownloadTracker(ScoreInfo trackedItem)
: base(trackedItem)
@ -25,8 +25,8 @@ namespace osu.Game.Online
private IBindable<WeakReference<ScoreInfo>>? managerUpdated;
private IBindable<WeakReference<ScoreInfo>>? managerRemoved;
private IBindable<WeakReference<ArchiveDownloadRequest<ScoreInfo>>>? managerDownloadBegan;
private IBindable<WeakReference<ArchiveDownloadRequest<ScoreInfo>>>? managerDownloadFailed;
private IBindable<WeakReference<ArchiveDownloadRequest<IScoreInfo>>>? managerDownloadBegan;
private IBindable<WeakReference<ArchiveDownloadRequest<IScoreInfo>>>? managerDownloadFailed;
[BackgroundDependencyLoader(true)]
private void load()
@ -56,7 +56,7 @@ namespace osu.Game.Online
managerRemoved.BindValueChanged(itemRemoved);
}
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<ScoreInfo>>> weakRequest)
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<IScoreInfo>>> weakRequest)
{
if (weakRequest.NewValue.TryGetTarget(out var request))
{
@ -68,7 +68,7 @@ namespace osu.Game.Online
}
}
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<ScoreInfo>>> weakRequest)
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<IScoreInfo>>> weakRequest)
{
if (weakRequest.NewValue.TryGetTarget(out var request))
{
@ -80,7 +80,7 @@ namespace osu.Game.Online
}
}
private void attachDownload(ArchiveDownloadRequest<ScoreInfo>? request)
private void attachDownload(ArchiveDownloadRequest<IScoreInfo>? request)
{
if (attachedRequest != null)
{
@ -144,7 +144,7 @@ namespace osu.Game.Online
}
}
private bool checkEquality(ScoreInfo x, ScoreInfo y) => x.OnlineScoreID == y.OnlineScoreID;
private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.OnlineID == y.OnlineID;
#region Disposal

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -22,6 +23,11 @@ namespace osu.Game.Overlays.BeatmapListing
public BeatmapSearchMultipleSelectionFilterRow(LocalisableString header)
: base(header)
{
}
[BackgroundDependencyLoader]
private void load()
{
Current.BindTo(filter.Current);
}
@ -31,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapListing
/// <summary>
/// Creates a filter control that can be used to simultaneously select multiple values of type <typeparamref name="T"/>.
/// </summary>
[NotNull]
protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter();
protected class MultipleSelectionFilter : FillFlowContainer<MultipleSelectionFilterTabItem>

View File

@ -147,7 +147,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
var icons = new List<DifficultyIcon>();
if (SetInfo.Beatmaps.Count() > maximum_difficulty_icons)
if (SetInfo.Beatmaps.Length > maximum_difficulty_icons)
{
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.Where(b => b.RulesetID == ruleset.OnlineID).ToList(), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -66,7 +67,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
if (value?.Scores.Any() != true)
return;
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.ToBeatmapInfo(rulesets))).ToArray(), loadCancellationSource.Token)
var apiBeatmap = Beatmap.Value;
Debug.Assert(apiBeatmap != null);
// TODO: temporary. should be removed once `OrderByTotalScore` can accept `IScoreInfo`.
var beatmapInfo = new BeatmapInfo
{
MaxCombo = apiBeatmap.MaxCombo,
Status = apiBeatmap.Status
};
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token)
.ContinueWith(ordered => Schedule(() =>
{
if (loadCancellationSource.IsCancellationRequested)
@ -74,11 +86,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
var topScore = ordered.Result.First();
scoreTable.DisplayScores(ordered.Result, topScore.BeatmapInfo?.Status.GrantsPerformancePoints() == true);
scoreTable.DisplayScores(ordered.Result, apiBeatmap.Status.GrantsPerformancePoints());
scoreTable.Show();
var userScore = value.UserScore;
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value.ToBeatmapInfo(rulesets));
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, beatmapInfo);
topScoresContainer.Add(new DrawableTopScore(topScore));

View File

@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Changelog
public static LocalisableString ListingString => LayoutStrings.HeaderChangelogIndex;
private readonly Bindable<APIUpdateStream> currentStream = new Bindable<APIUpdateStream>();
private Box streamsBackground;
public ChangelogHeader()
@ -39,7 +41,7 @@ namespace osu.Game.Overlays.Changelog
Build.ValueChanged += showBuild;
Streams.Current.ValueChanged += e =>
currentStream.ValueChanged += e =>
{
if (e.NewValue?.LatestBuild != null && !e.NewValue.Equals(Build.Value?.UpdateStream))
Build.Value = e.NewValue.LatestBuild;
@ -67,7 +69,7 @@ namespace osu.Game.Overlays.Changelog
else
{
Current.Value = ListingString;
Streams.Current.Value = null;
currentStream.Value = null;
}
}
@ -92,7 +94,7 @@ namespace osu.Game.Overlays.Changelog
Horizontal = 65,
Vertical = 20
},
Child = Streams = new ChangelogUpdateStreamControl()
Child = Streams = new ChangelogUpdateStreamControl { Current = currentStream },
}
}
};
@ -110,7 +112,7 @@ namespace osu.Game.Overlays.Changelog
if (Build.Value == null)
return;
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Build.Value.UpdateStream.Name);
currentStream.Value = Streams.Items.FirstOrDefault(s => s.Name == Build.Value.UpdateStream.Name);
}
private class ChangelogHeaderTitle : OverlayTitle

View File

@ -175,6 +175,8 @@ namespace osu.Game.Overlays.Comments
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
private readonly string text;
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
@ -184,10 +186,10 @@ namespace osu.Game.Overlays.Comments
public CommitButton(string text)
{
this.text = text;
AutoSizeAxes = Axes.Both;
LoadingAnimationSize = new Vector2(10);
drawableText.Text = text;
}
[BackgroundDependencyLoader]
@ -232,7 +234,8 @@ namespace osu.Game.Overlays.Comments
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Margin = new MarginPadding { Horizontal = 20 }
Margin = new MarginPadding { Horizontal = 20 },
Text = text,
}
}
};

View File

@ -366,14 +366,13 @@ namespace osu.Game.Overlays
private readonly WorkingBeatmap beatmap;
public Background(WorkingBeatmap beatmap = null)
: base(cachedFrameBuffer: true)
{
this.beatmap = beatmap;
Depth = float.MaxValue;
RelativeSizeAxes = Axes.Both;
CacheDrawnFrameBuffer = true;
Children = new Drawable[]
{
sprite = new Sprite

View File

@ -37,11 +37,7 @@ namespace osu.Game.Overlays
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(20),
Action = () =>
{
ScrollToStart();
Button.State = Visibility.Hidden;
}
Action = scrollToTop
});
}
@ -58,6 +54,12 @@ namespace osu.Game.Overlays
Button.State = Target > button_scroll_position ? Visibility.Visible : Visibility.Hidden;
}
private void scrollToTop()
{
ScrollToStart();
Button.State = Visibility.Hidden;
}
public class ScrollToTopButton : OsuHoverContainer
{
private const int fade_duration = 500;

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit.Checks
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
string audioFile = context.Beatmap.Metadata?.AudioFile;
if (audioFile == null)
if (string.IsNullOrEmpty(audioFile))
yield break;
var track = context.WorkingBeatmap.Track;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit.Checks
{
string filename = GetFilename(context.Beatmap);
if (filename == null)
if (string.IsNullOrEmpty(filename))
{
yield return new IssueTemplateNoneSet(this).Create(TypeOfFile);

View File

@ -30,11 +30,6 @@ namespace osu.Game.Rulesets.Mods
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
[Obsolete("Use the mod-supporting override")] // can be removed 20210731
public virtual Score CreateReplayScore(IBeatmap beatmap) => new Score { Replay = new Replay() };
#pragma warning disable 618
public virtual Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => CreateReplayScore(beatmap);
#pragma warning restore 618
public virtual Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score { Replay = new Replay() };
}
}

View File

@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring
{
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>
public class ScoreManager : IModelManager<ScoreInfo>, IModelImporter<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<IScoreInfo>
{
private readonly Scheduler scheduler;
private readonly Func<BeatmapDifficultyCache> difficulties;
@ -350,16 +350,14 @@ namespace osu.Game.Scoring
#region Implementation of IModelDownloader<ScoreInfo>
public IBindable<WeakReference<ArchiveDownloadRequest<ScoreInfo>>> DownloadBegan => scoreModelDownloader.DownloadBegan;
public IBindable<WeakReference<ArchiveDownloadRequest<IScoreInfo>>> DownloadBegan => scoreModelDownloader.DownloadBegan;
public IBindable<WeakReference<ArchiveDownloadRequest<ScoreInfo>>> DownloadFailed => scoreModelDownloader.DownloadFailed;
public IBindable<WeakReference<ArchiveDownloadRequest<IScoreInfo>>> DownloadFailed => scoreModelDownloader.DownloadFailed;
public bool Download(ScoreInfo model, bool minimiseDownloadSize)
{
return scoreModelDownloader.Download(model, minimiseDownloadSize);
}
public bool Download(IScoreInfo model, bool minimiseDownloadSize) =>
scoreModelDownloader.Download(model, minimiseDownloadSize);
public ArchiveDownloadRequest<ScoreInfo> GetExistingDownload(ScoreInfo model)
public ArchiveDownloadRequest<IScoreInfo> GetExistingDownload(IScoreInfo model)
{
return scoreModelDownloader.GetExistingDownload(model);
}

View File

@ -8,16 +8,16 @@ using osu.Game.Online.API.Requests;
namespace osu.Game.Scoring
{
public class ScoreModelDownloader : ModelDownloader<ScoreInfo>
public class ScoreModelDownloader : ModelDownloader<ScoreInfo, IScoreInfo>
{
public ScoreModelDownloader(ScoreModelManager scoreManager, IAPIProvider api, IIpcHost importHost = null)
public ScoreModelDownloader(IModelImporter<ScoreInfo> scoreManager, IAPIProvider api, IIpcHost importHost = null)
: base(scoreManager, api, importHost)
{
}
protected override ArchiveDownloadRequest<ScoreInfo> CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score);
protected override ArchiveDownloadRequest<IScoreInfo> CreateDownloadRequest(IScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score);
public override ArchiveDownloadRequest<ScoreInfo> GetExistingDownload(ScoreInfo model)
=> CurrentDownloads.Find(r => r.Model.OnlineScoreID == model.OnlineScoreID);
public override ArchiveDownloadRequest<IScoreInfo> GetExistingDownload(IScoreInfo model)
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID);
}
}

View File

@ -108,11 +108,20 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnMouseDown(MouseDownEvent e)
{
bool selectionPerformed = performMouseDownActions(e);
// even if a selection didn't occur, a drag event may still move the selection.
bool movementPossible = prepareSelectionMovement();
return selectionPerformed || (e.Button == MouseButton.Left && movementPossible);
// check if selection has occurred
if (selectionPerformed)
{
// only unmodified right click should show context menu
bool shouldShowContextMenu = e.Button == MouseButton.Right && !e.ShiftPressed && !e.AltPressed && !e.SuperPressed;
// stop propagation if not showing context menu
return !shouldShowContextMenu;
}
// even if a selection didn't occur, a drag event may still move the selection.
return e.Button == MouseButton.Left && movementPossible;
}
protected SelectionBlueprint<T> ClickedBlueprint { get; private set; }

View File

@ -393,6 +393,7 @@ namespace osu.Game.Screens.Menu
public class OutlineTriangle : BufferedContainer
{
public OutlineTriangle(bool outlineOnly, float size)
: base(cachedFrameBuffer: true)
{
Size = new Vector2(size);
@ -414,7 +415,6 @@ namespace osu.Game.Screens.Menu
}
Blending = BlendingParameters.Additive;
CacheDrawnFrameBuffer = true;
}
}
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osuTK;
using osuTK.Graphics;
@ -61,7 +62,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
var beatmap = playlistItem?.Beatmap.Value;
if (background?.BeatmapInfo?.BeatmapSet?.OnlineInfo?.Covers.Cover == beatmap?.BeatmapSet?.OnlineInfo?.Covers.Cover)
string? lastCover = (background?.Beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover;
string? newCover = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover;
if (lastCover == newCover)
return;
cancellationSource?.Cancel();

View File

@ -13,11 +13,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public class PlaylistItemBackground : Background
{
public readonly BeatmapInfo? BeatmapInfo;
public readonly IBeatmapInfo? Beatmap;
public PlaylistItemBackground(PlaylistItem? playlistItem)
{
BeatmapInfo = playlistItem?.Beatmap.Value;
Beatmap = playlistItem?.Beatmap.Value;
}
[BackgroundDependencyLoader]
@ -26,8 +26,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
Texture? texture = null;
// prefer online cover where available.
if (BeatmapInfo?.BeatmapSet?.OnlineInfo?.Covers.Cover != null)
texture = textures.Get(BeatmapInfo.BeatmapSet.OnlineInfo.Covers.Cover);
if (Beatmap?.BeatmapSet is IBeatmapSetOnlineInfo online)
texture = textures.Get(online.Covers.Cover);
Sprite.Texture = texture ?? beatmaps.DefaultBeatmap.Background;
}
@ -38,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
if (ReferenceEquals(this, other)) return true;
return other.GetType() == GetType()
&& ((PlaylistItemBackground)other).BeatmapInfo == BeatmapInfo;
&& ((PlaylistItemBackground)other).Beatmap == Beatmap;
}
}
}

View File

@ -119,7 +119,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
try
{
foreach (var pi in room.Playlist)
pi.MapObjects(beatmaps, rulesets);
pi.MapObjects(rulesets);
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
if (existing == null)

View File

@ -80,10 +80,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
{
var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarDifficulty).ToArray();
var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray();
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarDifficulty : 0, 0);
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarDifficulty : 0, 0);
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
minDisplay.Current.Value = minDifficulty;
maxDisplay.Current.Value = maxDifficulty;

View File

@ -27,6 +27,7 @@ using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@ -45,7 +46,7 @@ namespace osu.Game.Screens.OnlinePlay
private ExplicitContentBeatmapPill explicitContentPill;
private ModDisplay modDisplay;
private readonly Bindable<BeatmapInfo> beatmap = new Bindable<BeatmapInfo>();
private readonly Bindable<IBeatmapInfo> beatmap = new Bindable<IBeatmapInfo>();
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
private readonly BindableList<Mod> requiredMods = new BindableList<Mod>();
@ -96,6 +97,7 @@ namespace osu.Game.Screens.OnlinePlay
}
private ScheduledDelegate scheduledRefresh;
private PanelBackground panelBackground;
private void scheduleRefresh()
{
@ -105,23 +107,25 @@ namespace osu.Game.Screens.OnlinePlay
private void refresh()
{
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) };
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) };
panelBackground.Beatmap.Value = Item.Beatmap.Value;
beatmapText.Clear();
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text =>
{
text.Truncate = true;
});
authorText.Clear();
if (Item.Beatmap?.Value?.Metadata?.Author != null)
if (!string.IsNullOrEmpty(Item.Beatmap.Value?.Metadata.Author))
{
authorText.AddText("mapped by ");
authorText.AddUserLink(Item.Beatmap.Value?.Metadata.Author);
authorText.AddUserLink(new User { Username = Item.Beatmap.Value.Metadata.Author });
}
bool hasExplicitContent = Item.Beatmap.Value.BeatmapSet.OnlineInfo?.HasExplicitContent == true;
bool hasExplicitContent = (Item.Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true;
explicitContentPill.Alpha = hasExplicitContent ? 1 : 0;
modDisplay.Current.Value = requiredMods.ToArray();
@ -145,10 +149,9 @@ namespace osu.Game.Screens.OnlinePlay
Alpha = 0,
AlwaysPresent = true
},
new PanelBackground
panelBackground = new PanelBackground
{
RelativeSizeAxes = Axes.Both,
Beatmap = { BindTarget = beatmap }
},
new GridContainer
{
@ -181,8 +184,11 @@ namespace osu.Game.Screens.OnlinePlay
{
beatmapText = new LinkFlowContainer(fontParameters)
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
// workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end).
// TODO: remove when text/link flow can support truncation with ellipsis natively.
Height = OsuFont.DEFAULT_FONT_SIZE,
Masking = true
},
new FillFlowContainer
{
@ -334,7 +340,7 @@ namespace osu.Game.Screens.OnlinePlay
// For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap
private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222)
{
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
public PanelBackground()
{

View File

@ -353,7 +353,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
})
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
// workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end).
// TODO: remove when text/link flow can support truncation with ellipsis natively.
Height = 16,
Masking = true
}
}
}
@ -381,11 +384,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
statusText.Text = "Currently playing ";
beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap,
item.NewValue.Beatmap.Value.OnlineBeatmapID.ToString(),
item.NewValue.Beatmap.Value.OnlineID.ToString(),
creationParameters: s =>
{
s.Truncate = true;
s.RelativeSizeAxes = Axes.X;
});
}
}

View File

@ -369,7 +369,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
var beatmap = SelectedItem.Value?.Beatmap.Value;
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID);
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}

View File

@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private void load(IBindable<RulesetInfo> ruleset)
{
// Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem
if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineBeatmapID)
if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineID)
throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap");
if (ruleset.Value.ID != PlaylistItem.Ruleset.Value.ID)

View File

@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
/// <param name="pivot">An optional pivot around which the scores were retrieved.</param>
private void performSuccessCallback([NotNull] Action<IEnumerable<ScoreInfo>> callback, [NotNull] List<MultiplayerScore> scores, [CanBeNull] MultiplayerScores pivot = null)
{
var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem)).ToArray();
var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem, Beatmap.Value.BeatmapInfo)).ToArray();
// Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration,
// calculate the total scores locally before invoking the success callback.

View File

@ -32,9 +32,9 @@ namespace osu.Game.Screens.Play.Break
}
public BlurredIcon()
: base(cachedFrameBuffer: true)
{
RelativePositionAxes = Axes.X;
CacheDrawnFrameBuffer = true;
Child = icon = new SpriteIcon
{
Origin = Anchor.Centre,

View File

@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.Settings;
@ -50,7 +51,6 @@ namespace osu.Game.Screens.Play
private Container beatmapPanelContainer;
private TriangleButton watchButton;
private SettingsCheckbox automaticDownload;
private BeatmapSetInfo onlineBeatmap;
/// <summary>
/// The player's immediate online gameplay state.
@ -60,6 +60,8 @@ namespace osu.Game.Screens.Play
private GetBeatmapSetRequest onlineBeatmapRequest;
private APIBeatmapSet beatmapSet;
public SoloSpectator([NotNull] User targetUser)
: base(targetUser.Id)
{
@ -220,10 +222,10 @@ namespace osu.Game.Screens.Play
Debug.Assert(state.BeatmapID != null);
onlineBeatmapRequest = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId);
onlineBeatmapRequest.Success += res => Schedule(() =>
onlineBeatmapRequest.Success += beatmapSet => Schedule(() =>
{
onlineBeatmap = res.ToBeatmapSet(rulesets);
beatmapPanelContainer.Child = new GridBeatmapPanel(res);
this.beatmapSet = beatmapSet;
beatmapPanelContainer.Child = new GridBeatmapPanel(this.beatmapSet);
checkForAutomaticDownload();
});
@ -232,16 +234,16 @@ namespace osu.Game.Screens.Play
private void checkForAutomaticDownload()
{
if (onlineBeatmap == null)
if (beatmapSet == null)
return;
if (!automaticDownload.Current.Value)
return;
if (beatmaps.IsAvailableLocally(onlineBeatmap))
if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }))
return;
beatmaps.Download(onlineBeatmap);
beatmaps.Download(beatmapSet);
}
public override bool OnExiting(IScreen next)

View File

@ -98,9 +98,8 @@ namespace osu.Game.Screens.Play
/// </summary>
protected virtual void RecreateGraph()
{
var newColumns = new BufferedContainer<Column>
var newColumns = new BufferedContainer<Column>(cachedFrameBuffer: true)
{
CacheDrawnFrameBuffer = true,
RedrawOnScale = false,
RelativeSizeAxes = Axes.Both,
};

View File

@ -51,13 +51,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
Font = OsuFont.Numeric.With(size: 76),
UseFullGlyphHeight = false
},
superFlash = new BufferedContainer
superFlash = new BufferedContainer(cachedFrameBuffer: true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(85),
Size = new Vector2(600),
CacheDrawnFrameBuffer = true,
Blending = BlendingParameters.Additive,
Alpha = 0,
Children = new[]
@ -71,14 +70,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
},
},
},
flash = new BufferedContainer
flash = new BufferedContainer(cachedFrameBuffer: true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(35),
BypassAutoSizeAxes = Axes.Both,
Size = new Vector2(200),
CacheDrawnFrameBuffer = true,
Blending = BlendingParameters.Additive,
Alpha = 0,
Scale = new Vector2(1.8f),

View File

@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select
new PopupDialogOkButton
{
Text = @"Yes. Totally. Delete it.",
Action = () => manager.Delete(beatmap),
Action = () => manager?.Delete(beatmap),
},
new PopupDialogCancelButton
{

View File

@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select
[Resolved]
private RulesetStore rulesets { get; set; }
private BeatmapInfo beatmapInfo;
private IBeatmapInfo beatmapInfo;
private APIFailTimes failTimes;
private int[] ratings;
public BeatmapInfo BeatmapInfo
public IBeatmapInfo BeatmapInfo
{
get => beatmapInfo;
set
@ -56,8 +56,11 @@ namespace osu.Game.Screens.Select
beatmapInfo = value;
failTimes = beatmapInfo?.OnlineInfo?.FailTimes;
ratings = beatmapInfo?.BeatmapSet?.OnlineInfo?.Ratings;
var onlineInfo = beatmapInfo as IBeatmapOnlineInfo;
var onlineSetInfo = beatmapInfo.BeatmapSet as IBeatmapSetOnlineInfo;
failTimes = onlineInfo?.FailTimes;
ratings = onlineSetInfo?.Ratings;
Scheduler.AddOnce(updateStatistics);
}
@ -178,9 +181,9 @@ namespace osu.Game.Screens.Select
private void updateStatistics()
{
advanced.BeatmapInfo = BeatmapInfo;
description.Text = BeatmapInfo?.Version;
source.Text = BeatmapInfo?.Metadata?.Source;
tags.Text = BeatmapInfo?.Metadata?.Tags;
description.Text = BeatmapInfo?.DifficultyName;
source.Text = BeatmapInfo?.Metadata.Source;
tags.Text = BeatmapInfo?.Metadata.Tags;
// failTimes may have been previously fetched
if (ratings != null && failTimes != null)
@ -190,7 +193,7 @@ namespace osu.Game.Screens.Select
}
// for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time).
if (BeatmapInfo?.OnlineBeatmapID == null || api.State.Value == APIState.Offline)
if (BeatmapInfo == null || BeatmapInfo.OnlineID <= 0 || api.State.Value == APIState.Offline)
{
updateMetrics();
return;

View File

@ -27,9 +27,8 @@ namespace osu.Game.Screens.Select
{
RelativeSizeAxes = Axes.Both;
InternalChild = new BufferedContainer
InternalChild = new BufferedContainer(cachedFrameBuffer: true)
{
CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{

View File

@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select.Carousel
return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase);
case SortMode.Author:
return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase);
return string.Compare(BeatmapSet.Metadata.Author?.Username, otherSet.BeatmapSet.Metadata.Author?.Username, StringComparison.OrdinalIgnoreCase);
case SortMode.Source:
return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase);

View File

@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel
},
new OsuSpriteText
{
Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author.Username}",
Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author?.Username ?? string.Empty}",
Font = OsuFont.GetFont(italics: true),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft

View File

@ -15,8 +15,8 @@ namespace osu.Game.Screens.Select.Carousel
public class SetPanelBackground : BufferedContainer
{
public SetPanelBackground(WorkingBeatmap working)
: base(cachedFrameBuffer: true)
{
CacheDrawnFrameBuffer = true;
RedrawOnScale = false;
Children = new Drawable[]

View File

@ -70,14 +70,16 @@ namespace osu.Game.Skinning.Editor
if (anchor.HasFlagFast(Anchor.x1)) scale.X = 0;
if (anchor.HasFlagFast(Anchor.y1)) scale.Y = 0;
bool shouldAspectLock =
// for now aspect lock scale adjustments that occur at corners..
(!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1))
// ..or if any of the selection have been rotated.
// this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway).
|| SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0));
if (shouldAspectLock)
// for now aspect lock scale adjustments that occur at corners..
if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1))
{
// project scale vector along diagonal
Vector2 diag = (selectionRect.TopLeft - selectionRect.BottomRight).Normalized();
scale = Vector2.Dot(scale, diag) * diag;
}
// ..or if any of the selection have been rotated.
// this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway).
else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0)))
{
if (anchor.HasFlagFast(Anchor.x1))
// if dragging from the horizontal centre, only a vertical component is available.

View File

@ -77,10 +77,14 @@ namespace osu.Game.Storyboards
{
get
{
string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant();
if (backgroundPath == null)
string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile;
if (string.IsNullOrEmpty(backgroundPath))
return false;
// Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints.
backgroundPath = backgroundPath.ToLowerInvariant();
return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath);
}
}
@ -93,7 +97,7 @@ namespace osu.Game.Storyboards
Drawable drawable = null;
string storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
if (storyboardPath != null)
if (!string.IsNullOrEmpty(storyboardPath))
drawable = new Sprite { Texture = textureStore.Get(storyboardPath) };
// if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy.
else if (UseSkinSprites)

Some files were not shown because too many files have changed in this diff Show More