1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 12:33:01 +08:00

Merge pull request #14917 from peppy/new-interfaces

Add new read-only interfaces for all remaining model types
This commit is contained in:
Dan Balasescu 2021-10-06 12:18:31 +09:00 committed by GitHub
commit 13bea7833e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 553 additions and 127 deletions

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
// this is the same as osu!, so there's potential to share the implementation... maybe // this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
return new CatchDifficultyAttributes return new CatchDifficultyAttributes
{ {

View File

@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
} }

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
{ {
Direction.Value = ScrollingDirection.Down; Direction.Value = ScrollingDirection.Down;
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
} }
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count; int maxCombo = beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)

View File

@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN);
// Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects
double secondsDuration = Duration / 1000; double secondsDuration = Duration / 1000;
double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); double minimumRotationsPerSecond = stable_matching_fudge * IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5);
SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond); SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond);
MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration);

View File

@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
case IHasDuration endTimeData: case IHasDuration endTimeData:
{ {
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; double hitMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
yield return new Swell yield return new Swell
{ {

View File

@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring
{ {
base.ApplyBeatmap(beatmap); base.ApplyBeatmap(beatmap);
hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType<Hit>().Count()) * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType<Hit>().Count()) * IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); hpMissMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
} }
protected override double GetHealthIncreaseFor(JudgementResult result) protected override double GetHealthIncreaseFor(JudgementResult result)

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Localisation
Title = "Romanised title", Title = "Romanised title",
TitleUnicode = "Unicode Title" TitleUnicode = "Unicode Title"
}; };
var romanisableString = metadata.ToRomanisableString(); var romanisableString = metadata.GetDisplayTitleRomanisable();
Assert.AreEqual(metadata.ToString(), romanisableString.Romanised); Assert.AreEqual(metadata.ToString(), romanisableString.Romanised);
Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original); Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original);
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Localisation
Artist = "Romanised Artist", Artist = "Romanised Artist",
Title = "Romanised title" Title = "Romanised title"
}; };
var romanisableString = metadata.ToRomanisableString(); var romanisableString = metadata.GetDisplayTitleRomanisable();
Assert.AreEqual(romanisableString.Romanised, romanisableString.Original); Assert.AreEqual(romanisableString.Romanised, romanisableString.Original);
} }

View File

@ -117,7 +117,7 @@ namespace osu.Game.Tournament.Components
if ((mods & LegacyMods.DoubleTime) > 0) if ((mods & LegacyMods.DoubleTime) > 0)
{ {
// temporary local calculation (taken from OsuDifficultyCalculator) // temporary local calculation (taken from OsuDifficultyCalculator)
double preempt = (int)BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / 1.5; double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(ar, 1800, 1200, 450) / 1.5;
ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5);
bpm *= 1.5f; bpm *= 1.5f;

View File

@ -5,7 +5,7 @@ using osu.Game.Database;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
public class BeatmapDifficulty : IHasPrimaryKey public class BeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo
{ {
/// <summary> /// <summary>
/// The default value used for all difficulty settings except <see cref="SliderMultiplier"/> and <see cref="SliderTickRate"/>. /// The default value used for all difficulty settings except <see cref="SliderMultiplier"/> and <see cref="SliderTickRate"/>.
@ -49,47 +49,5 @@ namespace osu.Game.Beatmaps
difficulty.SliderMultiplier = SliderMultiplier; difficulty.SliderMultiplier = SliderMultiplier;
difficulty.SliderTickRate = SliderTickRate; difficulty.SliderTickRate = SliderTickRate;
} }
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
/// </summary>
/// <param name="difficulty">The difficulty value to be mapped.</param>
/// <param name="min">Minimum of the resulting range which will be achieved by a difficulty value of 0.</param>
/// <param name="mid">Midpoint of the resulting range which will be achieved by a difficulty value of 5.</param>
/// <param name="max">Maximum of the resulting range which will be achieved by a difficulty value of 10.</param>
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
public static double DifficultyRange(double difficulty, double min, double mid, double max)
{
if (difficulty > 5)
return mid + (max - mid) * (difficulty - 5) / 5;
if (difficulty < 5)
return mid - (mid - min) * (5 - difficulty) / 5;
return mid;
}
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
/// </summary>
/// <param name="difficulty">The difficulty value to be mapped.</param>
/// <param name="range">The values that define the two linear ranges.
/// <list type="table">
/// <item>
/// <term>od0</term>
/// <description>Minimum of the resulting range which will be achieved by a difficulty value of 0.</description>
/// </item>
/// <item>
/// <term>od5</term>
/// <description>Midpoint of the resulting range which will be achieved by a difficulty value of 5.</description>
/// </item>
/// <item>
/// <term>od10</term>
/// <description>Maximum of the resulting range which will be achieved by a difficulty value of 10.</description>
/// </item>
/// </list>
/// </param>
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
=> DifficultyRange(difficulty, range.od0, range.od5, range.od10);
} }
} }

