Merge pull request 'Chunithm: Add song ranks, improve non-S rating calculation' (#166) from mrarythmia/artemis:develop into develop

Reviewed-on: Hay1tsme/artemis#166
This commit is contained in:
Hay1tsme 2024-07-15 18:31:04 +00:00
commit 2647baef1e
3 changed files with 121 additions and 31 deletions

View File

@ -19,11 +19,13 @@ class ChuniConstants:
VER_CHUNITHM_CRYSTAL = 8 VER_CHUNITHM_CRYSTAL = 8
VER_CHUNITHM_CRYSTAL_PLUS = 9 VER_CHUNITHM_CRYSTAL_PLUS = 9
VER_CHUNITHM_PARADISE = 10 VER_CHUNITHM_PARADISE = 10
VER_CHUNITHM_NEW = 11 VER_CHUNITHM_NEW = 11
VER_CHUNITHM_NEW_PLUS = 12 VER_CHUNITHM_NEW_PLUS = 12
VER_CHUNITHM_SUN = 13 VER_CHUNITHM_SUN = 13
VER_CHUNITHM_SUN_PLUS = 14 VER_CHUNITHM_SUN_PLUS = 14
VER_CHUNITHM_LUMINOUS = 15 VER_CHUNITHM_LUMINOUS = 15
VERSION_NAMES = [ VERSION_NAMES = [
"CHUNITHM", "CHUNITHM",
"CHUNITHM PLUS", "CHUNITHM PLUS",
@ -43,6 +45,37 @@ class ChuniConstants:
"CHUNITHM LUMINOUS", "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 @classmethod
def game_ver_to_string(cls, ver: int): def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver] return cls.VERSION_NAMES[ver]

View File

@ -13,6 +13,69 @@ from .config import ChuniConfig
from .const import ChuniConstants 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): class ChuniFrontend(FE_Base):
def __init__( def __init__(
self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str
@ -91,37 +154,27 @@ class ChuniFrontend(FE_Base):
base_list=[] base_list=[]
if profile and rating: if profile and rating:
song_records = [] song_records = []
for song in rating: for song in rating:
music_chart = await self.data.static.get_music_chart(usr_sesh.chunithm_version, song.musicId, song.difficultId) music_chart = await self.data.static.get_music_chart(usr_sesh.chunithm_version, song.musicId, song.difficultId)
if music_chart: if not music_chart:
if (song.score < 800000): continue
song_rating = 0
elif (song.score >= 800000 and song.score < 900000): rank = calculate_song_rank(song.score, profile.version)
song_rating = music_chart.level / 2 - 5 rating = calculate_song_rating(song.score, music_chart.level, profile.version)
elif (song.score >= 900000 and song.score < 925000):
song_rating = music_chart.level - 5 song_rating = int(rating * 10 ** 2) / 10 ** 2
elif (song.score >= 925000 and song.score < 975000): song_records.append({
song_rating = music_chart.level - 3 "difficultId": song.difficultId,
elif (song.score >= 975000 and song.score < 1000000): "musicId": song.musicId,
song_rating = (song.score - 975000) / 2500 * 0.1 + music_chart.level "title": music_chart.title,
elif (song.score >= 1000000 and song.score < 1005000): "level": music_chart.level,
song_rating = (song.score - 1000000) / 1000 * 0.1 + 1 + music_chart.level "score": song.score,
elif (song.score >= 1005000 and song.score < 1007500): "type": song.type,
song_rating = (song.score - 1005000) / 500 * 0.1 + 1.5 + music_chart.level "rank": rank,
elif (song.score >= 1007500 and song.score < 1009000): "song_rating": song_rating,
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,
})
hot_list = [obj for obj in song_records if obj["type"] == "userRatingBaseHotList"] hot_list = [obj for obj in song_records if obj["type"] == "userRatingBaseHotList"]
base_list = [obj for obj in song_records if obj["type"] == "userRatingBaseList"] base_list = [obj for obj in song_records if obj["type"] == "userRatingBaseList"]
return Response(template.render( return Response(template.render(
@ -243,4 +296,4 @@ class ChuniFrontend(FE_Base):
resp.set_cookie("ARTEMIS_SESH", encoded_sesh) resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
return resp return resp
else: else:
return RedirectResponse("/gate/", 303) return RedirectResponse("/gate/", 303)

View File

@ -18,6 +18,7 @@
<th>Music</th> <th>Music</th>
<th>Difficulty</th> <th>Difficulty</th>
<th>Score</th> <th>Score</th>
<th>Rank</th>
<th>Rating</th> <th>Rating</th>
</tr> </tr>
{% for row in hot_list %} {% for row in hot_list %}
@ -28,6 +29,7 @@
{{ row.level }} {{ row.level }}
</td> </td>
<td>{{ row.score }}</td> <td>{{ row.score }}</td>
<td>{{ row.rank }}</td>
<td class="{% if row.song_rating >= 16 %}rainbow{% endif %}"> <td class="{% if row.song_rating >= 16 %}rainbow{% endif %}">
{{ row.song_rating }} {{ row.song_rating }}
</td> </td>
@ -48,6 +50,7 @@
<th>Music</th> <th>Music</th>
<th>Difficulty</th> <th>Difficulty</th>
<th>Score</th> <th>Score</th>
<th>Rank</th>
<th>Rating</th> <th>Rating</th>
</tr> </tr>
{% for row in base_list %} {% for row in base_list %}
@ -58,6 +61,7 @@
{{ row.level }} {{ row.level }}
</td> </td>
<td>{{ row.score }}</td> <td>{{ row.score }}</td>
<td>{{ row.rank }}</td>
<td class="{% if row.song_rating >= 16 %}rainbow{% endif %}"> <td class="{% if row.song_rating >= 16 %}rainbow{% endif %}">
{{ row.song_rating }} {{ row.song_rating }}
</td> </td>
@ -76,4 +80,4 @@
Login to view profile information. Login to view profile information.
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}