# vim: set fileencoding=utf-8 import copy from typing import Any, Dict, Iterator, List, Tuple from bemani.backend.ddr import DDRFactory, DDRBase from bemani.common import Profile, ValidatedDict, GameConstants, VersionConstants from bemani.data import Attempt, Link, RemoteUser, Score, Song, UserID from bemani.frontend.base import FrontendBase class DDRFrontend(FrontendBase): game: GameConstants = GameConstants.DDR version: int = 0 # We use a virtual version for DDR to tie charts together valid_charts: List[int] = [ DDRBase.CHART_SINGLE_BEGINNER, DDRBase.CHART_SINGLE_BASIC, DDRBase.CHART_SINGLE_DIFFICULT, DDRBase.CHART_SINGLE_EXPERT, DDRBase.CHART_SINGLE_CHALLENGE, DDRBase.CHART_DOUBLE_BASIC, DDRBase.CHART_DOUBLE_DIFFICULT, DDRBase.CHART_DOUBLE_EXPERT, DDRBase.CHART_DOUBLE_CHALLENGE, ] valid_rival_types: List[str] = [f'friend_{i}' for i in range(10)] max_active_rivals: Dict[int, int] = { VersionConstants.DDR_X2: 1, VersionConstants.DDR_X3_VS_2NDMIX: 3, VersionConstants.DDR_2013: 3, VersionConstants.DDR_2014: 3, VersionConstants.DDR_ACE: 3, VersionConstants.DDR_A20: 3, } def all_games(self) -> Iterator[Tuple[GameConstants, int, str]]: yield from DDRFactory.all_games() def update_name(self, profile: Profile, name: str) -> Profile: newprofile = copy.deepcopy(profile) newprofile.replace_str('name', name) return newprofile def update_weight(self, profile: Profile, weight: int, enabled: bool) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.get_int('version') in (VersionConstants.DDR_ACE, VersionConstants.DDR_A20): if enabled: newprofile.replace_int('weight', weight) newprofile.replace_bool('workout_mode', True) else: newprofile.replace_int('weight', 0) newprofile.replace_bool('workout_mode', False) else: if enabled: newprofile.replace_int('weight', weight) else: if 'weight' in newprofile: del newprofile['weight'] return newprofile def update_early_late(self, profile: Profile, display_early_late: bool) -> Profile: newprofile = copy.deepcopy(profile) newprofile.replace_int('early_late', 1 if display_early_late else 0) return newprofile def update_background_combo(self, profile: Profile, background_combo: bool) -> Profile: newprofile = copy.deepcopy(profile) newprofile.replace_int('combo', 1 if background_combo else 0) return newprofile def update_settings(self, profile: Profile, new_settings: Dict[str, Any]) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.get_int('version') in (VersionConstants.DDR_ACE, VersionConstants.DDR_A20): newprofile.replace_int('arrowskin', new_settings['arrowskin']) newprofile.replace_int('guidelines', new_settings['guidelines']) newprofile.replace_int('filter', new_settings['filter']) newprofile.replace_int('character', new_settings['character']) else: # No other versions have extra options yet. pass return newprofile def format_profile(self, profile: Profile, playstats: ValidatedDict) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) if profile.get_int('version') in (VersionConstants.DDR_ACE, VersionConstants.DDR_A20): formatted_profile.update({ 'sp': playstats.get_int('single_plays'), 'dp': playstats.get_int('double_plays'), 'early_late': profile.get_int('early_late') != 0, 'background_combo': profile.get_int('combo') != 0, 'workout_mode': profile.get_bool('workout_mode'), 'weight': profile.get_int('weight'), 'settings': { 'arrowskin': profile.get_int('arrowskin'), 'guidelines': profile.get_int('guidelines'), 'filter': profile.get_int('filter'), 'character': profile.get_int('character'), }, }) else: formatted_profile.update({ 'sp': playstats.get_int('single_plays'), 'dp': playstats.get_int('double_plays'), 'early_late': profile.get_int('early_late') != 0, 'background_combo': profile.get_int('combo') != 0, 'workout_mode': 'weight' in profile, 'weight': profile.get_int('weight'), }) return formatted_profile 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') formatted_score['lamp'] = score.data.get_int('halo') formatted_score['halo'] = { DDRBase.HALO_NONE: None, DDRBase.HALO_GOOD_FULL_COMBO: "GOOD FULL COMBO", DDRBase.HALO_GREAT_FULL_COMBO: "GREAT FULL COMBO", DDRBase.HALO_PERFECT_FULL_COMBO: "PERFECT FULL COMBO", DDRBase.HALO_MARVELOUS_FULL_COMBO: "MARVELOUS FULL COMBO", }.get(score.data.get_int('halo')) formatted_score['status'] = score.data.get_int('rank') formatted_score['rank'] = { DDRBase.RANK_AAA: "AAA", DDRBase.RANK_AA_PLUS: "AA+", DDRBase.RANK_AA: "AA", DDRBase.RANK_AA_MINUS: "AA-", DDRBase.RANK_A_PLUS: "A+", DDRBase.RANK_A: "A", DDRBase.RANK_A_MINUS: "A-", DDRBase.RANK_B_PLUS: "B+", DDRBase.RANK_B: "B", DDRBase.RANK_B_MINUS: "B-", DDRBase.RANK_C_PLUS: "C+", DDRBase.RANK_C: "C", DDRBase.RANK_C_MINUS: "C-", DDRBase.RANK_D_PLUS: "D+", DDRBase.RANK_D: "D", DDRBase.RANK_E: "E", }.get(score.data.get_int('rank'), 'NO PLAY') 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') formatted_attempt['halo'] = { DDRBase.HALO_NONE: None, DDRBase.HALO_GOOD_FULL_COMBO: "GOOD FULL COMBO", DDRBase.HALO_GREAT_FULL_COMBO: "GREAT FULL COMBO", DDRBase.HALO_PERFECT_FULL_COMBO: "PERFECT FULL COMBO", DDRBase.HALO_MARVELOUS_FULL_COMBO: "MARVELOUS FULL COMBO", }.get(attempt.data.get_int('halo')) formatted_attempt['rank'] = { DDRBase.RANK_AAA: "AAA", DDRBase.RANK_AA_PLUS: "AA+", DDRBase.RANK_AA: "AA", DDRBase.RANK_AA_MINUS: "AA-", DDRBase.RANK_A_PLUS: "A+", DDRBase.RANK_A: "A", DDRBase.RANK_A_MINUS: "A-", DDRBase.RANK_B_PLUS: "B+", DDRBase.RANK_B: "B", DDRBase.RANK_B_MINUS: "B-", DDRBase.RANK_C_PLUS: "C+", DDRBase.RANK_C: "C", DDRBase.RANK_C_MINUS: "C-", DDRBase.RANK_D_PLUS: "D+", DDRBase.RANK_D: "D", DDRBase.RANK_E: "E", }.get(attempt.data.get_int('rank'), 'NO PLAY') return formatted_attempt def format_song(self, song: Song) -> Dict[str, Any]: difficulties = [0] * 10 difficulties[song.chart] = song.data.get_int('difficulty', 20) 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['category'] = song.data.get_int('category', 0) formatted_song['groove'] = song.data.get_dict('groove', { 'voltage': 0, 'stream': 0, 'air': 0, 'chaos': 0, 'freeze': 0, }) formatted_song['difficulties'] = difficulties return formatted_song def merge_song(self, existing: Dict[str, Any], new: Song) -> Dict[str, Any]: new_song = super().merge_song(existing, new) if ( existing['difficulties'][new.chart] == 0 ): new_song['difficulties'][new.chart] = new.data.get_int('difficulty', 20) return new_song def activate_rival(self, profile: Profile, position: int) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.get_int('version') == VersionConstants.DDR_X2: # X2 only has one active rival lastdict = newprofile.get_dict('last') lastdict.replace_int('fri', position + 1) newprofile.replace_dict('last', lastdict) elif newprofile.get_int('version') in [VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, VersionConstants.DDR_A20]: # X3 has 3 active rivals, put this in the first open slot lastdict = newprofile.get_dict('last') if lastdict.get_int('rival1') < 1: lastdict.replace_int('rival1', position + 1) elif lastdict.get_int('rival2') < 1: lastdict.replace_int('rival2', position + 1) elif lastdict.get_int('rival3') < 1: lastdict.replace_int('rival3', position + 1) newprofile.replace_dict('last', lastdict) return newprofile def deactivate_rival(self, profile: Profile, position: int) -> Profile: newprofile = copy.deepcopy(profile) if newprofile.get_int('version') == VersionConstants.DDR_X2: # X2 only has one active rival lastdict = newprofile.get_dict('last') if lastdict.get_int('fri') == position + 1: lastdict.replace_int('fri', 0) newprofile.replace_dict('last', lastdict) elif newprofile.get_int('version') in [VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, VersionConstants.DDR_A20]: # X3 has 3 active rivals, put this in the first open slot lastdict = newprofile.get_dict('last') if lastdict.get_int('rival1') == position + 1: lastdict.replace_int('rival1', 0) elif lastdict.get_int('rival2') == position + 1: lastdict.replace_int('rival2', 0) elif lastdict.get_int('rival3') == position + 1: lastdict.replace_int('rival3', 0) newprofile.replace_dict('last', lastdict) return newprofile def format_rival(self, link: Link, profile: Profile) -> Dict[str, Any]: pos = int(link.type[7:]) if profile.get_int('version') == VersionConstants.DDR_X2: active = pos == (profile.get_dict('last').get_int('fri') - 1) elif profile.get_int('version') in { VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, VersionConstants.DDR_A20 }: actives = [ profile.get_dict('last').get_int('rival1') - 1, profile.get_dict('last').get_int('rival2') - 1, profile.get_dict('last').get_int('rival3') - 1, ] active = pos in actives else: active = False return { 'position': pos, 'active': active, 'userid': str(link.other_userid), 'remote': RemoteUser.is_remote(link.other_userid), }