mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 17:07:38 +08:00
Merge branch 'master' into multi-queueing-modes
This commit is contained in:
commit
65b920e4c1
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1026.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1029.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. -->
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -88,33 +89,27 @@ namespace osu.Game.Tests.Online
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeserialiseScoreInfoWithEmptyMods()
|
||||
public void TestDeserialiseSubmittableScoreWithEmptyMods()
|
||||
{
|
||||
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||
var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo });
|
||||
|
||||
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score));
|
||||
|
||||
if (deserialised != null)
|
||||
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
||||
|
||||
Assert.That(deserialised?.Mods.Length, Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeserialiseScoreInfoWithCustomModSetting()
|
||||
public void TestDeserialiseSubmittableScoreWithCustomModSetting()
|
||||
{
|
||||
var score = new ScoreInfo
|
||||
var score = new SubmittableScore(new ScoreInfo
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
|
||||
};
|
||||
});
|
||||
|
||||
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score));
|
||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
||||
|
||||
if (deserialised != null)
|
||||
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2));
|
||||
Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
|
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Online
|
||||
|
||||
private void addAvailabilityCheckStep(string description, Func<BeatmapAvailability> expected)
|
||||
{
|
||||
AddAssert(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke()));
|
||||
AddUntilStep(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke()));
|
||||
}
|
||||
|
||||
private static BeatmapInfo getTestBeatmapInfo(string archiveFile)
|
||||
|
@ -29,6 +29,15 @@ namespace osu.Game.Tests.Skins.IO
|
||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSingleImportWeirdIniFileCase() => runSkinTest(async osu =>
|
||||
{
|
||||
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk"));
|
||||
|
||||
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
@ -190,11 +199,11 @@ namespace osu.Game.Tests.Skins.IO
|
||||
return zipStream;
|
||||
}
|
||||
|
||||
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false)
|
||||
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini")
|
||||
{
|
||||
var zipStream = new MemoryStream();
|
||||
using var zip = ZipArchive.Create();
|
||||
zip.AddEntry("skin.ini", generateSkinIni(name, author, makeUnique));
|
||||
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique));
|
||||
zip.SaveTo(zipStream);
|
||||
return zipStream;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -132,11 +133,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable)
|
||||
{
|
||||
return new APILegacyScoreInfo
|
||||
return new APIScoreInfo
|
||||
{
|
||||
OnlineScoreID = 2553163309,
|
||||
OnlineID = 2553163309,
|
||||
OnlineRulesetID = 0,
|
||||
Replay = replayAvailable,
|
||||
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
|
||||
HasReplay = replayAvailable,
|
||||
User = new User
|
||||
{
|
||||
Id = 39828,
|
||||
|
@ -43,11 +43,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
};
|
||||
|
||||
var allScores = new APILegacyScores
|
||||
var allScores = new APIScoresCollection
|
||||
{
|
||||
Scores = new List<APILegacyScoreInfo>
|
||||
Scores = new List<APIScoreInfo>
|
||||
{
|
||||
new APILegacyScoreInfo
|
||||
new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567890,
|
||||
Accuracy = 1,
|
||||
},
|
||||
new APILegacyScoreInfo
|
||||
new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234789,
|
||||
Accuracy = 0.9997,
|
||||
},
|
||||
new APILegacyScoreInfo
|
||||
new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 12345678,
|
||||
Accuracy = 0.9854,
|
||||
},
|
||||
new APILegacyScoreInfo
|
||||
new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567,
|
||||
Accuracy = 0.8765,
|
||||
},
|
||||
new APILegacyScoreInfo
|
||||
new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -162,9 +162,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
}
|
||||
};
|
||||
|
||||
var myBestScore = new APILegacyUserTopScoreInfo
|
||||
var myBestScore = new APIScoreWithPosition
|
||||
{
|
||||
Score = new APILegacyScoreInfo
|
||||
Score = new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -185,9 +185,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Position = 1337,
|
||||
};
|
||||
|
||||
var myBestScoreWithNullPosition = new APILegacyUserTopScoreInfo
|
||||
var myBestScoreWithNullPosition = new APIScoreWithPosition
|
||||
{
|
||||
Score = new APILegacyScoreInfo
|
||||
Score = new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -208,11 +208,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Position = null,
|
||||
};
|
||||
|
||||
var oneScore = new APILegacyScores
|
||||
var oneScore = new APIScoresCollection
|
||||
{
|
||||
Scores = new List<APILegacyScoreInfo>
|
||||
Scores = new List<APIScoreInfo>
|
||||
{
|
||||
new APILegacyScoreInfo
|
||||
new APIScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private class TestScoresContainer : ScoresContainer
|
||||
{
|
||||
public new APILegacyScores Scores
|
||||
public new APIScoresCollection Scores
|
||||
{
|
||||
set => base.Scores = value;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
@ -684,6 +685,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
set.Beatmaps.Add(new BeatmapInfo
|
||||
{
|
||||
Version = $"Stars: {i}",
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
StarDifficulty = i,
|
||||
});
|
||||
}
|
||||
@ -868,6 +870,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
OnlineBeatmapID = id++ * 10,
|
||||
Version = version,
|
||||
StarDifficulty = diff,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = diff,
|
||||
|
@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
|
||||
public override async Task<StarDifficulty> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (blockCalculation)
|
||||
await calculationBlocker.Task.ConfigureAwait(false);
|
||||
|
@ -56,95 +56,71 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("Set width to 300", () => content.ResizeWidthTo(300, 500));
|
||||
}
|
||||
|
||||
private static readonly List<BeatmapSetInfo> new_beatmaps = new List<BeatmapSetInfo>
|
||||
private static readonly List<APIBeatmapSet> new_beatmaps = new List<APIBeatmapSet>
|
||||
{
|
||||
new BeatmapSetInfo
|
||||
new APIBeatmapSet
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
Title = "Very Long Title (TV size) [TATOE]",
|
||||
Artist = "This artist has a really long name how is this possible",
|
||||
Author = new User
|
||||
{
|
||||
Title = "Very Long Title (TV size) [TATOE]",
|
||||
Artist = "This artist has a really long name how is this possible",
|
||||
Author = new User
|
||||
{
|
||||
Username = "author",
|
||||
Id = 100
|
||||
}
|
||||
Username = "author",
|
||||
Id = 100
|
||||
},
|
||||
OnlineInfo = new APIBeatmapSet
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608",
|
||||
},
|
||||
Ranked = DateTimeOffset.Now
|
||||
}
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608",
|
||||
},
|
||||
Ranked = DateTimeOffset.Now
|
||||
},
|
||||
new BeatmapSetInfo
|
||||
new APIBeatmapSet
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
Title = "Very Long Title (TV size) [TATOE]",
|
||||
Artist = "This artist has a really long name how is this possible",
|
||||
Author = new User
|
||||
{
|
||||
Title = "Very Long Title (TV size) [TATOE]",
|
||||
Artist = "This artist has a really long name how is this possible",
|
||||
Author = new User
|
||||
{
|
||||
Username = "author",
|
||||
Id = 100
|
||||
}
|
||||
Username = "author",
|
||||
Id = 100
|
||||
},
|
||||
OnlineInfo = new APIBeatmapSet
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608",
|
||||
},
|
||||
Ranked = DateTimeOffset.MinValue
|
||||
}
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608",
|
||||
},
|
||||
Ranked = DateTimeOffset.Now
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly List<BeatmapSetInfo> popular_beatmaps = new List<BeatmapSetInfo>
|
||||
private static readonly List<APIBeatmapSet> popular_beatmaps = new List<APIBeatmapSet>
|
||||
{
|
||||
new BeatmapSetInfo
|
||||
new APIBeatmapSet
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
Title = "Very Long Title (TV size) [TATOE]",
|
||||
Artist = "This artist has a really long name how is this possible",
|
||||
Author = new User
|
||||
{
|
||||
Title = "Title",
|
||||
Artist = "Artist",
|
||||
Author = new User
|
||||
{
|
||||
Username = "author",
|
||||
Id = 100
|
||||
}
|
||||
Username = "author",
|
||||
Id = 100
|
||||
},
|
||||
OnlineInfo = new APIBeatmapSet
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg?1595295586",
|
||||
},
|
||||
FavouriteCount = 100
|
||||
}
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608",
|
||||
},
|
||||
Ranked = DateTimeOffset.Now
|
||||
},
|
||||
new BeatmapSetInfo
|
||||
new APIBeatmapSet
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
Title = "Very Long Title (TV size) [TATOE]",
|
||||
Artist = "This artist has a really long name how is this possible",
|
||||
Author = new User
|
||||
{
|
||||
Title = "Title 2",
|
||||
Artist = "Artist 2",
|
||||
Author = new User
|
||||
{
|
||||
Username = "someone",
|
||||
Id = 100
|
||||
}
|
||||
Username = "author",
|
||||
Id = 100
|
||||
},
|
||||
OnlineInfo = new APIBeatmapSet
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg?1595295586",
|
||||
},
|
||||
FavouriteCount = 10
|
||||
}
|
||||
Cover = "https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg?1595456608",
|
||||
},
|
||||
Ranked = DateTimeOffset.Now
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
new TournamentSpriteText
|
||||
{
|
||||
Text = Beatmap.GetDisplayTitleRomanisable(false),
|
||||
Text = Beatmap.GetDisplayTitleRomanisable(false, false),
|
||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
|
||||
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated).</returns>
|
||||
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
|
||||
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
|
||||
|
||||
@ -99,42 +99,45 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination.
|
||||
/// Retrieves a bindable containing the star difficulty of a <see cref="IBeatmapInfo"/> with a given <see cref="RulesetInfo"/> and <see cref="Mod"/> combination.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The bindable will not update to follow the currently-selected ruleset and mods or its settings.
|
||||
/// </remarks>
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
|
||||
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param>
|
||||
/// <param name="beatmapInfo">The <see cref="IBeatmapInfo"/> to get the difficulty of.</param>
|
||||
/// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with. If <c>null</c>, no mods will be assumed.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="IBeatmapInfo"/>.</param>
|
||||
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state.</returns>
|
||||
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods,
|
||||
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the difficulty of a <see cref="BeatmapInfo"/>.
|
||||
/// Retrieves the difficulty of a <see cref="IBeatmapInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
|
||||
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with.</param>
|
||||
/// <param name="beatmapInfo">The <see cref="IBeatmapInfo"/> to get the difficulty of.</param>
|
||||
/// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to get the difficulty with.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops computing the star difficulty.</param>
|
||||
/// <returns>The <see cref="StarDifficulty"/>.</returns>
|
||||
public virtual Task<StarDifficulty> GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null,
|
||||
public virtual Task<StarDifficulty> GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null,
|
||||
[CanBeNull] IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
|
||||
rulesetInfo ??= beatmapInfo.Ruleset;
|
||||
|
||||
var localBeatmapInfo = beatmapInfo as BeatmapInfo;
|
||||
var localRulesetInfo = rulesetInfo as RulesetInfo;
|
||||
|
||||
// Difficulty can only be computed if the beatmap and ruleset are locally available.
|
||||
if (beatmapInfo.ID == 0 || rulesetInfo.ID == null)
|
||||
if (localBeatmapInfo == null || localRulesetInfo == null)
|
||||
{
|
||||
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
||||
return Task.FromResult(new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0));
|
||||
return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
|
||||
}
|
||||
|
||||
return GetAsync(new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods), cancellationToken);
|
||||
return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken);
|
||||
}
|
||||
|
||||
protected override Task<StarDifficulty> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
|
||||
@ -227,12 +230,12 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BindableStarDifficulty"/> and triggers an initial value update.
|
||||
/// </summary>
|
||||
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> that star difficulty should correspond to.</param>
|
||||
/// <param name="initialRulesetInfo">The initial <see cref="RulesetInfo"/> to get the difficulty with.</param>
|
||||
/// <param name="beatmapInfo">The <see cref="IBeatmapInfo"/> that star difficulty should correspond to.</param>
|
||||
/// <param name="initialRulesetInfo">The initial <see cref="IRulesetInfo"/> to get the difficulty with.</param>
|
||||
/// <param name="initialMods">The initial <see cref="Mod"/>s to get the difficulty with.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
|
||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="IBeatmapInfo"/>.</param>
|
||||
/// <returns>The <see cref="BindableStarDifficulty"/>.</returns>
|
||||
private BindableStarDifficulty createBindable([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable<Mod> initialMods,
|
||||
private BindableStarDifficulty createBindable([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable<Mod> initialMods,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken);
|
||||
@ -244,12 +247,12 @@ namespace osu.Game.Beatmaps
|
||||
/// Updates the value of a <see cref="BindableStarDifficulty"/> with a given ruleset + mods.
|
||||
/// </summary>
|
||||
/// <param name="bindable">The <see cref="BindableStarDifficulty"/> to update.</param>
|
||||
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to update with.</param>
|
||||
/// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to update with.</param>
|
||||
/// <param name="mods">The <see cref="Mod"/>s to update with.</param>
|
||||
/// <param name="cancellationToken">A token that may be used to cancel this update.</param>
|
||||
private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods, CancellationToken cancellationToken = default)
|
||||
private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// GetDifficultyAsync will fall back to existing data from BeatmapInfo if not locally available
|
||||
// GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available
|
||||
// (contrary to GetAsync)
|
||||
GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken)
|
||||
.ContinueWith(t =>
|
||||
@ -343,10 +346,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private class BindableStarDifficulty : Bindable<StarDifficulty?>
|
||||
{
|
||||
public readonly BeatmapInfo BeatmapInfo;
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
public readonly CancellationToken CancellationToken;
|
||||
|
||||
public BindableStarDifficulty(BeatmapInfo beatmapInfo, CancellationToken cancellationToken)
|
||||
public BindableStarDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
BeatmapInfo = beatmapInfo;
|
||||
CancellationToken = cancellationToken;
|
||||
|
@ -16,9 +16,9 @@ namespace osu.Game.Beatmaps
|
||||
/// <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)
|
||||
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo, bool includeDifficultyName = true, bool includeCreator = true)
|
||||
{
|
||||
var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable();
|
||||
var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(includeCreator);
|
||||
|
||||
if (includeDifficultyName)
|
||||
{
|
||||
|
@ -34,9 +34,9 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
|
||||
/// </summary>
|
||||
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo)
|
||||
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo, bool includeCreator = true)
|
||||
{
|
||||
string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})";
|
||||
string author = !includeCreator || string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})";
|
||||
string artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode;
|
||||
string titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode;
|
||||
|
||||
|
@ -13,6 +13,9 @@ namespace osu.Game.Beatmaps
|
||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
||||
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
|
||||
|
||||
public override ArchiveDownloadRequest<BeatmapSetInfo> GetExistingDownload(BeatmapSetInfo model)
|
||||
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID);
|
||||
|
||||
public BeatmapModelDownloader(IBeatmapModelManager beatmapModelManager, IAPIProvider api, GameHost host = null)
|
||||
: base(beatmapModelManager, api, host)
|
||||
{
|
||||
|
@ -37,10 +37,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
private readonly IBeatmapInfo beatmapInfo;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly IRulesetInfo ruleset;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
|
||||
/// <param name="mods">The mods to show the difficulty with.</param>
|
||||
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
|
||||
public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
|
||||
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
|
||||
: this(beatmapInfo, shouldShowTooltip)
|
||||
{
|
||||
this.ruleset = ruleset ?? beatmapInfo.Ruleset;
|
||||
@ -73,7 +73,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// <param name="beatmapInfo">The beatmap to show the difficulty of.</param>
|
||||
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
|
||||
/// <param name="performBackgroundDifficultyLookup">Whether to perform difficulty lookup (including calculation if necessary).</param>
|
||||
public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
|
||||
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo));
|
||||
this.shouldShowTooltip = shouldShowTooltip;
|
||||
@ -84,6 +84,9 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
@ -105,7 +108,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Child = background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.ForStarDifficulty(beatmapInfo.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes
|
||||
Colour = colours.ForStarDifficulty(beatmapInfo.StarRating) // Default value that will be re-populated once difficulty calculation completes
|
||||
},
|
||||
},
|
||||
new ConstrainedIconContainer
|
||||
@ -114,18 +117,28 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
|
||||
Icon = (ruleset ?? beatmapInfo.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
|
||||
Icon = getRulesetIcon()
|
||||
},
|
||||
};
|
||||
|
||||
if (performBackgroundDifficultyLookup)
|
||||
iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0));
|
||||
else
|
||||
difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarDifficulty, 0);
|
||||
difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0);
|
||||
|
||||
difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars));
|
||||
}
|
||||
|
||||
private Drawable getRulesetIcon()
|
||||
{
|
||||
int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID;
|
||||
|
||||
if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance)
|
||||
return rulesetInstance.CreateIcon();
|
||||
|
||||
return new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
|
||||
}
|
||||
|
||||
ITooltip<DifficultyIconTooltipContent> IHasCustomTooltip<DifficultyIconTooltipContent>.GetCustomTooltip() => new DifficultyIconTooltip();
|
||||
|
||||
DifficultyIconTooltipContent IHasCustomTooltip<DifficultyIconTooltipContent>.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null;
|
||||
@ -134,8 +147,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public readonly Bindable<StarDifficulty> StarDifficulty = new Bindable<StarDifficulty>();
|
||||
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly IBeatmapInfo beatmapInfo;
|
||||
private readonly IRulesetInfo ruleset;
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
|
||||
private CancellationTokenSource difficultyCancellation;
|
||||
@ -143,7 +156,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; }
|
||||
|
||||
public DifficultyRetriever(BeatmapInfo beatmapInfo, RulesetInfo ruleset, IReadOnlyList<Mod> mods)
|
||||
public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList<Mod> mods)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo;
|
||||
this.ruleset = ruleset;
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
public void SetContent(DifficultyIconTooltipContent content)
|
||||
{
|
||||
difficultyName.Text = content.BeatmapInfo.Version;
|
||||
difficultyName.Text = content.BeatmapInfo.DifficultyName;
|
||||
|
||||
starDifficulty.UnbindAll();
|
||||
starDifficulty.BindTo(content.Difficulty);
|
||||
@ -109,10 +109,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
internal class DifficultyIconTooltipContent
|
||||
{
|
||||
public readonly BeatmapInfo BeatmapInfo;
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
public readonly IBindable<StarDifficulty> Difficulty;
|
||||
|
||||
public DifficultyIconTooltipContent(BeatmapInfo beatmapInfo, IBindable<StarDifficulty> difficulty)
|
||||
public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable<StarDifficulty> difficulty)
|
||||
{
|
||||
BeatmapInfo = beatmapInfo;
|
||||
Difficulty = difficulty;
|
||||
|
@ -19,8 +19,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </remarks>
|
||||
public class GroupedDifficultyIcon : DifficultyIcon
|
||||
{
|
||||
public GroupedDifficultyIcon(List<BeatmapInfo> beatmaps, RulesetInfo ruleset, Color4 counterColour)
|
||||
: base(beatmaps.OrderBy(b => b.StarDifficulty).Last(), ruleset, null, false)
|
||||
public GroupedDifficultyIcon(IEnumerable<IBeatmapInfo> beatmaps, IRulesetInfo ruleset, Color4 counterColour)
|
||||
: base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false)
|
||||
{
|
||||
AddInternal(new OsuSpriteText
|
||||
{
|
||||
@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Padding = new MarginPadding { Left = Size.X },
|
||||
Margin = new MarginPadding { Left = 2, Right = 5 },
|
||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
||||
Text = beatmaps.Count.ToString(),
|
||||
Text = beatmaps.Count().ToString(),
|
||||
Colour = counterColour,
|
||||
});
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// A single beatmap difficulty.
|
||||
/// </summary>
|
||||
public interface IBeatmapInfo : IHasOnlineID
|
||||
public interface IBeatmapInfo : IHasOnlineID<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// The user-specified name given to this beatmap.
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
|
||||
/// </summary>
|
||||
public interface IBeatmapSetInfo : IHasOnlineID
|
||||
public interface IBeatmapSetInfo : IHasOnlineID<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// The date when this beatmap was imported.
|
||||
|
@ -5,15 +5,15 @@
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public interface IHasOnlineID
|
||||
public interface IHasOnlineID<out T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID.
|
||||
/// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID (except in special cases where autoincrement is not used, like rulesets).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally we use -1 when specifying "missing" in code, but values of 0 are also considered missing as the online source
|
||||
/// is generally a MySQL autoincrement value, which can never be 0.
|
||||
/// </remarks>
|
||||
int OnlineID { get; }
|
||||
T OnlineID { get; }
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
||||
private readonly IModelManager<TModel> modelManager;
|
||||
private readonly IAPIProvider api;
|
||||
|
||||
private readonly List<ArchiveDownloadRequest<TModel>> currentDownloads = new List<ArchiveDownloadRequest<TModel>>();
|
||||
protected readonly List<ArchiveDownloadRequest<TModel>> CurrentDownloads = new List<ArchiveDownloadRequest<TModel>>();
|
||||
|
||||
protected ModelDownloader(IModelManager<TModel> modelManager, IAPIProvider api, IIpcHost importHost = null)
|
||||
{
|
||||
@ -74,7 +74,7 @@ namespace osu.Game.Database
|
||||
if (!imported.Any())
|
||||
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
|
||||
|
||||
currentDownloads.Remove(request);
|
||||
CurrentDownloads.Remove(request);
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
};
|
||||
|
||||
@ -86,7 +86,7 @@ namespace osu.Game.Database
|
||||
return true;
|
||||
};
|
||||
|
||||
currentDownloads.Add(request);
|
||||
CurrentDownloads.Add(request);
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
api.PerformAsync(request);
|
||||
@ -96,7 +96,7 @@ namespace osu.Game.Database
|
||||
|
||||
void triggerFailure(Exception error)
|
||||
{
|
||||
currentDownloads.Remove(request);
|
||||
CurrentDownloads.Remove(request);
|
||||
|
||||
downloadFailed.Value = new WeakReference<ArchiveDownloadRequest<TModel>>(request);
|
||||
|
||||
@ -107,7 +107,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
public ArchiveDownloadRequest<TModel> GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model));
|
||||
public abstract ArchiveDownloadRequest<TModel> GetExistingDownload(TModel model);
|
||||
|
||||
private bool canDownload(TModel model) => GetExistingDownload(model) == null && api != null;
|
||||
|
||||
|
@ -6,9 +6,11 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Statistics;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Models;
|
||||
using Realms;
|
||||
|
||||
@ -32,8 +34,9 @@ namespace osu.Game.Database
|
||||
/// Version history:
|
||||
/// 6 First tracked version (~20211018)
|
||||
/// 7 Changed OnlineID fields to non-nullable to add indexing support (20211018)
|
||||
/// 8 Rebind scroll adjust keys to not have control modifier (20211029)
|
||||
/// </summary>
|
||||
private const int schema_version = 7;
|
||||
private const int schema_version = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking context creation during blocking periods.
|
||||
@ -148,6 +151,21 @@ namespace osu.Game.Database
|
||||
|
||||
private void onMigration(Migration migration, ulong lastSchemaVersion)
|
||||
{
|
||||
if (lastSchemaVersion < 8)
|
||||
{
|
||||
// Ctrl -/+ now adjusts UI scale so let's clear any bindings which overlap these combinations.
|
||||
// New defaults will be populated by the key store afterwards.
|
||||
var keyBindings = migration.NewRealm.All<RealmKeyBinding>();
|
||||
|
||||
var increaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.IncreaseScrollSpeed);
|
||||
if (increaseSpeedBinding != null && increaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Plus }))
|
||||
migration.NewRealm.Remove(increaseSpeedBinding);
|
||||
|
||||
var decreaseSpeedBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.DecreaseScrollSpeed);
|
||||
if (decreaseSpeedBinding != null && decreaseSpeedBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.Minus }))
|
||||
migration.NewRealm.Remove(decreaseSpeedBinding);
|
||||
}
|
||||
|
||||
if (lastSchemaVersion < 7)
|
||||
{
|
||||
convertOnlineIDs<RealmBeatmap>();
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -58,39 +59,34 @@ namespace osu.Game.Graphics.Containers
|
||||
}
|
||||
|
||||
public void AddLink(string text, string url, Action<SpriteText> creationParameters = null) =>
|
||||
createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url);
|
||||
createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.External, url), url);
|
||||
|
||||
public void AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
||||
=> createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action);
|
||||
=> createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action);
|
||||
|
||||
public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
||||
=> createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText);
|
||||
=> createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText);
|
||||
|
||||
public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
||||
{
|
||||
var spriteText = new OsuSpriteText { Text = text };
|
||||
|
||||
AddText(spriteText, creationParameters);
|
||||
createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText);
|
||||
RemoveInternal(spriteText); // TODO: temporary, will go away when TextParts support localisation properly.
|
||||
createLink(new TextPartManual(spriteText.Yield()), new LinkDetails(action, argument), tooltipText);
|
||||
}
|
||||
|
||||
public void AddLink(IEnumerable<SpriteText> text, LinkAction action, string linkArgument, string tooltipText = null)
|
||||
{
|
||||
foreach (var t in text)
|
||||
AddArbitraryDrawable(t);
|
||||
|
||||
createLink(text, new LinkDetails(action, linkArgument), tooltipText);
|
||||
createLink(new TextPartManual(text), new LinkDetails(action, linkArgument), tooltipText);
|
||||
}
|
||||
|
||||
public void AddUserLink(User user, Action<SpriteText> creationParameters = null)
|
||||
=> createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile");
|
||||
=> createLink(CreateChunkFor(user.Username, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile");
|
||||
|
||||
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
|
||||
private void createLink(ITextPart textPart, LinkDetails link, LocalisableString tooltipText, Action action = null)
|
||||
{
|
||||
var linkCompiler = CreateLinkCompiler(drawables.OfType<SpriteText>());
|
||||
linkCompiler.RelativeSizeAxes = Axes.Both;
|
||||
linkCompiler.TooltipText = tooltipText;
|
||||
linkCompiler.Action = () =>
|
||||
Action onClickAction = () =>
|
||||
{
|
||||
if (action != null)
|
||||
action();
|
||||
@ -101,10 +97,41 @@ namespace osu.Game.Graphics.Containers
|
||||
host.OpenUrlExternally(link.Argument);
|
||||
};
|
||||
|
||||
AddInternal(linkCompiler);
|
||||
AddPart(new TextLink(textPart, tooltipText, onClickAction));
|
||||
}
|
||||
|
||||
protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new DrawableLinkCompiler(parts);
|
||||
private class TextLink : TextPart
|
||||
{
|
||||
private readonly ITextPart innerPart;
|
||||
private readonly LocalisableString tooltipText;
|
||||
private readonly Action action;
|
||||
|
||||
public TextLink(ITextPart innerPart, LocalisableString tooltipText, Action action)
|
||||
{
|
||||
this.innerPart = innerPart;
|
||||
this.tooltipText = tooltipText;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Drawable> CreateDrawablesFor(TextFlowContainer textFlowContainer)
|
||||
{
|
||||
var linkFlowContainer = (LinkFlowContainer)textFlowContainer;
|
||||
|
||||
innerPart.RecreateDrawablesFor(linkFlowContainer);
|
||||
var drawables = innerPart.Drawables.ToList();
|
||||
|
||||
drawables.Add(linkFlowContainer.CreateLinkCompiler(innerPart).With(c =>
|
||||
{
|
||||
c.RelativeSizeAxes = Axes.Both;
|
||||
c.TooltipText = tooltipText;
|
||||
c.Action = action;
|
||||
}));
|
||||
|
||||
return drawables;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new DrawableLinkCompiler(textPart);
|
||||
|
||||
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
|
||||
// However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation.
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@ -19,8 +19,8 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
||||
|
||||
public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable);
|
||||
public ITextPart AddArbitraryDrawable(Drawable drawable) => AddPart(new TextPartManual(drawable.Yield()));
|
||||
|
||||
public IEnumerable<Drawable> AddIcon(IconUsage icon, Action<SpriteText> creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters);
|
||||
public ITextPart AddIcon(IconUsage icon, Action<SpriteText> creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -10,7 +10,7 @@ namespace osu.Game.Graphics
|
||||
{
|
||||
public class ErrorTextFlowContainer : OsuTextFlowContainer
|
||||
{
|
||||
private readonly List<Drawable> errorDrawables = new List<Drawable>();
|
||||
private readonly List<ITextPart> errorTextParts = new List<ITextPart>();
|
||||
|
||||
public ErrorTextFlowContainer()
|
||||
: base(cp => cp.Font = cp.Font.With(size: 12))
|
||||
@ -19,7 +19,8 @@ namespace osu.Game.Graphics
|
||||
|
||||
public void ClearErrors()
|
||||
{
|
||||
errorDrawables.ForEach(d => d.Expire());
|
||||
foreach (var textPart in errorTextParts)
|
||||
RemovePart(textPart);
|
||||
}
|
||||
|
||||
public void AddErrors(string[] errors)
|
||||
@ -29,7 +30,7 @@ namespace osu.Game.Graphics
|
||||
if (errors == null) return;
|
||||
|
||||
foreach (string error in errors)
|
||||
errorDrawables.AddRange(AddParagraph(error, cp => cp.Colour = Color4.Red));
|
||||
errorTextParts.Add(AddParagraph(error, cp => cp.Colour = Color4.Red));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,8 +84,8 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(InputKey.ExtraMouseButton2, GlobalAction.SkipCutscene),
|
||||
new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
|
||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed),
|
||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed),
|
||||
new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface),
|
||||
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
|
||||
new KeyBinding(InputKey.Space, GlobalAction.TogglePauseReplay),
|
||||
|
@ -119,6 +119,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ShowCursorInScreenshots => new TranslatableString(getKey(@"show_cursor_in_screenshots"), @"Show menu cursor in screenshots");
|
||||
|
||||
/// <summary>
|
||||
/// "Video"
|
||||
/// </summary>
|
||||
public static LocalisableString VideoHeader => new TranslatableString(getKey(@"video_header"), @"Video");
|
||||
|
||||
/// <summary>
|
||||
/// "Use hardware acceleration"
|
||||
/// </summary>
|
||||
public static LocalisableString UseHardwareAcceleration => new TranslatableString(getKey(@"use_hardware_acceleration"), @"Use hardware acceleration");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,10 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetScoresRequest : APIRequest<APILegacyScores>
|
||||
public class GetScoresRequest : APIRequest<APIScoresCollection>
|
||||
{
|
||||
private readonly BeatmapInfo beatmapInfo;
|
||||
private readonly BeatmapLeaderboardScope scope;
|
||||
@ -32,27 +31,6 @@ namespace osu.Game.Online.API.Requests
|
||||
this.scope = scope;
|
||||
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
|
||||
this.mods = mods ?? Array.Empty<IMod>();
|
||||
|
||||
Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess(APILegacyScores r)
|
||||
{
|
||||
Debug.Assert(ruleset.ID != null, "ruleset.ID != null");
|
||||
|
||||
foreach (APILegacyScoreInfo score in r.Scores)
|
||||
{
|
||||
score.BeatmapInfo = beatmapInfo;
|
||||
score.OnlineRulesetID = ruleset.ID.Value;
|
||||
}
|
||||
|
||||
var userScore = r.UserScore;
|
||||
|
||||
if (userScore != null)
|
||||
{
|
||||
userScore.Score.BeatmapInfo = beatmapInfo;
|
||||
userScore.Score.OnlineRulesetID = ruleset.ID.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}";
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserScoresRequest : PaginatedAPIRequest<List<APILegacyScoreInfo>>
|
||||
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScoreInfo>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly ScoreType type;
|
||||
|
@ -1,35 +0,0 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APILegacyScores
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public List<APILegacyScoreInfo> Scores;
|
||||
|
||||
[JsonProperty(@"userScore")]
|
||||
public APILegacyUserTopScoreInfo UserScore;
|
||||
}
|
||||
|
||||
public class APILegacyUserTopScoreInfo
|
||||
{
|
||||
[JsonProperty(@"position")]
|
||||
public int? Position;
|
||||
|
||||
[JsonProperty(@"score")]
|
||||
public APILegacyScoreInfo Score;
|
||||
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets)
|
||||
{
|
||||
var score = Score.CreateScoreInfo(rulesets);
|
||||
score.Position = Position;
|
||||
return score;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,9 +15,69 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APILegacyScoreInfo
|
||||
public class APIScoreInfo : IScoreInfo
|
||||
{
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets)
|
||||
[JsonProperty(@"score")]
|
||||
public long TotalScore { get; set; }
|
||||
|
||||
[JsonProperty(@"max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonProperty(@"user")]
|
||||
public User User { get; set; }
|
||||
|
||||
[JsonProperty(@"id")]
|
||||
public long OnlineID { get; set; }
|
||||
|
||||
[JsonProperty(@"replay")]
|
||||
public bool HasReplay { get; set; }
|
||||
|
||||
[JsonProperty(@"created_at")]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmap")]
|
||||
public APIBeatmap Beatmap { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmapset")]
|
||||
public APIBeatmapSet BeatmapSet
|
||||
{
|
||||
set
|
||||
{
|
||||
// in the deserialisation case we need to ferry this data across.
|
||||
// the order of properties returned by the API guarantees that the beatmap is populated by this point.
|
||||
if (!(Beatmap is APIBeatmap apiBeatmap))
|
||||
throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response");
|
||||
|
||||
apiBeatmap.BeatmapSet = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<string, int> Statistics { get; set; }
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
public int OnlineRulesetID { get; set; }
|
||||
|
||||
[JsonProperty(@"mods")]
|
||||
public string[] Mods { get; set; }
|
||||
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||
/// </summary>
|
||||
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
|
||||
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
|
||||
/// <returns></returns>
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(OnlineRulesetID);
|
||||
|
||||
@ -32,19 +92,22 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
TotalScore = TotalScore,
|
||||
MaxCombo = MaxCombo,
|
||||
BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets),
|
||||
User = User,
|
||||
Accuracy = Accuracy,
|
||||
OnlineScoreID = OnlineScoreID,
|
||||
OnlineScoreID = OnlineID,
|
||||
Date = Date,
|
||||
PP = PP,
|
||||
BeatmapInfo = BeatmapInfo,
|
||||
RulesetID = OnlineRulesetID,
|
||||
Hash = Replay ? "online" : string.Empty, // todo: temporary?
|
||||
Hash = HasReplay ? "online" : string.Empty, // todo: temporary?
|
||||
Rank = Rank,
|
||||
Ruleset = ruleset,
|
||||
Mods = mods,
|
||||
};
|
||||
|
||||
if (beatmap != null)
|
||||
scoreInfo.BeatmapInfo = beatmap;
|
||||
|
||||
if (Statistics != null)
|
||||
{
|
||||
foreach (var kvp in Statistics)
|
||||
@ -81,57 +144,8 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
return scoreInfo;
|
||||
}
|
||||
|
||||
[JsonProperty(@"score")]
|
||||
public int TotalScore { get; set; }
|
||||
public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID };
|
||||
|
||||
[JsonProperty(@"max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonProperty(@"user")]
|
||||
public User User { get; set; }
|
||||
|
||||
[JsonProperty(@"id")]
|
||||
public long OnlineScoreID { get; set; }
|
||||
|
||||
[JsonProperty(@"replay")]
|
||||
public bool Replay { get; set; }
|
||||
|
||||
[JsonProperty(@"created_at")]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmap")]
|
||||
public BeatmapInfo BeatmapInfo { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty(@"beatmapset")]
|
||||
public BeatmapMetadata Metadata
|
||||
{
|
||||
set
|
||||
{
|
||||
// extract the set ID to its correct place.
|
||||
BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID };
|
||||
value.ID = 0;
|
||||
|
||||
BeatmapInfo.Metadata = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(@"statistics")]
|
||||
public Dictionary<string, int> Statistics { get; set; }
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
public int OnlineRulesetID { get; set; }
|
||||
|
||||
[JsonProperty(@"mods")]
|
||||
public string[] Mods { get; set; }
|
||||
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank Rank { get; set; }
|
||||
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APIScoreWithPosition
|
||||
{
|
||||
[JsonProperty(@"position")]
|
||||
public int? Position;
|
||||
|
||||
[JsonProperty(@"score")]
|
||||
public APIScoreInfo Score;
|
||||
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||
{
|
||||
var score = Score.CreateScoreInfo(rulesets, beatmap);
|
||||
score.Position = Position;
|
||||
return score;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APIScoresCollection
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public List<APIScoreInfo> Scores;
|
||||
|
||||
[JsonProperty(@"userScore")]
|
||||
public APIScoreWithPosition UserScore;
|
||||
}
|
||||
}
|
155
osu.Game/Online/BeatmapDownloadTracker.cs
Normal file
155
osu.Game/Online/BeatmapDownloadTracker.cs
Normal file
@ -0,0 +1,155 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
public class BeatmapDownloadTracker : DownloadTracker<IBeatmapSetInfo>
|
||||
{
|
||||
[Resolved(CanBeNull = true)]
|
||||
protected BeatmapManager? Manager { get; private set; }
|
||||
|
||||
private ArchiveDownloadRequest<BeatmapSetInfo>? attachedRequest;
|
||||
|
||||
public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem)
|
||||
: base(trackedItem)
|
||||
{
|
||||
}
|
||||
|
||||
private IBindable<WeakReference<BeatmapSetInfo>>? managerUpdated;
|
||||
private IBindable<WeakReference<BeatmapSetInfo>>? managerRemoved;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>>? managerDownloadBegan;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>>? managerDownloadFailed;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load()
|
||||
{
|
||||
if (Manager == null)
|
||||
return;
|
||||
|
||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||
var beatmapSetInfo = new BeatmapSetInfo { OnlineBeatmapSetID = TrackedItem.OnlineID };
|
||||
|
||||
if (Manager.IsAvailableLocally(beatmapSetInfo))
|
||||
UpdateState(DownloadState.LocallyAvailable);
|
||||
else
|
||||
attachDownload(Manager.GetExistingDownload(beatmapSetInfo));
|
||||
|
||||
managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy();
|
||||
managerDownloadBegan.BindValueChanged(downloadBegan);
|
||||
managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy();
|
||||
managerDownloadFailed.BindValueChanged(downloadFailed);
|
||||
managerUpdated = Manager.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(itemUpdated);
|
||||
managerRemoved = Manager.ItemRemoved.GetBoundCopy();
|
||||
managerRemoved.BindValueChanged(itemRemoved);
|
||||
}
|
||||
|
||||
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> weakRequest)
|
||||
{
|
||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(request.Model, TrackedItem))
|
||||
attachDownload(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> weakRequest)
|
||||
{
|
||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(request.Model, TrackedItem))
|
||||
attachDownload(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void attachDownload(ArchiveDownloadRequest<BeatmapSetInfo>? request)
|
||||
{
|
||||
if (attachedRequest != null)
|
||||
{
|
||||
attachedRequest.Failure -= onRequestFailure;
|
||||
attachedRequest.DownloadProgressed -= onRequestProgress;
|
||||
attachedRequest.Success -= onRequestSuccess;
|
||||
}
|
||||
|
||||
attachedRequest = request;
|
||||
|
||||
if (attachedRequest != null)
|
||||
{
|
||||
if (attachedRequest.Progress == 1)
|
||||
{
|
||||
UpdateProgress(1);
|
||||
UpdateState(DownloadState.Importing);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateProgress(attachedRequest.Progress);
|
||||
UpdateState(DownloadState.Downloading);
|
||||
|
||||
attachedRequest.Failure += onRequestFailure;
|
||||
attachedRequest.DownloadProgressed += onRequestProgress;
|
||||
attachedRequest.Success += onRequestSuccess;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateState(DownloadState.NotDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRequestSuccess(string _) => Schedule(() => UpdateState(DownloadState.Importing));
|
||||
|
||||
private void onRequestProgress(float progress) => Schedule(() => UpdateProgress(progress));
|
||||
|
||||
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
|
||||
|
||||
private void itemUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(item, TrackedItem))
|
||||
UpdateState(DownloadState.LocallyAvailable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void itemRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(item, TrackedItem))
|
||||
UpdateState(DownloadState.NotDownloaded);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool checkEquality(IBeatmapSetInfo x, IBeatmapSetInfo y) => x.OnlineID == y.OnlineID;
|
||||
|
||||
#region Disposal
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
attachDownload(null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -30,6 +32,11 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||
|
||||
public DrawableLinkCompiler(ITextPart part)
|
||||
: this(part.Drawables.OfType<SpriteText>())
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
|
39
osu.Game/Online/DownloadTracker.cs
Normal file
39
osu.Game/Online/DownloadTracker.cs
Normal file
@ -0,0 +1,39 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
public abstract class DownloadTracker<T> : Component
|
||||
where T : class
|
||||
{
|
||||
public readonly T TrackedItem;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current download state of the download - whether is has already been downloaded, is in progress, or is not downloaded.
|
||||
/// </summary>
|
||||
public IBindable<DownloadState> State => state;
|
||||
|
||||
private readonly Bindable<DownloadState> state = new Bindable<DownloadState>();
|
||||
|
||||
/// <summary>
|
||||
/// The progress of an active download.
|
||||
/// </summary>
|
||||
public IBindableNumber<double> Progress => progress;
|
||||
|
||||
private readonly BindableNumber<double> progress = new BindableNumber<double> { MinValue = 0, MaxValue = 1 };
|
||||
|
||||
protected DownloadTracker(T trackedItem)
|
||||
{
|
||||
TrackedItem = trackedItem;
|
||||
}
|
||||
|
||||
protected void UpdateState(DownloadState newState) => state.Value = newState;
|
||||
|
||||
protected void UpdateProgress(double newProgress) => progress.Value = newProgress;
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
/// <summary>
|
||||
/// A component which tracks a <typeparamref name="TModel"/> through potential download/import/deletion.
|
||||
/// </summary>
|
||||
public abstract class DownloadTrackingComposite<TModel, TModelManager> : CompositeDrawable
|
||||
where TModel : class, IEquatable<TModel>
|
||||
where TModelManager : class, IModelDownloader<TModel>, IModelManager<TModel>
|
||||
{
|
||||
protected readonly Bindable<TModel> Model = new Bindable<TModel>();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
protected TModelManager Manager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current download state of the <typeparamref name="TModel"/>, whether is has already been downloaded, is in progress, or is not downloaded.
|
||||
/// </summary>
|
||||
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
|
||||
|
||||
protected readonly BindableNumber<double> Progress = new BindableNumber<double> { MinValue = 0, MaxValue = 1 };
|
||||
|
||||
protected DownloadTrackingComposite(TModel model = null)
|
||||
{
|
||||
Model.Value = model;
|
||||
}
|
||||
|
||||
private IBindable<WeakReference<TModel>> managerUpdated;
|
||||
private IBindable<WeakReference<TModel>> managerRemoved;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> managerDownloadBegan;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> managerDownloadFailed;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load()
|
||||
{
|
||||
Model.BindValueChanged(modelInfo =>
|
||||
{
|
||||
if (modelInfo.NewValue == null)
|
||||
attachDownload(null);
|
||||
else if (IsModelAvailableLocally())
|
||||
State.Value = DownloadState.LocallyAvailable;
|
||||
else
|
||||
attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue));
|
||||
}, true);
|
||||
|
||||
if (Manager == null)
|
||||
return;
|
||||
|
||||
managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy();
|
||||
managerDownloadBegan.BindValueChanged(downloadBegan);
|
||||
managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy();
|
||||
managerDownloadFailed.BindValueChanged(downloadFailed);
|
||||
managerUpdated = Manager.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(itemUpdated);
|
||||
managerRemoved = Manager.ItemRemoved.GetBoundCopy();
|
||||
managerRemoved.BindValueChanged(itemRemoved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a database model matches the one expected to be downloaded.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For online play, this could be used to check that the databased model matches the online beatmap.
|
||||
/// </example>
|
||||
/// <param name="databasedModel">The model in database.</param>
|
||||
protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given model is available in the database.
|
||||
/// By default, this calls <see cref="IModelManager{TModel}.IsAvailableLocally"/>,
|
||||
/// but can be overriden to add additional checks for verifying the model in database.
|
||||
/// </summary>
|
||||
protected virtual bool IsModelAvailableLocally() => Manager?.IsAvailableLocally(Model.Value) == true;
|
||||
|
||||
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<TModel>>> weakRequest)
|
||||
{
|
||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (request.Model.Equals(Model.Value))
|
||||
attachDownload(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<TModel>>> weakRequest)
|
||||
{
|
||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (request.Model.Equals(Model.Value))
|
||||
attachDownload(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ArchiveDownloadRequest<TModel> attachedRequest;
|
||||
|
||||
private void attachDownload(ArchiveDownloadRequest<TModel> request)
|
||||
{
|
||||
if (attachedRequest != null)
|
||||
{
|
||||
attachedRequest.Failure -= onRequestFailure;
|
||||
attachedRequest.DownloadProgressed -= onRequestProgress;
|
||||
attachedRequest.Success -= onRequestSuccess;
|
||||
}
|
||||
|
||||
attachedRequest = request;
|
||||
|
||||
if (attachedRequest != null)
|
||||
{
|
||||
if (attachedRequest.Progress == 1)
|
||||
{
|
||||
Progress.Value = 1;
|
||||
State.Value = DownloadState.Importing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Progress.Value = attachedRequest.Progress;
|
||||
State.Value = DownloadState.Downloading;
|
||||
|
||||
attachedRequest.Failure += onRequestFailure;
|
||||
attachedRequest.DownloadProgressed += onRequestProgress;
|
||||
attachedRequest.Success += onRequestSuccess;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
State.Value = DownloadState.NotDownloaded;
|
||||
}
|
||||
}
|
||||
|
||||
private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing);
|
||||
|
||||
private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress);
|
||||
|
||||
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
|
||||
|
||||
private void itemUpdated(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (!item.Equals(Model.Value))
|
||||
return;
|
||||
|
||||
if (!VerifyDatabasedModel(item))
|
||||
{
|
||||
State.Value = DownloadState.NotDownloaded;
|
||||
return;
|
||||
}
|
||||
|
||||
State.Value = DownloadState.LocallyAvailable;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void itemRemoved(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (item.Equals(Model.Value))
|
||||
State.Value = DownloadState.NotDownloaded;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#region Disposal
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
State.UnbindAll();
|
||||
|
||||
attachDownload(null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -16,19 +18,27 @@ namespace osu.Game.Online.Rooms
|
||||
/// This differs from a regular download tracking composite as this accounts for the
|
||||
/// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap.
|
||||
/// </summary>
|
||||
public class OnlinePlayBeatmapAvailabilityTracker : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
|
||||
public sealed class OnlinePlayBeatmapAvailabilityTracker : CompositeDrawable
|
||||
{
|
||||
public readonly IBindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
// Required to allow child components to update. Can potentially be replaced with a `CompositeComponent` class if or when we make one.
|
||||
protected override bool RequiresChildrenUpdate => true;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The availability state of the currently selected playlist item.
|
||||
/// </summary>
|
||||
public IBindable<BeatmapAvailability> Availability => availability;
|
||||
|
||||
private readonly Bindable<BeatmapAvailability> availability = new Bindable<BeatmapAvailability>(BeatmapAvailability.LocallyAvailable());
|
||||
private readonly Bindable<BeatmapAvailability> availability = new Bindable<BeatmapAvailability>(BeatmapAvailability.NotDownloaded());
|
||||
|
||||
private ScheduledDelegate progressUpdate;
|
||||
|
||||
private BeatmapDownloadTracker downloadTracker;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -40,58 +50,38 @@ namespace osu.Game.Online.Rooms
|
||||
if (item.NewValue == null)
|
||||
return;
|
||||
|
||||
Model.Value = item.NewValue.Beatmap.Value.BeatmapSet;
|
||||
downloadTracker?.RemoveAndDisposeImmediately();
|
||||
|
||||
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
|
||||
downloadTracker.State.BindValueChanged(_ => updateAvailability());
|
||||
downloadTracker.Progress.BindValueChanged(_ =>
|
||||
{
|
||||
if (downloadTracker.State.Value != DownloadState.Downloading)
|
||||
return;
|
||||
|
||||
// incoming progress changes are going to be at a very high rate.
|
||||
// we don't want to flood the network with this, so rate limit how often we send progress updates.
|
||||
if (progressUpdate?.Completed != false)
|
||||
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
|
||||
});
|
||||
|
||||
AddInternal(downloadTracker);
|
||||
}, true);
|
||||
|
||||
Progress.BindValueChanged(_ =>
|
||||
{
|
||||
if (State.Value != DownloadState.Downloading)
|
||||
return;
|
||||
|
||||
// incoming progress changes are going to be at a very high rate.
|
||||
// we don't want to flood the network with this, so rate limit how often we send progress updates.
|
||||
if (progressUpdate?.Completed != false)
|
||||
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
|
||||
});
|
||||
|
||||
State.BindValueChanged(_ => updateAvailability(), true);
|
||||
}
|
||||
|
||||
protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet)
|
||||
{
|
||||
int beatmapId = SelectedItem.Value?.Beatmap.Value.OnlineID ?? -1;
|
||||
string checksum = SelectedItem.Value?.Beatmap.Value.MD5Hash;
|
||||
|
||||
var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum);
|
||||
|
||||
if (matchingBeatmap == null)
|
||||
{
|
||||
Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool IsModelAvailableLocally()
|
||||
{
|
||||
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
|
||||
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||
|
||||
var beatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum);
|
||||
return beatmap?.BeatmapSet.DeletePending == false;
|
||||
}
|
||||
|
||||
private void updateAvailability()
|
||||
{
|
||||
switch (State.Value)
|
||||
if (downloadTracker == null)
|
||||
return;
|
||||
|
||||
switch (downloadTracker.State.Value)
|
||||
{
|
||||
case DownloadState.NotDownloaded:
|
||||
availability.Value = BeatmapAvailability.NotDownloaded();
|
||||
break;
|
||||
|
||||
case DownloadState.Downloading:
|
||||
availability.Value = BeatmapAvailability.Downloading((float)Progress.Value);
|
||||
availability.Value = BeatmapAvailability.Downloading((float)downloadTracker.Progress.Value);
|
||||
break;
|
||||
|
||||
case DownloadState.Importing:
|
||||
@ -99,12 +89,27 @@ namespace osu.Game.Online.Rooms
|
||||
break;
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
availability.Value = BeatmapAvailability.LocallyAvailable();
|
||||
bool hashMatches = checkHashValidity();
|
||||
|
||||
availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded();
|
||||
|
||||
// only display a message to the user if a download seems to have just completed.
|
||||
if (!hashMatches && downloadTracker.Progress.Value == 1)
|
||||
Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(State));
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private bool checkHashValidity()
|
||||
{
|
||||
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
|
||||
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||
|
||||
return beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
155
osu.Game/Online/ScoreDownloadTracker.cs
Normal file
155
osu.Game/Online/ScoreDownloadTracker.cs
Normal file
@ -0,0 +1,155 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
public class ScoreDownloadTracker : DownloadTracker<ScoreInfo>
|
||||
{
|
||||
[Resolved(CanBeNull = true)]
|
||||
protected ScoreManager? Manager { get; private set; }
|
||||
|
||||
private ArchiveDownloadRequest<ScoreInfo>? attachedRequest;
|
||||
|
||||
public ScoreDownloadTracker(ScoreInfo trackedItem)
|
||||
: base(trackedItem)
|
||||
{
|
||||
}
|
||||
|
||||
private IBindable<WeakReference<ScoreInfo>>? managerUpdated;
|
||||
private IBindable<WeakReference<ScoreInfo>>? managerRemoved;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<ScoreInfo>>>? managerDownloadBegan;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<ScoreInfo>>>? managerDownloadFailed;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load()
|
||||
{
|
||||
if (Manager == null)
|
||||
return;
|
||||
|
||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||
var scoreInfo = new ScoreInfo { OnlineScoreID = TrackedItem.OnlineScoreID };
|
||||
|
||||
if (Manager.IsAvailableLocally(scoreInfo))
|
||||
UpdateState(DownloadState.LocallyAvailable);
|
||||
else
|
||||
attachDownload(Manager.GetExistingDownload(scoreInfo));
|
||||
|
||||
managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy();
|
||||
managerDownloadBegan.BindValueChanged(downloadBegan);
|
||||
managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy();
|
||||
managerDownloadFailed.BindValueChanged(downloadFailed);
|
||||
managerUpdated = Manager.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(itemUpdated);
|
||||
managerRemoved = Manager.ItemRemoved.GetBoundCopy();
|
||||
managerRemoved.BindValueChanged(itemRemoved);
|
||||
}
|
||||
|
||||
private void downloadBegan(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<ScoreInfo>>> weakRequest)
|
||||
{
|
||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(request.Model, TrackedItem))
|
||||
attachDownload(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadFailed(ValueChangedEvent<WeakReference<ArchiveDownloadRequest<ScoreInfo>>> weakRequest)
|
||||
{
|
||||
if (weakRequest.NewValue.TryGetTarget(out var request))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(request.Model, TrackedItem))
|
||||
attachDownload(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void attachDownload(ArchiveDownloadRequest<ScoreInfo>? request)
|
||||
{
|
||||
if (attachedRequest != null)
|
||||
{
|
||||
attachedRequest.Failure -= onRequestFailure;
|
||||
attachedRequest.DownloadProgressed -= onRequestProgress;
|
||||
attachedRequest.Success -= onRequestSuccess;
|
||||
}
|
||||
|
||||
attachedRequest = request;
|
||||
|
||||
if (attachedRequest != null)
|
||||
{
|
||||
if (attachedRequest.Progress == 1)
|
||||
{
|
||||
UpdateProgress(1);
|
||||
UpdateState(DownloadState.Importing);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateProgress(attachedRequest.Progress);
|
||||
UpdateState(DownloadState.Downloading);
|
||||
|
||||
attachedRequest.Failure += onRequestFailure;
|
||||
attachedRequest.DownloadProgressed += onRequestProgress;
|
||||
attachedRequest.Success += onRequestSuccess;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateState(DownloadState.NotDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRequestSuccess(string _) => Schedule(() => UpdateState(DownloadState.Importing));
|
||||
|
||||
private void onRequestProgress(float progress) => Schedule(() => UpdateProgress(progress));
|
||||
|
||||
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
|
||||
|
||||
private void itemUpdated(ValueChangedEvent<WeakReference<ScoreInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(item, TrackedItem))
|
||||
UpdateState(DownloadState.LocallyAvailable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void itemRemoved(ValueChangedEvent<WeakReference<ScoreInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (checkEquality(item, TrackedItem))
|
||||
UpdateState(DownloadState.NotDownloaded);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool checkEquality(ScoreInfo x, ScoreInfo y) => x.OnlineScoreID == y.OnlineScoreID;
|
||||
|
||||
#region Disposal
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
attachDownload(null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -16,13 +16,13 @@ namespace osu.Game.Online.Solo
|
||||
|
||||
private readonly int beatmapId;
|
||||
|
||||
private readonly ScoreInfo scoreInfo;
|
||||
private readonly SubmittableScore score;
|
||||
|
||||
public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo)
|
||||
{
|
||||
this.beatmapId = beatmapId;
|
||||
this.scoreId = scoreId;
|
||||
this.scoreInfo = scoreInfo;
|
||||
score = new SubmittableScore(scoreInfo);
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -32,7 +32,7 @@ namespace osu.Game.Online.Solo
|
||||
req.ContentType = "application/json";
|
||||
req.Method = HttpMethod.Put;
|
||||
|
||||
req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings
|
||||
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
}));
|
||||
|
75
osu.Game/Online/Solo/SubmittableScore.cs
Normal file
75
osu.Game/Online/Solo/SubmittableScore.cs
Normal file
@ -0,0 +1,75 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Solo
|
||||
{
|
||||
/// <summary>
|
||||
/// A class specifically for sending scores to the API during score submission.
|
||||
/// This is used instead of <see cref="APIScoreInfo"/> due to marginally different serialisation naming requirements.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SubmittableScore
|
||||
{
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
[JsonProperty("total_score")]
|
||||
public long TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty("max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[JsonProperty("passed")]
|
||||
public bool Passed { get; set; }
|
||||
|
||||
// Used for API serialisation/deserialisation.
|
||||
[JsonProperty("mods")]
|
||||
public APIMod[] Mods { get; set; }
|
||||
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics { get; set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public SubmittableScore()
|
||||
{
|
||||
}
|
||||
|
||||
public SubmittableScore(ScoreInfo score)
|
||||
{
|
||||
Rank = score.Rank;
|
||||
TotalScore = score.TotalScore;
|
||||
Accuracy = score.Accuracy;
|
||||
PP = score.PP;
|
||||
MaxCombo = score.MaxCombo;
|
||||
RulesetID = score.RulesetID;
|
||||
Passed = score.Passed;
|
||||
Mods = score.APIMods;
|
||||
User = score.User;
|
||||
Statistics = score.Statistics;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
@ -37,7 +36,7 @@ namespace osu.Game.Overlays.AccountCreation
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private ShakeContainer registerShake;
|
||||
private IEnumerable<Drawable> characterCheckText;
|
||||
private ITextPart characterCheckText;
|
||||
|
||||
private OsuTextBox[] textboxes;
|
||||
private LoadingLayer loadingLayer;
|
||||
@ -136,7 +135,7 @@ namespace osu.Game.Overlays.AccountCreation
|
||||
characterCheckText = passwordDescription.AddText("8 characters long");
|
||||
passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song.");
|
||||
|
||||
passwordTextBox.Current.ValueChanged += password => { characterCheckText.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); };
|
||||
passwordTextBox.Current.ValueChanged += password => { characterCheckText.Drawables.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); };
|
||||
}
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
|
@ -1,19 +0,0 @@
|
||||
// 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.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
|
||||
{
|
||||
public Bindable<BeatmapSetInfo> BeatmapSet => Model;
|
||||
|
||||
protected BeatmapDownloadTrackingComposite(BeatmapSetInfo set = null)
|
||||
: base(set)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -13,7 +14,7 @@ using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class BeatmapPanelDownloadButton : BeatmapDownloadTrackingComposite
|
||||
public class BeatmapPanelDownloadButton : CompositeDrawable
|
||||
{
|
||||
protected bool DownloadEnabled => button.Enabled.Value;
|
||||
|
||||
@ -26,16 +27,31 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
private readonly DownloadButton button;
|
||||
private Bindable<bool> noVideoSetting;
|
||||
|
||||
protected readonly BeatmapDownloadTracker DownloadTracker;
|
||||
|
||||
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
|
||||
|
||||
private readonly BeatmapSetInfo beatmapSet;
|
||||
|
||||
public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
InternalChild = shakeContainer = new ShakeContainer
|
||||
this.beatmapSet = beatmapSet;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = button = new DownloadButton
|
||||
shakeContainer = new ShakeContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = button = new DownloadButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { BindTarget = State }
|
||||
},
|
||||
},
|
||||
DownloadTracker = new BeatmapDownloadTracker(beatmapSet)
|
||||
{
|
||||
State = { BindTarget = State }
|
||||
}
|
||||
};
|
||||
|
||||
button.Add(new DownloadProgressBar(beatmapSet)
|
||||
@ -46,14 +62,6 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
button.State.BindTo(State);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig)
|
||||
{
|
||||
@ -61,7 +69,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
|
||||
button.Action = () =>
|
||||
{
|
||||
switch (State.Value)
|
||||
switch (DownloadTracker.State.Value)
|
||||
{
|
||||
case DownloadState.Downloading:
|
||||
case DownloadState.Importing:
|
||||
@ -73,11 +81,11 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
if (SelectedBeatmap.Value != null)
|
||||
findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineBeatmapID;
|
||||
|
||||
game?.PresentBeatmap(BeatmapSet.Value, findPredicate);
|
||||
game?.PresentBeatmap(beatmapSet, findPredicate);
|
||||
break;
|
||||
|
||||
default:
|
||||
beatmaps.Download(BeatmapSet.Value, noVideoSetting.Value);
|
||||
beatmaps.Download(beatmapSet, noVideoSetting.Value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -92,7 +100,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
break;
|
||||
|
||||
default:
|
||||
if (BeatmapSet.Value?.OnlineInfo?.Availability.DownloadDisabled ?? false)
|
||||
if (beatmapSet.OnlineInfo?.Availability.DownloadDisabled ?? false)
|
||||
{
|
||||
button.Enabled.Value = false;
|
||||
button.TooltipText = "this beatmap is currently not available for download.";
|
||||
@ -102,5 +110,11 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -12,13 +13,22 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class DownloadProgressBar : BeatmapDownloadTrackingComposite
|
||||
public class DownloadProgressBar : CompositeDrawable
|
||||
{
|
||||
private readonly ProgressBar progressBar;
|
||||
private readonly BeatmapDownloadTracker downloadTracker;
|
||||
|
||||
public DownloadProgressBar(BeatmapSetInfo beatmapSet)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
progressBar = new ProgressBar(false)
|
||||
{
|
||||
Height = 0,
|
||||
Alpha = 0,
|
||||
},
|
||||
downloadTracker = new BeatmapDownloadTracker(beatmapSet),
|
||||
};
|
||||
AddInternal(progressBar = new ProgressBar(false)
|
||||
{
|
||||
Height = 0,
|
||||
@ -34,9 +44,9 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
progressBar.FillColour = colours.Blue;
|
||||
progressBar.BackgroundColour = Color4.Black.Opacity(0.7f);
|
||||
progressBar.Current = Progress;
|
||||
progressBar.Current.BindTarget = downloadTracker.Progress;
|
||||
|
||||
State.BindValueChanged(state =>
|
||||
downloadTracker.State.BindValueChanged(state =>
|
||||
{
|
||||
switch (state.NewValue)
|
||||
{
|
||||
|
@ -3,12 +3,14 @@
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -21,8 +23,10 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
public class BeatmapSetHeaderContent : BeatmapDownloadTrackingComposite
|
||||
public class BeatmapSetHeaderContent : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<BeatmapSetInfo> BeatmapSet = new Bindable<BeatmapSetInfo>();
|
||||
|
||||
private const float transition_duration = 200;
|
||||
private const float buttons_height = 45;
|
||||
private const float buttons_spacing = 5;
|
||||
@ -45,6 +49,8 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
private readonly FillFlowContainer fadeContent;
|
||||
private readonly LoadingSpinner loading;
|
||||
|
||||
private BeatmapDownloadTracker downloadTracker;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
@ -222,13 +228,13 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
coverGradient.Colour = ColourInfo.GradientVertical(colourProvider.Background6.Opacity(0.3f), colourProvider.Background6.Opacity(0.8f));
|
||||
|
||||
State.BindValueChanged(_ => updateDownloadButtons());
|
||||
|
||||
BeatmapSet.BindValueChanged(setInfo =>
|
||||
{
|
||||
Picker.BeatmapSet = rulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue;
|
||||
cover.OnlineInfo = setInfo.NewValue?.OnlineInfo;
|
||||
|
||||
downloadTracker?.RemoveAndDisposeImmediately();
|
||||
|
||||
if (setInfo.NewValue == null)
|
||||
{
|
||||
onlineStatusPill.FadeTo(0.5f, 500, Easing.OutQuint);
|
||||
@ -241,6 +247,10 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadTracker = new BeatmapDownloadTracker(setInfo.NewValue);
|
||||
downloadTracker.State.BindValueChanged(_ => updateDownloadButtons());
|
||||
AddInternal(downloadTracker);
|
||||
|
||||
fadeContent.FadeIn(500, Easing.OutQuint);
|
||||
|
||||
loading.Hide();
|
||||
@ -266,13 +276,13 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
if (BeatmapSet.Value == null) return;
|
||||
|
||||
if (BeatmapSet.Value.OnlineInfo.Availability.DownloadDisabled && State.Value != DownloadState.LocallyAvailable)
|
||||
if (BeatmapSet.Value.OnlineInfo.Availability.DownloadDisabled && downloadTracker.State.Value != DownloadState.LocallyAvailable)
|
||||
{
|
||||
downloadButtonsContainer.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (State.Value)
|
||||
switch (downloadTracker.State.Value)
|
||||
{
|
||||
case DownloadState.LocallyAvailable:
|
||||
// temporary for UX until new design is implemented.
|
||||
|
@ -22,7 +22,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
{
|
||||
public class HeaderDownloadButton : BeatmapDownloadTrackingComposite, IHasTooltip
|
||||
public class HeaderDownloadButton : CompositeDrawable, IHasTooltip
|
||||
{
|
||||
private const int text_size = 12;
|
||||
|
||||
@ -35,9 +35,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
private ShakeContainer shakeContainer;
|
||||
private HeaderButton button;
|
||||
|
||||
private BeatmapDownloadTracker downloadTracker;
|
||||
private readonly BeatmapSetInfo beatmapSet;
|
||||
|
||||
public HeaderDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
this.beatmapSet = beatmapSet;
|
||||
this.noVideo = noVideo;
|
||||
|
||||
Width = 120;
|
||||
@ -49,13 +52,17 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
{
|
||||
FillFlowContainer textSprites;
|
||||
|
||||
AddInternal(shakeContainer = new ShakeContainer
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Child = button = new HeaderButton { RelativeSizeAxes = Axes.Both },
|
||||
});
|
||||
shakeContainer = new ShakeContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Child = button = new HeaderButton { RelativeSizeAxes = Axes.Both },
|
||||
},
|
||||
downloadTracker = new BeatmapDownloadTracker(beatmapSet),
|
||||
};
|
||||
|
||||
button.AddRange(new Drawable[]
|
||||
{
|
||||
@ -83,7 +90,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
},
|
||||
}
|
||||
},
|
||||
new DownloadProgressBar(BeatmapSet.Value)
|
||||
new DownloadProgressBar(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
@ -92,20 +99,20 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
|
||||
button.Action = () =>
|
||||
{
|
||||
if (State.Value != DownloadState.NotDownloaded)
|
||||
if (downloadTracker.State.Value != DownloadState.NotDownloaded)
|
||||
{
|
||||
shakeContainer.Shake();
|
||||
return;
|
||||
}
|
||||
|
||||
beatmaps.Download(BeatmapSet.Value, noVideo);
|
||||
beatmaps.Download(beatmapSet, noVideo);
|
||||
};
|
||||
|
||||
localUser.BindTo(api.LocalUser);
|
||||
localUser.BindValueChanged(userChanged, true);
|
||||
button.Enabled.BindValueChanged(enabledChanged, true);
|
||||
|
||||
State.BindValueChanged(state =>
|
||||
downloadTracker.State.BindValueChanged(state =>
|
||||
{
|
||||
switch (state.NewValue)
|
||||
{
|
||||
@ -161,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
|
||||
private LocalisableString getVideoSuffixText()
|
||||
{
|
||||
if (!BeatmapSet.Value.OnlineInfo.HasVideo)
|
||||
if (!beatmapSet.OnlineInfo.HasVideo)
|
||||
return string.Empty;
|
||||
|
||||
return noVideo ? BeatmapsetsStrings.ShowDetailsDownloadNoVideo : BeatmapsetsStrings.ShowDetailsDownloadVideo;
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
|
||||
private CancellationTokenSource loadCancellationSource;
|
||||
|
||||
protected APILegacyScores Scores
|
||||
protected APIScoresCollection Scores
|
||||
{
|
||||
set => Schedule(() =>
|
||||
{
|
||||
@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
if (value?.Scores.Any() != true)
|
||||
return;
|
||||
|
||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token)
|
||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value)).ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(ordered => Schedule(() =>
|
||||
{
|
||||
if (loadCancellationSource.IsCancellationRequested)
|
||||
@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
scoreTable.Show();
|
||||
|
||||
var userScore = value.UserScore;
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets);
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value);
|
||||
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -166,12 +165,12 @@ namespace osu.Game.Overlays.Changelog
|
||||
{
|
||||
}
|
||||
|
||||
protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new SupporterPromoLinkCompiler(parts);
|
||||
protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new SupporterPromoLinkCompiler(textPart);
|
||||
|
||||
private class SupporterPromoLinkCompiler : DrawableLinkCompiler
|
||||
{
|
||||
public SupporterPromoLinkCompiler(IEnumerable<Drawable> parts)
|
||||
: base(parts)
|
||||
public SupporterPromoLinkCompiler(ITextPart part)
|
||||
: base(part)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -5,17 +5,17 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
{
|
||||
public class DashboardBeatmapListing : CompositeDrawable
|
||||
{
|
||||
private readonly List<BeatmapSetInfo> newBeatmaps;
|
||||
private readonly List<BeatmapSetInfo> popularBeatmaps;
|
||||
private readonly List<APIBeatmapSet> newBeatmaps;
|
||||
private readonly List<APIBeatmapSet> popularBeatmaps;
|
||||
|
||||
public DashboardBeatmapListing(List<BeatmapSetInfo> newBeatmaps, List<BeatmapSetInfo> popularBeatmaps)
|
||||
public DashboardBeatmapListing(List<APIBeatmapSet> newBeatmaps, List<APIBeatmapSet> popularBeatmaps)
|
||||
{
|
||||
this.newBeatmaps = newBeatmaps;
|
||||
this.popularBeatmaps = popularBeatmaps;
|
||||
|
@ -7,11 +7,11 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
@ -24,14 +24,14 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
[Resolved(canBeNull: true)]
|
||||
private BeatmapSetOverlay beatmapOverlay { get; set; }
|
||||
|
||||
protected readonly BeatmapSetInfo SetInfo;
|
||||
protected readonly APIBeatmapSet BeatmapSet;
|
||||
|
||||
private Box hoverBackground;
|
||||
private SpriteIcon chevron;
|
||||
|
||||
protected DashboardBeatmapPanel(BeatmapSetInfo setInfo)
|
||||
protected DashboardBeatmapPanel(APIBeatmapSet beatmapSet)
|
||||
{
|
||||
SetInfo = setInfo;
|
||||
BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
OnlineInfo = SetInfo.OnlineInfo
|
||||
OnlineInfo = BeatmapSet
|
||||
}
|
||||
},
|
||||
new Container
|
||||
@ -103,14 +103,14 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
||||
Text = SetInfo.Metadata.Title
|
||||
Text = BeatmapSet.Title
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true,
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
|
||||
Text = SetInfo.Metadata.Artist
|
||||
Text = BeatmapSet.Artist
|
||||
},
|
||||
new LinkFlowContainer(f => f.Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular))
|
||||
{
|
||||
@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
}.With(c =>
|
||||
{
|
||||
c.AddText("by");
|
||||
c.AddUserLink(SetInfo.Metadata.Author);
|
||||
c.AddUserLink(BeatmapSet.Author);
|
||||
c.AddArbitraryDrawable(CreateInfo());
|
||||
})
|
||||
}
|
||||
@ -143,8 +143,8 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
|
||||
Action = () =>
|
||||
{
|
||||
if (SetInfo.OnlineBeatmapSetID.HasValue)
|
||||
beatmapOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value);
|
||||
if (BeatmapSet.OnlineID > 0)
|
||||
beatmapOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,19 +3,19 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
{
|
||||
public class DashboardNewBeatmapPanel : DashboardBeatmapPanel
|
||||
{
|
||||
public DashboardNewBeatmapPanel(BeatmapSetInfo setInfo)
|
||||
: base(setInfo)
|
||||
public DashboardNewBeatmapPanel(APIBeatmapSet beatmapSet)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateInfo() => new DrawableDate(SetInfo.OnlineInfo.Ranked ?? DateTimeOffset.Now, 10, false)
|
||||
protected override Drawable CreateInfo() => new DrawableDate(BeatmapSet.Ranked ?? DateTimeOffset.Now, 10, false)
|
||||
{
|
||||
Colour = ColourProvider.Foreground1
|
||||
};
|
||||
|
@ -4,17 +4,17 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
{
|
||||
public class DashboardPopularBeatmapPanel : DashboardBeatmapPanel
|
||||
{
|
||||
public DashboardPopularBeatmapPanel(BeatmapSetInfo setInfo)
|
||||
: base(setInfo)
|
||||
public DashboardPopularBeatmapPanel(APIBeatmapSet beatmapSet)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular),
|
||||
Text = SetInfo.OnlineInfo.FavouriteCount.ToString()
|
||||
Text = BeatmapSet.FavouriteCount.ToString()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -6,20 +6,20 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
{
|
||||
public abstract class DrawableBeatmapList : CompositeDrawable
|
||||
{
|
||||
private readonly List<BeatmapSetInfo> beatmaps;
|
||||
private readonly List<APIBeatmapSet> beatmapSets;
|
||||
|
||||
protected DrawableBeatmapList(List<BeatmapSetInfo> beatmaps)
|
||||
protected DrawableBeatmapList(List<APIBeatmapSet> beatmapSets)
|
||||
{
|
||||
this.beatmaps = beatmaps;
|
||||
this.beatmapSets = beatmapSets;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -46,11 +46,11 @@ namespace osu.Game.Overlays.Dashboard.Home
|
||||
}
|
||||
};
|
||||
|
||||
flow.AddRange(beatmaps.Select(CreateBeatmapPanel));
|
||||
flow.AddRange(beatmapSets.Select(CreateBeatmapPanel));
|
||||
}
|
||||
|
||||
protected abstract string Title { get; }
|
||||
|
||||
protected abstract DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo);
|
||||
protected abstract DashboardBeatmapPanel CreateBeatmapPanel(APIBeatmapSet beatmapSet);
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
{
|
||||
public class DrawableNewBeatmapList : DrawableBeatmapList
|
||||
{
|
||||
public DrawableNewBeatmapList(List<BeatmapSetInfo> beatmaps)
|
||||
: base(beatmaps)
|
||||
public DrawableNewBeatmapList(List<APIBeatmapSet> beatmapSets)
|
||||
: base(beatmapSets)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardNewBeatmapPanel(setInfo);
|
||||
protected override DashboardBeatmapPanel CreateBeatmapPanel(APIBeatmapSet beatmapSet) => new DashboardNewBeatmapPanel(beatmapSet);
|
||||
|
||||
protected override string Title => "New Ranked Beatmaps";
|
||||
}
|
||||
|
@ -2,18 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Home
|
||||
{
|
||||
public class DrawablePopularBeatmapList : DrawableBeatmapList
|
||||
{
|
||||
public DrawablePopularBeatmapList(List<BeatmapSetInfo> beatmaps)
|
||||
: base(beatmaps)
|
||||
public DrawablePopularBeatmapList(List<APIBeatmapSet> beatmapSets)
|
||||
: base(beatmapSets)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DashboardBeatmapPanel CreateBeatmapPanel(BeatmapSetInfo setInfo) => new DashboardPopularBeatmapPanel(setInfo);
|
||||
protected override DashboardBeatmapPanel CreateBeatmapPanel(APIBeatmapSet beatmapSet) => new DashboardPopularBeatmapPanel(beatmapSet);
|
||||
|
||||
protected override string Title => "Popular Beatmaps";
|
||||
}
|
||||
|
@ -3,12 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -25,7 +23,7 @@ namespace osu.Game.Overlays.Music
|
||||
public Action<BeatmapSetInfo> RequestSelection;
|
||||
|
||||
private TextFlowContainer text;
|
||||
private IEnumerable<Drawable> titleSprites;
|
||||
private ITextPart titlePart;
|
||||
|
||||
private ILocalisedBindableString title;
|
||||
private ILocalisedBindableString artist;
|
||||
@ -63,11 +61,16 @@ namespace osu.Game.Overlays.Music
|
||||
if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true)
|
||||
return;
|
||||
|
||||
foreach (Drawable s in titleSprites)
|
||||
s.FadeColour(set.NewValue.Equals(Model) ? selectedColour : Color4.White, FADE_DURATION);
|
||||
updateSelectionState(false);
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updateSelectionState(bool instant)
|
||||
{
|
||||
foreach (Drawable s in titlePart.Drawables)
|
||||
s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? selectedColour : Color4.White, instant ? 0 : FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => text = new OsuTextFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -79,7 +82,8 @@ namespace osu.Game.Overlays.Music
|
||||
text.Clear();
|
||||
|
||||
// space after the title to put a space between the title and artist
|
||||
titleSprites = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType<SpriteText>();
|
||||
titlePart = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular));
|
||||
updateSelectionState(true);
|
||||
|
||||
text.AddText(artist.Value, sprite =>
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
{
|
||||
public class PaginatedScoreContainer : PaginatedProfileSubsection<APILegacyScoreInfo>
|
||||
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScoreInfo>
|
||||
{
|
||||
private readonly ScoreType type;
|
||||
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnItemsReceived(List<APILegacyScoreInfo> items)
|
||||
protected override void OnItemsReceived(List<APIScoreInfo> items)
|
||||
{
|
||||
if (VisiblePages == 0)
|
||||
drawableItemIndex = 0;
|
||||
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
base.OnItemsReceived(items);
|
||||
}
|
||||
|
||||
protected override APIRequest<List<APILegacyScoreInfo>> CreateRequest() =>
|
||||
protected override APIRequest<List<APIScoreInfo>> CreateRequest() =>
|
||||
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
|
||||
|
||||
private int drawableItemIndex;
|
||||
|
||||
protected override Drawable CreateDrawableItem(APILegacyScoreInfo model)
|
||||
protected override Drawable CreateDrawableItem(APIScoreInfo model)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
@ -0,0 +1,43 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Video;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
{
|
||||
public class VideoSettings : SettingsSubsection
|
||||
{
|
||||
protected override LocalisableString Header => GraphicsSettingsStrings.VideoHeader;
|
||||
|
||||
private Bindable<HardwareVideoDecoder> hardwareVideoDecoder;
|
||||
private SettingsCheckbox hwAccelCheckbox;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FrameworkConfigManager config)
|
||||
{
|
||||
hardwareVideoDecoder = config.GetBindable<HardwareVideoDecoder>(FrameworkSetting.HardwareVideoDecoder);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hwAccelCheckbox = new SettingsCheckbox
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.UseHardwareAcceleration,
|
||||
},
|
||||
};
|
||||
|
||||
hwAccelCheckbox.Current.Default = hardwareVideoDecoder.Default != HardwareVideoDecoder.None;
|
||||
hwAccelCheckbox.Current.Value = hardwareVideoDecoder.Value != HardwareVideoDecoder.None;
|
||||
|
||||
hwAccelCheckbox.Current.BindValueChanged(val =>
|
||||
{
|
||||
hardwareVideoDecoder.Value = val.NewValue ? HardwareVideoDecoder.Any : HardwareVideoDecoder.None;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
{
|
||||
new LayoutSettings(),
|
||||
new RendererSettings(),
|
||||
new VideoSettings(),
|
||||
new ScreenshotSettings(),
|
||||
};
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets
|
||||
/// <summary>
|
||||
/// A representation of a ruleset's metadata.
|
||||
/// </summary>
|
||||
public interface IRulesetInfo : IHasOnlineID
|
||||
public interface IRulesetInfo : IHasOnlineID<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// The user-exposed name of this ruleset.
|
||||
|
39
osu.Game/Scoring/IScoreInfo.cs
Normal file
39
osu.Game/Scoring/IScoreInfo.cs
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
{
|
||||
public interface IScoreInfo : IHasOnlineID<long>
|
||||
{
|
||||
User User { get; }
|
||||
|
||||
long TotalScore { get; }
|
||||
|
||||
int MaxCombo { get; }
|
||||
|
||||
double Accuracy { get; }
|
||||
|
||||
bool HasReplay { get; }
|
||||
|
||||
DateTimeOffset Date { get; }
|
||||
|
||||
double? PP { get; }
|
||||
|
||||
IBeatmapInfo Beatmap { get; }
|
||||
|
||||
IRulesetInfo Ruleset { get; }
|
||||
|
||||
ScoreRank Rank { get; }
|
||||
|
||||
// Mods is currently missing from this interface as the `IMod` class has properties which can't be fulfilled by `APIMod`,
|
||||
// but also doesn't expose `Settings`. We can consider how to implement this in the future if required.
|
||||
|
||||
// Statistics is also missing. This can be reconsidered once changes in serialisation have been completed.
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
@ -19,47 +18,36 @@ using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
{
|
||||
public class ScoreInfo : IHasFiles<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo>
|
||||
public class ScoreInfo : IScoreInfo, IHasFiles<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo>
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
[JsonProperty("rank")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
[JsonProperty("total_score")]
|
||||
public long TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
[Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database.
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
|
||||
|
||||
[JsonProperty(@"pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
[JsonProperty("max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int Combo { get; set; } // Todo: Shouldn't exist in here
|
||||
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[JsonProperty("passed")]
|
||||
[NotMapped]
|
||||
public bool Passed { get; set; } = true;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual RulesetInfo Ruleset { get; set; }
|
||||
public RulesetInfo Ruleset { get; set; }
|
||||
|
||||
private APIMod[] localAPIMods;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
public Mod[] Mods
|
||||
{
|
||||
@ -74,7 +62,7 @@ namespace osu.Game.Scoring
|
||||
if (mods != null)
|
||||
scoreMods = mods;
|
||||
else if (localAPIMods != null)
|
||||
scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
|
||||
|
||||
return scoreMods;
|
||||
}
|
||||
@ -86,9 +74,8 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
|
||||
// Used for API serialisation/deserialisation.
|
||||
[JsonProperty("mods")]
|
||||
[NotMapped]
|
||||
private APIMod[] apiMods
|
||||
public APIMod[] APIMods
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -110,19 +97,16 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
|
||||
// Used for database serialisation/deserialisation.
|
||||
[JsonIgnore]
|
||||
[Column("Mods")]
|
||||
public string ModsJson
|
||||
{
|
||||
get => JsonConvert.SerializeObject(apiMods);
|
||||
set => apiMods = JsonConvert.DeserializeObject<APIMod[]>(value);
|
||||
get => JsonConvert.SerializeObject(APIMods);
|
||||
set => APIMods = JsonConvert.DeserializeObject<APIMod[]>(value);
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("User")]
|
||||
public string UserString
|
||||
{
|
||||
@ -134,7 +118,6 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("UserID")]
|
||||
public int? UserID
|
||||
{
|
||||
@ -146,23 +129,18 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public int BeatmapInfoID { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("Beatmap")]
|
||||
public virtual BeatmapInfo BeatmapInfo { get; set; }
|
||||
public BeatmapInfo BeatmapInfo { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public long? OnlineScoreID { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>();
|
||||
[NotMapped]
|
||||
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
|
||||
|
||||
[JsonIgnore]
|
||||
[Column("Statistics")]
|
||||
public string StatisticsJson
|
||||
{
|
||||
@ -180,29 +158,23 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public List<HitEvent> HitEvents { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScoreFileInfo> Files { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Hash { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool DeletePending { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position of this score, starting at 1.
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
[JsonProperty("position")]
|
||||
public int? Position { get; set; }
|
||||
public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone.
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
|
||||
|
||||
@ -271,5 +243,19 @@ namespace osu.Game.Scoring
|
||||
|
||||
return ReferenceEquals(this, other);
|
||||
}
|
||||
|
||||
#region Implementation of IHasOnlineID
|
||||
|
||||
public long OnlineID => OnlineScoreID ?? -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IScoreInfo
|
||||
|
||||
IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo;
|
||||
IRulesetInfo IScoreInfo.Ruleset => Ruleset;
|
||||
bool IScoreInfo.HasReplay => Files.Any();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -16,5 +16,8 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
|
||||
protected override ArchiveDownloadRequest<ScoreInfo> CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score);
|
||||
|
||||
public override ArchiveDownloadRequest<ScoreInfo> GetExistingDownload(ScoreInfo model)
|
||||
=> CurrentDownloads.Find(r => r.Model.OnlineScoreID == model.OnlineScoreID);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Menu
|
||||
private readonly Bindable<User> currentUser = new Bindable<User>();
|
||||
private FillFlowContainer fill;
|
||||
|
||||
private readonly List<Drawable> expendableText = new List<Drawable>();
|
||||
private readonly List<ITextPart> expendableText = new List<ITextPart>();
|
||||
|
||||
public Disclaimer(OsuScreen nextScreen = null)
|
||||
{
|
||||
@ -97,7 +97,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular));
|
||||
|
||||
expendableText.AddRange(textFlow.AddText("lazer", t =>
|
||||
expendableText.Add(textFlow.AddText("lazer", t =>
|
||||
{
|
||||
t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular);
|
||||
t.Colour = colours.PinkLight;
|
||||
@ -114,7 +114,7 @@ namespace osu.Game.Screens.Menu
|
||||
t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold);
|
||||
t.Colour = colours.Pink;
|
||||
});
|
||||
expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular));
|
||||
expendableText.Add(textFlow.AddText(" coming to osu!", formatRegular));
|
||||
textFlow.AddText(".", formatRegular);
|
||||
|
||||
textFlow.NewParagraph();
|
||||
@ -152,7 +152,7 @@ namespace osu.Game.Screens.Menu
|
||||
t.Font = t.Font.With(size: 20);
|
||||
t.Origin = Anchor.Centre;
|
||||
t.Colour = colours.Pink;
|
||||
}).First();
|
||||
}).Drawables.First();
|
||||
|
||||
if (IsLoaded)
|
||||
animateHeart();
|
||||
@ -193,7 +193,7 @@ namespace osu.Game.Screens.Menu
|
||||
using (BeginDelayedSequence(520 + 160))
|
||||
{
|
||||
fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart);
|
||||
Schedule(() => expendableText.ForEach(t =>
|
||||
Schedule(() => expendableText.SelectMany(t => t.Drawables).ForEach(t =>
|
||||
{
|
||||
t.FadeOut(100);
|
||||
t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart);
|
||||
|
@ -253,10 +253,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
protected virtual IEnumerable<Drawable> CreateButtons() =>
|
||||
new Drawable[]
|
||||
{
|
||||
new PlaylistDownloadButton(Item)
|
||||
{
|
||||
Size = new Vector2(50, 30)
|
||||
},
|
||||
new PlaylistDownloadButton(Item),
|
||||
new PlaylistRemoveButton
|
||||
{
|
||||
Size = new Vector2(30, 30),
|
||||
@ -287,28 +284,33 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
return true;
|
||||
}
|
||||
|
||||
private class PlaylistDownloadButton : BeatmapPanelDownloadButton
|
||||
private sealed class PlaylistDownloadButton : BeatmapPanelDownloadButton
|
||||
{
|
||||
private readonly PlaylistItem playlistItem;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||
// required for download tracking, as this button hides itself. can probably be removed with a bit of consideration.
|
||||
public override bool IsPresent => true;
|
||||
|
||||
private const float width = 50;
|
||||
|
||||
public PlaylistDownloadButton(PlaylistItem playlistItem)
|
||||
: base(playlistItem.Beatmap.Value.BeatmapSet)
|
||||
{
|
||||
this.playlistItem = playlistItem;
|
||||
|
||||
Size = new Vector2(width, 30);
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
State.BindValueChanged(stateChanged, true);
|
||||
FinishTransforms(true);
|
||||
|
||||
// base implementation calls FinishTransforms, so should be run after the above state update.
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
private void stateChanged(ValueChangedEvent<DownloadState> state)
|
||||
@ -320,12 +322,16 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null)
|
||||
State.Value = DownloadState.NotDownloaded;
|
||||
else
|
||||
this.FadeTo(0, 500);
|
||||
{
|
||||
this.FadeTo(0, 500)
|
||||
.ResizeWidthTo(0, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
this.FadeTo(1, 500);
|
||||
this.ResizeWidthTo(width, 500, Easing.OutQuint)
|
||||
.FadeTo(1, 500);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -65,9 +65,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
|
||||
|
||||
[Cached]
|
||||
protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; private set; }
|
||||
private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; }
|
||||
|
||||
protected IBindable<BeatmapAvailability> BeatmapAvailability => BeatmapAvailabilityTracker.Availability;
|
||||
protected IBindable<BeatmapAvailability> BeatmapAvailability => beatmapAvailabilityTracker.Availability;
|
||||
|
||||
public readonly Room Room;
|
||||
private readonly bool allowEdit;
|
||||
@ -88,7 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
|
||||
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = SelectedItem }
|
||||
};
|
||||
@ -103,7 +103,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
BeatmapAvailabilityTracker,
|
||||
beatmapAvailabilityTracker,
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -10,6 +10,7 @@ using ManagedBass.Fx;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -83,7 +84,7 @@ namespace osu.Game.Screens.Play
|
||||
Content,
|
||||
redFlashLayer = new Box
|
||||
{
|
||||
Colour = Color4.Red,
|
||||
Colour = Color4.Red.Opacity(0.6f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Depth = float.MinValue,
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
@ -12,13 +13,17 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
public class ReplayDownloadButton : DownloadTrackingComposite<ScoreInfo, ScoreManager>
|
||||
public class ReplayDownloadButton : CompositeDrawable
|
||||
{
|
||||
public Bindable<ScoreInfo> Score => Model;
|
||||
public readonly Bindable<ScoreInfo> Score = new Bindable<ScoreInfo>();
|
||||
|
||||
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
|
||||
|
||||
private DownloadButton button;
|
||||
private ShakeContainer shakeContainer;
|
||||
|
||||
private ScoreDownloadTracker downloadTracker;
|
||||
|
||||
private ReplayAvailability replayAvailability
|
||||
{
|
||||
get
|
||||
@ -26,7 +31,7 @@ namespace osu.Game.Screens.Ranking
|
||||
if (State.Value == DownloadState.LocallyAvailable)
|
||||
return ReplayAvailability.Local;
|
||||
|
||||
if (!string.IsNullOrEmpty(Model.Value?.Hash))
|
||||
if (!string.IsNullOrEmpty(Score.Value?.Hash))
|
||||
return ReplayAvailability.Online;
|
||||
|
||||
return ReplayAvailability.NotAvailable;
|
||||
@ -34,8 +39,8 @@ namespace osu.Game.Screens.Ranking
|
||||
}
|
||||
|
||||
public ReplayDownloadButton(ScoreInfo score)
|
||||
: base(score)
|
||||
{
|
||||
Score.Value = score;
|
||||
Size = new Vector2(50, 30);
|
||||
}
|
||||
|
||||
@ -56,11 +61,11 @@ namespace osu.Game.Screens.Ranking
|
||||
switch (State.Value)
|
||||
{
|
||||
case DownloadState.LocallyAvailable:
|
||||
game?.PresentScore(Model.Value, ScorePresentType.Gameplay);
|
||||
game?.PresentScore(Score.Value, ScorePresentType.Gameplay);
|
||||
break;
|
||||
|
||||
case DownloadState.NotDownloaded:
|
||||
scores.Download(Model.Value, false);
|
||||
scores.Download(Score.Value, false);
|
||||
break;
|
||||
|
||||
case DownloadState.Importing:
|
||||
@ -70,17 +75,25 @@ namespace osu.Game.Screens.Ranking
|
||||
}
|
||||
};
|
||||
|
||||
State.BindValueChanged(state =>
|
||||
Score.BindValueChanged(score =>
|
||||
{
|
||||
button.State.Value = state.NewValue;
|
||||
downloadTracker?.RemoveAndDisposeImmediately();
|
||||
|
||||
if (score.NewValue != null)
|
||||
{
|
||||
AddInternal(downloadTracker = new ScoreDownloadTracker(score.NewValue)
|
||||
{
|
||||
State = { BindTarget = State }
|
||||
});
|
||||
}
|
||||
|
||||
button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable;
|
||||
updateTooltip();
|
||||
}, true);
|
||||
|
||||
Model.BindValueChanged(_ =>
|
||||
State.BindValueChanged(state =>
|
||||
{
|
||||
button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable;
|
||||
|
||||
button.State.Value = state.NewValue;
|
||||
updateTooltip();
|
||||
}, true);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking
|
||||
return null;
|
||||
|
||||
getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset);
|
||||
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
|
||||
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo)));
|
||||
return getScoreRequest;
|
||||
}
|
||||
|
||||
|
@ -94,9 +94,6 @@ namespace osu.Game.Screens.Select.Details
|
||||
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
|
||||
modSettingChangeTracker.SettingChanged += m =>
|
||||
{
|
||||
if (!(m is IApplicableToDifficulty))
|
||||
return;
|
||||
|
||||
debouncedStatisticsUpdate?.Cancel();
|
||||
debouncedStatisticsUpdate = Scheduler.AddDelayed(updateStatistics, 100);
|
||||
};
|
||||
|
@ -192,14 +192,14 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
req.Success += r =>
|
||||
{
|
||||
scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token)
|
||||
scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, BeatmapInfo)).ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(ordered => Schedule(() =>
|
||||
{
|
||||
if (loadCancellationSource.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
scoresCallback?.Invoke(ordered.Result);
|
||||
TopScore = r.UserScore?.CreateScoreInfo(rulesets);
|
||||
TopScore = r.UserScore?.CreateScoreInfo(rulesets, BeatmapInfo);
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
};
|
||||
|
||||
|
@ -93,7 +93,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
private Stream getConfigurationStream()
|
||||
{
|
||||
string path = SkinInfo.Files.SingleOrDefault(f => f.Filename == "skin.ini")?.FileInfo.StoragePath;
|
||||
string path = SkinInfo.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk";
|
||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk";
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
|
||||
@ -149,9 +149,9 @@ namespace osu.Game.Skinning
|
||||
CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID);
|
||||
}
|
||||
|
||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? "No name" };
|
||||
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" };
|
||||
|
||||
private const string unknown_creator_string = "Unknown";
|
||||
private const string unknown_creator_string = @"Unknown";
|
||||
|
||||
protected override bool HasCustomHashFunction => true;
|
||||
|
||||
@ -164,7 +164,7 @@ namespace osu.Game.Skinning
|
||||
// `Skin` will parse the skin.ini and populate `Skin.Configuration` during construction above.
|
||||
string skinIniSourcedName = instance.Configuration.SkinInfo.Name;
|
||||
string skinIniSourcedCreator = instance.Configuration.SkinInfo.Creator;
|
||||
string archiveName = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase);
|
||||
string archiveName = item.Name.Replace(@".osk", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
bool isImport = item.ID == 0;
|
||||
|
||||
@ -177,7 +177,7 @@ namespace osu.Game.Skinning
|
||||
// In an ideal world, skin.ini would be the only source of metadata, but a lot of skin creators and users don't update it when making modifications.
|
||||
// In both of these cases, the expectation from the user is that the filename or folder name is displayed somewhere to identify the skin.
|
||||
if (archiveName != item.Name)
|
||||
item.Name = $"{item.Name} [{archiveName}]";
|
||||
item.Name = @$"{item.Name} [{archiveName}]";
|
||||
}
|
||||
|
||||
// By this point, the metadata in SkinInfo will be correct.
|
||||
@ -191,10 +191,10 @@ namespace osu.Game.Skinning
|
||||
|
||||
private void updateSkinIniMetadata(SkinInfo item)
|
||||
{
|
||||
string nameLine = $"Name: {item.Name}";
|
||||
string authorLine = $"Author: {item.Creator}";
|
||||
string nameLine = @$"Name: {item.Name}";
|
||||
string authorLine = @$"Author: {item.Creator}";
|
||||
|
||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename == "skin.ini");
|
||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existingFile != null)
|
||||
{
|
||||
@ -210,12 +210,12 @@ namespace osu.Game.Skinning
|
||||
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("Name:", StringComparison.Ordinal))
|
||||
if (line.StartsWith(@"Name:", StringComparison.Ordinal))
|
||||
{
|
||||
outputLines.Add(nameLine);
|
||||
addedName = true;
|
||||
}
|
||||
else if (line.StartsWith("Author:", StringComparison.Ordinal))
|
||||
else if (line.StartsWith(@"Author:", StringComparison.Ordinal))
|
||||
{
|
||||
outputLines.Add(authorLine);
|
||||
addedAuthor = true;
|
||||
@ -229,7 +229,7 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
outputLines.AddRange(new[]
|
||||
{
|
||||
"[General]",
|
||||
@"[General]",
|
||||
nameLine,
|
||||
authorLine,
|
||||
});
|
||||
@ -252,13 +252,13 @@ namespace osu.Game.Skinning
|
||||
{
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
{
|
||||
sw.WriteLine("[General]");
|
||||
sw.WriteLine(@"[General]");
|
||||
sw.WriteLine(nameLine);
|
||||
sw.WriteLine(authorLine);
|
||||
sw.WriteLine("Version: latest");
|
||||
sw.WriteLine(@"Version: latest");
|
||||
}
|
||||
|
||||
AddFile(item, stream, "skin.ini");
|
||||
AddFile(item, stream, @"skin.ini");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,7 +295,7 @@ namespace osu.Game.Skinning
|
||||
// if the user is attempting to save one of the default skin implementations, create a copy first.
|
||||
CurrentSkinInfo.Value = Import(new SkinInfo
|
||||
{
|
||||
Name = skin.SkinInfo.Name + " (modified)",
|
||||
Name = skin.SkinInfo.Name + @" (modified)",
|
||||
Creator = skin.SkinInfo.Creator,
|
||||
InstantiationInfo = skin.SkinInfo.InstantiationInfo,
|
||||
}).Result.Value;
|
||||
@ -312,7 +312,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
using (var streamContent = new MemoryStream(Encoding.UTF8.GetBytes(json)))
|
||||
{
|
||||
string filename = $"{drawableInfo.Key}.json";
|
||||
string filename = @$"{drawableInfo.Key}.json";
|
||||
|
||||
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.6.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1026.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
||||
<PackageReference Include="Sentry" Version="3.9.4" />
|
||||
<PackageReference Include="SharpCompress" Version="0.29.0" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1026.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1029.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||
@ -93,7 +93,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1026.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user