Skip to content

Commit

Permalink
Add BPM and star rating to score calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
minisbett committed Nov 19, 2023
1 parent 4afa27a commit ce864b6
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 25 deletions.
24 changes: 9 additions & 15 deletions huisbot/Embeds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,10 @@ public static Embed Calculating(bool local, bool liveOnly) => BaseEmbed
/// <param name="live">The score on the live servers.</param>
/// <param name="rework">The rework.</param>
/// <param name="beatmap">The beatmap.</param>
/// <param name="difficultyRating">The difficulty rating of the score.</param>
/// <returns>An embed for displaying a calculated score</returns>
public static Embed CalculatedScore(HuisCalculatedScore local, HuisCalculatedScore live, HuisRework rework, OsuBeatmap beatmap)
public static Embed CalculatedScore(HuisCalculatedScore local, HuisCalculatedScore live, HuisRework rework, OsuBeatmap beatmap, double difficultyRating)
{
// Split the map name into it's components using regex.
Match match = Regex.Match(local.MapName ?? "", @"^(\d+) - (.+) - (.+) \((.+)\) \[(.+)\]$");
string beatmapId = match.Groups[1].Value;
string artist = match.Groups[2].Value;
string title = match.Groups[3].Value;
string version = match.Groups[5].Value;

// Construct some strings for the embed.
string total = Math.Abs(local.TotalPP - live.TotalPP) < 0.01 ? $"**{local.TotalPP:N2}pp**" : $"{live.TotalPP:N2} → **{local.TotalPP:N2}pp** *({local.TotalPP - live.TotalPP:+#,##0.00;-#,##0.00}pp)*";
string aim = Math.Abs(live.AimPP - local.AimPP) < 0.01 ? $"**{local.AimPP:N2}pp**" : $"{live.AimPP:N2} → **{local.AimPP:N2}pp** *({local.AimPP - live.AimPP:+#,##0.00;-#,##0.00}pp)*";
Expand All @@ -175,21 +169,21 @@ public static Embed CalculatedScore(HuisCalculatedScore local, HuisCalculatedSco
string combo = $"{local.MaxCombo}/{beatmap.MaxCombo}x";
string modsStr = local.Mods.Replace(", ", "").Replace("CL", "");
string mods = modsStr == "" ? "" : $"+{modsStr}";
string stats1 = $"CS **{beatmap.AdjustedCS(modsStr):0.#}** AR **{beatmap.AdjustedAR(modsStr):0.#}**";
string stats1 = $"CS **{beatmap.AdjustedCS(modsStr):0.#}** AR **{beatmap.AdjustedAR(modsStr):0.#}** ▸ **{beatmap.BPM:0.###}** BPM";
string stats2 = $"OD **{beatmap.AdjustedOD(modsStr):0.#}** HP **{beatmap.AdjustedHP(modsStr):0.#}**";
string visualizer = $"[map visualizer](https://osu.direct/preview?b={beatmapId})";
string osu = $"[osu! page](https://osu.ppy.sh/b/{beatmapId})";
string visualizer = $"[map visualizer](https://osu.direct/preview?b={beatmap.Id})";
string osu = $"[osu! page](https://osu.ppy.sh/b/{beatmap.Id})";
string huisRework = $"[Huis Rework](https://pp.huismetbenen.nl/rankings/info/{rework.Code})";
string github = $"[Source]({rework.GetCommitUrl()})";

return BaseEmbed
.WithColor(new Color(0x4061E9))
.WithTitle($"{artist} - {title} [{version}] {mods}")
.WithTitle($"{beatmap.Artist} - {beatmap.Title} [{beatmap.Version}] {mods} [{difficultyRating:N2}★]")
.AddField("PP Comparison (Live → Local)", $"▸ **Total**: {total}\n▸ **Aim**: {aim}\n▸ **Tap**: {tap}\n▸ **Acc**: {acc}\n▸ **FL**: {fl}\n" +
$"{visualizer}{osu}{huisRework}{github}", true)
.AddField("Score Info", $"{local.Accuracy:N2}% ▸ {combo}\n{hits}\n{stats1}\n{stats2}", true)
.WithUrl($"https://osu.ppy.sh/b/{beatmapId}")
.WithImageUrl($"https://assets.ppy.sh/beatmaps/{beatmap.BeatmapSetId}/covers/[email protected]")
.WithUrl($"https://osu.ppy.sh/b/{beatmap.Id}")
.WithImageUrl($"https://assets.ppy.sh/beatmaps/{beatmap.SetId}/covers/[email protected]")
.WithFooter($"{rework.Name}{BaseEmbed.Footer.Text}", BaseEmbed.Footer.IconUrl)
.Build();
}
Expand Down
4 changes: 2 additions & 2 deletions huisbot/Models/Osu/OsuBeatmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ public class OsuBeatmap
/// The ID of the beatmap set the beatmap belongs to.
/// </summary>
[JsonProperty("beatmapset_id")]
public int BeatmapSetId { get; private set; }
public int SetId { get; private set; }

/// <summary>
/// The ID of the beatmap.
/// </summary>
[JsonProperty("beatmap_id")]
public int BeatmapId { get; private set; }
public int Id { get; private set; }

/// <summary>
/// The maximum combo of the beatmap.
Expand Down
14 changes: 8 additions & 6 deletions huisbot/Modules/Huis/Calculate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ public async Task HandleAsync(
if (rework is null)
return;

// Get the beatmap from the osu! api and check whether it was successful. If not, notify the user.
// Get the beatmap from the identifier.
OsuBeatmap? beatmap = await GetBeatmapAsync(beatmapId);
if (beatmap is null)
{
await ModifyOriginalResponseAsync(x => x.Embed = Embeds.InternalError("Failed to get the beatmap from the osu! API."));
return;
}

// Get the difficulty rating of the beatmap.
double? difficultyRating = await GetDifficultyRatingAsync(rework.RulesetId, beatmap.Id, mods);
if (difficultyRating is null)
return;

// Construct the HuisCalculationRequest.
HuisCalculationRequest request = new HuisCalculationRequest(beatmap.BeatmapId, rework.Code!)
HuisCalculationRequest request = new HuisCalculationRequest(beatmap.Id, rework.Code!)
{
Combo = combo,
Count100 = count100,
Expand Down Expand Up @@ -84,6 +86,6 @@ public async Task HandleAsync(
}

// Send the result in an embed to the user.
await ModifyOriginalResponseAsync(x => x.Embed = Embeds.CalculatedScore(local, live, rework, beatmap));
await ModifyOriginalResponseAsync(x => x.Embed = Embeds.CalculatedScore(local, live, rework, beatmap, difficultyRating.Value));
}
}
22 changes: 21 additions & 1 deletion huisbot/Modules/HuisModuleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
using huisbot.Models.Osu;
using huisbot.Models.Utility;
using huisbot.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Net.WebRequestMethods;

namespace huisbot.Modules;

Expand Down Expand Up @@ -247,7 +249,7 @@ public async Task<bool> QueuePlayerAsync(OsuUser player, int reworkId)
public async Task<OsuBeatmap?> GetBeatmapAsync(string beatmapId)
{
// If the identifier is not a number, try to find an alias.
if(!beatmapId.All(char.IsDigit))
if (!beatmapId.All(char.IsDigit))
{
// Get the beatmap alias. If none could be found, notify the user. Otherwise replace the identifier.
BeatmapAlias? alias = await _persistence.GetBeatmapAliasAsync(beatmapId);
Expand Down Expand Up @@ -283,4 +285,22 @@ public async Task<bool> QueuePlayerAsync(OsuUser player, int reworkId)

return scores;
}

/// <summary>
/// Returns the difficulty rating of the specified beatmap in the specified ruleset with the specified mods.
/// If it failed, the user will automatically be notified. In this case, this method returns null.
/// </summary>
/// <param name="rulesetId">The ruleset ID.</param>
/// <param name="beatmapId">The beatmap ID.</param>
/// <param name="mods">The mods.</param>
/// <returns>The difficulty rating.</returns>
public async Task<double?> GetDifficultyRatingAsync(int rulesetId, int beatmapId, string mods)
{
// Get the difficulty rating and check whether the request was successful. If not, notify the user.
double? rating = await _osu.GetDifficultyRatingAsync(rulesetId, beatmapId, mods);
if (rating is null)
await FollowupAsync(embed: Embeds.InternalError($"Failed to get the difficulty rating for the beatmap."));

return rating;
}
}
35 changes: 34 additions & 1 deletion huisbot/Services/OsuApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net;
using System.Text;

namespace huisbot.Services;

Expand Down Expand Up @@ -82,7 +83,7 @@ public async Task<bool> IsAvailableAsync()
{
// Get the user from the API.
string json = await _http.GetStringAsync($"get_beatmaps?b={id}&k={_apikey}");
OsuBeatmap? beatmap = JsonConvert.DeserializeObject<OsuBeatmap[]>(json)?.FirstOrDefault(x => x.BeatmapId == id);
OsuBeatmap? beatmap = JsonConvert.DeserializeObject<OsuBeatmap[]>(json)?.FirstOrDefault(x => x.Id == id);

// Check whether the deserialized json is null/an empty array. If so, the beatmap could not be found. The API returns "[]" when the beatmap could not be found.
if (beatmap is null)
Expand All @@ -96,4 +97,36 @@ public async Task<bool> IsAvailableAsync()
return null;
}
}

