def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: return { 'name': profile.get_str('name'), 'extid': ID.format_extid(profile.get_int('extid')), 'first_play_time': playstats.get_int('first_play_timestamp'), 'last_play_time': playstats.get_int('last_play_timestamp'), }
def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: name = 'なし' # Nothing shop = '未設定' # Not set shop_area = '未設定' # Not set for i in range(len(profile['strdatas'])): strdata = profile['strdatas'][i] # Figure out the profile type csvs = strdata.split(b',') if len(csvs) < 2: # Not long enough to care about continue datatype = csvs[1].decode('ascii') if datatype != 'IBBDAT00': # Not the right profile type requested continue name = self.__update_value(name, csvs[27]) shop = self.__update_value(shop, csvs[30]) shop_area = self.__update_value(shop_area, csvs[31]) return { 'name': name, 'extid': ID.format_extid(profile.get_int('extid')), 'shop': shop, 'shop_area': shop_area, 'first_play_time': playstats.get_int('first_play_timestamp'), 'last_play_time': playstats.get_int('last_play_timestamp'), 'plays': playstats.get_int('total_plays'), }
def __format_profile(self, cardids: List[str], profile: ValidatedDict, settings: ValidatedDict, exact: bool) -> Dict[str, Any]: base = { 'name': profile.get_str('name'), 'cards': cardids, 'registered': settings.get_int('first_play_timestamp', -1), 'updated': settings.get_int('last_play_timestamp', -1), 'plays': settings.get_int('total_plays', -1), 'match': 'exact' if exact else 'partial', } if self.game == GameConstants.DDR: base.update(self.__format_ddr_profile(profile, exact)) if self.game == GameConstants.IIDX: base.update(self.__format_iidx_profile(profile, exact)) if self.game == GameConstants.JUBEAT: base.update(self.__format_jubeat_profile(profile, exact)) if self.game == GameConstants.MUSECA: base.update(self.__format_museca_profile(profile, exact)) if self.game == GameConstants.POPN_MUSIC: base.update(self.__format_popn_profile(profile, exact)) if self.game == GameConstants.REFLEC_BEAT: base.update(self.__format_reflec_profile(profile, exact)) if self.game == GameConstants.SDVX: base.update(self.__format_sdvx_profile(profile, exact)) return base
def format_conversion(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('playerdata') root.add_child(Node.string('name', profile.get_str('name', 'なし'))) root.add_child(Node.s16('chara', profile.get_int('chara', -1))) root.add_child(Node.s32('option', profile.get_int('option', 0))) root.add_child(Node.u8('version', 0)) root.add_child(Node.u8('kind', 0)) root.add_child(Node.u8('season', 0)) clear_medal = [0] * self.GAME_MAX_MUSIC_ID scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: if score.id > self.GAME_MAX_MUSIC_ID: continue # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue clear_medal[score.id] = clear_medal[ score.id] | self.__format_medal_for_score(score) root.add_child(Node.u16_array('clear_medal', clear_medal)) return root
def format_qpro(self, qpro_dict: ValidatedDict) -> Dict[str, Any]: return { 'body': qpro_dict.get_int('body'), 'face': qpro_dict.get_int('face'), 'hair': qpro_dict.get_int('hair'), 'hand': qpro_dict.get_int('hand'), 'head': qpro_dict.get_int('head'), }
def update_course( self, userid: UserID, coursetype: str, courseid: int, chart: int, clear_status: int, pgreats: int, greats: int, ) -> None: # Range check course type if coursetype not in [ self.COURSE_TYPE_SECRET, self.COURSE_TYPE_INTERNET_RANKING, self.COURSE_TYPE_CLASSIC, ]: raise Exception(f"Invalid course type value {coursetype}") # Range check medals if clear_status not in [ self.CLEAR_STATUS_NO_PLAY, self.CLEAR_STATUS_FAILED, self.CLEAR_STATUS_ASSIST_CLEAR, self.CLEAR_STATUS_EASY_CLEAR, self.CLEAR_STATUS_CLEAR, self.CLEAR_STATUS_HARD_CLEAR, self.CLEAR_STATUS_EX_HARD_CLEAR, self.CLEAR_STATUS_FULL_COMBO, ]: raise Exception(f"Invalid clear status value {clear_status}") # Update achievement to track course statistics course_score = self.data.local.user.get_achievement( self.game, self.version, userid, courseid * 6 + chart, coursetype, ) if course_score is None: course_score = ValidatedDict() course_score.replace_int( 'clear_status', max(clear_status, course_score.get_int('clear_status'))) old_ex_score = (course_score.get_int('pgnum') * 2) + course_score.get_int('gnum') if old_ex_score < ((pgreats * 2) + greats): course_score.replace_int('pgnum', pgreats) course_score.replace_int('gnum', greats) self.data.local.user.put_achievement( self.game, self.version, userid, courseid * 6 + chart, coursetype, course_score, )
def format_conversion(self, userid: UserID, profile: ValidatedDict) -> Node: # Circular import, ugh from bemani.backend.popn.lapistoria import PopnMusicLapistoria root = Node.void('playerdata') root.add_child(Node.string('name', profile.get_str('name', 'なし'))) root.add_child(Node.s16('chara', profile.get_int('chara', -1))) root.add_child(Node.s32('option', profile.get_int('option', 0))) root.add_child(Node.s8('result', 1)) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: if score.id > self.GAME_MAX_MUSIC_ID: continue # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points medal = score.data.get_int('medal') music = Node.void('music') root.add_child(music) music.add_child(Node.s16('music_num', score.id)) music.add_child(Node.u8('sheet_num', { self.CHART_TYPE_EASY: PopnMusicLapistoria.GAME_CHART_TYPE_EASY, self.CHART_TYPE_NORMAL: PopnMusicLapistoria.GAME_CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER: PopnMusicLapistoria.GAME_CHART_TYPE_HYPER, self.CHART_TYPE_EX: PopnMusicLapistoria.GAME_CHART_TYPE_EX, }[score.chart])) music.add_child(Node.s16('cnt', score.plays)) music.add_child(Node.s32('score', 0)) music.add_child(Node.u8('clear_type', 0)) music.add_child(Node.s32('old_score', points)) music.add_child(Node.u8('old_clear_type', { self.PLAY_MEDAL_CIRCLE_FAILED: PopnMusicLapistoria.GAME_PLAY_MEDAL_CIRCLE_FAILED, self.PLAY_MEDAL_DIAMOND_FAILED: PopnMusicLapistoria.GAME_PLAY_MEDAL_DIAMOND_FAILED, self.PLAY_MEDAL_STAR_FAILED: PopnMusicLapistoria.GAME_PLAY_MEDAL_STAR_FAILED, self.PLAY_MEDAL_EASY_CLEAR: PopnMusicLapistoria.GAME_PLAY_MEDAL_EASY_CLEAR, self.PLAY_MEDAL_CIRCLE_CLEARED: PopnMusicLapistoria.GAME_PLAY_MEDAL_CIRCLE_CLEARED, self.PLAY_MEDAL_DIAMOND_CLEARED: PopnMusicLapistoria.GAME_PLAY_MEDAL_DIAMOND_CLEARED, self.PLAY_MEDAL_STAR_CLEARED: PopnMusicLapistoria.GAME_PLAY_MEDAL_STAR_CLEARED, self.PLAY_MEDAL_CIRCLE_FULL_COMBO: PopnMusicLapistoria.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, self.PLAY_MEDAL_DIAMOND_FULL_COMBO: PopnMusicLapistoria.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: PopnMusicLapistoria.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: PopnMusicLapistoria.GAME_PLAY_MEDAL_PERFECT, }[medal])) return root
def format_flags(self, settings_dict: ValidatedDict) -> Dict[str, Any]: flags = settings_dict.get_int('flags') return { 'grade': (flags & 0x001) != 0, 'status': (flags & 0x002) != 0, 'difficulty': (flags & 0x004) != 0, 'alphabet': (flags & 0x008) != 0, 'rival_played': (flags & 0x010) != 0, 'rival_win_lose': (flags & 0x040) != 0, 'rival_info': (flags & 0x080) != 0, 'hide_play_count': (flags & 0x100) != 0, 'disable_song_preview': settings_dict.get_int('disable_song_preview') != 0, 'effector_lock': settings_dict.get_int('effector_lock') != 0, }
def __format_iidx_profile(self, profile: ValidatedDict) -> Dict[str, Any]: updates: Dict[str, Any] = { 'qpro': {}, } area = profile.get_int('area', -1) if area != -1: updates['pid'] = area qpro = profile.get_dict('qpro') head = qpro.get_int('head', -1) if head != -1: updates['qpro']['head'] = head hair = qpro.get_int('hair', -1) if hair != -1: updates['qpro']['hair'] = hair face = qpro.get_int('face', -1) if face != -1: updates['qpro']['face'] = face body = qpro.get_int('body', -1) if body != -1: updates['qpro']['body'] = body hand = qpro.get_int('hand', -1) if hand != -1: updates['qpro']['hand'] = hand return updates
def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) formatted_profile['plays'] = playstats.get_int('total_plays') formatted_profile['emblem'] = self.format_emblem( profile.get_dict('last').get_int_array('emblem', 5)) return formatted_profile
def __format_popn_profile(self, profile: ValidatedDict) -> Dict[str, Any]: updates = {} chara = profile.get_int('character', -1) if chara != -1: updates['chara'] = chara return updates
def __format_reflec_profile(self, profile: ValidatedDict) -> Dict[str, Any]: updates = {} icon = profile.get_int('icon', -1) if icon != -1: updates['config'] = {'icon_id': icon} return updates
def __format_ddr_profile(self, profile: ValidatedDict) -> Dict[str, Any]: updates = {} area = profile.get_int('area', -1) if area != -1: updates['area'] = area return updates
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: game = Node.void('game') # Generic profile stuff game.add_child(Node.string('name', profile.get_str('name'))) game.add_child( Node.string('code', ID.format_extid(profile.get_int('extid')))) game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) game.add_child(Node.u32('exp_point', profile.get_int('exp'))) game.add_child(Node.u32('m_user_cnt', profile.get_int('m_user_cnt'))) game_config = self.get_game_config() if game_config.get_bool('force_unlock_cards'): game.add_child(Node.bool_array('have_item', [True] * 512)) else: game.add_child( Node.bool_array( 'have_item', [x > 0 for x in profile.get_int_array('have_item', 512)])) if game_config.get_bool('force_unlock_songs'): game.add_child(Node.bool_array('have_note', [True] * 512)) else: game.add_child( Node.bool_array( 'have_note', [x > 0 for x in profile.get_int_array('have_note', 512)])) # Last played stuff lastdict = profile.get_dict('last') last = Node.void('last') game.add_child(last) last.set_attribute('music_id', str(lastdict.get_int('music_id'))) last.set_attribute('music_type', str(lastdict.get_int('music_type'))) last.set_attribute('sort_type', str(lastdict.get_int('sort_type'))) last.set_attribute('headphone', str(lastdict.get_int('headphone'))) last.set_attribute('hispeed', str(lastdict.get_int('hispeed'))) last.set_attribute('appeal_id', str(lastdict.get_int('appeal_id'))) last.set_attribute('frame0', str(lastdict.get_int('frame0'))) last.set_attribute('frame1', str(lastdict.get_int('frame1'))) last.set_attribute('frame2', str(lastdict.get_int('frame2'))) last.set_attribute('frame3', str(lastdict.get_int('frame3'))) last.set_attribute('frame4', str(lastdict.get_int('frame4'))) return game
def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) formatted_profile.update({ 'arcade': "", 'prefecture': profile.get_int('pid', 51), 'settings': self.format_settings(profile.get_dict('settings')), 'flags': self.format_flags(profile.get_dict('settings')), 'sdjp': playstats.get_int('single_dj_points'), 'ddjp': playstats.get_int('double_dj_points'), 'sp': playstats.get_int('single_plays'), 'dp': playstats.get_int('double_plays'), 'sdan': self.format_dan_rank(profile.get_int('sgrade', -1)), 'ddan': self.format_dan_rank(profile.get_int('dgrade', -1)), 'srank': profile.get_int('sgrade', -1), 'drank': profile.get_int('dgrade', -1), 'qpro': self.format_qpro(profile.get_dict('qpro')), }) if 'shop_location' in profile: shop_id = profile.get_int('shop_location') if shop_id in self.machines: formatted_profile['arcade'] = self.machines[shop_id] else: pcbid = self.data.local.machine.from_machine_id(shop_id) if pcbid is not None: machine = self.data.local.machine.get_machine(pcbid) self.machines[shop_id] = machine.name formatted_profile['arcade'] = machine.name return formatted_profile
def __format_iidx_profile(self, profile: ValidatedDict, exact: bool) -> Dict[str, Any]: qpro = profile.get_dict('qpro') return { 'area': profile.get_int('pid', -1), 'qpro': { 'head': qpro.get_int('head', -1) if exact else -1, 'hair': qpro.get_int('hair', -1) if exact else -1, 'face': qpro.get_int('face', -1) if exact else -1, 'body': qpro.get_int('body', -1) if exact else -1, 'hand': qpro.get_int('hand', -1) if exact else -1, } }
def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) formatted_profile['plays'] = playstats.get_int('total_plays') formatted_profile['emblem'] = self.format_emblem( profile.get_dict('last').get_int_array('emblem', 5)) formatted_profile['jubility'] = profile.get_int('jubility') formatted_profile['pick_up_jubility'] = profile.get_float( 'pick_up_jubility') # Only reason this is a dictionary of dictionaries is because ValidatedDict doesn't support a list of dictionaries. Probably intentionally lol # Just listify the pickup/common charts and then sort them by the value key in each dictionary since that's the actual number of points formatted_profile['pick_up_chart'] = sorted(list( profile.get_dict('pick_up_chart').values()), key=lambda x: x['value'], reverse=True) formatted_profile['common_jubility'] = profile.get_float( 'common_jubility') formatted_profile['common_chart'] = sorted(list( profile.get_dict('common_chart').values()), key=lambda x: x['value'], reverse=True) formatted_profile['ex_count'] = profile.get_int('ex_cnt') formatted_profile['fc_count'] = profile.get_int('fc_cnt') return formatted_profile
def format_rival(self, link: Link, profile: ValidatedDict) -> Dict[str, Any]: pos = int(link.type[7:]) if profile.get_int('version') == VersionConstants.DDR_X2: active = pos == (profile.get_dict('last').get_int('fri') - 1) elif profile.get_int('version') in [ VersionConstants.DDR_X3_VS_2NDMIX, VersionConstants.DDR_2013, VersionConstants.DDR_2014, VersionConstants.DDR_ACE, VersionConstants.DDR_A20 ]: actives = [ profile.get_dict('last').get_int('rival1') - 1, profile.get_dict('last').get_int('rival2') - 1, profile.get_dict('last').get_int('rival3') - 1, ] active = pos in actives else: active = False return { 'position': pos, 'active': active, 'userid': str(link.other_userid), 'remote': RemoteUser.is_remote(link.other_userid), }
def format_conversion(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('player23') root.add_child(Node.string('name', profile.get_str('name', 'なし'))) root.add_child(Node.s16('chara', profile.get_int('chara', -1))) root.add_child(Node.s8('result', 1)) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points medal = score.data.get_int('medal') music = Node.void('music') root.add_child(music) music.add_child(Node.s16('music_num', score.id)) music.add_child(Node.u8('sheet_num', { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, }[score.chart])) music.add_child(Node.s32('score', points)) music.add_child(Node.u8('clear_type', { self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, }[medal])) music.add_child(Node.s16('cnt', score.plays)) return root
def update_score( self, userid: Optional[UserID], songid: int, chart: int, clear_status: int, pgreats: int, greats: int, miss_count: int, ghost: Optional[bytes], shop: Optional[int], ) -> None: """ Given various pieces of a score, update the user's high score and score history in a controlled manner, so all games in IIDX series can expect the same attributes in a score. Note that the medals passed here are expected to be converted from game identifier to our internal identifier, so that any game in the series may convert them back. In this way, a song played on Pendual that exists in Tricoro will still have scores/medals going back all versions. """ # Range check medals if clear_status not in [ self.CLEAR_STATUS_NO_PLAY, self.CLEAR_STATUS_FAILED, self.CLEAR_STATUS_ASSIST_CLEAR, self.CLEAR_STATUS_EASY_CLEAR, self.CLEAR_STATUS_CLEAR, self.CLEAR_STATUS_HARD_CLEAR, self.CLEAR_STATUS_EX_HARD_CLEAR, self.CLEAR_STATUS_FULL_COMBO, ]: raise Exception(f"Invalid clear status value {clear_status}") # Calculate ex score ex_score = (2 * pgreats) + greats if userid is not None: if ghost is None: raise Exception("Expected a ghost for user score save!") oldscore = self.data.local.music.get_score( self.game, self.music_version, userid, songid, chart, ) else: # Storing an anonymous attempt if ghost is not None: raise Exception("Expected no ghost for anonymous score save!") oldscore = None # Score history is verbatum, instead of highest score history = ValidatedDict({ 'clear_status': clear_status, 'miss_count': miss_count, }) old_ex_score = ex_score if ghost is not None: history['ghost'] = ghost if oldscore is None: # If it is a new score, create a new dictionary to add to scoredata = ValidatedDict({ 'clear_status': clear_status, 'miss_count': miss_count, 'pgreats': pgreats, 'greats': greats, }) if ghost is not None: scoredata['ghost'] = ghost raised = True highscore = True else: # Set the score to any new record achieved raised = ex_score > oldscore.points highscore = ex_score >= oldscore.points ex_score = max(ex_score, oldscore.points) scoredata = oldscore.data scoredata.replace_int( 'clear_status', max(scoredata.get_int('clear_status'), clear_status)) if raised: scoredata.replace_int('miss_count', miss_count) scoredata.replace_int('pgreats', pgreats) scoredata.replace_int('greats', greats) if ghost is not None: scoredata.replace_bytes('ghost', ghost) if shop is not None: history.replace_int('shop', shop) scoredata.replace_int('shop', shop) # Look up where this score was earned lid = self.get_machine_id() if userid is not None: # Write the new score back self.data.local.music.put_score( self.game, self.music_version, userid, songid, chart, lid, ex_score, scoredata, highscore, ) # Save the history of this score too self.data.local.music.put_attempt( self.game, self.music_version, userid, songid, chart, lid, old_ex_score, history, raised, )
def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: formatted_profile = super().format_profile(profile, playstats) formatted_profile['plays'] = playstats.get_int('total_plays') return formatted_profile
def update_score( self, userid: UserID, songid: int, chart: int, points: int, achievement_rate: int, clear_type: int, combo_type: int, miss_count: int, combo: Optional[int] = None, stats: Optional[Dict[str, int]] = None, param: Optional[int] = None, kflag: Optional[int] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score history in a controlled manner, so all games in Reflec series can expect the same attributes in a score. Note that the clear_types passed here are expected to be converted from game identifier to our internal identifier, so that any game in the series may convert them back. """ # Range check clear type if clear_type not in [ self.CLEAR_TYPE_NO_PLAY, self.CLEAR_TYPE_FAILED, self.CLEAR_TYPE_CLEARED, self.CLEAR_TYPE_HARD_CLEARED, self.CLEAR_TYPE_S_HARD_CLEARED, ]: raise Exception(f"Invalid clear_type value {clear_type}") # Range check combo type if combo_type not in [ self.COMBO_TYPE_NONE, self.COMBO_TYPE_ALMOST_COMBO, self.COMBO_TYPE_FULL_COMBO, self.COMBO_TYPE_FULL_COMBO_ALL_JUST, ]: raise Exception(f"Invalid combo_type value {combo_type}") oldscore = self.data.local.music.get_score( self.game, self.version, userid, songid, chart, ) # Score history is verbatum, instead of highest score now = Time.now() history = ValidatedDict({}) oldpoints = points if oldscore is None: # If it is a new score, create a new dictionary to add to scoredata = ValidatedDict({}) highscore = True else: # Set the score to any new record achieved highscore = points >= oldscore.points points = max(points, oldscore.points) scoredata = oldscore.data # Update the last played time scoredata.replace_int('last_played_time', now) # Replace clear type with highest value and timestamps if clear_type >= scoredata.get_int('clear_type'): scoredata.replace_int( 'clear_type', max(scoredata.get_int('clear_type'), clear_type)) scoredata.replace_int('best_clear_type_time', now) history.replace_int('clear_type', clear_type) # Replace combo type with highest value and timestamps if combo_type >= scoredata.get_int('combo_type'): scoredata.replace_int( 'combo_type', max(scoredata.get_int('combo_type'), combo_type)) scoredata.replace_int('best_clear_type_time', now) history.replace_int('combo_type', combo_type) # Update the combo for this song if combo is not None: scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) history.replace_int('combo', combo) # Update the param for this song if param is not None: scoredata.replace_int('param', max(scoredata.get_int('param'), param)) history.replace_int('param', param) # Update the kflag for this song if kflag is not None: scoredata.replace_int('kflag', max(scoredata.get_int('kflag'), kflag)) history.replace_int('kflag', kflag) # Update win/lost/draw stats for this song if stats is not None: scoredata.replace_dict('stats', stats) history.replace_dict('stats', stats) # Update the achievement rate with timestamps if achievement_rate >= scoredata.get_int('achievement_rate'): scoredata.replace_int( 'achievement_rate', max(scoredata.get_int('achievement_rate'), achievement_rate)) scoredata.replace_int('best_achievement_rate_time', now) history.replace_int('achievement_rate', achievement_rate) # Update the miss count with timestamps, either if it was lowered, or if the old value was blank. # If the new value is -1 (we didn't get a miss count this time), never update the old value. if miss_count >= 0: if miss_count <= scoredata.get_int( 'miss_count', 999999) or scoredata.get_int('miss_count') == -1: scoredata.replace_int( 'miss_count', min(scoredata.get_int('miss_count', 999999), miss_count)) scoredata.replace_int('best_miss_count_time', now) history.replace_int('miss_count', miss_count) # Look up where this score was earned lid = self.get_machine_id() # Reflec Beat happens to send all songs that were played by a player # at the end of the round. It sends timestamps for the songs, but as of # Colette they were identical for each song in the round. So, if a user # plays the same song/chart# more than once in a round, we will end up # failing to store the attempt since we don't allow two of the same # attempt at the same time for the same user and song/chart. So, bump # the timestamp by one second and retry well past the maximum number of # songs. for bump in range(10): timestamp = now + bump # Write the new score back self.data.local.music.put_score( self.game, self.version, userid, songid, chart, lid, points, scoredata, highscore, timestamp=timestamp, ) try: # Save the history of this score too self.data.local.music.put_attempt( self.game, self.version, userid, songid, chart, lid, oldpoints, history, highscore, timestamp=timestamp, ) except ScoreSaveException: # Try again one second in the future continue # We saved successfully break
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('player22') # Result root.add_child(Node.s8('result', 0)) # Set up account account = Node.void('account') root.add_child(account) account.add_child(Node.string('name', profile.get_str('name', 'なし'))) account.add_child( Node.string('g_pm_id', ID.format_extid(profile.get_int('extid')))) account.add_child(Node.s8('tutorial', profile.get_int('tutorial', -1))) account.add_child( Node.s16('read_news', profile.get_int('read_news', 0))) account.add_child(Node.s8('staff', 0)) account.add_child(Node.s8('is_conv', 0)) account.add_child(Node.s16('item_type', 0)) account.add_child(Node.s16('item_id', 0)) account.add_child( Node.s16_array('license_data', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) # Statistics section and scores section statistics = self.get_play_statistics(userid) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 account.add_child(Node.u8('active_fr_num', 0)) # TODO: Hook up rivals code? account.add_child( Node.s16('total_play_cnt', statistics.get_int('total_plays', 0))) account.add_child(Node.s16('today_play_cnt', today_count)) account.add_child( Node.s16('consecutive_days', statistics.get_int('consecutive_days', 0))) account.add_child( Node.s16('total_days', statistics.get_int('total_days', 0))) account.add_child(Node.s16('interval_day', 0)) # Add scores section last_played = [ x[0] for x in self.data.local.music.get_last_played( self.game, self.version, userid, 5) ] most_played = [ x[0] for x in self.data.local.music.get_most_played( self.game, self.version, userid, 10) ] while len(last_played) < 5: last_played.append(-1) while len(most_played) < 10: most_played.append(-1) account.add_child(Node.s16_array('my_best', most_played)) account.add_child(Node.s16_array('latest_music', last_played)) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points medal = score.data.get_int('medal') music = Node.void('music') root.add_child(music) music.add_child(Node.s16('music_num', score.id)) music.add_child( Node.u8( 'sheet_num', { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, }[score.chart])) music.add_child(Node.s16('cnt', score.plays)) music.add_child(Node.s32('score', points)) music.add_child( Node.u8( 'clear_type', { self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, }[medal])) music.add_child(Node.s32('old_score', 0)) music.add_child(Node.u8('old_clear_type', 0)) # Net VS section netvs = Node.void('netvs') root.add_child(netvs) netvs.add_child(Node.s32('rank_point', 0)) netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) netvs.add_child(Node.u8('rank', 0)) netvs.add_child(Node.s8('vs_rank_old', 0)) netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) netvs.add_child(Node.u32('netvs_play_cnt', 0)) for dialog in [0, 1, 2, 3, 4, 5]: # TODO: Configure this, maybe? netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) # Set up config config = Node.void('config') root.add_child(config) config.add_child(Node.u8('mode', profile.get_int('mode', 0))) config.add_child(Node.s16('chara', profile.get_int('chara', -1))) config.add_child(Node.s16('music', profile.get_int('music', -1))) config.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) config.add_child(Node.s8('category', profile.get_int('category', 1))) config.add_child( Node.s8('sub_category', profile.get_int('sub_category', -1))) config.add_child( Node.s8('chara_category', profile.get_int('chara_category', -1))) config.add_child(Node.s16('story_id', profile.get_int('story_id', -1))) config.add_child( Node.s16('course_id', profile.get_int('course_id', -1))) config.add_child( Node.s8('course_folder', profile.get_int('course_folder', -1))) config.add_child( Node.s8('story_folder', profile.get_int('story_folder', -1))) config.add_child( Node.s8('ms_banner_disp', profile.get_int('ms_banner_disp'))) config.add_child( Node.s8('ms_down_info', profile.get_int('ms_down_info'))) config.add_child( Node.s8('ms_side_info', profile.get_int('ms_side_info'))) config.add_child( Node.s8('ms_raise_type', profile.get_int('ms_raise_type'))) config.add_child(Node.s8('ms_rnd_type', profile.get_int('ms_rnd_type'))) # Set up option option_dict = profile.get_dict('option') option = Node.void('option') root.add_child(option) option.add_child( Node.s16('hispeed', option_dict.get_int('hispeed', 10))) option.add_child(Node.u8('popkun', option_dict.get_int('popkun', 0))) option.add_child( Node.bool('hidden', option_dict.get_bool('hidden', False))) option.add_child( Node.s16('hidden_rate', option_dict.get_int('hidden_rate', -1))) option.add_child( Node.bool('sudden', option_dict.get_bool('sudden', False))) option.add_child( Node.s16('sudden_rate', option_dict.get_int('sudden_rate', -1))) option.add_child(Node.s8('randmir', option_dict.get_int('randmir', 0))) option.add_child( Node.s8('gauge_type', option_dict.get_int('gauge_type', 0))) option.add_child(Node.u8('ojama_0', option_dict.get_int('ojama_0', 0))) option.add_child(Node.u8('ojama_1', option_dict.get_int('ojama_1', 0))) option.add_child( Node.bool('forever_0', option_dict.get_bool('forever_0', False))) option.add_child( Node.bool('forever_1', option_dict.get_bool('forever_1', False))) option.add_child( Node.bool('full_setting', option_dict.get_bool('full_setting', False))) # Set up info info = Node.void('info') root.add_child(info) info.add_child(Node.u16('ep', profile.get_int('ep', 0))) info.add_child(Node.u16('ap', profile.get_int('ap', 0))) # Set up custom_cate custom_cate = Node.void('custom_cate') root.add_child(custom_cate) custom_cate.add_child(Node.s8('valid', 0)) custom_cate.add_child(Node.s8('lv_min', -1)) custom_cate.add_child(Node.s8('lv_max', -1)) custom_cate.add_child(Node.s8('medal_min', -1)) custom_cate.add_child(Node.s8('medal_max', -1)) custom_cate.add_child(Node.s8('friend_no', -1)) custom_cate.add_child(Node.s8('score_flg', -1)) # Set up customize customize_dict = profile.get_dict('customize') customize = Node.void('customize') root.add_child(customize) customize.add_child( Node.u16('effect', customize_dict.get_int('effect'))) customize.add_child( Node.u16('hukidashi', customize_dict.get_int('hukidashi'))) customize.add_child(Node.u16('font', customize_dict.get_int('font'))) customize.add_child( Node.u16('comment_1', customize_dict.get_int('comment_1'))) customize.add_child( Node.u16('comment_2', customize_dict.get_int('comment_2'))) # Set up achievements achievements = self.data.local.user.get_achievements( self.game, self.version, userid) for achievement in achievements: if achievement.type == 'item': itemtype = achievement.data.get_int('type') param = achievement.data.get_int('param') item = Node.void('item') root.add_child(item) item.add_child(Node.u8('type', itemtype)) item.add_child(Node.u16('id', achievement.id)) item.add_child(Node.u16('param', param)) item.add_child(Node.bool('is_new', False)) elif achievement.type == 'achievement': count = achievement.data.get_int('count') ach_node = Node.void('achievement') root.add_child(ach_node) ach_node.add_child(Node.u8('type', achievement.id)) ach_node.add_child(Node.u32('count', count)) elif achievement.type == 'chara': friendship = achievement.data.get_int('friendship') chara = Node.void('chara_param') root.add_child(chara) chara.add_child(Node.u16('chara_id', achievement.id)) chara.add_child(Node.u16('friendship', friendship)) elif achievement.type == 'story': chapter = achievement.data.get_int('chapter') gauge = achievement.data.get_int('gauge') cleared = achievement.data.get_bool('cleared') clear_chapter = achievement.data.get_int('clear_chapter') story = Node.void('story') root.add_child(story) story.add_child(Node.u32('story_id', achievement.id)) story.add_child(Node.u32('chapter_id', chapter)) story.add_child(Node.u16('gauge_point', gauge)) story.add_child(Node.bool('is_cleared', cleared)) story.add_child(Node.u32('clear_chapter', clear_chapter)) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('gametop') data = Node.void('data') root.add_child(data) player = Node.void('player') data.add_child(player) # Player info and statistics info = Node.void('info') player.add_child(info) info.add_child(Node.s16('jubility', profile.get_int('jubility'))) info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) if 'total_best_score' in profile: info.add_child(Node.s32('total_best_score', profile.get_int('total_best_score'))) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. info.add_child(Node.bool('inherit', profile.get_bool('has_old_version'))) # Not saved, but loaded info.add_child(Node.s32('mtg_entry_cnt', 123)) info.add_child(Node.s32('mtg_hold_cnt', 456)) info.add_child(Node.u8('mtg_result', 10)) # Secret unlocks item = Node.void('item') player.add_child(item) item.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list', 32, [-1] * 32, ), )) item.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list', 96, [-1] * 96, ), )) item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) new = Node.void('new') item.add_child(new) new.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list_new', 32, [-1] * 32, ), )) new.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list_new', 96, [-1] * 96, ), )) new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) # Last played data, for showing cursor and such lastdict = profile.get_dict('last') last = Node.void('last') player.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) last.add_child(Node.s8('marker', lastdict.get_int('marker'))) last.add_child(Node.s16('title', lastdict.get_int('title'))) last.add_child(Node.s8('theme', lastdict.get_int('theme'))) last.add_child(Node.s8('sort', lastdict.get_int('sort'))) last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) last.add_child(Node.s16('parts', lastdict.get_int('parts'))) last.add_child(Node.s8('category', lastdict.get_int('category'))) last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) # Miscelaneous crap player.add_child(Node.s32('session_id', 1)) # Maybe hook this up? Unsure what it does, is it like IIDX dailies? today_music = Node.void('today_music') player.add_child(today_music) today_music.add_child(Node.s32('music_id', 0)) # No news, ever. news = Node.void('news') player.add_child(news) news.add_child(Node.s16('checked', 0)) # No rival support, yet. rivallist = Node.void('rivallist') player.add_child(rivallist) rivallist.set_attribute('count', '0') mylist = Node.void('mylist') player.add_child(mylist) mylist.set_attribute('count', '0') # No collaboration support yet. collabo = Node.void('collabo') player.add_child(collabo) collabo.add_child(Node.bool('success', False)) collabo.add_child(Node.bool('completed', False)) # Daily FC challenge. entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') if entry is None: entry = ValidatedDict() # Figure out if we've played these songs start_time, end_time = self.data.local.network.get_schedule_duration('daily') today_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time) challenge = Node.void('challenge') player.add_child(challenge) today = Node.void('today') challenge.add_child(today) today.add_child(Node.s32('music_id', entry.get_int('today', -1))) today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) onlynow = Node.void('onlynow') challenge.add_child(onlynow) onlynow.add_child(Node.s32('magic_no', 0)) onlynow.add_child(Node.s16('cycle', 0)) # Bistro event bistro = Node.void('bistro') player.add_child(bistro) # Presumably these can affect the speed of the event info_1 = Node.void('info') bistro.add_child(info_1) info_1.add_child(Node.float('delicious_rate', 1.0)) info_1.add_child(Node.float('favorite_rate', 1.0)) bistro.add_child(Node.s32('carry_over', profile.get_int('bistro_carry_over'))) # Your chef dude, I guess? chefdict = profile.get_dict('chef') chef = Node.void('chef') bistro.add_child(chef) chef.add_child(Node.s32('id', chefdict.get_int('id', 1))) chef.add_child(Node.u8('ability', chefdict.get_int('ability', 2))) chef.add_child(Node.u8('remain', chefdict.get_int('remain', 30))) chef.add_child(Node.u8('rate', chefdict.get_int('rate', 1))) # Routes, similar to story mode in Pop'n I guess? routes = [ { 'id': 50000284, 'price': 20, 'satisfaction': 10, 'favorite': True, }, { 'id': 50000283, 'price': 20, 'satisfaction': 20, 'favorite': False, }, { 'id': 50000282, 'price': 30, 'satisfaction': 10, 'favorite': False, }, { 'id': 50000275, 'price': 10, 'satisfaction': 55, 'favorite': False, }, { 'id': 50000274, 'price': 40, 'satisfaction': 40, 'favorite': False, }, { 'id': 50000273, 'price': 80, 'satisfaction': 60, 'favorite': False, }, { 'id': 50000272, 'price': 70, 'satisfaction': 60, 'favorite': False, }, { 'id': 50000271, 'price': 90, 'satisfaction': 80, 'favorite': False, }, { 'id': 50000270, 'price': 90, 'satisfaction': 20, 'favorite': False, }, ] for route_no in range(len(routes)): routedata = routes[route_no] route = Node.void('route') bistro.add_child(route) route.set_attribute('no', str(route_no)) music = Node.void('music') route.add_child(music) music.add_child(Node.s32('id', routedata['id'])) music.add_child(Node.u16('price', routedata['price'])) music.add_child(Node.s32('price_s32', routedata['price'])) # Look up any updated satisfaction stored by the game routesaved = self.data.local.user.get_achievement(self.game, self.version, userid, route_no + 1, 'route') if routesaved is None: routesaved = ValidatedDict() satisfaction = routesaved.get_int('satisfaction', routedata['satisfaction']) gourmates = Node.void('gourmates') route.add_child(gourmates) gourmates.add_child(Node.s32('id', route_no + 1)) gourmates.add_child(Node.u8('favorite', 1 if routedata['favorite'] else 0)) gourmates.add_child(Node.u16('satisfaction', satisfaction)) gourmates.add_child(Node.s32('satisfaction_s32', satisfaction)) # Sane defaults for unknown nodes only_now_music = Node.void('only_now_music') player.add_child(only_now_music) only_now_music.set_attribute('count', '0') requested_music = Node.void('requested_music') player.add_child(requested_music) requested_music.set_attribute('count', '0') kac_music = Node.void('kac_music') player.add_child(kac_music) kac_music.set_attribute('count', '0') history = Node.void('history') player.add_child(history) history.set_attribute('count', '0') # Basic profile info player.add_child(Node.string('name', profile.get_str('name', 'なし'))) player.add_child(Node.s32('jid', profile.get_int('extid'))) player.add_child(Node.string('refid', profile.get_str('refid'))) # Miscelaneous history stuff data.add_child(Node.u8('termver', 16)) data.add_child(Node.u32('season_etime', 0)) data.add_child(Node.s32('bistro_last_music_id', 0)) data.add_child(Node.s32_array( 'white_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.s32_array( 'old_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.s32_array( 'open_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) # Unsupported collaboration events with other games collabo_info = Node.void('collabo_info') data.add_child(collabo_info) # Unsupported marathon stuff run_run_marathon = Node.void('run_run_marathon') collabo_info.add_child(run_run_marathon) run_run_marathon.set_attribute('type', '1') run_run_marathon.add_child(Node.u8('state', 1)) run_run_marathon.add_child(Node.bool('is_report_end', True)) # Unsupported policy break stuff policy_break = Node.void('policy_break') collabo_info.add_child(policy_break) policy_break.set_attribute('type', '1') policy_break.add_child(Node.u8('state', 1)) policy_break.add_child(Node.bool('is_report_end', False)) # Unsupported vocaloid stuff vocaloid_event = Node.void('vocaloid_event') collabo_info.add_child(vocaloid_event) vocaloid_event.set_attribute('type', '1') vocaloid_event.add_child(Node.u8('state', 0)) vocaloid_event.add_child(Node.s32('music_id', 0)) # No obnoxious 30 second wait to play. matching_off = Node.void('matching_off') data.add_child(matching_off) matching_off.add_child(Node.bool('is_open', True)) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> 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) root = Node.void('player') pdata = Node.void('pdata') root.add_child(pdata) base = Node.void('base') pdata.add_child(base) base.add_child(Node.s32('uid', profile.get_int('extid'))) base.add_child(Node.string('name', profile.get_str('name'))) base.add_child(Node.s16('lv', profile.get_int('lvl'))) base.add_child(Node.s32('exp', profile.get_int('exp'))) base.add_child(Node.s16('mg', profile.get_int('mg'))) base.add_child(Node.s16('ap', profile.get_int('ap'))) base.add_child(Node.s32('flag', profile.get_int('flag'))) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 con = Node.void('con') pdata.add_child(con) con.add_child(Node.s32('day', today_count)) con.add_child(Node.s32('cnt', statistics.get_int('total_plays'))) con.add_child( Node.s32('last', statistics.get_int('last_play_timestamp'))) con.add_child(Node.s32('now', Time.now())) team = Node.void('team') pdata.add_child(team) team.add_child(Node.s32('id', -1)) team.add_child(Node.string('name', '')) custom = Node.void('custom') customdict = profile.get_dict('custom') pdata.add_child(custom) custom.add_child(Node.u8('bgm_m', customdict.get_int('bgm_m'))) custom.add_child(Node.u8('st_f', customdict.get_int('st_f'))) custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) custom.add_child(Node.u8('st_bg_b', customdict.get_int('st_bg_b'))) custom.add_child(Node.u8('eff_e', customdict.get_int('eff_e'))) custom.add_child(Node.u8('se_s', customdict.get_int('se_s'))) custom.add_child(Node.u8('se_s_v', customdict.get_int('se_s_v'))) 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)) if game_config.get_bool('force_unlock_songs'): songs = { song.id for song in self.data.local.music.get_all_songs( self.game, self.version) } for songid in songs: info = Node.void('info') released.add_child(info) info.add_child(Node.u8('type', 0)) info.add_child(Node.u16('id', songid)) # Scores record = Node.void('record') pdata.add_child(record) for score in scores: rec = Node.void('rec') record.add_child(rec) rec.add_child(Node.u16('mid', score.id)) rec.add_child(Node.u8('ng', score.chart)) rec.add_child( Node.s32('win', score.data.get_dict('stats').get_int('win'))) rec.add_child( Node.s32('lose', score.data.get_dict('stats').get_int('lose'))) rec.add_child( Node.s32('draw', score.data.get_dict('stats').get_int('draw'))) rec.add_child( Node.u8( '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', int(score.data.get_int('achievement_rate') / 10))) rec.add_child(Node.s16('bs', score.points)) rec.add_child(Node.s16('mc', score.data.get_int('combo'))) rec.add_child(Node.s16('bmc', score.data.get_int('miss_count'))) # In original ReflecBeat, the entire battle log was returned for each battle. # We don't support storing all of that info, so don't return anything here. blog = Node.void('blog') pdata.add_child(blog) # Comment (seems unused?) pdata.add_child(Node.string('cmnt', '')) return root
def update_rank( self, userid: UserID, dantype: str, rank: int, percent: int, cleared: bool, stages_cleared: int, ) -> None: # Range check type if dantype not in [ self.DAN_RANKING_SINGLE, self.DAN_RANKING_DOUBLE, ]: raise Exception(f"Invalid dan rank type value {dantype}") # Range check rank if rank not in [ self.DAN_RANK_7_KYU, self.DAN_RANK_6_KYU, self.DAN_RANK_5_KYU, self.DAN_RANK_4_KYU, self.DAN_RANK_3_KYU, self.DAN_RANK_2_KYU, self.DAN_RANK_1_KYU, self.DAN_RANK_1_DAN, self.DAN_RANK_2_DAN, self.DAN_RANK_3_DAN, self.DAN_RANK_4_DAN, self.DAN_RANK_5_DAN, self.DAN_RANK_6_DAN, self.DAN_RANK_7_DAN, self.DAN_RANK_8_DAN, self.DAN_RANK_9_DAN, self.DAN_RANK_10_DAN, self.DAN_RANK_CHUDEN, self.DAN_RANK_KAIDEN, ]: raise Exception(f"Invalid dan rank {rank}") if cleared: # Update profile if needed profile = self.get_profile(userid) if profile is None: profile = ValidatedDict() profile.replace_int(dantype, max(rank, profile.get_int(dantype, -1))) self.put_profile(userid, profile) # Update achievement to track pass rate dan_score = self.data.local.user.get_achievement( self.game, self.version, userid, rank, dantype, ) if dan_score is None: dan_score = ValidatedDict() dan_score.replace_int('percent', max(percent, dan_score.get_int('percent'))) dan_score.replace_int( 'stages_cleared', max(stages_cleared, dan_score.get_int('stages_cleared'))) self.data.local.user.put_achievement(self.game, self.version, userid, rank, dantype, dan_score)
def format_settings(self, settings_dict: ValidatedDict) -> Dict[str, Any]: return { 'frame': settings_dict.get_int('frame'), 'turntable': settings_dict.get_int('turntable'), 'burst': settings_dict.get_int('burst'), 'bgm': settings_dict.get_int('bgm'), 'towel': settings_dict.get_int('towel'), 'judge_pos': settings_dict.get_int('judge_pos'), 'voice': settings_dict.get_int('voice'), 'noteskin': settings_dict.get_int('noteskin'), 'full_combo': settings_dict.get_int('full_combo'), 'beam': settings_dict.get_int('beam'), 'judge': settings_dict.get_int('judge'), 'pacemaker': settings_dict.get_int('pacemaker'), 'effector_preset': settings_dict.get_int('effector_preset'), 'explosion_size': settings_dict.get_int('explosion_size'), 'note_preview': settings_dict.get_int('note_preview'), }
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: game = Node.void('game_3') # Generic profile stuff game.add_child(Node.string('name', profile.get_str('name'))) game.add_child(Node.string('code', ID.format_extid(profile.get_int('extid')))) game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) game.add_child(Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) game.add_child(Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) game.add_child(Node.u32('blaster_energy', profile.get_int('blaster_energy'))) game.add_child(Node.u32('blaster_count', profile.get_int('blaster_count'))) # Enable Ryusei Festa ryusei_festa = Node.void('ryusei_festa') game.add_child(ryusei_festa) ryusei_festa.add_child(Node.bool('ryusei_festa_trigger', True)) # Play statistics statistics = self.get_play_statistics(userid) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if ( last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2] ): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 game.add_child(Node.u32('play_count', statistics.get_int('total_plays', 0))) game.add_child(Node.u32('daily_count', today_count)) game.add_child(Node.u32('play_chain', statistics.get_int('consecutive_days', 0))) # Last played stuff if 'last' in profile: lastdict = profile.get_dict('last') last = Node.void('last') game.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id', -1))) last.add_child(Node.u8('music_type', lastdict.get_int('music_type'))) last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) last.add_child(Node.u8('narrow_down', lastdict.get_int('narrow_down'))) last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) last.add_child(Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) last.add_child(Node.u16('comment_id', lastdict.get_int('comment_id'))) last.add_child(Node.u8('gauge_option', lastdict.get_int('gauge_option'))) # Item unlocks itemnode = Node.void('item') game.add_child(itemnode) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements(self.game, self.version, userid) for item in achievements: if item.type[:5] != 'item_': continue itemtype = int(item.type[5:]) if game_config.get_bool('force_unlock_songs') and itemtype == self.GAME_CATALOG_TYPE_SONG: # Don't echo unlocked songs, we will add all of them later continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', itemtype)) info.add_child(Node.u32('id', item.id)) info.add_child(Node.u32('param', item.data.get_int('param'))) if 'diff_param' in item.data: info.add_child(Node.s32('diff_param', item.data.get_int('diff_param'))) if game_config.get_bool('force_unlock_songs'): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 if song.data.get_int('difficulty') > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) info.add_child(Node.u32('id', itemid)) info.add_child(Node.u32('param', ids[itemid])) return game
def get_ghost( self, ghost_type: int, parameter: str, ghost_length: int, musicid: int, chart: int, userid: UserID, ) -> Optional[Dict[str, Any]]: ghost_score: Dict[str, Any] = None if ghost_type == self.GHOST_TYPE_RIVAL: rival_extid = int(parameter) rival_userid = self.data.remote.user.from_extid( self.game, self.version, rival_extid) if rival_userid is not None: rival_profile = self.get_profile(rival_userid) rival_score = self.data.remote.music.get_score( self.game, self.music_version, rival_userid, musicid, chart) if rival_score is not None and rival_profile is not None: ghost_score = { 'score': rival_score.points, 'ghost': rival_score.data.get_bytes('ghost'), 'name': rival_profile.get_str('name'), 'pid': rival_profile.get_int('pid'), } if (ghost_type == self.GHOST_TYPE_GLOBAL_TOP or ghost_type == self.GHOST_TYPE_LOCAL_TOP or ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE): if (ghost_type == self.GHOST_TYPE_LOCAL_TOP or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE): all_scores = sorted( self.data.local.music.get_all_scores( game=self.game, version=self.music_version, songid=musicid, songchart=chart), key=lambda s: s[1].points, reverse=True, ) # Figure out what arcade this user joined and filter scores by # other users who have also joined that arcade. my_profile = self.get_profile(userid) if my_profile is None: my_profile = ValidatedDict() if 'shop_location' in my_profile: shop_id = my_profile.get_int('shop_location') machine = self.get_machine_by_id(shop_id) else: machine = None if machine is not None: all_scores = [ score for score in all_scores if self.user_joined_arcade( machine, self.get_any_profile(score[0])) ] else: # Not joined an arcade, so nobody matches our scores all_scores = [] else: all_scores = sorted( self.data.remote.music.get_all_scores( game=self.game, version=self.music_version, songid=musicid, songchart=chart), key=lambda s: s[1].points, reverse=True, ) if (ghost_type == self.GHOST_TYPE_GLOBAL_TOP or ghost_type == self.GHOST_TYPE_LOCAL_TOP): for potential_top in all_scores: top_userid = potential_top[0] top_score = potential_top[1] top_profile = self.get_any_profile(top_userid) if top_profile is not None: ghost_score = { 'score': top_score.points, 'ghost': top_score.data.get_bytes('ghost'), 'name': top_profile.get_str('name'), 'pid': top_profile.get_int('pid'), 'extid': top_profile.get_int('extid'), } break if (ghost_type == self.GHOST_TYPE_GLOBAL_AVERAGE or ghost_type == self.GHOST_TYPE_LOCAL_AVERAGE): average_score, delta_ghost = self.delta_score( [score[1] for score in all_scores], ghost_length) if average_score is not None and delta_ghost is not None: ghost_score = { 'score': average_score, 'ghost': bytes([0] * ghost_length), } if (ghost_type == self.GHOST_TYPE_DAN_TOP or ghost_type == self.GHOST_TYPE_DAN_AVERAGE): is_dp = chart not in [ self.CHART_TYPE_N7, self.CHART_TYPE_H7, self.CHART_TYPE_A7, ] my_profile = self.get_profile(userid) if my_profile is None: my_profile = ValidatedDict() if is_dp: dan_rank = my_profile.get_int(self.DAN_RANKING_DOUBLE, -1) else: dan_rank = my_profile.get_int(self.DAN_RANKING_SINGLE, -1) if dan_rank != -1: all_scores = sorted( self.data.local.music.get_all_scores( game=self.game, version=self.music_version, songid=musicid, songchart=chart), key=lambda s: s[1].points, reverse=True, ) all_profiles = self.data.local.user.get_all_profiles( self.game, self.version) relevant_userids = { profile[0] for profile in all_profiles if profile[1].get_int(self.DAN_RANKING_DOUBLE if is_dp else self.DAN_RANKING_SINGLE) == dan_rank } relevant_scores = [ score for score in all_scores if score[0] in relevant_userids ] if ghost_type == self.GHOST_TYPE_DAN_TOP: for potential_top in relevant_scores: top_userid = potential_top[0] top_score = potential_top[1] top_profile = self.get_any_profile(top_userid) if top_profile is not None: ghost_score = { 'score': top_score.points, 'ghost': top_score.data.get_bytes('ghost'), 'name': top_profile.get_str('name'), 'pid': top_profile.get_int('pid'), 'extid': top_profile.get_int('extid'), } break if ghost_type == self.GHOST_TYPE_DAN_AVERAGE: average_score, delta_ghost = self.delta_score( [score[1] for score in relevant_scores], ghost_length) if average_score is not None and delta_ghost is not None: ghost_score = { 'score': average_score, 'ghost': bytes([0] * ghost_length), } if (ghost_type == self.GHOST_TYPE_RIVAL_TOP or ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE): rival_extids = [int(e[1:-1]) for e in parameter.split(',')] rival_userids = [ self.data.remote.user.from_extid(self.game, self.version, rival_extid) for rival_extid in rival_extids ] all_scores = sorted( [ score for score in self.data.remote.music.get_all_scores( game=self.game, version=self.music_version, songid=musicid, songchart=chart) if score[0] in rival_userids ], key=lambda s: s[1].points, reverse=True, ) if ghost_type == self.GHOST_TYPE_RIVAL_TOP: for potential_top in all_scores: top_userid = potential_top[0] top_score = potential_top[1] top_profile = self.get_any_profile(top_userid) if top_profile is not None: ghost_score = { 'score': top_score.points, 'ghost': top_score.data.get_bytes('ghost'), 'name': top_profile.get_str('name'), 'pid': top_profile.get_int('pid'), 'extid': top_profile.get_int('extid'), } break if ghost_type == self.GHOST_TYPE_RIVAL_AVERAGE: average_score, delta_ghost = self.delta_score( [score[1] for score in all_scores], ghost_length) if average_score is not None and delta_ghost is not None: ghost_score = { 'score': average_score, 'ghost': bytes([0] * ghost_length), } return ghost_score
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('playerdata') # Set up the base profile base = Node.void('base') root.add_child(base) base.add_child(Node.string('name', profile.get_str('name', 'なし'))) base.add_child( Node.string('g_pm_id', ID.format_extid(profile.get_int('extid')))) base.add_child(Node.u8('mode', profile.get_int('mode', 0))) base.add_child(Node.s8('button', profile.get_int('button', 0))) base.add_child( Node.s8('last_play_flag', profile.get_int('last_play_flag', -1))) base.add_child( Node.u8('medal_and_friend', profile.get_int('medal_and_friend', 0))) base.add_child(Node.s8('category', profile.get_int('category', -1))) base.add_child( Node.s8('sub_category', profile.get_int('sub_category', -1))) base.add_child(Node.s16('chara', profile.get_int('chara', -1))) base.add_child( Node.s8('chara_category', profile.get_int('chara_category', -1))) base.add_child(Node.u8('collabo', profile.get_int('collabo', 255))) base.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) base.add_child(Node.s8('tutorial', profile.get_int('tutorial', 0))) base.add_child( Node.s32('music_open_pt', profile.get_int('music_open_pt', 0))) base.add_child(Node.s8('is_conv', -1)) base.add_child(Node.s32('option', profile.get_int('option', 0))) base.add_child(Node.s16('music', profile.get_int('music', -1))) base.add_child(Node.u16('ep', profile.get_int('ep', 0))) base.add_child( Node.s32_array('sp_color_flg', profile.get_int_array('sp_color_flg', 2))) base.add_child(Node.s32('read_news', profile.get_int('read_news', 0))) base.add_child( Node.s16('consecutive_days_coupon', profile.get_int('consecutive_days_coupon', 0))) base.add_child(Node.s8('staff', 0)) # Player card section player_card_dict = profile.get_dict('player_card') player_card = Node.void('player_card') root.add_child(player_card) player_card.add_child( Node.u8_array('title', player_card_dict.get_int_array('title', 2, [0, 1]))) player_card.add_child( Node.u8('frame', player_card_dict.get_int('frame'))) player_card.add_child(Node.u8('base', player_card_dict.get_int('base'))) player_card.add_child( Node.u8_array('seal', player_card_dict.get_int_array('seal', 2))) player_card.add_child( Node.s32_array('get_title', player_card_dict.get_int_array('get_title', 4))) player_card.add_child( Node.s32('get_frame', player_card_dict.get_int('get_frame'))) player_card.add_child( Node.s32('get_base', player_card_dict.get_int('get_base'))) player_card.add_child( Node.s32_array('get_seal', player_card_dict.get_int_array('get_seal', 2))) # Player card EX section player_card_ex = Node.void('player_card_ex') root.add_child(player_card_ex) player_card_ex.add_child( Node.s32('get_title_ex', player_card_dict.get_int('get_title_ex'))) player_card_ex.add_child( Node.s32('get_frame_ex', player_card_dict.get_int('get_frame_ex'))) player_card_ex.add_child( Node.s32('get_base_ex', player_card_dict.get_int('get_base_ex'))) player_card_ex.add_child( Node.s32('get_seal_ex', player_card_dict.get_int('get_seal_ex'))) # Statistics section and scores section statistics = self.get_play_statistics(userid) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 base.add_child(Node.u8('active_fr_num', 0)) # TODO: Hook up rivals code? base.add_child( Node.s32('total_play_cnt', statistics.get_int('total_plays', 0))) base.add_child(Node.s16('today_play_cnt', today_count)) base.add_child( Node.s16('consecutive_days', statistics.get_int('consecutive_days', 0))) last_played = [ x[0] for x in self.data.local.music.get_last_played( self.game, self.version, userid, 3) ] most_played = [ x[0] for x in self.data.local.music.get_most_played( self.game, self.version, userid, 20) ] while len(last_played) < 3: last_played.append(-1) while len(most_played) < 20: most_played.append(-1) hiscore_array = [0] * int( (((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) clear_medal = [0] * self.GAME_MAX_MUSIC_ID clear_medal_sub = [0] * self.GAME_MAX_MUSIC_ID scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: if score.id > self.GAME_MAX_MUSIC_ID: continue # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points clear_medal[score.id] = clear_medal[ score.id] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER_POSITION, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX_POSITION, }[score.chart] hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = points << hiscore_bit_pos hiscore_array[hiscore_byte_pos] = hiscore_array[ hiscore_byte_pos] | (hiscore_value & 0xFF) hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ( (hiscore_value >> 8) & 0xFF) hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ( (hiscore_value >> 16) & 0xFF) hiscore = bytes(hiscore_array) player_card.add_child(Node.s16_array('best_music', most_played[0:3])) base.add_child(Node.s16_array('my_best', most_played)) base.add_child(Node.s16_array('latest_music', last_played)) base.add_child(Node.u16_array('clear_medal', clear_medal)) base.add_child(Node.u8_array('clear_medal_sub', clear_medal_sub)) # Goes outside of base for some reason root.add_child(Node.binary('hiscore', hiscore)) # Net VS section netvs = Node.void('netvs') root.add_child(netvs) netvs.add_child(Node.s32_array('get_ojama', [0, 0])) netvs.add_child(Node.s32('rank_point', 0)) netvs.add_child(Node.s32('play_point', 0)) netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) netvs.add_child(Node.u8('rank', 0)) netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) netvs.add_child(Node.s8_array('jewelry', [0] * 15)) for dialog in [0, 1, 2, 3, 4, 5]: # TODO: Configure this, maybe? netvs.add_child(Node.string('dialog', 'dialog#{}'.format(dialog))) sp_data = Node.void('sp_data') root.add_child(sp_data) sp_data.add_child(Node.s32('sp', profile.get_int('sp', 0))) reflec_data = Node.void('reflec_data') root.add_child(reflec_data) reflec_data.add_child( Node.s8_array('reflec', profile.get_int_array('reflec', 2))) # Navigate section navigate_dict = profile.get_dict('navigate') navigate = Node.void('navigate') root.add_child(navigate) navigate.add_child(Node.s8('genre', navigate_dict.get_int('genre'))) navigate.add_child(Node.s8('image', navigate_dict.get_int('image'))) navigate.add_child(Node.s8('level', navigate_dict.get_int('level'))) navigate.add_child(Node.s8('ojama', navigate_dict.get_int('ojama'))) navigate.add_child( Node.s16('limit_num', navigate_dict.get_int('limit_num'))) navigate.add_child(Node.s8('button', navigate_dict.get_int('button'))) navigate.add_child(Node.s8('life', navigate_dict.get_int('life'))) navigate.add_child( Node.s16('progress', navigate_dict.get_int('progress'))) return root