from typing import Optional, Dict, List, Tuple, Any from typing_extensions import Final from bemani.backend.reflec.base import ReflecBeatBase from bemani.backend.reflec.limelight import ReflecBeatLimelight from bemani.common import Profile, ValidatedDict, VersionConstants, ID, Time from bemani.data import UserID, Achievement from bemani.protocol import Node class ReflecBeatColette(ReflecBeatBase): name: str = "REFLEC BEAT colette" version: int = VersionConstants.REFLEC_BEAT_COLETTE # Clear types according to the game GAME_CLEAR_TYPE_NO_PLAY: Final[int] = 0 GAME_CLEAR_TYPE_FAILED: Final[int] = 1 GAME_CLEAR_TYPE_CLEARED: Final[int] = 2 GAME_CLEAR_TYPE_ALMOST_COMBO: Final[int] = 3 GAME_CLEAR_TYPE_FULL_COMBO: Final[int] = 4 def previous_version(self) -> Optional[ReflecBeatBase]: return ReflecBeatLimelight(self.data, self.config, self.model) @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", }, ], "ints": [], } def __db_to_game_clear_type(self, db_clear_type: int, db_combo_type: int) -> int: if db_clear_type == self.CLEAR_TYPE_NO_PLAY: return self.GAME_CLEAR_TYPE_NO_PLAY if db_clear_type == self.CLEAR_TYPE_FAILED: return self.GAME_CLEAR_TYPE_FAILED if db_clear_type in [ self.CLEAR_TYPE_CLEARED, self.CLEAR_TYPE_HARD_CLEARED, self.CLEAR_TYPE_S_HARD_CLEARED, ]: if db_combo_type == self.COMBO_TYPE_NONE: return self.GAME_CLEAR_TYPE_CLEARED if db_combo_type == self.COMBO_TYPE_ALMOST_COMBO: return self.GAME_CLEAR_TYPE_ALMOST_COMBO if db_combo_type in [ self.COMBO_TYPE_FULL_COMBO, self.COMBO_TYPE_FULL_COMBO_ALL_JUST, ]: return self.GAME_CLEAR_TYPE_FULL_COMBO raise Exception(f"Invalid db_combo_type {db_combo_type}") raise Exception(f"Invalid db_clear_type {db_clear_type}") def __game_to_db_clear_type(self, game_clear_type: int) -> Tuple[int, int]: if game_clear_type == self.GAME_CLEAR_TYPE_NO_PLAY: return (self.CLEAR_TYPE_NO_PLAY, self.COMBO_TYPE_NONE) if game_clear_type == self.GAME_CLEAR_TYPE_FAILED: return (self.CLEAR_TYPE_FAILED, self.COMBO_TYPE_NONE) if game_clear_type == self.GAME_CLEAR_TYPE_CLEARED: return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_NONE) if game_clear_type == self.GAME_CLEAR_TYPE_ALMOST_COMBO: return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_ALMOST_COMBO) if game_clear_type == self.GAME_CLEAR_TYPE_FULL_COMBO: return (self.CLEAR_TYPE_CLEARED, self.COMBO_TYPE_FULL_COMBO) raise Exception(f"Invalid game_clear_type {game_clear_type}") def handle_pcb_error_request(self, request: Node) -> Node: return Node.void("pcb") def handle_pcb_uptime_update_request(self, request: Node) -> Node: return Node.void("pcb") def handle_pcb_boot_request(self, request: Node) -> Node: shop_id = ID.parse_machine_id(request.child_value("lid")) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name close = machine.data.get_bool("close") hour = machine.data.get_int("hour") minute = machine.data.get_int("minute") else: machine_name = "" close = False hour = 0 minute = 0 root = Node.void("pcb") sinfo = Node.void("sinfo") root.add_child(sinfo) sinfo.add_child(Node.string("nm", machine_name)) sinfo.add_child(Node.bool("cl_enbl", close)) sinfo.add_child(Node.u8("cl_h", hour)) sinfo.add_child(Node.u8("cl_m", minute)) return root def handle_shop_setting_write_request(self, request: Node) -> Node: return Node.void("shop") def handle_shop_info_write_request(self, request: Node) -> Node: self.update_machine_name(request.child_value("sinfo/nm")) self.update_machine_data( { "close": request.child_value("sinfo/cl_enbl"), "hour": request.child_value("sinfo/cl_h"), "minute": request.child_value("sinfo/cl_m"), "pref": request.child_value("sinfo/prf"), } ) return Node.void("shop") def __add_event_info(self, root: Node) -> None: event_ctrl = Node.void("event_ctrl") root.add_child(event_ctrl) events: Dict[int, int] = { # Tricolette Park unlock event. 9: 0, } for eventid, phase in events.items(): data = Node.void("data") event_ctrl.add_child(data) data.add_child(Node.s32("type", eventid)) data.add_child(Node.s32("phase", phase)) item_lock_ctrl = Node.void("item_lock_ctrl") root.add_child(item_lock_ctrl) # Contains zero or more nodes like: # # any # any # 0-3 # def handle_info_common_request(self, request: Node) -> Node: root = Node.void("info") self.__add_event_info(root) return root def handle_info_ranking_request(self, request: Node) -> Node: version = request.child_value("ver") root = Node.void("info") root.add_child(Node.s32("ver", version)) ranking = Node.void("ranking") root.add_child(ranking) def add_hitchart( name: str, start: int, end: int, hitchart: List[Tuple[int, int]] ) -> None: base = Node.void(name) ranking.add_child(base) base.add_child(Node.s32("bt", start)) base.add_child(Node.s32("et", end)) new = Node.void("new") base.add_child(new) for mid, plays in hitchart: d = Node.void("d") new.add_child(d) d.add_child(Node.s16("mid", mid)) d.add_child(Node.s32("cnt", plays)) # Weekly hit chart add_hitchart( "weekly", Time.now() - Time.SECONDS_IN_WEEK, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 7), ) # Monthly hit chart add_hitchart( "monthly", Time.now() - Time.SECONDS_IN_DAY * 30, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 30), ) # All time hit chart add_hitchart( "total", Time.now() - Time.SECONDS_IN_DAY * 365, Time.now(), self.data.local.music.get_hit_chart(self.game, self.version, 1024, 365), ) return root def handle_info_pzlcmt_read_request(self, request: Node) -> Node: extid = request.child_value("uid") teamid = request.child_value("tid") limit = request.child_value("limit") userid = self.data.remote.user.from_extid(self.game, self.version, extid) comments = [ achievement for achievement in self.data.local.user.get_all_time_based_achievements( self.game, self.version ) if achievement[1].type == "puzzle_comment" ] comments.sort(key=lambda x: x[1].timestamp, reverse=True) favorites = [comment for comment in comments if comment[0] == userid] teamcomments = [ comment for comment in comments if comment[1].data.get_int("teamid") == teamid ] # Cap all comment blocks to the limit if limit >= 0: comments = comments[:limit] favorites = favorites[:limit] teamcomments = teamcomments[:limit] root = Node.void("info") comment = Node.void("comment") root.add_child(comment) comment.add_child(Node.s32("time", Time.now())) # Mapping of profiles to userIDs uid_mapping = { uid: prof for (uid, prof) in self.get_any_profiles([c[0] for c in comments]) } # Handle anonymous comments by returning a default profile uid_mapping[UserID(0)] = Profile( self.game, self.version, "", 0, {"name": "PLAYER"}, ) def add_comments(name: str, selected: List[Tuple[UserID, Achievement]]) -> None: for uid, ach in selected: cmnt = Node.void(name) root.add_child(cmnt) cmnt.add_child(Node.s32("uid", uid_mapping[uid].extid)) cmnt.add_child(Node.string("name", uid_mapping[uid].get_str("name"))) cmnt.add_child(Node.s16("icon", ach.data.get_int("icon"))) cmnt.add_child(Node.s8("bln", ach.data.get_int("bln"))) cmnt.add_child(Node.s32("tid", ach.data.get_int("teamid"))) cmnt.add_child(Node.string("t_name", ach.data.get_str("teamname"))) cmnt.add_child(Node.s8("pref", ach.data.get_int("prefecture"))) cmnt.add_child(Node.s32("time", ach.timestamp)) cmnt.add_child(Node.string("comment", ach.data.get_str("comment"))) cmnt.add_child(Node.bool("is_tweet", ach.data.get_bool("tweet"))) # Add all comments add_comments("c", comments) # Add personal comments (favorites) add_comments("cf", favorites) # Add team comments add_comments("ct", teamcomments) return root def handle_info_pzlcmt_write_request(self, request: Node) -> Node: extid = request.child_value("uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is None: # Anonymous comment userid = UserID(0) icon = request.child_value("icon") bln = request.child_value("bln") teamid = request.child_value("tid") teamname = request.child_value("t_name") prefecture = request.child_value("pref") comment = request.child_value("comment") is_tweet = request.child_value("is_tweet") # Link comment to user's profile self.data.local.user.put_time_based_achievement( self.game, self.version, userid, 0, # We never have an ID for this, since comments are add-only "puzzle_comment", { "icon": icon, "bln": bln, "teamid": teamid, "teamname": teamname, "prefecture": prefecture, "comment": comment, "tweet": is_tweet, }, ) return Node.void("info") def handle_jbrbcollabo_save_request(self, request: Node) -> Node: jbrbcollabo = Node.void("jbrbcollabo") jbrbcollabo.add_child(Node.u16("marathontype", 0)) jbrbcollabo.add_child(Node.u32("smith_start", 0)) jbrbcollabo.add_child(Node.u32("pastel_start", 0)) jbrbcollabo.add_child(Node.u32("smith_run", 0)) jbrbcollabo.add_child(Node.u32("pastel_run", 0)) jbrbcollabo.add_child(Node.u16("smith_ouen", 0)) jbrbcollabo.add_child(Node.u16("pastel_ouen", 0)) jbrbcollabo.add_child(Node.u32("smith_water_run", 0)) jbrbcollabo.add_child(Node.u32("pastel_water_run", 0)) jbrbcollabo.add_child(Node.bool("getwater", False)) jbrbcollabo.add_child(Node.bool("smith_goal", False)) jbrbcollabo.add_child(Node.bool("pastel_goal", False)) jbrbcollabo.add_child(Node.u16("distancetype", 0)) jbrbcollabo.add_child(Node.bool("run1_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run1_2_j_flg", False)) jbrbcollabo.add_child(Node.bool("run1_3_j_flg", False)) jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) jbrbcollabo.add_child(Node.bool("run1_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run1_2_r_flg", False)) jbrbcollabo.add_child(Node.bool("run1_3_r_flg", False)) jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) jbrbcollabo.add_child(Node.bool("run2_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run2_2_j_flg", False)) jbrbcollabo.add_child(Node.bool("run2_3_j_flg", False)) jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) jbrbcollabo.add_child(Node.bool("run2_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run2_2_r_flg", False)) jbrbcollabo.add_child(Node.bool("run2_3_r_flg", False)) jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) jbrbcollabo.add_child(Node.bool("run3_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run3_2_j_flg", False)) jbrbcollabo.add_child(Node.bool("run3_3_j_flg", False)) jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) jbrbcollabo.add_child(Node.bool("run3_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run3_2_r_flg", False)) jbrbcollabo.add_child(Node.bool("run3_3_r_flg", False)) jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) jbrbcollabo.add_child(Node.bool("run4_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run4_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) jbrbcollabo.add_child(Node.bool("start_flg", False)) return jbrbcollabo def handle_lobby_entry_request(self, request: Node) -> Node: root = Node.void("lobby") root.add_child(Node.s32("interval", 120)) root.add_child(Node.s32("interval_p", 120)) # Create a lobby entry for this user extid = request.child_value("e/uid") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) self.data.local.lobby.put_lobby( self.game, self.version, userid, { "mid": request.child_value("e/mid"), "ng": request.child_value("e/ng"), "mopt": request.child_value("e/mopt"), "tid": request.child_value("e/tid"), "tn": request.child_value("e/tn"), "topt": request.child_value("e/topt"), "lid": request.child_value("e/lid"), "sn": request.child_value("e/sn"), "pref": request.child_value("e/pref"), "stg": request.child_value("e/stg"), "pside": request.child_value("e/pside"), "eatime": request.child_value("e/eatime"), "ga": request.child_value("e/ga"), "gp": request.child_value("e/gp"), "la": request.child_value("e/la"), "ver": request.child_value("e/ver"), }, ) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) root.add_child(Node.s32("eid", lobby.get_int("id"))) e = Node.void("e") root.add_child(e) e.add_child(Node.s32("eid", lobby.get_int("id"))) e.add_child(Node.u16("mid", lobby.get_int("mid"))) e.add_child(Node.u8("ng", lobby.get_int("ng"))) e.add_child(Node.s32("uid", profile.extid)) e.add_child(Node.s32("uattr", profile.get_int("uattr"))) e.add_child(Node.string("pn", profile.get_str("name"))) e.add_child(Node.s16("mg", profile.get_int("mg"))) e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) e.add_child(Node.s32("tid", lobby.get_int("tid"))) e.add_child(Node.string("tn", lobby.get_str("tn"))) e.add_child(Node.s32("topt", lobby.get_int("topt"))) e.add_child(Node.string("lid", lobby.get_str("lid"))) e.add_child(Node.string("sn", lobby.get_str("sn"))) e.add_child(Node.u8("pref", lobby.get_int("pref"))) e.add_child(Node.s8("stg", lobby.get_int("stg"))) e.add_child(Node.s8("pside", lobby.get_int("pside"))) e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) e.add_child(Node.u16("gp", lobby.get_int("gp"))) e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) e.add_child(Node.u8("ver", lobby.get_int("ver"))) return root def handle_lobby_read_request(self, request: Node) -> Node: root = Node.void("lobby") root.add_child(Node.s32("interval", 120)) root.add_child(Node.s32("interval_p", 120)) # Look up all lobbies matching the criteria specified ver = request.child_value("var") mg = request.child_value("m_grade") # noqa: F841 extid = request.child_value("uid") limit = request.child_value("max") userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: lobbies = self.data.local.lobby.get_all_lobbies(self.game, self.version) for user, lobby in lobbies: if limit <= 0: break if user == userid: # If we have our own lobby, don't return it continue if ver != lobby.get_int("ver"): # Don't return lobby data for different versions continue profile = self.get_profile(user) if profile is None: # No profile info, don't return this lobby continue e = Node.void("e") root.add_child(e) e.add_child(Node.s32("eid", lobby.get_int("id"))) e.add_child(Node.u16("mid", lobby.get_int("mid"))) e.add_child(Node.u8("ng", lobby.get_int("ng"))) e.add_child(Node.s32("uid", profile.extid)) e.add_child(Node.s32("uattr", profile.get_int("uattr"))) e.add_child(Node.string("pn", profile.get_str("name"))) e.add_child(Node.s16("mg", profile.get_int("mg"))) e.add_child(Node.s32("mopt", lobby.get_int("mopt"))) e.add_child(Node.s32("tid", lobby.get_int("tid"))) e.add_child(Node.string("tn", lobby.get_str("tn"))) e.add_child(Node.s32("topt", lobby.get_int("topt"))) e.add_child(Node.string("lid", lobby.get_str("lid"))) e.add_child(Node.string("sn", lobby.get_str("sn"))) e.add_child(Node.u8("pref", lobby.get_int("pref"))) e.add_child(Node.s8("stg", lobby.get_int("stg"))) e.add_child(Node.s8("pside", lobby.get_int("pside"))) e.add_child(Node.s16("eatime", lobby.get_int("eatime"))) e.add_child(Node.u8_array("ga", lobby.get_int_array("ga", 4))) e.add_child(Node.u16("gp", lobby.get_int("gp"))) e.add_child(Node.u8_array("la", lobby.get_int_array("la", 4))) e.add_child(Node.u8("ver", lobby.get_int("ver"))) limit = limit - 1 return root def handle_lobby_delete_request(self, request: Node) -> Node: eid = request.child_value("eid") self.data.local.lobby.destroy_lobby(eid) return Node.void("lobby") def handle_player_start_request(self, request: Node) -> Node: root = Node.void("player") # Create a new play session based on info from the request refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: self.data.local.lobby.put_play_session_info( self.game, self.version, userid, { "ga": request.child_value("ga"), "gp": request.child_value("gp"), "la": request.child_value("la"), }, ) info = self.data.local.lobby.get_play_session_info( self.game, self.version, userid, ) if info is not None: play_id = info.get_int("id") else: play_id = 0 else: play_id = 0 # Session stuff, and resend global defaults root.add_child(Node.s32("plyid", play_id)) root.add_child(Node.u64("start_time", Time.now() * 1000)) self.__add_event_info(root) # Event settings and such lincle_link_4 = Node.void("lincle_link_4") root.add_child(lincle_link_4) lincle_link_4.add_child(Node.u32("qpro", 0)) lincle_link_4.add_child(Node.u32("glass", 0)) lincle_link_4.add_child(Node.u32("treasure", 0)) lincle_link_4.add_child(Node.bool("for_iidx_0_0", False)) lincle_link_4.add_child(Node.bool("for_iidx_0_1", False)) lincle_link_4.add_child(Node.bool("for_iidx_0_2", False)) lincle_link_4.add_child(Node.bool("for_iidx_0_3", False)) lincle_link_4.add_child(Node.bool("for_iidx_0_4", False)) lincle_link_4.add_child(Node.bool("for_iidx_0_5", False)) lincle_link_4.add_child(Node.bool("for_iidx_0_6", False)) lincle_link_4.add_child(Node.bool("for_iidx_0", False)) lincle_link_4.add_child(Node.bool("for_iidx_1", False)) lincle_link_4.add_child(Node.bool("for_iidx_2", False)) lincle_link_4.add_child(Node.bool("for_iidx_3", False)) lincle_link_4.add_child(Node.bool("for_iidx_4", False)) lincle_link_4.add_child(Node.bool("for_rb_0_0", False)) lincle_link_4.add_child(Node.bool("for_rb_0_1", False)) lincle_link_4.add_child(Node.bool("for_rb_0_2", False)) lincle_link_4.add_child(Node.bool("for_rb_0_3", False)) lincle_link_4.add_child(Node.bool("for_rb_0_4", False)) lincle_link_4.add_child(Node.bool("for_rb_0_5", False)) lincle_link_4.add_child(Node.bool("for_rb_0_6", False)) lincle_link_4.add_child(Node.bool("for_rb_0", False)) lincle_link_4.add_child(Node.bool("for_rb_1", False)) lincle_link_4.add_child(Node.bool("for_rb_2", False)) lincle_link_4.add_child(Node.bool("for_rb_3", False)) lincle_link_4.add_child(Node.bool("for_rb_4", False)) lincle_link_4.add_child(Node.bool("qproflg", False)) lincle_link_4.add_child(Node.bool("glassflg", False)) lincle_link_4.add_child(Node.bool("complete", False)) jbrbcollabo = Node.void("jbrbcollabo") root.add_child(jbrbcollabo) jbrbcollabo.add_child(Node.bool("run1_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run1_2_j_flg", False)) jbrbcollabo.add_child(Node.bool("run1_3_j_flg", False)) jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) jbrbcollabo.add_child(Node.bool("run1_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run1_2_r_flg", False)) jbrbcollabo.add_child(Node.bool("run1_3_r_flg", False)) jbrbcollabo.add_child(Node.bool("run1_4_flg", False)) jbrbcollabo.add_child(Node.bool("run2_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run2_2_j_flg", False)) jbrbcollabo.add_child(Node.bool("run2_3_j_flg", False)) jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) jbrbcollabo.add_child(Node.bool("run2_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run2_2_r_flg", False)) jbrbcollabo.add_child(Node.bool("run2_3_r_flg", False)) jbrbcollabo.add_child(Node.bool("run2_4_flg", False)) jbrbcollabo.add_child(Node.bool("run3_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run3_2_j_flg", False)) jbrbcollabo.add_child(Node.bool("run3_3_j_flg", False)) jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) jbrbcollabo.add_child(Node.bool("run3_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run3_2_r_flg", False)) jbrbcollabo.add_child(Node.bool("run3_3_r_flg", False)) jbrbcollabo.add_child(Node.bool("run3_4_flg", False)) jbrbcollabo.add_child(Node.u16("marathontype", 0)) jbrbcollabo.add_child(Node.u32("smith_start", 0)) jbrbcollabo.add_child(Node.u32("pastel_start", 0)) jbrbcollabo.add_child(Node.u16("smith_ouen", 0)) jbrbcollabo.add_child(Node.u16("pastel_ouen", 0)) jbrbcollabo.add_child(Node.u16("distancetype", 0)) jbrbcollabo.add_child(Node.bool("smith_goal", False)) jbrbcollabo.add_child(Node.bool("pastel_goal", False)) jbrbcollabo.add_child(Node.bool("run4_1_j_flg", False)) jbrbcollabo.add_child(Node.bool("run4_1_r_flg", False)) jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) jbrbcollabo.add_child(Node.bool("run4_2_flg", False)) jbrbcollabo.add_child(Node.bool("start_flg", False)) tricolettepark = Node.void("tricolettepark") root.add_child(tricolettepark) tricolettepark.add_child(Node.s32("open_music", -1)) tricolettepark.add_child(Node.s32("boss0_damage", -1)) tricolettepark.add_child(Node.s32("boss1_damage", -1)) tricolettepark.add_child(Node.s32("boss2_damage", -1)) tricolettepark.add_child(Node.s32("boss3_damage", -1)) tricolettepark.add_child(Node.s32("boss0_stun", -1)) tricolettepark.add_child(Node.s32("boss1_stun", -1)) tricolettepark.add_child(Node.s32("boss2_stun", -1)) tricolettepark.add_child(Node.s32("boss3_stun", -1)) tricolettepark.add_child(Node.s32("magic_gauge", -1)) tricolettepark.add_child(Node.s32("today_party", -1)) tricolettepark.add_child(Node.bool("union_magic", False)) tricolettepark.add_child(Node.bool("is_complete", False)) tricolettepark.add_child(Node.float("base_attack_rate", 1.0)) return root def handle_player_delete_request(self, request: Node) -> Node: return Node.void("player") def handle_player_end_request(self, request: Node) -> Node: # Destroy play session based on info from the request refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Kill any lingering lobbies by this user lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) if lobby is not None: self.data.local.lobby.destroy_lobby(lobby.get_int("id")) self.data.local.lobby.destroy_play_session_info( self.game, self.version, userid ) return Node.void("player") def handle_player_succeed_request(self, request: Node) -> Node: refid = request.child_value("rid") userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: previous_version = self.previous_version() profile = previous_version.get_profile(userid) achievements = self.data.local.user.get_achievements( previous_version.game, previous_version.version, userid ) scores = self.data.remote.music.get_scores( previous_version.game, previous_version.version, userid ) else: profile = None root = Node.void("player") if profile is None: root.add_child(Node.string("name", "")) root.add_child(Node.s16("lv", -1)) root.add_child(Node.s32("exp", -1)) root.add_child(Node.s32("grd", -1)) root.add_child(Node.s32("ap", -1)) root.add_child(Node.void("released")) root.add_child(Node.void("mrecord")) else: root.add_child(Node.string("name", profile.get_str("name"))) root.add_child(Node.s16("lv", profile.get_int("lvl"))) root.add_child(Node.s32("exp", profile.get_int("exp"))) root.add_child(Node.s32("grd", profile.get_int("mg"))) # This is a guess root.add_child(Node.s32("ap", profile.get_int("ap"))) released = Node.void("released") root.add_child(released) for item in achievements: if item.type != "item_0": continue released.add_child(Node.s16("i", item.id)) mrecord = Node.void("mrecord") root.add_child(mrecord) for score in scores: mrec = Node.void("mrec") mrecord.add_child(mrec) mrec.add_child(Node.s16("mid", score.id)) mrec.add_child(Node.s8("ntgrd", score.chart)) mrec.add_child(Node.s32("pc", score.plays)) mrec.add_child( Node.s8( "ct", self.__db_to_game_clear_type( score.data.get_int("clear_type"), score.data.get_int("combo_type"), ), ) ) mrec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) mrec.add_child(Node.s16("scr", score.points)) mrec.add_child(Node.s16("cmb", score.data.get_int("combo"))) mrec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) mrec.add_child(Node.u16("ver", 0)) return root def handle_player_read_request(self, request: Node) -> Node: refid = request.child_value("rid") profile = self.get_profile_by_refid(refid) if profile: return profile return Node.void("player") def handle_player_write_request(self, request: Node) -> Node: refid = request.child_value("pdata/account/rid") profile = self.put_profile_by_refid(refid, request) root = Node.void("player") if profile is None: root.add_child(Node.s32("uid", 0)) else: root.add_child(Node.s32("uid", profile.extid)) return root def format_profile(self, userid: UserID, profile: Profile) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements( self.game, self.version, userid ) scores = self.data.remote.music.get_scores(self.game, self.version, userid) links = self.data.local.user.get_links(self.game, self.version, userid) root = Node.void("player") pdata = Node.void("pdata") root.add_child(pdata) account = Node.void("account") pdata.add_child(account) account.add_child(Node.s32("usrid", profile.extid)) account.add_child(Node.s32("tpc", statistics.total_plays)) account.add_child(Node.s32("dpc", statistics.today_plays)) account.add_child(Node.s32("crd", 1)) account.add_child(Node.s32("brd", 1)) account.add_child(Node.s32("tdc", statistics.total_days)) account.add_child(Node.s32("intrvld", 0)) account.add_child(Node.s16("ver", 5)) account.add_child(Node.u64("pst", 0)) account.add_child(Node.u64("st", Time.now() * 1000)) # Base account info base = Node.void("base") pdata.add_child(base) base.add_child(Node.string("name", profile.get_str("name"))) base.add_child(Node.s32("exp", profile.get_int("exp"))) base.add_child(Node.s32("lv", profile.get_int("lvl"))) base.add_child(Node.s32("mg", profile.get_int("mg"))) base.add_child(Node.s32("ap", profile.get_int("ap"))) base.add_child(Node.s32("tid", profile.get_int("team_id", -1))) base.add_child(Node.string("tname", profile.get_str("team_name", ""))) base.add_child(Node.string("cmnt", "")) base.add_child(Node.s32("uattr", profile.get_int("uattr"))) base.add_child( Node.s32_array("hidden_param", profile.get_int_array("hidden_param", 50)) ) base.add_child(Node.s32("tbs", -1)) base.add_child(Node.s32("tbs_r", -1)) # Rivals rival = Node.void("rival") pdata.add_child(rival) slotid = 0 for link in links: if link.type != "rival": continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue r = Node.void("r") rival.add_child(r) r.add_child(Node.s32("slot_id", slotid)) r.add_child(Node.s32("id", rprofile.extid)) r.add_child(Node.string("name", rprofile.get_str("name"))) r.add_child(Node.bool("friend", True)) r.add_child(Node.bool("locked", False)) r.add_child(Node.s32("rc", 0)) slotid = slotid + 1 # Player customizations custom = Node.void("custom") customdict = profile.get_dict("custom") pdata.add_child(custom) custom.add_child(Node.u8("st_shot", customdict.get_int("st_shot"))) custom.add_child(Node.u8("st_frame", customdict.get_int("st_frame"))) custom.add_child(Node.u8("st_expl", customdict.get_int("st_expl"))) custom.add_child(Node.u8("st_bg", customdict.get_int("st_bg"))) custom.add_child(Node.u8("st_shot_vol", customdict.get_int("st_shot_vol"))) custom.add_child(Node.u8("st_bg_bri", customdict.get_int("st_bg_bri"))) custom.add_child(Node.u8("st_obj_size", customdict.get_int("st_obj_size"))) custom.add_child(Node.u8("st_jr_gauge", customdict.get_int("st_jr_gauge"))) custom.add_child(Node.u8("st_clr_gauge", customdict.get_int("st_clr_gauge"))) custom.add_child(Node.u8("st_jdg_disp", customdict.get_int("st_jdg_disp"))) custom.add_child(Node.u8("st_tm_disp", customdict.get_int("st_tm_disp"))) custom.add_child(Node.u8("st_rnd", customdict.get_int("st_rnd"))) custom.add_child( Node.s16_array("schat_0", customdict.get_int_array("schat_0", 9)) ) custom.add_child( Node.s16_array("schat_1", customdict.get_int_array("schat_1", 9)) ) custom.add_child( Node.s16_array("ichat_0", customdict.get_int_array("ichat_0", 6)) ) custom.add_child( Node.s16_array("ichat_1", customdict.get_int_array("ichat_1", 6)) ) # Player external config config = Node.void("config") configdict = profile.get_dict("config") pdata.add_child(config) config.add_child(Node.u8("msel_bgm", configdict.get_int("msel_bgm"))) config.add_child( Node.u8("narrowdown_type", configdict.get_int("narrowdown_type")) ) config.add_child(Node.s16("icon_id", configdict.get_int("icon_id"))) config.add_child(Node.s16("byword_0", configdict.get_int("byword_0"))) config.add_child(Node.s16("byword_1", configdict.get_int("byword_1"))) config.add_child( Node.bool("is_auto_byword_0", configdict.get_bool("is_auto_byword_0")) ) config.add_child( Node.bool("is_auto_byword_1", configdict.get_bool("is_auto_byword_1")) ) config.add_child(Node.u8("mrec_type", configdict.get_int("mrec_type"))) config.add_child(Node.u8("tab_sel", configdict.get_int("tab_sel"))) config.add_child(Node.u8("card_disp", configdict.get_int("card_disp"))) config.add_child( Node.u8("score_tab_disp", configdict.get_int("score_tab_disp")) ) config.add_child( Node.s16("last_music_id", configdict.get_int("last_music_id", -1)) ) config.add_child( Node.u8("last_note_grade", configdict.get_int("last_note_grade")) ) config.add_child(Node.u8("sort_type", configdict.get_int("sort_type"))) config.add_child( Node.u8("rival_panel_type", configdict.get_int("rival_panel_type")) ) config.add_child( Node.u64("random_entry_work", configdict.get_int("random_entry_work")) ) config.add_child( Node.u8("folder_lamp_type", configdict.get_int("folder_lamp_type")) ) config.add_child(Node.bool("is_tweet", configdict.get_bool("is_tweet"))) config.add_child( Node.bool("is_link_twitter", configdict.get_bool("is_link_twitter")) ) # Stamps stamp = Node.void("stamp") stampdict = profile.get_dict("stamp") pdata.add_child(stamp) stamp.add_child( Node.s32_array("stmpcnt", stampdict.get_int_array("stmpcnt", 5)) ) stamp.add_child( Node.s32_array("tcktcnt", stampdict.get_int_array("tcktcnt", 5)) ) stamp.add_child(Node.s64("area", stampdict.get_int("area"))) stamp.add_child(Node.s64("prfvst", stampdict.get_int("prfvst"))) stamp.add_child(Node.s32("reserve", stampdict.get_int("reserve"))) # Unlocks released = Node.void("released") pdata.add_child(released) 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 == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue info = Node.void("info") released.add_child(info) info.add_child(Node.u8("type", itemtype)) info.add_child(Node.u16("id", item.id)) info.add_child(Node.u16("param", item.data.get_int("param"))) if game_config.get_bool("force_unlock_songs"): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.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 songid in ids: if ids[songid] == 0: continue info = Node.void("info") released.add_child(info) info.add_child(Node.u8("type", 0)) info.add_child(Node.u16("id", songid)) info.add_child(Node.u16("param", ids[songid])) # Favorite songs fav_music_slot = Node.void("fav_music_slot") pdata.add_child(fav_music_slot) for item in achievements: if item.type != "music": continue slot = Node.void("slot") fav_music_slot.add_child(slot) slot.add_child(Node.u8("slot_id", item.id)) slot.add_child(Node.s16("music_id", item.data.get_int("music_id"))) # Event stuff order = Node.void("order") pdata.add_child(order) order.add_child(Node.s32("exp", profile.get_int("order_exp"))) for item in achievements: if item.type != "order": continue data = Node.void("d") order.add_child(data) data.add_child(Node.s16("order", item.id)) data.add_child(Node.s16("slt", item.data.get_int("slt"))) data.add_child(Node.s32("ccnt", item.data.get_int("ccnt"))) data.add_child(Node.s32("fcnt", item.data.get_int("fcnt"))) data.add_child(Node.s32("fcnt1", item.data.get_int("fcnt1"))) data.add_child(Node.s32("prm", item.data.get_int("param"))) seedpod = Node.void("seedpod") pdata.add_child(seedpod) for item in achievements: if item.type != "seedpod": continue data = Node.void("data") seedpod.add_child(data) data.add_child(Node.s16("id", item.id)) data.add_child(Node.s16("pod", item.data.get_int("pod"))) eqpexp = Node.void("eqpexp") pdata.add_child(eqpexp) for item in achievements: if item.type[:7] != "eqpexp_": continue stype = int(item.type[7:]) data = Node.void("data") eqpexp.add_child(data) data.add_child(Node.s16("id", item.id)) data.add_child(Node.s32("exp", item.data.get_int("exp"))) data.add_child(Node.s16("stype", stype)) eventexp = Node.void("evntexp") pdata.add_child(eventexp) for item in achievements: if item.type != "eventexp": continue data = Node.void("data") eventexp.add_child(data) data.add_child(Node.s16("id", item.id)) data.add_child(Node.s32("exp", item.data.get_int("exp"))) # Scores record = Node.void("record") pdata.add_child(record) record_old = Node.void("record_old") pdata.add_child(record_old) for score in scores: rec = Node.void("rec") record.add_child(rec) rec.add_child(Node.s16("mid", score.id)) rec.add_child(Node.s8("ntgrd", score.chart)) rec.add_child(Node.s32("pc", score.plays)) rec.add_child( Node.s8( "ct", self.__db_to_game_clear_type( score.data.get_int("clear_type"), score.data.get_int("combo_type"), ), ) ) rec.add_child(Node.s16("ar", score.data.get_int("achievement_rate"))) rec.add_child(Node.s16("scr", score.points)) rec.add_child(Node.s16("cmb", score.data.get_int("combo"))) rec.add_child(Node.s16("ms", score.data.get_int("miss_count"))) rec.add_child(Node.s32("bscrt", score.timestamp)) rec.add_child( Node.s32("bart", score.data.get_int("best_achievement_rate_time")) ) rec.add_child(Node.s32("bctt", score.data.get_int("best_clear_type_time"))) rec.add_child(Node.s32("bmst", score.data.get_int("best_miss_count_time"))) rec.add_child(Node.s32("time", score.data.get_int("last_played_time"))) return root def unformat_profile( self, userid: UserID, request: Node, oldprofile: Profile ) -> Profile: game_config = self.get_game_config() newprofile = oldprofile.clone() newprofile.replace_int( "lid", ID.parse_machine_id(request.child_value("pdata/account/lid")) ) newprofile.replace_str("name", request.child_value("pdata/base/name")) newprofile.replace_int("exp", request.child_value("pdata/base/exp")) newprofile.replace_int("lvl", request.child_value("pdata/base/lvl")) newprofile.replace_int("mg", request.child_value("pdata/base/mg")) newprofile.replace_int("ap", request.child_value("pdata/base/ap")) newprofile.replace_int_array( "hidden_param", 50, request.child_value("pdata/base/hidden_param") ) configdict = newprofile.get_dict("config") config = request.child("pdata/config") if config: configdict.replace_int("msel_bgm", config.child_value("msel_bgm")) configdict.replace_int( "narrowdown_type", config.child_value("narrowdown_type") ) configdict.replace_int("icon_id", config.child_value("icon_id")) configdict.replace_int("byword_0", config.child_value("byword_0")) configdict.replace_int("byword_1", config.child_value("byword_1")) configdict.replace_bool( "is_auto_byword_0", config.child_value("is_auto_byword_0") ) configdict.replace_bool( "is_auto_byword_1", config.child_value("is_auto_byword_1") ) configdict.replace_int("mrec_type", config.child_value("mrec_type")) configdict.replace_int("tab_sel", config.child_value("tab_sel")) configdict.replace_int("card_disp", config.child_value("card_disp")) configdict.replace_int( "score_tab_disp", config.child_value("score_tab_disp") ) configdict.replace_int("last_music_id", config.child_value("last_music_id")) configdict.replace_int( "last_note_grade", config.child_value("last_note_grade") ) configdict.replace_int("sort_type", config.child_value("sort_type")) configdict.replace_int( "rival_panel_type", config.child_value("rival_panel_type") ) configdict.replace_int( "random_entry_work", config.child_value("random_entry_work") ) configdict.replace_int( "folder_lamp_type", config.child_value("folder_lamp_type") ) configdict.replace_bool("is_tweet", config.child_value("is_tweet")) configdict.replace_bool( "is_link_twitter", config.child_value("is_link_twitter") ) newprofile.replace_dict("config", configdict) customdict = newprofile.get_dict("custom") custom = request.child("pdata/custom") if custom: customdict.replace_int("st_shot", custom.child_value("st_shot")) customdict.replace_int("st_frame", custom.child_value("st_frame")) customdict.replace_int("st_expl", custom.child_value("st_expl")) customdict.replace_int("st_bg", custom.child_value("st_bg")) customdict.replace_int("st_shot_vol", custom.child_value("st_shot_vol")) customdict.replace_int("st_bg_bri", custom.child_value("st_bg_bri")) customdict.replace_int("st_obj_size", custom.child_value("st_obj_size")) customdict.replace_int("st_jr_gauge", custom.child_value("st_jr_gauge")) customdict.replace_int("st_clr_gauge", custom.child_value("st_clr_gauge")) customdict.replace_int("st_jdg_disp", custom.child_value("st_jdg_disp")) customdict.replace_int("st_tm_disp", custom.child_value("st_tm_disp")) customdict.replace_int("st_rnd", custom.child_value("st_rnd")) customdict.replace_int_array("schat_0", 9, custom.child_value("schat_0")) customdict.replace_int_array("schat_1", 9, custom.child_value("schat_1")) customdict.replace_int_array("ichat_0", 6, custom.child_value("ichat_0")) customdict.replace_int_array("ichat_1", 6, custom.child_value("ichat_1")) newprofile.replace_dict("custom", customdict) # Stamps stampdict = newprofile.get_dict("stamp") stamp = request.child("pdata/stamp") if stamp: stampdict.replace_int_array("stmpcnt", 5, stamp.child_value("stmpcnt")) stampdict.replace_int_array("tcktcnt", 5, stamp.child_value("tcktcnt")) stampdict.replace_int("area", stamp.child_value("area")) stampdict.replace_int("prfvst", stamp.child_value("prfvst")) stampdict.replace_int("reserve", stamp.child_value("reserve")) newprofile.replace_dict("stamp", stampdict) # Unlockable orders newprofile.replace_int("order_exp", request.child_value("pdata/order/exp")) order = request.child("pdata/order") if order: for child in order.children: if child.name != "d": continue orderid = child.child_value("order") slt = child.child_value("slt") ccnt = child.child_value("ccnt") fcnt = child.child_value("fcnt") fcnt1 = child.child_value("fcnt1") param = child.child_value("prm") if slt == -1: # The game doesn't return valid data for this selection # type, so be sure not to accidentally overwrite the # finished flags. continue self.data.local.user.put_achievement( self.game, self.version, userid, orderid, "order", { "slt": slt, "ccnt": ccnt, "fcnt": fcnt, "fcnt1": fcnt1, "param": param, }, ) # Music unlocks and other stuff released = request.child("pdata/released") if released: for child in released.children: if child.name != "info": continue item_id = child.child_value("id") item_type = child.child_value("type") param = child.child_value("param") if game_config.get_bool("force_unlock_songs") and item_type == 0: # Don't save unlocks when we're force unlocking continue self.data.local.user.put_achievement( self.game, self.version, userid, item_id, f"item_{item_type}", { "param": param, }, ) # Favorite music fav_music_slot = request.child("pdata/fav_music_slot") if fav_music_slot: for child in fav_music_slot.children: if child.name != "slot": continue slot_id = child.child_value("slot_id") music_id = child.child_value("music_id") if music_id == -1: # Delete this favorite self.data.local.user.destroy_achievement( self.game, self.version, userid, slot_id, "music", ) else: # Add/update this favorite self.data.local.user.put_achievement( self.game, self.version, userid, slot_id, "music", { "music_id": music_id, }, ) # Event stuff seedpod = request.child("pdata/seedpod") if seedpod: for child in seedpod.children: if child.name != "data": continue seedpod_id = child.child_value("id") seedpod_pod = child.child_value("pod") self.data.local.user.put_achievement( self.game, self.version, userid, seedpod_id, "seedpod", { "pod": seedpod_pod, }, ) eventexp = request.child("pdata/evntexp") if eventexp: for child in eventexp.children: if child.name != "data": continue eventexp_id = child.child_value("id") eventexp_exp = child.child_value("exp") # Experience is additive, so load it first and add the updated amount data = ( self.data.local.user.get_achievement( self.game, self.version, userid, eventexp_id, "eventexp", ) or ValidatedDict() ) self.data.local.user.put_achievement( self.game, self.version, userid, eventexp_id, "eventexp", { "exp": data.get_int("exp") + eventexp_exp, }, ) eqpexp = request.child("pdata/eqpexp") if eqpexp: for child in eqpexp.children: if child.name != "data": continue eqpexp_id = child.child_value("id") eqpexp_exp = child.child_value("exp") eqpexp_stype = child.child_value("stype") # Experience is additive, so load it first and add the updated amount data = ( self.data.local.user.get_achievement( self.game, self.version, userid, eqpexp_id, f"eqpexp_{eqpexp_stype}", ) or ValidatedDict() ) self.data.local.user.put_achievement( self.game, self.version, userid, eqpexp_id, f"eqpexp_{eqpexp_stype}", { "exp": data.get_int("exp") + eqpexp_exp, }, ) # Grab any new records set during this play session songplays = request.child("pdata/stglog") if songplays: for child in songplays.children: if child.name != "log": continue songid = child.child_value("mid") chart = child.child_value("ng") clear_type = child.child_value("ct") if songid == 0 and chart == 0 and clear_type == -1: # Dummy song save during profile create continue points = child.child_value("sc") achievement_rate = child.child_value("ar") clear_type, combo_type = self.__game_to_db_clear_type(clear_type) combo = child.child_value("cmb") miss_count = child.child_value("jt_ms") self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, combo=combo, ) # Keep track of play statistics self.update_play_statistics(userid) return newprofile