import copy from typing import Optional, Dict, List, Tuple, Any 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 = "REFLEC BEAT colette" version = VersionConstants.REFLEC_BEAT_COLETTE # Clear types according to the game GAME_CLEAR_TYPE_NO_PLAY = 0 GAME_CLEAR_TYPE_FAILED = 1 GAME_CLEAR_TYPE_CLEARED = 2 GAME_CLEAR_TYPE_ALMOST_COMBO = 3 GAME_CLEAR_TYPE_FULL_COMBO = 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 = copy.deepcopy(oldprofile) 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