View File

@ -7,7 +7,6 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Localisation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -17,7 +16,7 @@ namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
[Serializable] [Serializable]
public class BeatmapInfo : IEquatable<BeatmapInfo>, IHasPrimaryKey public class BeatmapInfo : IEquatable<BeatmapInfo>, IHasPrimaryKey, IBeatmapInfo
{ {
public int ID { get; set; } public int ID { get; set; }
@ -152,18 +151,7 @@ namespace osu.Game.Beatmaps
[JsonIgnore] [JsonIgnore]
public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty); public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty);
public string[] SearchableTerms => new[] public override string ToString() => this.GetDisplayTitle();
{
Version
}.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty<string>()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {versionString}".Trim();
public RomanisableString ToRomanisableString()
{
var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null);
return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim());
}
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)
{ {
@ -187,5 +175,22 @@ namespace osu.Game.Beatmaps
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>. /// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
/// </summary> /// </summary>
public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone(); public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone();
#region Implementation of IHasOnlineID
public int? OnlineID => OnlineBeatmapID;
#endregion
#region Implementation of IBeatmapInfo
string IBeatmapInfo.DifficultyName => Version;
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty;
IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSet;
IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
double IBeatmapInfo.StarRating => StarDifficulty;
#endregion
} }
} }

View File

@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Localisation;
namespace osu.Game.Beatmaps
{
public static class BeatmapInfoExtensions
{
/// <summary>
/// A user-presentable display title representing this beatmap.
/// </summary>
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{getClosestMetadata(beatmapInfo)} {getVersionString(beatmapInfo)}".Trim();
/// <summary>
/// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
/// </summary>
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo)
{
var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable();
var versionString = getVersionString(beatmapInfo);
return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim());
}
public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[]
{
beatmapInfo.DifficultyName
}.Concat(getClosestMetadata(beatmapInfo).GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
// temporary helper methods until we figure which metadata should be where.
private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) =>
beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet?.Metadata ?? new BeatmapMetadata();
}
}

View File

@ -4,9 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Localisation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Users; using osu.Game.Users;
@ -15,7 +13,7 @@ namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
[Serializable] [Serializable]
public class BeatmapMetadata : IEquatable<BeatmapMetadata>, IHasPrimaryKey public class BeatmapMetadata : IEquatable<BeatmapMetadata>, IHasPrimaryKey, IBeatmapMetadataInfo
{ {
public int ID { get; set; } public int ID { get; set; }
@ -83,50 +81,13 @@ namespace osu.Game.Beatmaps
public int PreviewTime { get; set; } public int PreviewTime { get; set; }
public string AudioFile { get; set; } public string AudioFile { get; set; }
public string BackgroundFile { get; set; } public string BackgroundFile { get; set; }
public override string ToString() public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other);
{
string author = Author == null ? string.Empty : $"({Author})";
return $"{Artist} - {Title} {author}".Trim();
}
public RomanisableString ToRomanisableString() public override string ToString() => this.GetDisplayTitle();
{
string author = Author == null ? string.Empty : $"({Author})";
var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode;
var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode;
return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); string IBeatmapMetadataInfo.Author => AuthorString;
}
[JsonIgnore]
public string[] SearchableTerms => new[]
{
Author?.Username,
Artist,
ArtistUnicode,
Title,
TitleUnicode,
Source,
Tags
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
public bool Equals(BeatmapMetadata other)
{
if (other == null)
return false;
return Title == other.Title
&& TitleUnicode == other.TitleUnicode
&& Artist == other.Artist
&& ArtistUnicode == other.ArtistUnicode
&& AuthorString == other.AuthorString
&& Source == other.Source
&& Tags == other.Tags
&& PreviewTime == other.PreviewTime
&& AudioFile == other.AudioFile
&& BackgroundFile == other.BackgroundFile;
}
} }
} }

