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
|
|
|
]
|
|
|
|
|
2022-10-15 20:56:30 +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,
|
|
|
|
}
|
|
|
|
|
2023-02-17 04:02:41 +01:00
|
|
|
for game, version, name in self.all_games():
|
2019-12-08 22:43:49 +01:00
|
|
|
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)
|
2022-10-15 20:56:30 +02:00
|
|
|
result[version] = emblem["emblems"]
|
2022-10-07 02:32:37 +02:00
|
|
|
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)
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_score["combo"] = score.data.get_int("combo", -1)
|
|
|
|
formatted_score["music_rate"] = score.data.get_int("music_rate", -1)
|
|
|
|
if formatted_score["music_rate"] >= 0:
|
|
|
|
formatted_score["music_rate"] /= 10
|
|
|
|
formatted_score["medal"] = score.data.get_int("medal")
|
|
|
|
formatted_score["status"] = {
|
2019-12-08 22:43:49 +01:00
|
|
|
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",
|
2022-10-15 20:56:30 +02:00
|
|
|
}.get(score.data.get_int("medal"), "NO PLAY")
|
|
|
|
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)
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_attempt["combo"] = attempt.data.get_int("combo", -1)
|
|
|
|
formatted_attempt["medal"] = attempt.data.get_int("medal")
|
|
|
|
formatted_attempt["music_rate"] = attempt.data.get_int("music_rate", -1)
|
|
|
|
if formatted_attempt["music_rate"] >= 0:
|
|
|
|
formatted_attempt["music_rate"] /= 10
|
|
|
|
formatted_attempt["status"] = {
|
2019-12-08 22:43:49 +01:00
|
|
|
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",
|
2022-10-15 20:56:30 +02:00
|
|
|
}.get(attempt.data.get_int("medal"), "NO PLAY")
|
|
|
|
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 {
|
2022-10-15 20:56:30 +02:00
|
|
|
"background": emblem[0],
|
|
|
|
"main": emblem[1],
|
|
|
|
"ornament": emblem[2],
|
|
|
|
"effect": emblem[3],
|
|
|
|
"speech_bubble": emblem[4],
|
2022-10-07 02:32:37 +02:00
|
|
|
}
|
|
|
|
|
2022-10-15 20:56:30 +02:00
|
|
|
def format_profile(
|
|
|
|
self, profile: Profile, playstats: ValidatedDict
|
|
|
|
) -> Dict[str, Any]:
|
2022-11-13 01:21:26 +01:00
|
|
|
# Grab achievements for both jubility in festo, as well as emblem parts in
|
|
|
|
# prop onward.
|
|
|
|
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 = []
|
|
|
|
|
2019-12-08 22:43:49 +01:00
|
|
|
formatted_profile = super().format_profile(profile, playstats)
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_profile["plays"] = playstats.get_int("total_plays")
|
|
|
|
formatted_profile["emblem"] = self.format_emblem(
|
|
|
|
profile.get_dict("last").get_int_array("emblem", 5)
|
|
|
|
)
|
2022-11-13 01:21:26 +01:00
|
|
|
formatted_profile["owned_emblems"] = [
|
2022-11-13 02:43:39 +01:00
|
|
|
str(ach.id) for ach in achievements if ach.type == "emblem"
|
2022-11-13 01:21:26 +01:00
|
|
|
]
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_profile["jubility"] = (
|
|
|
|
profile.get_int("jubility")
|
|
|
|
if profile.version
|
|
|
|
not in {
|
|
|
|
VersionConstants.JUBEAT_PROP,
|
|
|
|
VersionConstants.JUBEAT_QUBELL,
|
|
|
|
VersionConstants.JUBEAT_FESTO,
|
|
|
|
}
|
2022-10-08 03:32:59 +02:00
|
|
|
else 0
|
|
|
|
)
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_profile["pick_up_jubility"] = (
|
|
|
|
profile.get_float("pick_up_jubility")
|
2022-10-08 03:32:59 +02:00
|
|
|
if profile.version == VersionConstants.JUBEAT_FESTO
|
|
|
|
else 0
|
|
|
|
)
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_profile["common_jubility"] = (
|
|
|
|
profile.get_float("common_jubility")
|
2022-10-08 03:32:59 +02:00
|
|
|
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.
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_profile["pick_up_chart"] = list(
|
|
|
|
profile.get_dict("pick_up_chart").values()
|
|
|
|
)
|
|
|
|
formatted_profile["common_chart"] = list(
|
|
|
|
profile.get_dict("common_chart").values()
|
|
|
|
)
|
2022-10-08 03:32:59 +02:00
|
|
|
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.
|
|
|
|
jubeat_entries: List[ValidatedDict] = []
|
|
|
|
for achievement in achievements:
|
2022-10-15 20:56:30 +02:00
|
|
|
if achievement.type != "jubility":
|
2022-10-08 03:32:59 +02:00
|
|
|
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)
|
2022-10-15 20:56:30 +02:00
|
|
|
jubeat_entries = sorted(
|
|
|
|
jubeat_entries, key=lambda entry: entry.get_int("value"), reverse=True
|
|
|
|
)[:30]
|
|
|
|
formatted_profile["chart"] = jubeat_entries
|
2022-10-08 03:32:59 +02:00
|
|
|
|
2022-10-15 20:56:30 +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]
|
2022-10-15 20:56:30 +02:00
|
|
|
difficulties[song.chart] = song.data.get_float("difficulty", 13)
|
2022-08-17 06:15:27 +02:00
|
|
|
if difficulties[song.chart] == 13.0:
|
2022-10-15 20:56:30 +02:00
|
|
|
difficulties[song.chart] = float(song.data.get_int("difficulty", 13))
|
2019-12-08 22:43:49 +01:00
|
|
|
|
|
|
|
formatted_song = super().format_song(song)
|
2022-10-15 20:56:30 +02:00
|
|
|
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
|
|
|
|
version = song.data.get_int("version", 0)
|
2022-08-17 06:58:31 +02:00
|
|
|
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.
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_song["version"] = int(song.id / 10000000)
|
2022-08-17 06:58:31 +02:00
|
|
|
else:
|
2022-10-15 20:56:30 +02:00
|
|
|
formatted_song["version"] = {
|
2022-08-17 06:58:31 +02:00
|
|
|
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-10-15 20:56:30 +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
|