from typing import Any, Dict from typing_extensions import Final from bemani.backend.ess import EventLogHandler from bemani.backend.museca.base import MusecaBase from bemani.backend.museca.common import ( MusecaGameFrozenHandler, MusecaGameHiscoreHandler, MusecaGameNewHandler, MusecaGamePlayEndHandler, MusecaGameSaveHandler, MusecaGameSaveMusicHandler, MusecaGameShopHandler, ) from bemani.common import VersionConstants, Profile, ID from bemani.data import UserID from bemani.protocol import Node class Museca1( EventLogHandler, MusecaGameFrozenHandler, MusecaGameHiscoreHandler, MusecaGameNewHandler, MusecaGamePlayEndHandler, MusecaGameSaveHandler, MusecaGameSaveMusicHandler, MusecaGameShopHandler, MusecaBase, ): name: str = "MÚSECA" version: int = VersionConstants.MUSECA GAME_LIMITED_LOCKED: Final[int] = 1 GAME_LIMITED_UNLOCKABLE: Final[int] = 2 GAME_LIMITED_UNLOCKED: Final[int] = 3 GAME_CATALOG_TYPE_SONG: Final[int] = 0 GAME_CATALOG_TYPE_GRAFICA: Final[int] = 15 GAME_CATALOG_TYPE_MISSION: Final[int] = 16 GAME_GRADE_DEATH: Final[int] = 0 GAME_GRADE_POOR: Final[int] = 1 GAME_GRADE_MEDIOCRE: Final[int] = 2 GAME_GRADE_GOOD: Final[int] = 3 GAME_GRADE_GREAT: Final[int] = 4 GAME_GRADE_EXCELLENT: Final[int] = 5 GAME_GRADE_SUPERB: Final[int] = 6 GAME_GRADE_MASTERPIECE: Final[int] = 7 GAME_CLEAR_TYPE_FAILED: Final[int] = 1 GAME_CLEAR_TYPE_CLEARED: Final[int] = 2 GAME_CLEAR_TYPE_FULL_COMBO: Final[int] = 4 @classmethod def get_settings(cls) -> Dict[str, Any]: """ Return all of our front-end modifiably settings. """ return { 'bools': [ { 'name': 'Force Song Unlock', 'tip': 'Force unlock all songs.', 'category': 'game_config', 'setting': 'force_unlock_songs', }, ], } def game_to_db_clear_type(self, clear_type: int) -> int: return { self.GAME_CLEAR_TYPE_FAILED: self.CLEAR_TYPE_FAILED, self.GAME_CLEAR_TYPE_CLEARED: self.CLEAR_TYPE_CLEARED, self.GAME_CLEAR_TYPE_FULL_COMBO: self.CLEAR_TYPE_FULL_COMBO, }[clear_type] def db_to_game_clear_type(self, clear_type: int) -> int: return { self.CLEAR_TYPE_FAILED: self.GAME_CLEAR_TYPE_FAILED, self.CLEAR_TYPE_CLEARED: self.GAME_CLEAR_TYPE_CLEARED, self.CLEAR_TYPE_FULL_COMBO: self.GAME_CLEAR_TYPE_FULL_COMBO, }[clear_type] def game_to_db_grade(self, grade: int) -> int: return { self.GAME_GRADE_DEATH: self.GRADE_DEATH, self.GAME_GRADE_POOR: self.GRADE_POOR, self.GAME_GRADE_MEDIOCRE: self.GRADE_MEDIOCRE, self.GAME_GRADE_GOOD: self.GRADE_GOOD, self.GAME_GRADE_GREAT: self.GRADE_GREAT, self.GAME_GRADE_EXCELLENT: self.GRADE_EXCELLENT, self.GAME_GRADE_SUPERB: self.GRADE_SUPERB, self.GAME_GRADE_MASTERPIECE: self.GRADE_MASTERPIECE, }[grade] def db_to_game_grade(self, grade: int) -> int: return { self.GRADE_DEATH: self.GAME_GRADE_DEATH, self.GRADE_POOR: self.GAME_GRADE_POOR, self.GRADE_MEDIOCRE: self.GAME_GRADE_MEDIOCRE, self.GRADE_GOOD: self.GAME_GRADE_GOOD, self.GRADE_GREAT: self.GAME_GRADE_GREAT, self.GRADE_EXCELLENT: self.GAME_GRADE_EXCELLENT, self.GRADE_SUPERB: self.GAME_GRADE_SUPERB, self.GRADE_MASTERPIECE: self.GAME_GRADE_MASTERPIECE, self.GRADE_PERFECT: self.GAME_GRADE_MASTERPIECE, }[grade] def handle_game_3_common_request(self, request: Node) -> Node: game = Node.void('game_3') limited = Node.void('music_limited') game.add_child(limited) # Song unlock config game_config = self.get_game_config() if game_config.get_bool('force_unlock_songs'): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.music_version) for song in songs: if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): ids.add((song.id, song.chart)) for (songid, chart) in ids: info = Node.void('info') limited.add_child(info) info.add_child(Node.s32('music_id', songid)) info.add_child(Node.u8('music_type', chart)) info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) # Event config event = Node.void('event') game.add_child(event) def enable_event(eid: int) -> None: evt = Node.void('info') event.add_child(evt) evt.add_child(Node.u32('event_id', eid)) # Allow PASELI light start enable_event(83) # If you want song unlock news to show up, enable one of the following: # 94 - 5/25/2016 unlocks # 95 - 4/27/2016 second unlocks # 89 - 4/27/2016 unlocks # 87 - 4/13/2016 unlocks # 82 - 3/23/2016 second unlocks # 80 - 3/23/2016 unlocks # 76 - 12/22/2016 unlocks return game def handle_game_3_exception_request(self, request: Node) -> Node: return Node.void('game_3') def handle_game_3_load_request(self, request: Node) -> Node: refid = request.child_value('refid') root = self.get_profile_by_refid(refid) if root is not None: return root # No data succession, there's nothing older than this! root = Node.void('game_3') root.add_child(Node.u8('result', 1)) return root def handle_game_3_load_m_request(self, request: Node) -> Node: refid = request.child_value('dataid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) else: scores = [] # Output to the game game = Node.void('game_3') new = Node.void('new') game.add_child(new) for score in scores: music = Node.void('music') new.add_child(music) music.add_child(Node.u32('music_id', score.id)) music.add_child(Node.u32('music_type', score.chart)) music.add_child(Node.u32('score', score.points)) music.add_child(Node.u32('cnt', score.plays)) music.add_child(Node.u32('clear_type', self.db_to_game_clear_type(score.data.get_int('clear_type')))) music.add_child(Node.u32('score_grade', self.db_to_game_grade(score.data.get_int('grade')))) stats = score.data.get_dict('stats') music.add_child(Node.u32('btn_rate', stats.get_int('btn_rate'))) music.add_child(Node.u32('long_rate', stats.get_int('long_rate'))) music.add_child(Node.u32('vol_rate', stats.get_int('vol_rate'))) return game def format_profile(self, userid: UserID, profile: Profile) -> Node: game = Node.void('game_3') # Generic profile stuff game.add_child(Node.string('name', profile.get_str('name'))) game.add_child(Node.string('code', ID.format_extid(profile.extid))) game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) # Play statistics statistics = self.get_play_statistics(userid) game.add_child(Node.u32('play_count', statistics.total_plays)) game.add_child(Node.u32('daily_count', statistics.today_plays)) game.add_child(Node.u32('play_chain', statistics.consecutive_days)) # Last played stuff if 'last' in profile: lastdict = profile.get_dict('last') last = Node.void('last') game.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) # Item unlocks itemnode = Node.void('item') game.add_child(itemnode) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements(self.game, self.version, userid) for item in achievements: if item.type[:5] != 'item_': continue itemtype = int(item.type[5:]) if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: # Don't echo unlocked songs, we will add all of them later continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', itemtype)) info.add_child(Node.u32('id', item.id)) info.add_child(Node.u32('param', item.data.get_int('param'))) if 'diff_param' in item.data: info.add_child(Node.s32('diff_param', item.data.get_int('diff_param'))) if game_config.get_bool('force_unlock_songs'): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.music_version) for song in songs: if song.id not in ids: ids[song.id] = 0 if song.data.get_int('difficulty') > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) info.add_child(Node.u32('id', itemid)) info.add_child(Node.u32('param', ids[itemid])) return game def unformat_profile(self, userid: UserID, request: Node, oldprofile: Profile) -> Profile: newprofile = oldprofile.clone() # Update blaster energy and in-game currencies earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') if earned_gamecoin_packet is not None: newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) earned_gamecoin_block = request.child_value('earned_gamecoin_block') if earned_gamecoin_block is not None: newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) earned_blaster_energy = request.child_value('earned_blaster_energy') if earned_blaster_energy is not None: newprofile.replace_int('blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) # Miscelaneous stuff newprofile.replace_int('blaster_count', request.child_value('blaster_count')) newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() if request.child('item') is not None: for child in request.child('item').children: if child.name != 'info': continue item_id = child.child_value('id') item_type = child.child_value('type') param = child.child_value('param') diff_param = child.child_value('diff_param') if game_config.get_bool('force_unlock_songs') and item_type == self.GAME_CATALOG_TYPE_SONG: # Don't save back songs, because they were force unlocked continue if diff_param is not None: paramvals = { 'diff_param': diff_param, 'param': param, } else: paramvals = { 'param': param, } self.data.local.user.put_achievement( self.game, self.version, userid, item_id, f'item_{item_type}', paramvals, ) # Grab last information. lastdict = newprofile.get_dict('last') lastdict.replace_int('headphone', request.child_value('headphone')) lastdict.replace_int('appeal_id', request.child_value('appeal_id')) lastdict.replace_int('comment_id', request.child_value('comment_id')) lastdict.replace_int('music_id', request.child_value('music_id')) lastdict.replace_int('music_type', request.child_value('music_type')) lastdict.replace_int('sort_type', request.child_value('sort_type')) lastdict.replace_int('narrow_down', request.child_value('narrow_down')) lastdict.replace_int('gauge_option', request.child_value('gauge_option')) # Save back last information gleaned from results newprofile.replace_dict('last', lastdict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile