예제 #1
0
파일: base.py 프로젝트: vangar/bemaniutils
 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'),
     }
예제 #2
0
파일: bishi.py 프로젝트: vangar/bemaniutils
    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'),
        }
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
 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'),
     }
예제 #6
0
    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,
        )
예제 #7
0
    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
예제 #8
0
 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,
     }
예제 #9
0
파일: user.py 프로젝트: vangar/bemaniutils
    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
예제 #10
0
 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
예제 #11
0
파일: user.py 프로젝트: vangar/bemaniutils
    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
예제 #12
0
파일: user.py 프로젝트: vangar/bemaniutils
    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
예제 #13
0
파일: user.py 프로젝트: vangar/bemaniutils
    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
예제 #14
0
파일: booth.py 프로젝트: vangar/bemaniutils
    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
예제 #15
0
 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
예제 #16
0
    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,
            }
        }
예제 #17
0
 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
예제 #18
0
파일: ddr.py 프로젝트: vangar/bemaniutils
 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),
     }
예제 #19
0
파일: eclale.py 프로젝트: tumes/bemaniutils
    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
예제 #20
0
    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,
        )
예제 #21
0
 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
예제 #22
0
    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
예제 #23
0
    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
예제 #24
0
    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
예제 #25
0
    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
예제 #26
0
    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)
예제 #27
0
 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'),
     }
예제 #28
0
    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
예제 #29
0
    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
예제 #30
0
    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