View File

@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Localisation;
namespace osu.Game.Beatmaps
{
public static class BeatmapMetadataInfoExtensions
{
/// <summary>
/// An array of all searchable terms provided in contained metadata.
/// </summary>
public static string[] GetSearchableTerms(this IBeatmapMetadataInfo metadataInfo) => new[]
{
metadataInfo.Author,
metadataInfo.Artist,
metadataInfo.ArtistUnicode,
metadataInfo.Title,
metadataInfo.TitleUnicode,
metadataInfo.Source,
metadataInfo.Tags
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
/// <summary>
/// A user-presentable display title representing this metadata.
/// </summary>
public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo)
{
string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})";
return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim();
}
/// <summary>
/// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
/// </summary>
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo)
{
string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})";
var artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode;
var titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode;
return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim());
}
}
}

View File

@ -7,7 +7,7 @@ using osu.Game.IO;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage
{ {
public int ID { get; set; } public int ID { get; set; }
@ -19,5 +19,7 @@ namespace osu.Game.Beatmaps
[Required] [Required]
public string Filename { get; set; } public string Filename { get; set; }
public IFileInfo File => FileInfo;
} }
} }

View File

@ -12,7 +12,7 @@ using osu.Game.Database;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>, ISoftDelete, IEquatable<BeatmapSetInfo> public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>, ISoftDelete, IEquatable<BeatmapSetInfo>, IBeatmapSetInfo
{ {
public int ID { get; set; } public int ID { get; set; }
@ -61,8 +61,6 @@ namespace osu.Game.Beatmaps
public string Hash { get; set; } public string Hash { get; set; }
public string StoryboardFile => Files.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
/// <summary> /// <summary>
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
/// The path returned is relative to the user file storage. /// The path returned is relative to the user file storage.
@ -90,5 +88,19 @@ namespace osu.Game.Beatmaps
return ReferenceEquals(this, other); return ReferenceEquals(this, other);
} }
#region Implementation of IHasOnlineID
public int? OnlineID => OnlineBeatmapSetID;
#endregion
#region Implementation of IBeatmapSetInfo
IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata;
IEnumerable<IBeatmapInfo> IBeatmapSetInfo.Beatmaps => Beatmaps;
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;
#endregion
} }
} }

View File