/// <summary>
/// Returns the difficulty rating of the specified beatmap in the specified ruleset with the specified mods.
/// </summary>
/// <param name="rulesetId">The ruleset ID.</param>
/// <param name="beatmapId">The beatmap ID.</param>
/// <param name="mods">The mods.</param>
/// <returns>The difficulty rating.</returns>
public async Task<double?> GetDifficultyRatingAsync(int rulesetId, int beatmapId, string mods)
{
try
{
// Get the difficulty rating from the API.
var request = new { ruleset_id = rulesetId, beatmap_id = beatmapId, mods = mods.Select(x => $"{{\"acronym\": \"{x}\"}}") };
string s = JsonConvert.SerializeObject(request);
HttpResponseMessage response = await _http.PostAsync($"https://osu.ppy.sh/difficulty-rating",
new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"));

// Try to parse the difficulty rating from the response.
string result = await response.Content.ReadAsStringAsync();
if (!double.TryParse(result, out double rating))
throw new Exception("Failed to parse the difficulty rating from the response.");

return rating;
}
catch (Exception ex)
{
_logger.LogError("Failed to get the difficulty rating for beatmap in ruleset {Ruleset} with ID {Id} and mods {Mods} from the osu! API: {Message}",
rulesetId, beatmapId, mods, ex.Message);
return null;
}
}
}

0 comments on commit ce864b6

Please sign in to comment.