From 39e89d49080229cba3de26bfe1fa1c1805f4dfa5 Mon Sep 17 00:00:00 2001 From: Mr Arythmia Date: Mon, 15 Jul 2024 03:40:56 +0200 Subject: [PATCH] Add song ranks, improve non-S rating calculation --- titles/chuni/const.py | 33 +++++++ titles/chuni/frontend.py | 113 ++++++++++++++++------ titles/chuni/templates/chuni_rating.jinja | 6 +- 3 files changed, 121 insertions(+), 31 deletions(-) diff --git a/titles/chuni/const.py b/titles/chuni/const.py index d037842..003c618 100644 --- a/titles/chuni/const.py +++ b/titles/chuni/const.py @@ -19,11 +19,13 @@ class ChuniConstants: VER_CHUNITHM_CRYSTAL = 8 VER_CHUNITHM_CRYSTAL_PLUS = 9 VER_CHUNITHM_PARADISE = 10 + VER_CHUNITHM_NEW = 11 VER_CHUNITHM_NEW_PLUS = 12 VER_CHUNITHM_SUN = 13 VER_CHUNITHM_SUN_PLUS = 14 VER_CHUNITHM_LUMINOUS = 15 + VERSION_NAMES = [ "CHUNITHM", "CHUNITHM PLUS", @@ -43,6 +45,37 @@ class ChuniConstants: "CHUNITHM LUMINOUS", ] + SCORE_RANK_INTERVALS_OLD = [ + (1007500, "SSS"), + (1000000, "SS"), + ( 975000, "S"), + ( 950000, "AAA"), + ( 925000, "AA"), + ( 900000, "A"), + ( 800000, "BBB"), + ( 700000, "BB"), + ( 600000, "B"), + ( 500000, "C"), + ( 0, "D"), + ] + + SCORE_RANK_INTERVALS_NEW = [ + (1009000, "SSS+"), # New only + (1007500, "SSS"), + (1005000, "SS+"), # New only + (1000000, "SS"), + ( 990000, "S+"), # New only + ( 975000, "S"), + ( 950000, "AAA"), + ( 925000, "AA"), + ( 900000, "A"), + ( 800000, "BBB"), + ( 700000, "BB"), + ( 600000, "B"), + ( 500000, "C"), + ( 0, "D"), + ] + @classmethod def game_ver_to_string(cls, ver: int): return cls.VERSION_NAMES[ver] diff --git a/titles/chuni/frontend.py b/titles/chuni/frontend.py index 510ae08..74f7794 100644 --- a/titles/chuni/frontend.py +++ b/titles/chuni/frontend.py @@ -13,6 +13,69 @@ from .config import ChuniConfig from .const import ChuniConstants +def pairwise(iterable): + # https://docs.python.org/3/library/itertools.html#itertools.pairwise + # but for Python < 3.10. pairwise('ABCDEFG') → AB BC CD DE EF FG + iterator = iter(iterable) + a = next(iterator, None) + for b in iterator: + yield a, b + a = b + + +def calculate_song_rank(score: int, game_version: int) -> str: + if game_version >= ChuniConstants.VER_CHUNITHM_NEW: + intervals = ChuniConstants.SCORE_RANK_INTERVALS_NEW + else: + intervals = ChuniConstants.SCORE_RANK_INTERVALS_OLD + + for (min_score, rank) in intervals: + if score >= min_score: + return rank + + return "D" + + +def calculate_song_rating(score: int, chart_constant: float, game_version: int) -> float: + is_new = game_version >= ChuniConstants.VER_CHUNITHM_NEW + + if is_new: # New and later + max_score = 1009000 + max_rating_modifier = 2.15 + else: # Up to Paradise Lost + max_score = 1007500 + max_rating_modifier = 2.0 + + if (score < 500000): + return 0.0 # D + elif (score >= max_score): + return chart_constant + max_rating_modifier # SSS/SSS+ + + # Okay, we're doing this the hard way. + # Rating goes up linearly between breakpoints listed below. + # Pick the score interval in which we are in, then calculate + # the position between possible ratings. + score_intervals = [ + ( 500000, 0.0), # C + ( 800000, max(0.0, (chart_constant - 5.0) / 2)), # BBB + ( 900000, max(0.0, (chart_constant - 5.0))), # A + ( 925000, max(0.0, (chart_constant - 3.0))), # AA + ( 975000, chart_constant), # S + (1000000, chart_constant + 1.0), # SS + (1005000, chart_constant + 1.5), # SS+ + (1007500, chart_constant + 2.0), # SSS + (1009000, chart_constant + max_rating_modifier), # SSS+! + ] + + for ((lo_score, lo_rating), (hi_score, hi_rating)) in pairwise(score_intervals): + if not (lo_score <= score < hi_score): + continue + + interval_pos = (score - lo_score) / (hi_score - lo_score) + return lo_rating + ((hi_rating - lo_rating) * interval_pos) + + + class ChuniFrontend(FE_Base): def __init__( self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str @@ -91,37 +154,27 @@ class ChuniFrontend(FE_Base): base_list=[] if profile and rating: song_records = [] + for song in rating: music_chart = await self.data.static.get_music_chart(usr_sesh.chunithm_version, song.musicId, song.difficultId) - if music_chart: - if (song.score < 800000): - song_rating = 0 - elif (song.score >= 800000 and song.score < 900000): - song_rating = music_chart.level / 2 - 5 - elif (song.score >= 900000 and song.score < 925000): - song_rating = music_chart.level - 5 - elif (song.score >= 925000 and song.score < 975000): - song_rating = music_chart.level - 3 - elif (song.score >= 975000 and song.score < 1000000): - song_rating = (song.score - 975000) / 2500 * 0.1 + music_chart.level - elif (song.score >= 1000000 and song.score < 1005000): - song_rating = (song.score - 1000000) / 1000 * 0.1 + 1 + music_chart.level - elif (song.score >= 1005000 and song.score < 1007500): - song_rating = (song.score - 1005000) / 500 * 0.1 + 1.5 + music_chart.level - elif (song.score >= 1007500 and song.score < 1009000): - song_rating = (song.score - 1007500) / 100 * 0.01 + 2 + music_chart.level - elif (song.score >= 1009000): - song_rating = 2.15 + music_chart.level - song_rating = int(song_rating * 10 ** 2) / 10 ** 2 - song_records.append({ - "difficultId": song.difficultId, - "musicId": song.musicId, - "title": music_chart.title, - "level": music_chart.level, - "score": song.score, - "type": song.type, - "song_rating": song_rating, - }) + if not music_chart: + continue + + rank = calculate_song_rank(song.score, profile.version) + rating = calculate_song_rating(song.score, music_chart.level, profile.version) + + song_rating = int(rating * 10 ** 2) / 10 ** 2 + song_records.append({ + "difficultId": song.difficultId, + "musicId": song.musicId, + "title": music_chart.title, + "level": music_chart.level, + "score": song.score, + "type": song.type, + "rank": rank, + "song_rating": song_rating, + }) + hot_list = [obj for obj in song_records if obj["type"] == "userRatingBaseHotList"] base_list = [obj for obj in song_records if obj["type"] == "userRatingBaseList"] return Response(template.render( @@ -243,4 +296,4 @@ class ChuniFrontend(FE_Base): resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: - return RedirectResponse("/gate/", 303) \ No newline at end of file + return RedirectResponse("/gate/", 303) diff --git a/titles/chuni/templates/chuni_rating.jinja b/titles/chuni/templates/chuni_rating.jinja index c094e6f..37ed6d0 100644 --- a/titles/chuni/templates/chuni_rating.jinja +++ b/titles/chuni/templates/chuni_rating.jinja @@ -18,6 +18,7 @@ Music Difficulty Score + Rank Rating {% for row in hot_list %} @@ -28,6 +29,7 @@ {{ row.level }} {{ row.score }} + {{ row.rank }} {{ row.song_rating }} @@ -48,6 +50,7 @@ Music Difficulty Score + Rank Rating {% for row in base_list %} @@ -58,6 +61,7 @@ {{ row.level }} {{ row.score }} + {{ row.rank }} {{ row.song_rating }} @@ -76,4 +80,4 @@ Login to view profile information. {% endif %} -{% endblock content %} \ No newline at end of file +{% endblock content %}