@ -0,0 +1,90 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable enable
namespace osu.Game.Beatmaps
{
/// <summary>
/// A representation of all top-level difficulty settings for a beatmap.
/// </summary>
public interface IBeatmapDifficultyInfo
{
/// <summary>
/// The default value used for all difficulty settings except <see cref="SliderMultiplier"/> and <see cref="SliderTickRate"/>.
/// </summary>
const float DEFAULT_DIFFICULTY = 5;
/// <summary>
/// The drain rate of the associated beatmap.
/// </summary>
float DrainRate { get; }
/// <summary>
/// The circle size of the associated beatmap.
/// </summary>
float CircleSize { get; }
/// <summary>
/// The overall difficulty of the associated beatmap.
/// </summary>
float OverallDifficulty { get; }
/// <summary>
/// The approach rate of the associated beatmap.
/// </summary>
float ApproachRate { get; }
/// <summary>
/// The slider multiplier of the associated beatmap.
/// </summary>
double SliderMultiplier { get; }
/// <summary>
/// The slider tick rate of the associated beatmap.
/// </summary>
double SliderTickRate { get; }
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
/// </summary>
/// <param name="difficulty">The difficulty value to be mapped.</param>
/// <param name="min">Minimum of the resulting range which will be achieved by a difficulty value of 0.</param>
/// <param name="mid">Midpoint of the resulting range which will be achieved by a difficulty value of 5.</param>
/// <param name="max">Maximum of the resulting range which will be achieved by a difficulty value of 10.</param>
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
static double DifficultyRange(double difficulty, double min, double mid, double max)
{
if (difficulty > 5)
return mid + (max - mid) * (difficulty - 5) / 5;
if (difficulty < 5)
return mid - (mid - min) * (5 - difficulty) / 5;
return mid;
}
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
/// </summary>
/// <param name="difficulty">The difficulty value to be mapped.</param>
/// <param name="range">The values that define the two linear ranges.
/// <list type="table">
/// <item>
/// <term>od0</term>
/// <description>Minimum of the resulting range which will be achieved by a difficulty value of 0.</description>
/// </item>
/// <item>
/// <term>od5</term>
/// <description>Midpoint of the resulting range which will be achieved by a difficulty value of 5.</description>
/// </item>
/// <item>
/// <term>od10</term>
/// <description>Maximum of the resulting range which will be achieved by a difficulty value of 10.</description>
/// </item>
/// </list>
/// </param>
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
=> DifficultyRange(difficulty, range.od0, range.od5, range.od10);
}
}

View File

