mirror of
https://github.com/ppy/osu.git
synced 2026-05-21 21:40:56 +08:00
d9e182230d
Closes https://github.com/ppy/osu/issues/36453. My omission was in assuming that web was going to start filtering out the tags below the threshold from API responses, which is not the case. Whether or not the consumers want or not to display tags below threshold is subjective. I figured that the matchmaking card tooltip might want to display below threshold but I dunno.
231 lines
7.7 KiB
C#
231 lines
7.7 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Newtonsoft.Json;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Rulesets;
|
|
|
|
namespace osu.Game.Online.API.Requests.Responses
|
|
{
|
|
public class APIBeatmap : IBeatmapInfo, IBeatmapOnlineInfo
|
|
{
|
|
[JsonProperty(@"id")]
|
|
public int OnlineID { get; set; }
|
|
|
|
[JsonProperty(@"beatmapset_id")]
|
|
public int OnlineBeatmapSetID { get; set; }
|
|
|
|
[JsonProperty(@"status")]
|
|
public BeatmapOnlineStatus Status { get; set; }
|
|
|
|
[JsonProperty("checksum")]
|
|
public string Checksum { get; set; } = string.Empty;
|
|
|
|
[JsonProperty(@"user_id")]
|
|
public int AuthorID { get; set; }
|
|
|
|
[JsonProperty(@"beatmapset")]
|
|
public APIBeatmapSet? BeatmapSet { get; set; }
|
|
|
|
[JsonProperty(@"playcount")]
|
|
public int PlayCount { get; set; }
|
|
|
|
[JsonProperty(@"current_user_playcount")]
|
|
public int UserPlayCount { get; set; }
|
|
|
|
[JsonProperty(@"passcount")]
|
|
public int PassCount { get; set; }
|
|
|
|
[JsonProperty(@"mode_int")]
|
|
public int RulesetID { get; set; }
|
|
|
|
[JsonProperty(@"difficulty_rating")]
|
|
public double StarRating { get; set; }
|
|
|
|
public int EndTimeObjectCount => SliderCount + SpinnerCount;
|
|
|
|
public int TotalObjectCount => CircleCount + SliderCount + SpinnerCount;
|
|
|
|
[JsonProperty(@"drain")]
|
|
public float DrainRate { get; set; }
|
|
|
|
[JsonProperty(@"cs")]
|
|
public float CircleSize { get; set; }
|
|
|
|
[JsonProperty(@"ar")]
|
|
public float ApproachRate { get; set; }
|
|
|
|
[JsonProperty(@"accuracy")]
|
|
public float OverallDifficulty { get; set; }
|
|
|
|
[JsonIgnore]
|
|
public double Length { get; set; }
|
|
|
|
[JsonProperty(@"total_length")]
|
|
private double lengthInSeconds
|
|
{
|
|
get => TimeSpan.FromMilliseconds(Length).TotalSeconds;
|
|
set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds;
|
|
}
|
|
|
|
[JsonIgnore]
|
|
public double HitLength { get; set; }
|
|
|
|
[JsonProperty(@"hit_length")]
|
|
private double hitLengthInSeconds
|
|
{
|
|
get => TimeSpan.FromMilliseconds(HitLength).TotalSeconds;
|
|
set => HitLength = TimeSpan.FromSeconds(value).TotalMilliseconds;
|
|
}
|
|
|
|
[JsonProperty(@"convert")]
|
|
public bool Convert { get; set; }
|
|
|
|
[JsonProperty(@"count_circles")]
|
|
public int CircleCount { get; set; }
|
|
|
|
[JsonProperty(@"count_sliders")]
|
|
public int SliderCount { get; set; }
|
|
|
|
[JsonProperty(@"count_spinners")]
|
|
public int SpinnerCount { get; set; }
|
|
|
|
[JsonProperty(@"version")]
|
|
public string DifficultyName { get; set; } = string.Empty;
|
|
|
|
[JsonProperty(@"failtimes")]
|
|
public APIFailTimes? FailTimes { get; set; }
|
|
|
|
[JsonProperty(@"top_tag_ids")]
|
|
public APIBeatmapTag[]? TopTags { get; set; }
|
|
|
|
[JsonProperty(@"current_user_tag_ids")]
|
|
public long[]? OwnTagIds { get; set; }
|
|
|
|
[JsonProperty(@"max_combo")]
|
|
public int? MaxCombo { get; set; }
|
|
|
|
[JsonProperty(@"last_updated")]
|
|
public DateTimeOffset LastUpdated { get; set; }
|
|
|
|
public double BPM { get; set; }
|
|
|
|
[JsonProperty(@"owners")]
|
|
public BeatmapOwner[] BeatmapOwners { get; set; } = Array.Empty<BeatmapOwner>();
|
|
|
|
/// <summary>
|
|
/// Minimum count of votes required to display a tag on the beatmap's page.
|
|
/// Should match value specified web-side as https://github.com/ppy/osu-web/blob/cae2fdf03cfb8c30c8e332cfb142e03188ceffef/config/osu.php#L59.
|
|
/// </summary>
|
|
public const int MINIMUM_USER_TAG_VOTES_FOR_DISPLAY = 5;
|
|
|
|
/// <summary>
|
|
/// Retrieves top user tags for the beatmap, ordered in a way matching osu!web.
|
|
/// Requires <see cref="BeatmapSet"/> to be populated.
|
|
/// </summary>
|
|
/// <param name="confirmedOnly">
|
|
/// If <see langword="true"/>, only tags above <see cref="MINIMUM_USER_TAG_VOTES_FOR_DISPLAY"/> will be shown.
|
|
/// If <see langword="false"/>, all tags regardless of vote count will be shown.
|
|
/// </param>
|
|
public (APITag Tag, int VoteCount)[] GetTopUserTags(bool confirmedOnly)
|
|
{
|
|
if (TopTags == null || TopTags.Length == 0 || BeatmapSet?.RelatedTags == null)
|
|
return [];
|
|
|
|
var tagsById = BeatmapSet.RelatedTags.ToDictionary(t => t.Id);
|
|
|
|
return TopTags
|
|
.Select(t => (topTag: t, relatedTag: tagsById.GetValueOrDefault(t.TagId)))
|
|
.Where(t => t.relatedTag != null && (!confirmedOnly || t.topTag.VoteCount >= MINIMUM_USER_TAG_VOTES_FOR_DISPLAY))
|
|
// see https://github.com/ppy/osu-web/blob/bb3bd2e7c6f84f26066df5ea20a81c77ec9bb60a/resources/js/beatmapsets-show/controller.ts#L103-L106 for sort criteria
|
|
.OrderByDescending(t => t.topTag.VoteCount)
|
|
.ThenBy(t => t.relatedTag!.Name)
|
|
.Select(t => (t.relatedTag!, t.topTag.VoteCount))
|
|
.ToArray();
|
|
}
|
|
|
|
#region Implementation of IBeatmapInfo
|
|
|
|
public IBeatmapMetadataInfo Metadata => (BeatmapSet as IBeatmapSetInfo)?.Metadata ?? new BeatmapMetadata();
|
|
|
|
public IBeatmapDifficultyInfo Difficulty => new BeatmapDifficulty
|
|
{
|
|
DrainRate = DrainRate,
|
|
CircleSize = CircleSize,
|
|
ApproachRate = ApproachRate,
|
|
OverallDifficulty = OverallDifficulty
|
|
};
|
|
|
|
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
|
|
|
public string MD5Hash => Checksum;
|
|
|
|
public IRulesetInfo Ruleset => new APIRuleset { OnlineID = RulesetID };
|
|
|
|
[JsonIgnore]
|
|
public string Hash => throw new NotImplementedException();
|
|
|
|
#endregion
|
|
|
|
public bool Equals(IBeatmapInfo? other) => other is APIBeatmap b && this.MatchesOnlineID(b);
|
|
|
|
public class APIRuleset : IRulesetInfo
|
|
{
|
|
public int OnlineID { get; set; } = -1;
|
|
|
|
public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})";
|
|
|
|
[JsonIgnore]
|
|
public string ShortName
|
|
{
|
|
get
|
|
{
|
|
// TODO: this should really not exist.
|
|
switch (OnlineID)
|
|
{
|
|
case 0: return "osu";
|
|
|
|
case 1: return "taiko";
|
|
|
|
case 2: return "fruits";
|
|
|
|
case 3: return "mania";
|
|
|
|
default: throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
}
|
|
|
|
public string InstantiationInfo => string.Empty;
|
|
|
|
public Ruleset CreateInstance() => throw new NotImplementedException();
|
|
|
|
public bool Equals(IRulesetInfo? other) => other is APIRuleset r && this.MatchesOnlineID(r);
|
|
|
|
public int CompareTo(IRulesetInfo? other)
|
|
{
|
|
if (!(other is APIRuleset ruleset))
|
|
throw new ArgumentException($@"Object is not of type {nameof(APIRuleset)}.", nameof(other));
|
|
|
|
return OnlineID.CompareTo(ruleset.OnlineID);
|
|
}
|
|
|
|
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
|
public override int GetHashCode() => OnlineID;
|
|
}
|
|
|
|
public class BeatmapOwner
|
|
{
|
|
[JsonProperty(@"id")]
|
|
public int Id { get; set; }
|
|
|
|
[JsonProperty(@"username")]
|
|
public string Username { get; set; } = string.Empty;
|
|
}
|
|
}
|
|
}
|