def handle_gameend_regist_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') if player is not None: refid = player.child_value('refid') else: refid = None 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: oldprofile = self.get_profile(userid) newprofile = self.unformat_profile(userid, request, oldprofile) else: newprofile = None if userid is not None and newprofile is not None: self.put_profile(userid, newprofile) gameend = Node.void('gameend') data = Node.void('data') gameend.add_child(data) player = Node.void('player') data.add_child(player) player.add_child(Node.s32('session_id', 1)) player.add_child(Node.s32('end_final_session_id', 1)) return gameend
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict, is_new: bool) -> ValidatedDict: # Profile save request, data values are base64 encoded. # d is a CSV, and bin1 is binary data. newprofile = copy.deepcopy(oldprofile) strdatas: List[bytes] = [] bindatas: List[bytes] = [] record = request.child('data/record') for node in record.children: if node.name != 'd': continue profile = base64.b64decode(node.value) # Update the shop name if this is a new profile, since we know it came # from this cabinet. This is the only source of truth for what the # cabinet shop name is set to. if is_new: self.__update_shop_name(profile) strdatas.append(profile) bindatas.append(base64.b64decode(node.child_value('bin1'))) newprofile['strdatas'] = strdatas newprofile['bindatas'] = bindatas # Keep track of play statistics across all versions self.update_play_statistics(userid) return newprofile
def handle_gametop_regist_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') passnode = player.child('pass') refid = passnode.child_value('refid') name = player.child_value('name') root = self.new_profile_by_refid(refid, name) return root
def handle_game_save_c_request(self, request: Node) -> Node: refid = request.attribute('refid') courseid = int(request.attribute('cid')) chart = int(request.attribute('ctype')) userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: # Calculate statistics data = request.child('data') points = int(data.attribute('score')) combo = int(data.attribute('combo')) combo_type = int(data.attribute('combo_type')) stage = int(data.attribute('stage')) rank = self.game_to_db_rank(int(data.attribute('rank'))) trace = request.child_value('trace') # Grab the old course score oldcourse = self.data.local.user.get_achievement( self.game, self.version, userid, (courseid * 4) + chart, 'course', ) if oldcourse is not None: highscore = points > oldcourse.get_int('score') points = max(points, oldcourse.get_int('score')) combo = max(combo, oldcourse.get_int('combo')) stage = max(stage, oldcourse.get_int('stage')) rank = max(rank, oldcourse.get_int('rank')) combo_type = max(combo_type, oldcourse.get_int('combo_type')) if not highscore: # Don't overwrite the ghost for a non-highscore trace = oldcourse.get_int_array('trace', len(trace)) self.data.local.user.put_achievement( self.game, self.version, userid, (courseid * 4) + chart, 'course', { 'score': points, 'combo': combo, 'stage': stage, 'rank': rank, 'combo_type': combo_type, 'trace': trace, }, ) # No response needed game = Node.void('game') return game
def handle_gametop_get_mdata_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') extid = player.child_value('jid') root = self.get_scores_by_extid(extid) if root is None: root = Node.void('gametop') root.set_attribute('status', str(Status.NO_PROFILE)) return root
def handle_gametop_get_pdata_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') passnode = player.child('pass') refid = passnode.child_value('refid') root = self.get_profile_by_refid(refid) if root is None: root = Node.void('gametop') root.set_attribute('status', str(Status.NO_PROFILE)) return root
def handle_playerdata_usergamedata_advanced_request( self, request: Node) -> Optional[Node]: playerdata = Node.void('playerdata') # DDR Ace decides to be difficult and have a third level of packet switching mode = request.child_value('data/mode') refid = request.child_value('data/refid') extid = request.child_value('data/ddrcode') userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: # Possibly look up by extid instead userid = self.data.remote.user.from_extid(self.game, self.version, extid) if mode == 'userload': self.__handle_userload(userid, request.child('data'), playerdata) elif mode == 'usersave': self.__handle_usersave(userid, request.child('data'), playerdata) elif mode == 'rivalload': self.__handle_rivalload(userid, request.child('data'), playerdata) elif mode == 'usernew': self.__handle_usernew(userid, request.child('data'), playerdata) elif mode == 'inheritance': self.__handle_inheritance(userid, request.child('data'), playerdata) elif mode == 'ghostload': self.__handle_ghostload(userid, request.child('data'), playerdata) else: # We don't support this return None playerdata.add_child(Node.s32('result', 0)) return playerdata
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) # Update experience 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) gain_exp = request.child_value('gain_exp') if gain_exp is not None: newprofile.replace_int('exp', newprofile.get_int('exp') + gain_exp) # Miscelaneous stuff newprofile.replace_int('m_user_cnt', request.child_value('m_user_cnt')) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() if not game_config.get_bool('force_unlock_cards'): have_item = request.child_value('have_item') if have_item is not None: newprofile.replace_int_array('have_item', 512, [1 if x else 0 for x in have_item]) if not game_config.get_bool('force_unlock_songs'): have_note = request.child_value('have_note') if have_note is not None: newprofile.replace_int_array('have_note', 512, [1 if x else 0 for x in have_note]) # Grab last information. lastdict = newprofile.get_dict('last') lastdict.replace_int('headphone', request.child_value('headphone')) lastdict.replace_int('hispeed', request.child_value('hispeed')) lastdict.replace_int('appeal_id', request.child_value('appeal_id')) lastdict.replace_int('frame0', request.child_value('frame0')) lastdict.replace_int('frame1', request.child_value('frame1')) lastdict.replace_int('frame2', request.child_value('frame2')) lastdict.replace_int('frame3', request.child_value('frame3')) lastdict.replace_int('frame4', request.child_value('frame4')) last = request.child('last') if last is not None: lastdict.replace_int('music_id', intish(last.attribute('music_id'))) lastdict.replace_int('music_type', intish(last.attribute('music_type'))) lastdict.replace_int('sort_type', intish(last.attribute('sort_type'))) # Save back last information gleaned from results newprofile.replace_dict('last', lastdict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node: playerdata = Node.void('playerdata') refid = request.child_value('data/refid') userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: profile = self.get_profile(userid) or ValidatedDict() usergamedata = profile.get_dict('usergamedata') for record in request.child('data/record').children: if record.name != 'd': continue strdata = base64.b64decode(record.value) bindata = base64.b64decode(record.child_value('bin1')) # Grab and format the profile objects strdatalist = strdata.split(b',') profiletype = strdatalist[1].decode('utf-8') strdatalist = strdatalist[2:] # Extract relevant bits for frontend/API if profiletype == 'COMMON': profile.replace_str('name', strdatalist[self.GAME_COMMON_NAME_OFFSET].decode('ascii')) profile.replace_int('area', intish(strdatalist[self.GAME_COMMON_AREA_OFFSET].decode('ascii'), 16)) profile.replace_bool('workout_mode', int(strdatalist[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET].decode('ascii'), 16) != 0) profile.replace_int('weight', int(float(strdatalist[self.GAME_COMMON_WEIGHT_OFFSET].decode('ascii')) * 10)) profile.replace_int('character', int(strdatalist[self.GAME_COMMON_CHARACTER_OFFSET].decode('ascii'), 16)) if profiletype == 'OPTION': profile.replace_int('combo', int(strdatalist[self.GAME_OPTION_COMBO_POSITION_OFFSET].decode('ascii'), 16)) profile.replace_int('early_late', int(strdatalist[self.GAME_OPTION_FAST_SLOW_OFFSET].decode('ascii'), 16)) profile.replace_int('arrowskin', int(strdatalist[self.GAME_OPTION_ARROW_SKIN_OFFSET].decode('ascii'), 16)) profile.replace_int('guidelines', int(strdatalist[self.GAME_OPTION_GUIDELINE_OFFSET].decode('ascii'), 16)) profile.replace_int('filter', int(strdatalist[self.GAME_OPTION_FILTER_OFFSET].decode('ascii'), 16)) usergamedata[profiletype] = { 'strdata': b','.join(strdatalist), 'bindata': bindata, } profile.replace_dict('usergamedata', usergamedata) self.put_profile(userid, profile) playerdata.add_child(Node.s32('result', 0)) return playerdata
def handle_gametop_get_mdata_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') extid = player.child_value('jid') mdata_ver = player.child_value('mdata_ver') # Game requests mdata 3 times per profile for some reason if mdata_ver != 1: root = Node.void('gametop') datanode = Node.void('data') root.add_child(datanode) player = Node.void('player') datanode.add_child(player) player.add_child(Node.s32('jid', extid)) playdata = Node.void('mdata_list') player.add_child(playdata) return root root = self.get_scores_by_extid(extid) if root is None: root = Node.void('gametop') root.set_attribute('status', str(Status.NO_PROFILE)) return root
def handle_lobby_entry_request(self, request: Node) -> Node: root = Node.void('lobby') data = Node.void('data') root.add_child(data) roomid = Node.s64('roomid', -2) roomid.set_attribute('master', '1') data.add_child(roomid) refresh_intr = Node.s16('refresh_intr', 3) data.add_child(refresh_intr) # Grab music id from the request request_data = request.child('data') request_music = request_data.child('music') music_id = request_music.child('id') seq_id = request_music.child('seq') music = Node.void('music') music.add_child(music_id) music.add_child(seq_id) data.add_child(music) return root
def handle_game_save_m_request(self, request: Node) -> Node: refid = request.attribute('refid') songid = int(request.attribute('mid')) chart = self.game_to_db_chart(int(request.attribute('mtype'))) # Calculate statistics data = request.child('data') points = int(data.attribute('score')) combo = int(data.attribute('combo')) rank = self.game_to_db_rank(int(data.attribute('rank'))) if points == 1000000: halo = self.HALO_MARVELOUS_FULL_COMBO elif int(data.attribute('perf_fc')) != 0: halo = self.HALO_PERFECT_FULL_COMBO elif int(data.attribute('great_fc')) != 0: halo = self.HALO_GREAT_FULL_COMBO elif int(data.attribute('good_fc')) != 0: halo = self.HALO_GOOD_FULL_COMBO else: halo = self.HALO_NONE trace = request.child_value('trace') # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the # number of plays for that song/chart. userid = self.data.remote.user.from_refid(self.game, self.version, refid) self.update_score( userid, songid, chart, points, rank, halo, combo, trace, ) # No response needed game = Node.void('game') return game
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('option', request.child_value('option')) newprofile.replace_int('chara', request.child_value('chara')) newprofile.replace_int('mode', request.child_value('mode')) newprofile.replace_int('button', request.child_value('button')) newprofile.replace_int('music', request.child_value('music')) newprofile.replace_int('sheet', request.child_value('sheet')) newprofile.replace_int('last_play_flag', request.child_value('last_play_flag')) newprofile.replace_int('category', request.child_value('category')) newprofile.replace_int('sub_category', request.child_value('sub_category')) newprofile.replace_int('chara_category', request.child_value('chara_category')) newprofile.replace_int('medal_and_friend', request.child_value('medal_and_friend')) newprofile.replace_int('ep', request.child_value('ep')) newprofile.replace_int_array('sp_color_flg', 2, request.child_value('sp_color_flg')) newprofile.replace_int('read_news', request.child_value('read_news')) newprofile.replace_int('consecutive_days_coupon', request.child_value('consecutive_days_coupon')) newprofile.replace_int('tutorial', request.child_value('tutorial')) newprofile.replace_int('music_open_pt', request.child_value('music_open_pt')) newprofile.replace_int_array('gitadora_point', 3, request.child_value('gitadora_point')) newprofile.replace_int('gitadora_select', request.child_value('gitadora_select')) sp_node = request.child('sp_data') if sp_node is not None: newprofile.replace_int('sp', sp_node.child_value('sp')) zoo_dict = newprofile.get_dict('zoo') zoo_node = request.child('zoo') if zoo_node is not None: zoo_dict.replace_int_array('point', 5, zoo_node.child_value('point')) zoo_dict.replace_int_array('music_list', 2, zoo_node.child_value('music_list')) zoo_dict.replace_int_array('today_play_flag', 4, zoo_node.child_value('today_play_flag')) newprofile.replace_dict('zoo', zoo_dict) avatar_dict = newprofile.get_dict('avatar') avatar_dict.replace_int('hair', request.child_value('hair')) avatar_dict.replace_int('face', request.child_value('face')) avatar_dict.replace_int('body', request.child_value('body')) avatar_dict.replace_int('effect', request.child_value('effect')) avatar_dict.replace_int('object', request.child_value('object')) avatar_dict.replace_int_array('comment', 2, request.child_value('comment')) avatar_dict.replace_int_array('get_hair', 2, request.child_value('get_hair')) avatar_dict.replace_int_array('get_face', 2, request.child_value('get_face')) avatar_dict.replace_int_array('get_body', 2, request.child_value('get_body')) avatar_dict.replace_int_array('get_effect', 2, request.child_value('get_effect')) avatar_dict.replace_int_array('get_object', 2, request.child_value('get_object')) avatar_dict.replace_int_array('get_comment_over', 3, request.child_value('get_comment_over')) avatar_dict.replace_int_array('get_comment_under', 3, request.child_value('get_comment_under')) newprofile.replace_dict('avatar', avatar_dict) avatar_add_dict = newprofile.get_dict('avatar_add') avatar_add_node = request.child('avatar_add') if avatar_add_node is not None: avatar_add_dict.replace_int_array( 'get_hair', 2, avatar_add_node.child_value('get_hair')) avatar_add_dict.replace_int_array( 'get_face', 2, avatar_add_node.child_value('get_face')) avatar_add_dict.replace_int_array( 'get_body', 2, avatar_add_node.child_value('get_body')) avatar_add_dict.replace_int_array( 'get_effect', 2, avatar_add_node.child_value('get_effect')) avatar_add_dict.replace_int_array( 'get_object', 2, avatar_add_node.child_value('get_object')) avatar_add_dict.replace_int_array( 'get_comment_over', 2, avatar_add_node.child_value('get_comment_over')) avatar_add_dict.replace_int_array( 'get_comment_under', 2, avatar_add_node.child_value('get_comment_under')) avatar_add_dict.replace_int_array( 'new_hair', 2, avatar_add_node.child_value('new_hair')) avatar_add_dict.replace_int_array( 'new_face', 2, avatar_add_node.child_value('new_face')) avatar_add_dict.replace_int_array( 'new_body', 2, avatar_add_node.child_value('new_body')) avatar_add_dict.replace_int_array( 'new_effect', 2, avatar_add_node.child_value('new_effect')) avatar_add_dict.replace_int_array( 'new_object', 2, avatar_add_node.child_value('new_object')) avatar_add_dict.replace_int_array( 'new_comment_over', 2, avatar_add_node.child_value('new_comment_over')) avatar_add_dict.replace_int_array( 'new_comment_under', 2, avatar_add_node.child_value('new_comment_under')) newprofile.replace_dict('avatar_add', avatar_add_dict) # Keep track of play statistics self.update_play_statistics(userid) # Extract scores for node in request.children: if node.name == 'stage': songid = node.child_value('no') chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, }[node.child_value('sheet')] medal = (node.child_value('n_data') >> (chart * 4)) & 0x000F medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED, self.GAME_PLAY_MEDAL_STAR_FAILED: self.PLAY_MEDAL_STAR_FAILED, self.GAME_PLAY_MEDAL_CIRCLE_CLEARED: self.PLAY_MEDAL_CIRCLE_CLEARED, self.GAME_PLAY_MEDAL_DIAMOND_CLEARED: self.PLAY_MEDAL_DIAMOND_CLEARED, self.GAME_PLAY_MEDAL_STAR_CLEARED: self.PLAY_MEDAL_STAR_CLEARED, self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO: self.PLAY_MEDAL_CIRCLE_FULL_COMBO, self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO: self.PLAY_MEDAL_DIAMOND_FULL_COMBO, self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] points = node.child_value('score') self.update_score(userid, songid, chart, points, medal) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) data = request.child('data') # Grab player information player = data.child('player') # Grab last information. Lots of this will be filled in while grabbing scores last = newprofile.get_dict('last') last.replace_int('play_time', player.child_value('time_gameend')) last.replace_str('shopname', player.child_value('shopname')) last.replace_str('areaname', player.child_value('areaname')) # Grab player info for echoing back info = player.child('info') if info is not None: newprofile.replace_int('jubility', info.child_value('jubility')) newprofile.replace_int('jubility_yday', info.child_value('jubility_yday')) newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) newprofile.replace_int('save_cnt', info.child_value('save_cnt')) newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) newprofile.replace_int('ex_cnt', info.child_value('exc_cnt')) # Not a mistake, Jubeat is weird newprofile.replace_int('pf_cnt', info.child_value('pf_cnt')) newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) newprofile.replace_int('match_cnt', info.child_value('match_cnt')) newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) newprofile.replace_int('total_best_score', info.child_value('total_best_score')) newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) newprofile.replace_int('extra_point', info.child_value('extra_point')) newprofile.replace_bool('is_extra_played', info.child_value('is_extra_played')) last.replace_int('expert_option', info.child_value('expert_option')) last.replace_int('matching', info.child_value('matching')) last.replace_int('hazard', info.child_value('hazard')) last.replace_int('hard', info.child_value('hard')) # Grab unlock progress item = player.child('item') if item is not None: newprofile.replace_int_array('secret_list', 32, item.child_value('secret_list')) newprofile.replace_int_array('title_list', 96, item.child_value('title_list')) newprofile.replace_int('theme_list', item.child_value('theme_list')) newprofile.replace_int_array('marker_list', 2, item.child_value('marker_list')) newprofile.replace_int_array('parts_list', 96, item.child_value('parts_list')) newprofile.replace_int_array('secret_list_new', 32, item.child_value('secret_new')) newprofile.replace_int_array('title_list_new', 96, item.child_value('title_new')) newprofile.replace_int('theme_list_new', item.child_value('theme_new')) newprofile.replace_int_array('marker_list_new', 2, item.child_value('marker_new')) # Grab macchiato event macchiatodict = newprofile.get_dict('macchiato') macchiato = player.child('macchiato') if macchiato is not None: macchiatodict.replace_int('pack_id', macchiato.child_value('pack_id')) macchiatodict.replace_int('bean_num', macchiato.child_value('bean_num')) macchiatodict.replace_int('daily_milk_num', macchiato.child_value('daily_milk_num')) macchiatodict.replace_bool('is_received_daily_milk', macchiato.child_value('is_received_daily_milk')) macchiatodict.replace_bool('sub_menu_is_completed', macchiato.child_value('sub_menu_is_completed')) macchiatodict.replace_int('today_tune_cnt', macchiato.child_value('today_tune_cnt')) macchiatodict.replace_int_array('daily_milk_bonus', 9, macchiato.child_value('daily_milk_bonus')) macchiatodict.replace_int('compensation_milk', macchiato.child_value('compensation_milk')) macchiatodict.replace_int('match_cnt', macchiato.child_value('match_cnt')) macchiatodict.replace_int('used_bean', macchiato.child_value('used_bean')) macchiatodict.replace_int('used_milk', macchiato.child_value('used_milk')) macchiatodict.replace_int('daily_play_burst', macchiato.child_value('daily_play_burst')) newprofile.replace_dict('macchiato', macchiatodict) # Get timestamps for played songs timestamps: Dict[int, int] = {} history = player.child('history') if history is not None: for tune in history.children: if tune.name != 'tune': continue entry = int(tune.attribute('log_id')) ts = int(tune.child_value('timestamp') / 1000) timestamps[entry] = ts # Grab scores and save those result = data.child('result') if result is not None: for tune in result.children: if tune.name != 'tune': continue result = tune.child('player') last.replace_int('marker', tune.child_value('marker')) last.replace_int('title', tune.child_value('title')) last.replace_int('parts', tune.child_value('parts')) last.replace_int('theme', tune.child_value('theme')) last.replace_int('sort', tune.child_value('sort')) last.replace_int('category', tune.child_value('category')) last.replace_int('rank_sort', tune.child_value('rank_sort')) last.replace_int('combo_disp', tune.child_value('combo_disp')) songid = tune.child_value('music') entry = int(tune.attribute('id')) timestamp = timestamps.get(entry, Time.now()) chart = int(result.child('score').attribute('seq')) points = result.child_value('score') flags = int(result.child('score').attribute('clear')) combo = int(result.child('score').attribute('combo')) ghost = result.child_value('mbar') # Miscelaneous last data for echoing to profile get last.replace_int('music_id', songid) last.replace_int('seq_id', chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, self.GAME_FLAG_BIT_FULL_COMBO: self.PLAY_MEDAL_FULL_COMBO, self.GAME_FLAG_BIT_EXCELLENT: self.PLAY_MEDAL_EXCELLENT, self.GAME_FLAG_BIT_NEARLY_FULL_COMBO: self.PLAY_MEDAL_NEARLY_FULL_COMBO, self.GAME_FLAG_BIT_NEARLY_EXCELLENT: self.PLAY_MEDAL_NEARLY_EXCELLENT, } # Figure out the highest medal based on bits passed in medal = self.PLAY_MEDAL_FAILED for bit in mapping: if flags & bit > 0: medal = max(medal, mapping[bit]) self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost) # Grab the course results as well course = data.child('course') if course is not None: courseid = course.child_value('course_id') rating = { self.GAME_COURSE_RATING_FAILED: self.COURSE_RATING_FAILED, self.GAME_COURSE_RATING_BRONZE: self.COURSE_RATING_BRONZE, self.GAME_COURSE_RATING_SILVER: self.COURSE_RATING_SILVER, self.GAME_COURSE_RATING_GOLD: self.COURSE_RATING_GOLD, }[course.child_value('rating')] scores = [0] * 5 for music in course.children: if music.name != 'music': continue index = int(music.attribute('index')) scores[index] = music.child_value('score') # Save course itself self.save_course(userid, courseid, rating, scores) # Save the last course ID last.replace_int('last_course_id', courseid) # Save back last information gleaned from results newprofile.replace_dict('last', last) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: game_config = self.get_game_config() newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid'))) newprofile.replace_str('name', request.child_value('pdata/base/name')) newprofile.replace_int('lvl', request.child_value('pdata/base/lv')) newprofile.replace_int('exp', request.child_value('pdata/base/exp')) newprofile.replace_int('mg', request.child_value('pdata/base/mg')) newprofile.replace_int('ap', request.child_value('pdata/base/ap')) newprofile.replace_int('flag', request.child_value('pdata/base/flag')) customdict = newprofile.get_dict('custom') custom = request.child('pdata/custom') if custom: customdict.replace_int('bgm_m', custom.child_value('bgm_m')) customdict.replace_int('st_f', custom.child_value('st_f')) customdict.replace_int('st_bg', custom.child_value('st_bg')) customdict.replace_int('st_bg_b', custom.child_value('st_bg_b')) customdict.replace_int('eff_e', custom.child_value('eff_e')) customdict.replace_int('se_s', custom.child_value('se_s')) customdict.replace_int('se_s_v', custom.child_value('se_s_v')) newprofile.replace_dict('custom', customdict) # 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') 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, 'item_{}'.format(item_type), {}, ) # Grab any new records set during this play session. Reflec Beat original only sends # the top record back for songs that were played at least once during the session. # Note that it sends the top record, so if you play the song twice, it will return # only one record. Also, if you get a lower score than a previous try, it will return # the previous try. So, we must also look at the battle log for the actual play scores, # and combine the data if we can. savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {} songplays = request.child('pdata/record') if songplays: for child in songplays.children: if child.name != 'rec': continue songid = child.child_value('mid') chart = child.child_value('ng') # These don't get sent with the battle logs, so we try to construct # the values here. if songid not in savedrecords: savedrecords[songid] = {} savedrecords[songid][chart] = { 'achievement_rate': child.child_value('ar') * 10, 'points': child.child_value('bs'), 'combo': child.child_value('mc'), 'miss_count': child.child_value('bmc'), 'win': child.child_value('win'), 'lose': child.child_value('lose'), 'draw': child.child_value('draw'), } # Now, see the actual battles that were played. If we can, unify the data with a record. # We only do that when the record achievement rate and score matches the battle achievement # rate and score, so we know for a fact that that record was generated by this battle. battlelogs = request.child('pdata/blog') if battlelogs: for child in battlelogs.children: if child.name != 'log': continue songid = child.child_value('mid') chart = child.child_value('ng') clear_type = child.child_value('myself/ct') achievement_rate = child.child_value('myself/ar') * 10 points = child.child_value('myself/s') clear_type, combo_type = self.__game_to_db_clear_type( clear_type, achievement_rate) combo = None miss_count = -1 stats = None if songid in savedrecords: if chart in savedrecords[songid]: data = savedrecords[songid][chart] if (data['achievement_rate'] == achievement_rate and data['points'] == points): # This is the same record! Use the stats from it to update our # internal representation. combo = data['combo'] miss_count = data['miss_count'] stats = { 'win': data['win'], 'lose': data['lose'], 'draw': data['draw'], } self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, combo=combo, stats=stats, ) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) # 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
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: # For some reason, Pop'n 20 sends us two profile saves, one with 'not done yet' # so we only want to process the done yet node. The 'not gameover' save has # jubeat collabo stuff set in it, but we don't use that so it doesn't matter. if request.child_value('is_not_gameover') == 1: return oldprofile newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('option', request.child_value('option')) newprofile.replace_int('chara', request.child_value('chara')) newprofile.replace_int('mode', request.child_value('mode')) newprofile.replace_int('button', request.child_value('button')) newprofile.replace_int('music', request.child_value('music')) newprofile.replace_int('sheet', request.child_value('sheet')) newprofile.replace_int('last_play_flag', request.child_value('last_play_flag')) newprofile.replace_int('category', request.child_value('category')) newprofile.replace_int('sub_category', request.child_value('sub_category')) newprofile.replace_int('chara_category', request.child_value('chara_category')) newprofile.replace_int('medal_and_friend', request.child_value('medal_and_friend')) newprofile.replace_int('ep', request.child_value('ep')) newprofile.replace_int_array('sp_color_flg', 2, request.child_value('sp_color_flg')) newprofile.replace_int('read_news', request.child_value('read_news')) newprofile.replace_int('consecutive_days_coupon', request.child_value('consecutive_days_coupon')) newprofile.replace_int('tutorial', request.child_value('tutorial')) newprofile.replace_int('music_open_pt', request.child_value('music_open_pt')) newprofile.replace_int('collabo', request.child_value('collabo')) sp_node = request.child('sp_data') if sp_node is not None: newprofile.replace_int('sp', sp_node.child_value('sp')) reflec_node = request.child('reflec_data') if reflec_node is not None: newprofile.replace_int_array('reflec', 2, reflec_node.child_value('reflec')) # Keep track of play statistics self.update_play_statistics(userid) # Extract player card stuff player_card_dict = newprofile.get_dict('player_card') player_card_dict.replace_int_array('title', 2, request.child_value('title')) player_card_dict.replace_int('frame', request.child_value('frame')) player_card_dict.replace_int('base', request.child_value('base')) player_card_dict.replace_int_array('seal', 2, request.child_value('seal')) player_card_dict.replace_int_array('get_title', 4, request.child_value('get_title')) player_card_dict.replace_int('get_frame', request.child_value('get_frame')) player_card_dict.replace_int('get_base', request.child_value('get_base')) player_card_dict.replace_int_array('get_seal', 2, request.child_value('get_seal')) player_card_ex = request.child('player_card_ex') if player_card_ex is not None: player_card_dict.replace_int( 'get_title_ex', player_card_ex.child_value('get_title_ex')) player_card_dict.replace_int( 'get_frame_ex', player_card_ex.child_value('get_frame_ex')) player_card_dict.replace_int( 'get_base_ex', player_card_ex.child_value('get_base_ex')) player_card_dict.replace_int( 'get_seal_ex', player_card_ex.child_value('get_seal_ex')) newprofile.replace_dict('player_card', player_card_dict) # Extract navigate stuff navigate_dict = newprofile.get_dict('navigate') navigate = request.child('navigate') if navigate is not None: navigate_dict.replace_int('genre', navigate.child_value('genre')) navigate_dict.replace_int('image', navigate.child_value('image')) navigate_dict.replace_int('level', navigate.child_value('level')) navigate_dict.replace_int('ojama', navigate.child_value('ojama')) navigate_dict.replace_int('limit_num', navigate.child_value('limit_num')) navigate_dict.replace_int('button', navigate.child_value('button')) navigate_dict.replace_int('life', navigate.child_value('life')) navigate_dict.replace_int('progress', navigate.child_value('progress')) newprofile.replace_dict('navigate', navigate_dict) # Extract scores for node in request.children: if node.name == 'stage': songid = node.child_value('no') chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, }[node.child_value('sheet')] medal = (node.child_value('n_data') >> (chart * 4)) & 0x000F medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED, self.GAME_PLAY_MEDAL_STAR_FAILED: self.PLAY_MEDAL_STAR_FAILED, self.GAME_PLAY_MEDAL_CIRCLE_CLEARED: self.PLAY_MEDAL_CIRCLE_CLEARED, self.GAME_PLAY_MEDAL_DIAMOND_CLEARED: self.PLAY_MEDAL_DIAMOND_CLEARED, self.GAME_PLAY_MEDAL_STAR_CLEARED: self.PLAY_MEDAL_STAR_CLEARED, self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO: self.PLAY_MEDAL_CIRCLE_FULL_COMBO, self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO: self.PLAY_MEDAL_DIAMOND_FULL_COMBO, self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] points = node.child_value('score') self.update_score(userid, songid, chart, points, medal) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type last = request.child('last') lastdict = newprofile.get_dict('last') mode = int(last.attribute('mode')) style = int(last.attribute('style')) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings title = request.child('title') title_gr = request.child('title_gr') titledict = newprofile.get_dict('title') title_grdict = newprofile.get_dict('title_gr') # Groove radar level ups gr = request.child('gr') # Set the correct values depending on if we're single or double play if is_dp: play_stats.increment_int('double_plays') if gr is not None: newprofile.replace_int_array( 'gr_d', 5, [ intish(gr.attribute('gr1')), intish(gr.attribute('gr2')), intish(gr.attribute('gr3')), intish(gr.attribute('gr4')), intish(gr.attribute('gr5')), ], ) if title is not None: titledict.replace_int('d', title.value) newprofile.replace_dict('title', titledict) if title_gr is not None: title_grdict.replace_int('d', title.value) newprofile.replace_dict('title_gr', title_grdict) else: play_stats.increment_int('single_plays') if gr is not None: newprofile.replace_int_array( 'gr_s', 5, [ intish(gr.attribute('gr1')), intish(gr.attribute('gr2')), intish(gr.attribute('gr3')), intish(gr.attribute('gr4')), intish(gr.attribute('gr5')), ], ) if title is not None: titledict.replace_int('s', title.value) newprofile.replace_dict('title', titledict) if title_gr is not None: title_grdict.replace_int('s', title.value) newprofile.replace_dict('title_gr', title_grdict) play_stats.increment_int(f'cnt_m{mode}') # Result stars result_star = request.child('result_star') if result_star is not None: newprofile.replace_int_array( 'result_stars', 9, [ intish(result_star.attribute('slot1')), intish(result_star.attribute('slot2')), intish(result_star.attribute('slot3')), intish(result_star.attribute('slot4')), intish(result_star.attribute('slot5')), intish(result_star.attribute('slot6')), intish(result_star.attribute('slot7')), intish(result_star.attribute('slot8')), intish(result_star.attribute('slot9')), ], ) # Target stuff target = request.child('target') if target is not None: newprofile.replace_int('target_flag', intish(target.attribute('flag'))) newprofile.replace_int('target_setnum', intish(target.attribute('setnum'))) # Update last attributes lastdict.replace_int('rival1', intish(last.attribute('rival1'))) lastdict.replace_int('rival2', intish(last.attribute('rival2'))) lastdict.replace_int('rival3', intish(last.attribute('rival3'))) lastdict.replace_int('style', intish(last.attribute('style'))) lastdict.replace_int('mode', intish(last.attribute('mode'))) lastdict.replace_int('cate', intish(last.attribute('cate'))) lastdict.replace_int('sort', intish(last.attribute('sort'))) lastdict.replace_int('mid', intish(last.attribute('mid'))) lastdict.replace_int('mtype', intish(last.attribute('mtype'))) lastdict.replace_int('cid', intish(last.attribute('cid'))) lastdict.replace_int('ctype', intish(last.attribute('ctype'))) lastdict.replace_int('sid', intish(last.attribute('sid'))) newprofile.replace_dict('last', lastdict) # Grab character options chara = request.child('chara') if chara is not None: newprofile.replace_int('chara', intish(chara.attribute('my'))) chara_opt = request.child('chara_opt') if chara_opt is not None: # A bug in old versions of AVS returns the wrong number for set newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96]) # Options opt = request.child('opt') if opt is not None: # A bug in old versions of AVS returns the wrong number for set newprofile.replace_int_array('opt', 16, opt.value[:16]) # Experience and stars exp = request.child_value('exp') if exp is not None: play_stats.replace_int('exp', play_stats.get_int('exp') + exp) star = request.child_value('star') if star is not None: newprofile.replace_int('star', newprofile.get_int('star') + star) star_c = request.child_value('star_c') if star_c is not None: newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) # Update game flags for child in request.children: if child.name != 'flag': continue try: value = int(child.attribute('data')) offset = int(child.attribute('no')) except ValueError: continue flags = newprofile.get_int_array('flag', 256, [1] * 256) if offset < 0 or offset >= len(flags): continue flags[offset] = value newprofile.replace_int_array('flag', 256, flags) # Workout mode support newweight = -1 oldweight = newprofile.get_int('weight') for child in request.children: if child.name != 'weight': continue newweight = child.value if newweight < 0: newweight = oldweight # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile if 'weight' in newprofile: del newprofile['weight'] else: # Weight has been set or previously retrieved, we should save calories newprofile.replace_int('weight', newweight) total = 0 for child in request.children: if child.name != 'calory': continue total += child.value self.data.local.user.put_time_based_achievement( self.game, self.version, userid, 0, 'workout', { 'calories': total, 'weight': newweight, }, ) # Look up old friends oldfriends: List[Optional[UserID]] = [None] * 10 links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: if link.type[:7] != 'friend_': continue pos = int(link.type[7:]) oldfriends[pos] = link.other_userid # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: if child.name != 'friend': continue code = int(child.attribute('code')) pos = int(child.attribute('pos')) if pos >= 0 and pos < 10: if code == 0: # We cleared this friend newfriends[pos] = None else: # Try looking up the userid newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) # Diff the set of links to determine updates for i in range(10): if newfriends[i] == oldfriends[i]: continue if newfriends[i] is None: # Kill the rival in this location self.data.local.user.destroy_link( self.game, self.version, userid, f'friend_{i}', oldfriends[i], ) elif oldfriends[i] is None: # Add rival in this location self.data.local.user.put_link( self.game, self.version, userid, f'friend_{i}', newfriends[i], {}, ) else: # Changed the rival here, kill the old one, add the new one self.data.local.user.destroy_link( self.game, self.version, userid, f'friend_{i}', oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, f'friend_{i}', newfriends[i], {}, ) # Play area counter shop_area = int(request.attribute('shop_area')) if shop_area >= 0 and shop_area < 55: areas = newprofile.get_int_array('play_area', 55) areas[shop_area] = areas[shop_area] + 1 newprofile.replace_int_array('play_area', 55, areas) # Keep track of play statistics self.update_play_statistics(userid, play_stats) return newprofile
def handle_game_save_m_request(self, request: Node) -> Node: refid = request.attribute('refid') songid = int(request.attribute('mid')) chart = self.game_to_db_chart(int(request.attribute('mtype'))) userid = self.data.remote.user.from_refid(self.game, self.version, refid) # Calculate statistics data = request.child('data') playmode = int(data.attribute('playmode')) if playmode == self.GAME_PLAY_MODE_2NDMIX: if userid is not None: # These are special cased and treated like achievements points = int(data.attribute('score_2nd')) combo = int(data.attribute('combo_2nd')) rank = int(data.attribute('rank_2nd')) # Grab the old 2nd mix score existingscore = self.data.local.user.get_achievement( self.game, self.music_version, userid, (songid * 100) + chart, '2ndmix', ) if existingscore is not None: highscore = points > existingscore.get_int('points') plays = existingscore.get_int('plays', 0) + 1 points = max(points, existingscore.get_int('points')) if not highscore: combo = existingscore.get_int('combo', combo) rank = existingscore.get_int('rank', rank) else: plays = 1 self.data.local.user.put_achievement( self.game, self.music_version, userid, (songid * 100) + chart, '2ndmix', { 'points': points, 'combo': combo, 'rank': rank, 'plays': plays, }, ) else: points = int(data.attribute('score')) combo = int(data.attribute('combo')) rank = self.game_to_db_rank(int(data.attribute('rank'))) if int(data.attribute('full')) == 0: halo = self.HALO_NONE elif int(data.attribute('perf')) == 0: halo = self.HALO_GREAT_FULL_COMBO elif points < 1000000: halo = self.HALO_PERFECT_FULL_COMBO else: halo = self.HALO_MARVELOUS_FULL_COMBO trace = request.child_value('trace') # Save the score, regardless of whether we have a refid. If we save # an anonymous score, it only goes into the DB to count against the # number of plays for that song/chart. self.update_score( userid, songid, chart, points, rank, halo, combo, trace, ) # No response needed game = Node.void('game') return game
def handle_gametop_get_course_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') extid = player.child_value('jid') gametop = Node.void('gametop') data = Node.void('data') gametop.add_child(data) # Course list available course_list = Node.void('course_list') data.add_child(course_list) validcourses: List[int] = [] for course in self.get_all_courses(): coursenode = Node.void('course') course_list.add_child(coursenode) # Basic course info if course['id'] in validcourses: raise Exception('Cannot have same course ID specified twice!') validcourses.append(course['id']) coursenode.add_child(Node.s32('id', course['id'])) coursenode.add_child(Node.string('name', course['name'])) coursenode.add_child(Node.u8('level', course['level'])) # Translate internal to game def translate_req(internal_req: int) -> int: return { self.COURSE_REQUIREMENT_SCORE: self.GAME_COURSE_REQUIREMENT_SCORE, self.COURSE_REQUIREMENT_FULL_COMBO: self.GAME_COURSE_REQUIREMENT_FULL_COMBO, self.COURSE_REQUIREMENT_PERFECT_PERCENT: self.GAME_COURSE_REQUIREMENT_PERFECT_PERCENT, }.get(internal_req, 0) # Course bronze/silver/gold rules ids = [0] * 3 bronze_values = [0] * 3 silver_values = [0] * 3 gold_values = [0] * 3 slot = 0 for req in course['requirements']: req_values = course['requirements'][req] ids[slot] = translate_req(req) bronze_values[slot] = req_values[0] silver_values[slot] = req_values[1] gold_values[slot] = req_values[2] slot = slot + 1 norma = Node.void('norma') coursenode.add_child(norma) norma.add_child(Node.s32_array('norma_id', ids)) norma.add_child(Node.s32_array('bronze_value', bronze_values)) norma.add_child(Node.s32_array('silver_value', silver_values)) norma.add_child(Node.s32_array('gold_value', gold_values)) # Music list for course music_index = 0 music_list = Node.void('music_list') coursenode.add_child(music_list) for entry in course['music']: music = Node.void('music') music.set_attribute('index', str(music_index)) music_list.add_child(music) music.add_child(Node.s32('music_id', entry[0])) music.add_child(Node.u8('seq', entry[1])) music_index = music_index + 1 # Look up profile so we can load the last course played userid = self.data.remote.user.from_extid(self.game, self.version, extid) profile = self.get_profile(userid) if profile is None: profile = ValidatedDict() # Player scores for courses player_list = Node.void('player_list') data.add_child(player_list) player = Node.void('player') player_list.add_child(player) player.add_child(Node.s32('jid', extid)) result_list = Node.void('result_list') player.add_child(result_list) playercourses = self.get_courses(userid) for courseid in playercourses: if courseid not in validcourses: continue rating = { self.COURSE_RATING_FAILED: self.GAME_COURSE_RATING_FAILED, self.COURSE_RATING_BRONZE: self.GAME_COURSE_RATING_BRONZE, self.COURSE_RATING_SILVER: self.GAME_COURSE_RATING_SILVER, self.COURSE_RATING_GOLD: self.GAME_COURSE_RATING_GOLD, }[playercourses[courseid]['rating']] scores = playercourses[courseid]['scores'] result = Node.void('result') result_list.add_child(result) result.add_child(Node.s32('id', courseid)) result.add_child(Node.u8('rating', rating)) result.add_child(Node.s32_array('score', scores)) # Last course ID last_course_id = Node.s32('last_course_id', profile.get_dict('last').get_int('last_course_id', -1)) data.add_child(last_course_id) return gametop
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) data = request.child('data') # Grab player information player = data.child('player') # Grab last information. Lots of this will be filled in while grabbing scores last = newprofile.get_dict('last') last.replace_int('play_time', player.child_value('time_gameend')) last.replace_str('shopname', player.child_value('shopname')) last.replace_str('areaname', player.child_value('areaname')) # Grab player info for echoing back info = player.child('info') if info is not None: newprofile.replace_int('jubility', info.child_value('jubility')) newprofile.replace_int('jubility_yday', info.child_value('jubility_yday')) newprofile.replace_int('tune_cnt', info.child_value('tune_cnt')) newprofile.replace_int('save_cnt', info.child_value('save_cnt')) newprofile.replace_int('saved_cnt', info.child_value('saved_cnt')) newprofile.replace_int('fc_cnt', info.child_value('fc_cnt')) newprofile.replace_int('ex_cnt', info.child_value('exc_cnt')) # Not a mistake, Jubeat is weird newprofile.replace_int('pf_cnt', info.child_value('pf_cnt')) newprofile.replace_int('clear_cnt', info.child_value('clear_cnt')) newprofile.replace_int('match_cnt', info.child_value('match_cnt')) newprofile.replace_int('beat_cnt', info.child_value('beat_cnt')) newprofile.replace_int('total_best_score', info.child_value('total_best_score')) newprofile.replace_int('mynews_cnt', info.child_value('mynews_cnt')) # Grab unlock progress item = player.child('item') if item is not None: newprofile.replace_int_array('secret_list', 32, item.child_value('secret_list')) newprofile.replace_int_array('title_list', 96, item.child_value('title_list')) newprofile.replace_int('theme_list', item.child_value('theme_list')) newprofile.replace_int_array('marker_list', 2, item.child_value('marker_list')) newprofile.replace_int_array('parts_list', 96, item.child_value('parts_list')) newprofile.replace_int_array('secret_list_new', 32, item.child_value('secret_new')) newprofile.replace_int_array('title_list_new', 96, item.child_value('title_new')) newprofile.replace_int('theme_list_new', item.child_value('theme_new')) newprofile.replace_int_array('marker_list_new', 2, item.child_value('marker_new')) # Grab bistro progress bistro = player.child('bistro') if bistro is not None: newprofile.replace_int('bistro_carry_over', bistro.child_value('carry_over')) chefdata = newprofile.get_dict('chef') chef = bistro.child('chef') if chef is not None: chefdata.replace_int('id', chef.child_value('id')) chefdata.replace_int('ability', chef.child_value('ability')) chefdata.replace_int('remain', chef.child_value('remain')) chefdata.replace_int('rate', chef.child_value('rate')) newprofile.replace_dict('chef', chefdata) for route in bistro.children: if route.name != 'route': continue gourmates = route.child('gourmates') routeid = gourmates.child_value('id') satisfaction = gourmates.child_value('satisfaction_s32') self.data.local.user.put_achievement( self.game, self.version, userid, routeid, 'route', { 'satisfaction': satisfaction, }, ) # Get timestamps for played songs timestamps: Dict[int, int] = {} history = player.child('history') if history is not None: for tune in history.children: if tune.name != 'tune': continue entry = int(tune.attribute('log_id')) ts = int(tune.child_value('timestamp') / 1000) timestamps[entry] = ts # Grab scores and save those result = data.child('result') if result is not None: for tune in result.children: if tune.name != 'tune': continue result = tune.child('player') last.replace_int('marker', tune.child_value('marker')) last.replace_int('title', tune.child_value('title')) last.replace_int('parts', tune.child_value('parts')) last.replace_int('theme', tune.child_value('theme')) last.replace_int('sort', tune.child_value('sort')) last.replace_int('category', tune.child_value('category')) last.replace_int('rank_sort', tune.child_value('rank_sort')) last.replace_int('combo_disp', tune.child_value('combo_disp')) songid = tune.child_value('music') entry = int(tune.attribute('id')) timestamp = timestamps.get(entry, Time.now()) chart = int(result.child('score').attribute('seq')) points = result.child_value('score') flags = int(result.child('score').attribute('clear')) combo = int(result.child('score').attribute('combo')) ghost = result.child_value('mbar') # Miscelaneous last data for echoing to profile get last.replace_int('music_id', songid) last.replace_int('seq_id', chart) mapping = { self.GAME_FLAG_BIT_CLEARED: self.PLAY_MEDAL_CLEARED, self.GAME_FLAG_BIT_FULL_COMBO: self.PLAY_MEDAL_FULL_COMBO, self.GAME_FLAG_BIT_EXCELLENT: self.PLAY_MEDAL_EXCELLENT, self.GAME_FLAG_BIT_NEARLY_FULL_COMBO: self.PLAY_MEDAL_NEARLY_FULL_COMBO, self.GAME_FLAG_BIT_NEARLY_EXCELLENT: self.PLAY_MEDAL_NEARLY_EXCELLENT, } # Figure out the highest medal based on bits passed in medal = self.PLAY_MEDAL_FAILED for bit in mapping: if flags & bit > 0: medal = max(medal, mapping[bit]) self.update_score(userid, timestamp, songid, chart, points, medal, combo, ghost) # Save back last information gleaned from results newprofile.replace_dict('last', last) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) # Set that we've seen this profile newprofile.replace_bool('welcom_pack', True) account = request.child('account') if account is not None: newprofile.replace_int('tutorial', account.child_value('tutorial')) newprofile.replace_int('read_news', account.child_value('read_news')) newprofile.replace_int('area_id', account.child_value('area_id')) newprofile.replace_int('lumina', account.child_value('lumina')) newprofile.replace_int_array('medal_set', 4, account.child_value('medal_set')) newprofile.replace_int_array('nice', 30, account.child_value('nice')) newprofile.replace_int_array('favorite_chara', 20, account.child_value('favorite_chara')) newprofile.replace_int_array('special_area', 8, account.child_value('special_area')) newprofile.replace_int_array( 'chocolate_charalist', 5, account.child_value('chocolate_charalist')) newprofile.replace_int_array( 'teacher_setting', 10, account.child_value('teacher_setting')) info = request.child('info') if info is not None: newprofile.replace_int('ep', info.child_value('ep')) config = request.child('config') if config is not None: newprofile.replace_int('mode', config.child_value('mode')) newprofile.replace_int('chara', config.child_value('chara')) newprofile.replace_int('music', config.child_value('music')) newprofile.replace_int('sheet', config.child_value('sheet')) newprofile.replace_int('category', config.child_value('category')) newprofile.replace_int('sub_category', config.child_value('sub_category')) newprofile.replace_int('chara_category', config.child_value('chara_category')) newprofile.replace_int('course_id', config.child_value('course_id')) newprofile.replace_int('course_folder', config.child_value('course_folder')) newprofile.replace_int('ms_banner_disp', config.child_value('ms_banner_disp')) newprofile.replace_int('ms_down_info', config.child_value('ms_down_info')) newprofile.replace_int('ms_side_info', config.child_value('ms_side_info')) newprofile.replace_int('ms_raise_type', config.child_value('ms_raise_type')) newprofile.replace_int('ms_rnd_type', config.child_value('ms_rnd_type')) option_dict = newprofile.get_dict('option') option = request.child('option') if option is not None: option_dict.replace_int('hispeed', option.child_value('hispeed')) option_dict.replace_int('popkun', option.child_value('popkun')) option_dict.replace_bool('hidden', option.child_value('hidden')) option_dict.replace_int('hidden_rate', option.child_value('hidden_rate')) option_dict.replace_bool('sudden', option.child_value('sudden')) option_dict.replace_int('sudden_rate', option.child_value('sudden_rate')) option_dict.replace_int('randmir', option.child_value('randmir')) option_dict.replace_int('gauge_type', option.child_value('gauge_type')) option_dict.replace_int('ojama_0', option.child_value('ojama_0')) option_dict.replace_int('ojama_1', option.child_value('ojama_1')) option_dict.replace_bool('forever_0', option.child_value('forever_0')) option_dict.replace_bool('forever_1', option.child_value('forever_1')) option_dict.replace_bool('full_setting', option.child_value('full_setting')) option_dict.replace_int('judge', option.child_value('judge')) newprofile.replace_dict('option', option_dict) customize = request.child('customize') if customize is not None: newprofile.replace_int('effect_left', customize.child_value('effect_left')) newprofile.replace_int('effect_center', customize.child_value('effect_center')) newprofile.replace_int('effect_right', customize.child_value('effect_right')) newprofile.replace_int('hukidashi', customize.child_value('hukidashi')) newprofile.replace_int('comment_1', customize.child_value('comment_1')) newprofile.replace_int('comment_2', customize.child_value('comment_2')) event = request.child('event') if event is not None: newprofile.replace_int('event_enemy_medal', event.child_value('enemy_medal')) newprofile.replace_int('event_hp', event.child_value('hp')) stamp = request.child('stamp') if stamp is not None: newprofile.replace_int('stamp_id', stamp.child_value('stamp_id')) newprofile.replace_int('stamp_cnt', stamp.child_value('cnt')) # Extract achievements for node in request.children: if node.name == 'item': itemid = node.child_value('id') itemtype = node.child_value('type') param = node.child_value('param') is_new = node.child_value('is_new') self.data.local.user.put_achievement( self.game, self.version, userid, itemid, f'item_{itemtype}', { 'param': param, 'is_new': is_new, }, ) elif node.name == 'chara_param': charaid = node.child_value('chara_id') friendship = node.child_value('friendship') self.data.local.user.put_achievement( self.game, self.version, userid, charaid, 'chara', { 'friendship': friendship, }, ) elif node.name == 'medal': medalid = node.child_value('medal_id') level = node.child_value('level') exp = node.child_value('exp') set_count = node.child_value('set_count') get_count = node.child_value('get_count') self.data.local.user.put_achievement( self.game, self.version, userid, medalid, 'medal', { 'level': level, 'exp': exp, 'set_count': set_count, 'get_count': get_count, }, ) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: game_config = self.get_game_config() newprofile = copy.deepcopy(oldprofile) # Save base player profile info 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('mg', request.child_value('pdata/base/mg')) newprofile.replace_int('ap', request.child_value('pdata/base/ap')) newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) newprofile.replace_int('money', request.child_value('pdata/base/money')) newprofile.replace_int('class', request.child_value('pdata/base/class')) newprofile.replace_int('class_ar', request.child_value('pdata/base/class_ar')) newprofile.replace_int('skill_point', request.child_value('pdata/base/skill_point')) newprofile.replace_int('mgid', request.child_value('pdata/minigame/mgid')) newprofile.replace_int('mgsc', request.child_value('pdata/minigame/sc')) newprofile.replace_int_array( 'favorites', 30, request.child_value('pdata/mylist/list/mlst')) # Save player config 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('custom_folder_work', config.child_value('custom_folder_work')) configdict.replace_int('folder_type', config.child_value('folder_type')) 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) # Save player custom settings 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_gr_gauge_type', custom.child_value('st_gr_gauge_type')) customdict.replace_int('voice_message_set', custom.child_value('voice_message_set')) customdict.replace_int('same_time_note_disp', custom.child_value('same_time_note_disp')) customdict.replace_int('st_score_disp_type', custom.child_value('st_score_disp_type')) customdict.replace_int('st_bonus_type', custom.child_value('st_bonus_type')) customdict.replace_int('st_rivalnote_type', custom.child_value('st_rivalnote_type')) customdict.replace_int('st_topassist_type', custom.child_value('st_topassist_type')) customdict.replace_int('high_speed', custom.child_value('high_speed')) customdict.replace_int('st_hazard', custom.child_value('st_hazard')) customdict.replace_int('st_clr_cond', custom.child_value('st_clr_cond')) customdict.replace_int('voice_message_volume', custom.child_value('voice_message_volume')) newprofile.replace_dict('custom', customdict) # Save player parameter info params = request.child('pdata/player_param') if params: for child in params.children: if child.name != 'item': continue item_type = child.child_value('type') bank = child.child_value('bank') data = child.child_value('data') while len(data) < 256: data.append(0) self.data.local.user.put_achievement( self.game, self.version, userid, bank, f'player_param_{item_type}', { 'data': data, }, ) # Save player episode info episode = request.child('pdata/episode') if episode: for child in episode.children: if child.name != 'info': continue # I assume this is copypasta, but I want to be sure extid = child.child_value('user_id') if extid != newprofile.get_int('extid'): raise Exception( f'Unexpected user ID, got {extid} expecting {newprofile.get_int("extid")}' ) episode_type = child.child_value('type') episode_value0 = child.child_value('value0') episode_value1 = child.child_value('value1') episode_text = child.child_value('text') episode_time = child.child_value('time') self.data.local.user.put_achievement( self.game, self.version, userid, episode_type, 'episode', { 'value0': episode_value0, 'value1': episode_value1, 'text': episode_text, 'time': episode_time, }, ) # Save released info 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') time = child.child_value('insert_time') or Time.now() 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, 'time': time, }, ) # Save announce info announce = request.child('pdata/announce') if announce: for child in announce.children: if child.name != 'info': continue announce_id = child.child_value('id') announce_type = child.child_value('type') param = child.child_value('param') need = child.child_value('bneedannounce') self.data.local.user.put_achievement( self.game, self.version, userid, announce_id, f'announcement_{announce_type}', { 'param': param, 'need': need, }, ) # 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') param = child.child_value('param') miss_count = child.child_value('jt_ms') k_flag = child.child_value('k_flag') # Param is some random bits along with the combo type combo_type = param & 0x3 param = param ^ combo_type clear_type = self._game_to_db_clear_type(clear_type) combo_type = self._game_to_db_combo_type( combo_type, miss_count) self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, param=param, kflag=k_flag, ) # Grab any new rivals added during this play session rivalnode = request.child('pdata/rival') if rivalnode: for child in rivalnode.children: if child.name != 'r': continue extid = child.child_value('id') other_userid = self.data.remote.user.from_extid( self.game, self.version, extid) if other_userid is None: continue self.data.local.user.put_link( self.game, self.version, userid, 'rival', other_userid, {}, ) # Save player dojo dojo = request.child('pdata/dojo') if dojo: dojoid = dojo.child_value('class') clear_type = dojo.child_value('clear_type') ar = dojo.child_value('t_ar') score = dojo.child_value('t_score') # Figure out timestamp stuff data = self.data.local.user.get_achievement( self.game, self.version, userid, dojoid, 'dojo', ) or ValidatedDict() if ar >= data.get_int('ar'): # We set a new achievement rate, keep the new values record_time = Time.now() else: # We didn't, keep the old values for achievement rate, but # override score and clear_type only if they were better. record_time = data.get_int('record_timestamp') ar = data.get_int('ar') score = max(score, data.get_int('score')) clear_type = max(clear_type, data.get_int('clear_type')) play_time = Time.now() plays = data.get_int('plays') + 1 self.data.local.user.put_achievement( self.game, self.version, userid, dojoid, 'dojo', { 'clear_type': clear_type, 'ar': ar, 'score': score, 'plays': plays, 'play_timestamp': play_time, 'record_timestamp': record_time, }, ) # Save yurukome stuff yurukome_list = request.child('pdata/yurukome_list') if yurukome_list: for child in yurukome_list.children: if child.name != 'yurukome': continue yurukome_id = child.child_value('yurukome_id') self.data.local.user.put_achievement( self.game, self.version, userid, yurukome_id, 'yurukome', {}, ) # Save mycourse stuff mycoursedict = newprofile.get_dict('mycourse') mycourse = request.child('pdata/mycourse') if mycourse: # Only replace course if it was a new record score-wise. score_1 = mycourse.child_value('score_1') score_2 = mycourse.child_value('score_2') score_3 = mycourse.child_value('score_3') score_4 = mycourse.child_value('score_4') total = 0 for score in [score_1, score_2, score_3, score_4]: if score is not None and score >= 0: total = total + score oldtotal = (mycoursedict.get_int('score_1', 0) + mycoursedict.get_int('score_2', 0) + mycoursedict.get_int('score_3', 0) + mycoursedict.get_int('score_4', 0)) if total >= oldtotal: mycoursedict.replace_int('music_id_1', mycourse.child_value('music_id_1')) mycoursedict.replace_int('note_grade_1', mycourse.child_value('note_grade_1')) mycoursedict.replace_int('score_1', score_1) mycoursedict.replace_int('music_id_2', mycourse.child_value('music_id_2')) mycoursedict.replace_int('note_grade_2', mycourse.child_value('note_grade_2')) mycoursedict.replace_int('score_2', score_2) mycoursedict.replace_int('music_id_3', mycourse.child_value('music_id_3')) mycoursedict.replace_int('note_grade_3', mycourse.child_value('note_grade_3')) mycoursedict.replace_int('score_3', score_3) mycoursedict.replace_int('music_id_4', mycourse.child_value('music_id_4')) mycoursedict.replace_int('note_grade_4', mycourse.child_value('note_grade_4')) mycoursedict.replace_int('score_4', score_4) mycoursedict.replace_int('insert_time', Time.now()) newprofile.replace_dict('mycourse', mycoursedict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) account = request.child('account') if account is not None: newprofile.replace_int('tutorial', account.child_value('tutorial')) newprofile.replace_int('read_news', account.child_value('read_news')) info = request.child('info') if info is not None: newprofile.replace_int('ep', info.child_value('ep')) newprofile.replace_int('ap', info.child_value('ap')) config = request.child('config') if config is not None: newprofile.replace_int('mode', config.child_value('mode')) newprofile.replace_int('chara', config.child_value('chara')) newprofile.replace_int('music', config.child_value('music')) newprofile.replace_int('sheet', config.child_value('sheet')) newprofile.replace_int('category', config.child_value('category')) newprofile.replace_int('sub_category', config.child_value('sub_category')) newprofile.replace_int('chara_category', config.child_value('chara_category')) newprofile.replace_int('story_id', config.child_value('story_id')) newprofile.replace_int('course_id', config.child_value('course_id')) newprofile.replace_int('course_folder', config.child_value('course_folder')) newprofile.replace_int('story_folder', config.child_value('story_folder')) newprofile.replace_int('ms_banner_disp', config.child_value('ms_banner_disp')) newprofile.replace_int('ms_down_info', config.child_value('ms_down_info')) newprofile.replace_int('ms_side_info', config.child_value('ms_side_info')) newprofile.replace_int('ms_raise_type', config.child_value('ms_raise_type')) newprofile.replace_int('ms_rnd_type', config.child_value('ms_rnd_type')) option_dict = newprofile.get_dict('option') option = request.child('option') if option is not None: option_dict.replace_int('hispeed', option.child_value('hispeed')) option_dict.replace_int('popkun', option.child_value('popkun')) option_dict.replace_bool('hidden', option.child_value('hidden')) option_dict.replace_bool('sudden', option.child_value('sudden')) option_dict.replace_int('hidden_rate', option.child_value('hidden_rate')) option_dict.replace_int('sudden_rate', option.child_value('sudden_rate')) option_dict.replace_int('randmir', option.child_value('randmir')) option_dict.replace_int('gauge_type', option.child_value('gauge_type')) option_dict.replace_int('ojama_0', option.child_value('ojama_0')) option_dict.replace_int('ojama_1', option.child_value('ojama_1')) option_dict.replace_bool('forever_0', option.child_value('forever_0')) option_dict.replace_bool('forever_1', option.child_value('forever_1')) option_dict.replace_bool('full_setting', option.child_value('full_setting')) newprofile.replace_dict('option', option_dict) customize_dict = newprofile.get_dict('customize') customize = request.child('customize') if customize is not None: customize_dict.replace_int('effect', customize.child_value('effect')) customize_dict.replace_int('hukidashi', customize.child_value('hukidashi')) customize_dict.replace_int('font', customize.child_value('font')) customize_dict.replace_int('comment_1', customize.child_value('comment_1')) customize_dict.replace_int('comment_2', customize.child_value('comment_2')) newprofile.replace_dict('customize', customize_dict) # Keep track of play statistics self.update_play_statistics(userid) # Extract achievements for node in request.children: if node.name == 'item': if not node.child_value('is_new'): # No need to save this one continue itemid = node.child_value('id') itemtype = node.child_value('type') param = node.child_value('param') self.data.local.user.put_achievement( self.game, self.version, userid, itemid, 'item', { 'type': itemtype, 'param': param, }, ) elif node.name == 'achievement': achievementid = node.child_value('type') count = node.child_value('count') self.data.local.user.put_achievement( self.game, self.version, userid, achievementid, 'achievement', { 'count': count, }, ) elif node.name == 'chara_param': charaid = node.child_value('chara_id') friendship = node.child_value('friendship') self.data.local.user.put_achievement( self.game, self.version, userid, charaid, 'chara', { 'friendship': friendship, }, ) elif node.name == 'story': storyid = node.child_value('story_id') chapter = node.child_value('chapter_id') gauge = node.child_value('gauge_point') cleared = node.child_value('is_cleared') clear_chapter = node.child_value('clear_chapter') self.data.local.user.put_achievement( self.game, self.version, userid, storyid, 'story', { 'chapter': chapter, 'gauge': gauge, 'cleared': cleared, 'clear_chapter': clear_chapter, }, ) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: game_config = self.get_game_config() newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid'))) newprofile.replace_str('name', request.child_value('pdata/base/name')) newprofile.replace_int('icon', request.child_value('pdata/base/icon_id')) newprofile.replace_int('lvl', request.child_value('pdata/base/lv')) newprofile.replace_int('exp', request.child_value('pdata/base/exp')) newprofile.replace_int('mg', request.child_value('pdata/base/mg')) newprofile.replace_int('ap', request.child_value('pdata/base/ap')) newprofile.replace_int('pc', request.child_value('pdata/base/pc')) newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) customdict = newprofile.get_dict('custom') custom = request.child('pdata/custom') if custom: customdict.replace_int('s_gls', custom.child_value('s_gls')) customdict.replace_int('bgm_m', custom.child_value('bgm_m')) customdict.replace_int('st_f', custom.child_value('st_f')) customdict.replace_int('st_bg', custom.child_value('st_bg')) customdict.replace_int('st_bg_b', custom.child_value('st_bg_b')) customdict.replace_int('eff_e', custom.child_value('eff_e')) customdict.replace_int('se_s', custom.child_value('se_s')) customdict.replace_int('se_s_v', custom.child_value('se_s_v')) customdict.replace_int('last_music_id', custom.child_value('last_music_id')) customdict.replace_int('last_note_grade', custom.child_value('last_note_grade')) customdict.replace_int('sort_type', custom.child_value('sort_type')) customdict.replace_int('narrowdown_type', custom.child_value('narrowdown_type')) customdict.replace_bool( 'is_begginer', custom.child_value( 'is_begginer')) # Yes, this is spelled right customdict.replace_bool('is_tut', custom.child_value('is_tut')) customdict.replace_int_array('symbol_chat_0', 6, custom.child_value('symbol_chat_0')) customdict.replace_int_array('symbol_chat_1', 6, custom.child_value('symbol_chat_1')) customdict.replace_int('gauge_style', custom.child_value('gauge_style')) customdict.replace_int('obj_shade', custom.child_value('obj_shade')) customdict.replace_int('obj_size', custom.child_value('obj_size')) customdict.replace_int_array('byword', 2, custom.child_value('byword')) customdict.replace_bool_array('is_auto_byword', 2, custom.child_value('is_auto_byword')) customdict.replace_bool('is_tweet', custom.child_value('is_tweet')) customdict.replace_bool('is_link_twitter', custom.child_value('is_link_twitter')) customdict.replace_int('mrec_type', custom.child_value('mrec_type')) customdict.replace_int('card_disp_type', custom.child_value('card_disp_type')) customdict.replace_int('tab_sel', custom.child_value('tab_sel')) customdict.replace_int_array('hidden_param', 20, custom.child_value('hidden_param')) newprofile.replace_dict('custom', customdict) # 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, }, ) # Grab any new records set during this play session. Reflec Beat Limelight only sends # the top record back for songs that were played at least once during the session. # Note that it sends the top record, so if you play the song twice, it will return # only one record. Also, if you get a lower score than a previous try, it will return # the previous try. So, we must also look at the battle log for the actual play scores, # and combine the data if we can. savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {} songplays = request.child('pdata/record') if songplays: for child in songplays.children: if child.name != 'rec': continue songid = child.child_value('mid') chart = child.child_value('ng') # These don't get sent with the battle logs, so we try to construct # the values here. if songid not in savedrecords: savedrecords[songid] = {} savedrecords[songid][chart] = { 'achievement_rate': child.child_value('mrec_0/ar') * 10, 'points': child.child_value('mrec_0/bs'), 'combo': child.child_value('mrec_0/mc'), 'miss_count': child.child_value('mrec_0/bmc'), 'win': child.child_value('mrec_0/win'), 'lose': child.child_value('mrec_0/lose'), 'draw': child.child_value('mrec_0/draw'), 'earned_points': child.child_value('point'), } # Now, see the actual battles that were played. If we can, unify the data with a record. # We only do that when the record achievement rate and score matches the battle achievement # rate and score, so we know for a fact that that record was generated by this battle. battlelogs = request.child('pdata/blog') if battlelogs: for child in battlelogs.children: if child.name != 'log': continue songid = child.child_value('mid') chart = child.child_value('ng') clear_type = child.child_value('myself/ct') achievement_rate = child.child_value('myself/ar') * 10 points = child.child_value('myself/s') clear_type, combo_type = self.__game_to_db_clear_type( clear_type) combo = None miss_count = -1 stats = None if songid in savedrecords: if chart in savedrecords[songid]: data = savedrecords[songid][chart] if (data['achievement_rate'] == achievement_rate and data['points'] == points): # This is the same record! Use the stats from it to update our # internal representation. combo = data['combo'] miss_count = data['miss_count'] stats = { 'win': data['win'], 'lose': data['lose'], 'draw': data['draw'], 'earned_points': data['earned_points'], } self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, combo=combo, stats=stats, ) # Keep track of glass points so unlocks work glass = request.child('pdata/glass') if glass: for child in glass.children: if child.name != 'g': continue gid = child.child_value('id') exp = child.child_value('exp') self.data.local.user.put_achievement( self.game, self.version, userid, gid, 'glass', { 'exp': exp, }, ) # Keep track of favorite music selections 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, }, ) # Keep track of play statistics self.update_play_statistics(userid) return newprofile