@ -0,0 +1,66 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Database;
using osu.Game.Rulesets;
#nullable enable
namespace osu.Game.Beatmaps
{
/// <summary>
/// A single beatmap difficulty.
/// </summary>
public interface IBeatmapInfo : IHasOnlineID
{
/// <summary>
/// The user-specified name given to this beatmap.
/// </summary>
string DifficultyName { get; }
/// <summary>
/// The metadata representing this beatmap. May be shared between multiple beatmaps.
/// </summary>
IBeatmapMetadataInfo? Metadata { get; }
/// <summary>
/// The difficulty settings for this beatmap.
/// </summary>
IBeatmapDifficultyInfo Difficulty { get; }
/// <summary>
/// The beatmap set this beatmap is part of.
/// </summary>
IBeatmapSetInfo? BeatmapSet { get; }
/// <summary>
/// The playable length in milliseconds of this beatmap.
/// </summary>
double Length { get; }
/// <summary>
/// The most common BPM of this beatmap.
/// </summary>
double BPM { get; }
/// <summary>
/// The SHA-256 hash representing this beatmap's contents.
/// </summary>
string Hash { get; }
/// <summary>
/// MD5 is kept for legacy support (matching against replays etc.).
/// </summary>
string MD5Hash { get; }
/// <summary>
/// The ruleset this beatmap was made for.
/// </summary>
IRulesetInfo Ruleset { get; }
/// <summary>
/// The basic star rating for this beatmap (with no mods applied).
/// </summary>
double StarRating { get; }
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
#nullable enable
namespace osu.Game.Beatmaps
{
/// <summary>
/// Metadata representing a beatmap. May be shared between multiple beatmap difficulties.
/// </summary>
public interface IBeatmapMetadataInfo : IEquatable<IBeatmapMetadataInfo>
{
/// <summary>
/// The romanised title of this beatmap.
/// </summary>
string Title { get; }
/// <summary>
/// The unicode title of this beatmap.
/// </summary>
string TitleUnicode { get; }
/// <summary>
/// The romanised artist of this beatmap.
/// </summary>
string Artist { get; }
/// <summary>
/// The unicode artist of this beatmap.
/// </summary>
string ArtistUnicode { get; }
/// <summary>
/// The author of this beatmap.
/// </summary>
string Author { get; } // eventually should be linked to a persisted User.
/// <summary>
/// The source of this beatmap.
/// </summary>
string Source { get; }
/// <summary>
/// The tags of this beatmap.
/// </summary>
string Tags { get; }
/// <summary>
/// The time in milliseconds to begin playing the track for preview purposes.
/// If -1, the track should begin playing at 40% of its length.
/// </summary>
int PreviewTime { get; }
/// <summary>
/// The filename of the audio file consumed by this beatmap.
/// </summary>
string AudioFile { get; }
/// <summary>
/// The filename of the background image file consumed by this beatmap.
/// </summary>
string BackgroundFile { get; }
bool IEquatable<IBeatmapMetadataInfo>.Equals(IBeatmapMetadataInfo? other)
{
if (other == null)
return false;
return Title == other.Title
&& TitleUnicode == other.TitleUnicode
&& Artist == other.Artist
&& ArtistUnicode == other.ArtistUnicode
&& Author == other.Author
&& Source == other.Source
&& Tags == other.Tags
&& PreviewTime == other.PreviewTime
&& AudioFile == other.AudioFile
&& BackgroundFile == other.BackgroundFile;
}
}
}

View File

@ -0,0 +1,52 @@
// 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 osu.Game.Database;
#nullable enable
namespace osu.Game.Beatmaps
{
/// <summary>
/// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
/// </summary>
public interface IBeatmapSetInfo : IHasOnlineID
{
/// <summary>
/// The date when this beatmap was imported.
/// </summary>
DateTimeOffset DateAdded { get; }
/// <summary>
/// The best-effort metadata representing this set. In the case metadata differs between contained beatmaps, one is arbitrarily chosen.
/// </summary>
IBeatmapMetadataInfo? Metadata { get; }
/// <summary>
/// All beatmaps contained in this set.
/// </summary>
IEnumerable<IBeatmapInfo> Beatmaps { get; }
/// <summary>
/// All files used by this set.
/// </summary>
IEnumerable<INamedFileUsage> Files { get; }
/// <summary>
/// The maximum star difficulty of all beatmaps in this set.
/// </summary>
double MaxStarDifficulty { get; }
/// <summary>
/// The maximum playable length in milliseconds of all beatmaps in this set.
/// </summary>
double MaxLength { get; }
/// <summary>
/// The maximum BPM of all beatmaps in this set.
/// </summary>
double MaxBPM { get; }
}
}

View File

@ -201,12 +201,14 @@ namespace osu.Game.Beatmaps
{ {
var decoder = Decoder.GetDecoder<Storyboard>(stream); var decoder = Decoder.GetDecoder<Storyboard>(stream);
var storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
// todo: support loading from both set-wide storyboard *and* beatmap specific. // todo: support loading from both set-wide storyboard *and* beatmap specific.
if (BeatmapSetInfo?.StoryboardFile == null) if (string.IsNullOrEmpty(storyboardFilename))
storyboard = decoder.Decode(stream); storyboard = decoder.Decode(stream);
else else
{ {
using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile)))) using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename))))
storyboard = decoder.Decode(stream, secondaryStream); storyboard = decoder.Decode(stream, secondaryStream);
} }
} }

View File

