Files
Ez2Lazer/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
2026-03-04 10:34:35 +01:00

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 = true)
{
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;
}
}
}