2019-12-08 22:43:49 +01:00
|
|
|
# vim: set fileencoding=utf-8
|
2022-10-07 02:32:37 +02:00
|
|
|
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
2019-12-08 22:43:49 +01:00
|
|
|
|
|
|
|
from bemani.backend.jubeat import JubeatFactory, JubeatBase
|
2021-08-20 06:43:13 +02:00
|
|
|
from bemani.common import Profile, ValidatedDict, GameConstants, VersionConstants
|
2019-12-08 22:43:49 +01:00
|
|
|
from bemani.data import Attempt, Score, Song, UserID
|
|
|
|
from bemani.frontend.base import FrontendBase
|
|
|
|
|
|
|
|
|
|
|
|
class JubeatFrontend(FrontendBase):
|
|
|
|
|
2021-09-07 19:57:31 +02:00
|
|
|
game: GameConstants = GameConstants.JUBEAT
|
2019-12-08 22:43:49 +01:00
|
|
|
|
2021-09-07 19:57:31 +02:00
|
|
|
valid_charts: List[int] = [
|
2019-12-08 22:43:49 +01:00
|
|
|
JubeatBase.CHART_TYPE_BASIC,
|
|
|
|
JubeatBase.CHART_TYPE_ADVANCED,
|
|
|
|
JubeatBase.CHART_TYPE_EXTREME,
|
2022-08-17 06:15:27 +02:00
|
|
|
JubeatBase.CHART_TYPE_HARD_BASIC,
|
|
|
|
JubeatBase.CHART_TYPE_HARD_ADVANCED,
|
|
|
|
JubeatBase.CHART_TYPE_HARD_EXTREME,
|
2019-12-08 22:43:49 +01:00
|
|
|
]
|
|
|
|
|
2021-09-07 19:57:31 +02:00
|
|
|
valid_rival_types: List[str] = ['rival']
|
2020-05-09 00:40:21 +02:00
|
|
|
|
2021-08-19 21:21:22 +02:00
|
|
|
def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
2019-12-08 22:43:49 +01:00
|
|
|
yield from JubeatFactory.all_games()
|
|
|
|
|
2021-08-19 21:21:22 +02:00
|
|
|
def sanitized_games(self) -> Iterator[Tuple[GameConstants, int, str]]:
|
2019-12-08 22:43:49 +01:00
|
|
|
mapping = {
|
|
|
|
VersionConstants.JUBEAT: 1,
|
|
|
|
VersionConstants.JUBEAT_RIPPLES: 2,
|
|
|
|
VersionConstants.JUBEAT_KNIT: 3,
|
|
|
|
VersionConstants.JUBEAT_COPIOUS: 4,
|
|
|
|
VersionConstants.JUBEAT_SAUCER: 5,
|
|
|
|
VersionConstants.JUBEAT_PROP: 6,
|
|
|
|
VersionConstants.JUBEAT_QUBELL: 7,
|
|
|
|
VersionConstants.JUBEAT_CLAN: 8,
|
|
|
|
VersionConstants.JUBEAT_FESTO: 9,
|
|
|
|
}
|
|
|
|
|
|
|
|
for (game, version, name) in self.all_games():
|
|
|
|
if version in mapping:
|
|
|
|
yield (game, mapping[version], name)
|
|
|
|
|
2022-10-07 02:32:37 +02:00
|
|
|
def get_duplicate_id(self, musicid: int, chart: int) -> Optional[Tuple[int, int]]:
|
|
|
|
# In qubell and clan omnimix, PPAP and Bonjour the world are placed
|
|
|
|
# at this arbitrary songid since they weren't assigned one originally
|
|
|
|
# In jubeat festo, these songs were given proper songids so we need to account for this
|
|
|
|
legacy_to_modern_map = {
|
|
|
|
71000001: 70000124, # PPAP
|
|
|
|
71000002: 70000154, # Bonjour the world
|
|
|
|
50000020: 80000037, # 千本桜 was removed and then revived in clan
|
|
|
|
60000063: 70000100, # Khamen break sdvx had the first id for prop(never released officially)
|
|
|
|
}
|
|
|
|
oldid = legacy_to_modern_map.get(musicid)
|
|
|
|
oldchart = chart
|
|
|
|
if oldid is not None:
|
|
|
|
return (oldid, oldchart)
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_all_items(self, versions: list) -> Dict[str, List[Dict[str, Any]]]:
|
|
|
|
result = {}
|
|
|
|
for version in versions:
|
|
|
|
emblem = self.__format_jubeat_extras(version)
|
|
|
|
result[version] = emblem['emblems']
|
|
|
|
return result
|
|
|
|
|
|
|
|
def __format_jubeat_extras(self, version: int) -> Dict[str, List[Dict[str, Any]]]:
|
|
|
|
# Gotta look up the unlock catalog
|
|
|
|
items = self.data.local.game.get_items(self.game, version)
|
|
|
|
|
|
|
|
# Format it depending on the version
|
|
|
|
if version in {
|
|
|
|
VersionConstants.JUBEAT_PROP,
|
|
|
|
VersionConstants.JUBEAT_QUBELL,
|
|
|
|
VersionConstants.JUBEAT_CLAN,
|
|
|
|
VersionConstants.JUBEAT_FESTO,
|
|
|
|
}:
|
|
|
|
return {
|
|
|
|
"emblems": [
|
|
|
|
{
|
|
|
|
"index": str(item.id),
|
|
|
|
"song": item.data.get_int("music_id"),
|
|
|
|
"layer": item.data.get_int("layer"),
|
|
|
|
"evolved": item.data.get_int("evolved"),
|
|
|
|
"rarity": item.data.get_int("rarity"),
|
|
|
|
"name": item.data.get_str("name"),
|
|
|
|
}
|
|
|
|
for item in items
|
|
|
|
if item.type == "emblem"
|
|
|
|
],
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
return {"emblems": []}
|
|
|
|
|
2019-12-08 22:43:49 +01:00
|
|
|
def format_score(self, userid: UserID, score: Score) -> Dict[str, Any]:
|
|
|
|
formatted_score = super().format_score(userid, score)
|
|
|
|
formatted_score['combo'] = score.data.get_int('combo', -1)
|
2022-08-17 06:15:27 +02:00
|
|
|
formatted_score['music_rate'] = score.data.get_int('music_rate', -1)
|
|
|
|
if formatted_score['music_rate'] >= 0:
|
|
|
|
formatted_score['music_rate'] /= 10
|
2019-12-08 22:43:49 +01:00
|
|
|
formatted_score['medal'] = score.data.get_int('medal')
|
|
|
|
formatted_score['status'] = {
|
|
|
|
JubeatBase.PLAY_MEDAL_FAILED: "FAILED",
|
|
|
|
JubeatBase.PLAY_MEDAL_CLEARED: "CLEARED",
|
|
|
|
JubeatBase.PLAY_MEDAL_NEARLY_FULL_COMBO: "NEARLY FULL COMBO",
|
|
|
|
JubeatBase.PLAY_MEDAL_FULL_COMBO: "FULL COMBO",
|
|
|
|
JubeatBase.PLAY_MEDAL_NEARLY_EXCELLENT: "NEARLY EXCELLENT",
|
|
|
|
JubeatBase.PLAY_MEDAL_EXCELLENT: "EXCELLENT",
|
|
|
|
}.get(score.data.get_int('medal'), 'NO PLAY')
|
2022-10-07 02:32:37 +02:00
|
|
|
formatted_score['music_rate'] = score.data.get_int('music_rate', 0) / 10
|
|
|
|
formatted_score['clear_cnt'] = score.data.get_int('clear_count', 0)
|
|
|
|
formatted_score['stats'] = score.data.get_dict('stats')
|
2019-12-08 22:43:49 +01:00
|
|
|
return formatted_score
|
|
|
|
|
|
|
|
def format_attempt(self, userid: UserID, attempt: Attempt) -> Dict[str, Any]:
|
|
|
|
formatted_attempt = super().format_attempt(userid, attempt)
|
|
|
|
formatted_attempt['combo'] = attempt.data.get_int('combo', -1)
|
|
|
|
formatted_attempt['medal'] = attempt.data.get_int('medal')
|
2022-08-17 06:15:27 +02:00
|
|
|
formatted_attempt['music_rate'] = attempt.data.get_int('music_rate', -1)
|
|
|
|
if formatted_attempt['music_rate'] >= 0:
|
|
|
|
formatted_attempt['music_rate'] /= 10
|
2019-12-08 22:43:49 +01:00
|
|
|
formatted_attempt['status'] = {
|
|
|
|
JubeatBase.PLAY_MEDAL_FAILED: "FAILED",
|
|
|
|
JubeatBase.PLAY_MEDAL_CLEARED: "CLEARED",
|
|
|
|
JubeatBase.PLAY_MEDAL_NEARLY_FULL_COMBO: "NEARLY FULL COMBO",
|
|
|
|
JubeatBase.PLAY_MEDAL_FULL_COMBO: "FULL COMBO",
|
|
|
|
JubeatBase.PLAY_MEDAL_NEARLY_EXCELLENT: "NEARLY EXCELLENT",
|
|
|
|
JubeatBase.PLAY_MEDAL_EXCELLENT: "EXCELLENT",
|
|
|
|
}.get(attempt.data.get_int('medal'), 'NO PLAY')
|
2022-10-07 02:32:37 +02:00
|
|
|
formatted_attempt['music_rate'] = attempt.data.get_int('music_rate', 0) / 10
|
|
|
|
formatted_attempt['stats'] = attempt.data.get_dict('stats')
|
2019-12-08 22:43:49 +01:00
|
|
|
return formatted_attempt
|
|
|
|
|
2022-10-07 02:32:37 +02:00
|
|
|
def format_emblem(self, emblem: list) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
'background': emblem[0],
|
|
|
|
'main': emblem[1],
|
|
|
|
'ornament': emblem[2],
|
|
|
|
'effect': emblem[3],
|
|
|
|
'speech_bubble': emblem[4],
|
|
|
|
}
|
|
|
|
|
2021-08-20 06:43:13 +02:00
|
|
|
def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]:
|
2019-12-08 22:43:49 +01:00
|
|
|
formatted_profile = super().format_profile(profile, playstats)
|
|
|
|
formatted_profile['plays'] = playstats.get_int('total_plays')
|
2022-10-07 02:32:37 +02:00
|
|
|
formatted_profile['emblem'] = self.format_emblem(profile.get_dict('last').get_int_array('emblem', 5))
|
2022-10-08 03:32:59 +02:00
|
|
|
formatted_profile['jubility'] = (
|
|
|
|
profile.get_int('jubility')
|
|
|
|
if profile.version not in {VersionConstants.JUBEAT_PROP, VersionConstants.JUBEAT_QUBELL, VersionConstants.JUBEAT_FESTO}
|
|
|
|
else 0
|
|
|
|
)
|
|
|
|
formatted_profile['pick_up_jubility'] = (
|
|
|
|
profile.get_float('pick_up_jubility')
|
|
|
|
if profile.version == VersionConstants.JUBEAT_FESTO
|
|
|
|
else 0
|
|
|
|
)
|
|
|
|
formatted_profile['common_jubility'] = (
|
|
|
|
profile.get_float('common_jubility')
|
|
|
|
if profile.version == VersionConstants.JUBEAT_FESTO
|
|
|
|
else 0
|
|
|
|
)
|
|
|
|
if profile.version == VersionConstants.JUBEAT_FESTO:
|
|
|
|
# Only reason this is a dictionary of dictionaries is because ValidatedDict doesn't support a list of dictionaries.
|
|
|
|
# Probably intentionally lol. Just listify the pickup/common charts.
|
|
|
|
formatted_profile['pick_up_chart'] = list(profile.get_dict('pick_up_chart').values())
|
|
|
|
formatted_profile['common_chart'] = list(profile.get_dict('common_chart').values())
|
|
|
|
elif profile.version == VersionConstants.JUBEAT_CLAN:
|
|
|
|
# Look up achievements which is where jubility was stored. This is a bit of a hack
|
|
|
|
# due to the fact that this could be formatting remote profiles, but then they should
|
|
|
|
# have no achievements.
|
|
|
|
userid = self.data.local.user.from_refid(profile.game, profile.version, profile.refid)
|
|
|
|
if userid is not None:
|
|
|
|
achievements = self.data.local.user.get_achievements(profile.game, profile.version, userid)
|
|
|
|
else:
|
|
|
|
achievements = []
|
|
|
|
|
|
|
|
jubeat_entries: List[ValidatedDict] = []
|
|
|
|
for achievement in achievements:
|
|
|
|
if achievement.type != 'jubility':
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Figure out for each song, what's the highest value jubility and
|
|
|
|
# keep that.
|
|
|
|
bestentry = ValidatedDict()
|
|
|
|
for chart in [0, 1, 2]:
|
|
|
|
entry = achievement.data.get_dict(str(chart))
|
|
|
|
if entry.get_int("value") >= bestentry.get_int("value"):
|
|
|
|
bestentry = entry.clone()
|
|
|
|
bestentry.replace_int("songid", achievement.id)
|
|
|
|
bestentry.replace_int("chart", chart)
|
|
|
|
jubeat_entries.append(bestentry)
|
|
|
|
jubeat_entries = sorted(jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True)[:30]
|
|
|
|
formatted_profile['chart'] = jubeat_entries
|
|
|
|
|
2022-10-07 02:32:37 +02:00
|
|
|
formatted_profile['ex_count'] = profile.get_int('ex_cnt')
|
|
|
|
formatted_profile['fc_count'] = profile.get_int('fc_cnt')
|
2019-12-08 22:43:49 +01:00
|
|
|
return formatted_profile
|
|
|
|
|
|
|
|
def format_song(self, song: Song) -> Dict[str, Any]:
|
2022-08-17 06:15:27 +02:00
|
|
|
difficulties = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
|
|
|
difficulties[song.chart] = song.data.get_float('difficulty', 13)
|
|
|
|
if difficulties[song.chart] == 13.0:
|
|
|
|
difficulties[song.chart] = float(song.data.get_int('difficulty', 13))
|
2019-12-08 22:43:49 +01:00
|
|
|
|
|
|
|
formatted_song = super().format_song(song)
|
|
|
|
formatted_song['bpm_min'] = song.data.get_int('bpm_min', 120)
|
|
|
|
formatted_song['bpm_max'] = song.data.get_int('bpm_max', 120)
|
|
|
|
formatted_song['difficulties'] = difficulties
|
2022-08-17 06:58:31 +02:00
|
|
|
version = song.data.get_int('version', 0)
|
|
|
|
if version == 0:
|
|
|
|
# The default here is a nasty hack for installations that existed prior to importing
|
|
|
|
# version using read.py. This ensures that not importing again won't break existing
|
|
|
|
# installations.
|
|
|
|
formatted_song['version'] = int(song.id / 10000000)
|
|
|
|
else:
|
|
|
|
formatted_song['version'] = {
|
|
|
|
VersionConstants.JUBEAT: 1,
|
|
|
|
VersionConstants.JUBEAT_RIPPLES: 2,
|
|
|
|
VersionConstants.JUBEAT_RIPPLES_APPEND: 2,
|
|
|
|
VersionConstants.JUBEAT_KNIT: 3,
|
|
|
|
VersionConstants.JUBEAT_KNIT_APPEND: 3,
|
|
|
|
VersionConstants.JUBEAT_COPIOUS: 4,
|
|
|
|
VersionConstants.JUBEAT_COPIOUS_APPEND: 4,
|
|
|
|
VersionConstants.JUBEAT_SAUCER: 5,
|
|
|
|
VersionConstants.JUBEAT_SAUCER_FULFILL: 5,
|
|
|
|
VersionConstants.JUBEAT_PROP: 6,
|
|
|
|
VersionConstants.JUBEAT_QUBELL: 7,
|
|
|
|
VersionConstants.JUBEAT_CLAN: 8,
|
|
|
|
VersionConstants.JUBEAT_FESTO: 9,
|
|
|
|
}[version]
|
2019-12-08 22:43:49 +01:00
|
|
|
return formatted_song
|
|
|
|
|
|
|
|
def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]:
|
|
|
|
new_song = super().merge_song(existing, new)
|
2022-08-17 06:15:27 +02:00
|
|
|
if existing['difficulties'][new.chart] == 0.0:
|
|
|
|
new_song['difficulties'][new.chart] = new.data.get_float('difficulty', 13)
|
|
|
|
if new_song['difficulties'][new.chart] == 13.0:
|
|
|
|
new_song['difficulties'][new.chart] = float(new.data.get_int('difficulty', 13))
|
2019-12-08 22:43:49 +01:00
|
|
|
return new_song
|