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