@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable enable
namespace osu.Game.Database
{
public interface IHasOnlineID
{
/// <summary>
/// The server-side ID representing this instance, if one exists.
/// </summary>
int? OnlineID { get; }
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.IO;
#nullable enable
namespace osu.Game.Database
{
/// <summary>
/// A usage of a file, with a local filename attached.
/// </summary>
public interface INamedFileUsage
{
/// <summary>
/// The underlying file on disk.
/// </summary>
IFileInfo File { get; }
/// <summary>
/// The filename for this usage.
/// </summary>
string Filename { get; }
}
}

View File

@ -6,7 +6,7 @@ using osu.Game.Database;
namespace osu.Game.IO namespace osu.Game.IO
{ {
public class FileInfo : IHasPrimaryKey public class FileInfo : IHasPrimaryKey, IFileInfo
{ {
public int ID { get; set; } public int ID { get; set; }

18
osu.Game/IO/IFileInfo.cs Normal file
View File

@ -0,0 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable enable
namespace osu.Game.IO
{
/// <summary>
/// A representation of a tracked file.
/// </summary>
public interface IFileInfo
{
/// <summary>
/// SHA-256 hash of the file content.
/// </summary>
string Hash { get; }
}
}

View File

@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Music
{ {
Padding = new MarginPadding { Left = 5 }; Padding = new MarginPadding { Left = 5 };
FilterTerms = item.Metadata.SearchableTerms; FilterTerms = item.Metadata.GetSearchableTerms();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -0,0 +1,47 @@
// 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.Database;
#nullable enable
namespace osu.Game.Rulesets
{
/// <summary>
/// A representation of a ruleset's metadata.
/// </summary>
public interface IRulesetInfo : IHasOnlineID
{
/// <summary>
/// The user-exposed name of this ruleset.
/// </summary>
string Name { get; }
/// <summary>
/// An acronym defined by the ruleset that can be used as a permanent identifier.
/// </summary>
string ShortName { get; }
/// <summary>
/// A string representation of this ruleset, to be used with reflection to instantiate the ruleset represented by this metadata.
/// </summary>
string InstantiationInfo { get; }
Ruleset? CreateInstance()
{
var type = Type.GetType(InstantiationInfo);
if (type == null)
return null;
var ruleset = Activator.CreateInstance(type) as Ruleset;
// overwrite the pre-populated RulesetInfo with a potentially database attached copy.
// TODO: figure if we still want/need this after switching to realm.
// ruleset.RulesetInfo = this;
return ruleset;
}
}
}

View File

@ -10,7 +10,7 @@ using osu.Framework.Testing;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
{ {
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
public class RulesetInfo : IEquatable<RulesetInfo> public class RulesetInfo : IEquatable<RulesetInfo>, IRulesetInfo
{ {
public int? ID { get; set; } public int? ID { get; set; }
@ -54,5 +54,11 @@ namespace osu.Game.Rulesets
} }
public override string ToString() => Name ?? $"{Name} ({ShortName}) ID: {ID}"; public override string ToString() => Name ?? $"{Name} ({ShortName}) ID: {ID}";
#region Implementation of IHasOnlineID
public int? OnlineID => ID;
#endregion
} }
} }

View File

@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Scoring
.First() .First()
))); )));
targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
// Add back a portion of the amount of HP to be drained, depending on the lenience requested. // Add back a portion of the amount of HP to be drained, depending on the lenience requested.
targetMinimumHealth += drainLenience * (1 - targetMinimumHealth); targetMinimumHealth += drainLenience * (1 - targetMinimumHealth);

View File

@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Scoring
{ {
foreach (var range in GetRanges()) foreach (var range in GetRanges())
{ {
var value = BeatmapDifficulty.DifficultyRange(difficulty, (range.Min, range.Average, range.Max)); var value = IBeatmapDifficultyInfo.DifficultyRange(difficulty, (range.Min, range.Average, range.Max));
switch (range.Result) switch (range.Result)
{ {

View File

@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) };
beatmapText.Clear(); beatmapText.Clear();
beatmapText.AddLink(Item.Beatmap.Value.ToRomanisableString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text => beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
{ {
text.Truncate = true; text.Truncate = true;
text.RelativeSizeAxes = Axes.X; text.RelativeSizeAxes = Axes.X;

View File

@ -379,7 +379,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
if (item.NewValue?.Beatmap.Value != null) if (item.NewValue?.Beatmap.Value != null)
{ {
statusText.Text = "Currently playing "; statusText.Text = "Currently playing ";
beatmapText.AddLink(item.NewValue.Beatmap.Value.ToRomanisableString(), beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap, LinkAction.OpenBeatmap,
item.NewValue.Beatmap.Value.OnlineBeatmapID.ToString(), item.NewValue.Beatmap.Value.OnlineBeatmapID.ToString(),
creationParameters: s => creationParameters: s =>

View File

@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Carousel
if (match) if (match)
{ {
var terms = BeatmapInfo.SearchableTerms; var terms = BeatmapInfo.GetSearchableTerms();
foreach (var criteriaTerm in criteria.SearchTerms) foreach (var criteriaTerm in criteria.SearchTerms)
match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)); match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase));