예제 #1
0
def updatesettings(arcadeid: int) -> Dict[str, Any]:
    # Attempt to look this arcade up
    arcade = g.data.local.machine.get_arcade(arcadeid)

    if arcade is None:
        raise Exception('Unable to find arcade to update!')
    if g.userID not in arcade.owners:
        raise Exception('You don\'t own this arcade, refusing to update!')

    game = request.get_json()['game']
    version = request.get_json()['version']

    for game_setting in request.get_json()['bools']:
        # Grab the value to update
        category = game_setting['category']
        setting = game_setting['setting']
        new_value = game_setting['value']

        # Update the value
        current_settings = g.data.local.machine.get_settings(
            arcade.id, game, version, category)
        if current_settings is None:
            current_settings = ValidatedDict()

        current_settings.replace_bool(setting, new_value)

        # Save it back
        g.data.local.machine.put_settings(arcade.id, game, version, category,
                                          current_settings)

    for game_setting in request.get_json()['ints']:
        # Grab the value to update
        category = game_setting['category']
        setting = game_setting['setting']
        new_value = game_setting['value']

        # Update the value
        current_settings = g.data.local.machine.get_settings(
            arcade.id, game, version, category)
        if current_settings is None:
            current_settings = ValidatedDict()

        current_settings.replace_int(setting, new_value)

        # Save it back
        g.data.local.machine.put_settings(arcade.id, game, version, category,
                                          current_settings)

    # Return the updated value
    return {
        'game_settings': [
            gs for gs in get_game_settings(arcade)
            if gs['game'] == game and gs['version'] == version
        ][0],
    }
예제 #2
0
    def new_profile_by_refid(
            self,
            refid: Optional[str],
            name: Optional[str],
            chara: Optional[int] = None,
            achievements: Sequence[Achievement] = (),
    ) -> Node:
        """
        Given a RefID and an optional name, create a profile and then return
        a formatted profile node. Similar rationale to get_profile_by_refid.
        """
        if refid is None:
            return None

        if name is None:
            name = 'なし'

        userid = self.data.remote.user.from_refid(self.game, self.version,
                                                  refid)
        if userid is None:
            raise Exception("Logic error! Didn't find user to tie profile to!")
        defaultprofile = ValidatedDict({
            'name': name,
        })
        if chara is not None:
            defaultprofile.replace_int('chara', chara)
        self.put_profile(userid, defaultprofile)
        for achievement in achievements:
            self.data.local.user.put_achievement(
                self.game,
                self.version,
                userid,
                achievement.id,
                achievement.type,
                achievement.data,
            )

        profile = self.get_profile(userid)
        if profile is None:
            raise Exception(
                "Logic error! Didn't find profile after writing it!")
        return self.format_profile(userid, profile)
예제 #3
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,
        )
예제 #4
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)
예제 #5
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,
        )
예제 #6
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
예제 #7
0
    def update_score(
        self,
        userid: UserID,
        songid: int,
        chart: int,
        points: int,
        medal: int,
        combo: Optional[int] = None,
        stats: Optional[Dict[str, 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 Pop'n 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 Pop'n 22 that exists in Pop'n 19 will still have scores/medals
        going back all versions.
        """
        # Range check medals
        if medal not in [
                self.PLAY_MEDAL_CIRCLE_FAILED,
                self.PLAY_MEDAL_DIAMOND_FAILED,
                self.PLAY_MEDAL_STAR_FAILED,
                self.PLAY_MEDAL_EASY_CLEAR,
                self.PLAY_MEDAL_CIRCLE_CLEARED,
                self.PLAY_MEDAL_DIAMOND_CLEARED,
                self.PLAY_MEDAL_STAR_CLEARED,
                self.PLAY_MEDAL_CIRCLE_FULL_COMBO,
                self.PLAY_MEDAL_DIAMOND_FULL_COMBO,
                self.PLAY_MEDAL_STAR_FULL_COMBO,
                self.PLAY_MEDAL_PERFECT,
        ]:
            raise Exception("Invalid medal value {}".format(medal))

        oldscore = self.data.local.music.get_score(
            self.game,
            self.version,
            userid,
            songid,
            chart,
        )

        # Score history is verbatum, instead of highest score
        history = ValidatedDict({})
        oldpoints = points

        if oldscore is None:
            # If it is a new score, create a new dictionary to add to
            scoredata = ValidatedDict({})
            raised = True
            highscore = True
        else:
            # Set the score to any new record achieved
            raised = points > oldscore.points
            highscore = points >= oldscore.points
            points = max(points, oldscore.points)
            scoredata = oldscore.data

        # Replace medal with highest value
        scoredata.replace_int('medal', max(scoredata.get_int('medal'), medal))
        history.replace_int('medal', medal)

        if stats is not None:
            if raised:
                # We have stats, and there's a new high score, update the stats
                scoredata.replace_dict('stats', stats)
            history.replace_dict('stats', stats)

        if combo is not None:
            # If we have a combo, replace it
            scoredata.replace_int('combo',
                                  max(scoredata.get_int('combo'), combo))
            history.replace_int('combo', combo)

        # Look up where this score was earned
        lid = self.get_machine_id()

        # Pop'n Music for all versions before Lapistoria sends all of the songs
        # a player played at the end of the round. It doesn't send timestamps
        # for those songs (Jubeat does). 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.
        now = Time.now()
        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,
                    raised,
                    timestamp=timestamp,
                )
            except ScoreSaveException:
                # Try again one second in the future
                continue

            # We saved successfully
            break
예제 #8
0
    def update_score(
        self,
        userid: UserID,
        timestamp: int,
        songid: int,
        chart: int,
        points: int,
        medal: int,
        combo: int,
        ghost: Optional[List[int]] = None,
        stats: Optional[Dict[str, int]] = None,
        music_rate: 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 Jubeat series can expect
        the same attributes in a score.
        """
        # Range check medals
        if medal not in [
                self.PLAY_MEDAL_FAILED,
                self.PLAY_MEDAL_CLEARED,
                self.PLAY_MEDAL_NEARLY_FULL_COMBO,
                self.PLAY_MEDAL_FULL_COMBO,
                self.PLAY_MEDAL_NEARLY_EXCELLENT,
                self.PLAY_MEDAL_EXCELLENT,
        ]:
            raise Exception(f"Invalid medal value {medal}")

        oldscore = self.data.local.music.get_score(
            self.game,
            self.music_version,
            userid,
            songid,
            chart,
        )

        # Score history is verbatum, instead of highest score
        history = ValidatedDict({})
        oldpoints = points

        if oldscore is None:
            # If it is a new score, create a new dictionary to add to
            scoredata = ValidatedDict({})
            raised = True
            highscore = True
        else:
            # Set the score to any new record achieved
            raised = points > oldscore.points
            highscore = points >= oldscore.points
            points = max(oldscore.points, points)
            scoredata = oldscore.data

        # Replace medal with highest value
        scoredata.replace_int('medal', max(scoredata.get_int('medal'), medal))
        history.replace_int('medal', medal)

        # Increment counters based on medal
        if medal == self.PLAY_MEDAL_CLEARED:
            scoredata.increment_int('clear_count')
        if medal == self.PLAY_MEDAL_FULL_COMBO:
            scoredata.increment_int('full_combo_count')
        if medal == self.PLAY_MEDAL_EXCELLENT:
            scoredata.increment_int('excellent_count')

        # If we have a combo, replace it
        scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
        history.replace_int('combo', combo)

        if stats is not None:
            if raised:
                # We have stats, and there's a new high score, update the stats
                scoredata.replace_dict('stats', stats)
            history.replace_dict('stats', stats)

        if ghost is not None:
            # Update the ghost regardless, but don't bother with it in history
            scoredata.replace_int_array('ghost', len(ghost), ghost)

        if music_rate is not None:
            if oldscore is not None:
                if music_rate > oldscore.data.get_int('music_rate'):
                    scoredata.replace_int('music_rate', music_rate)
            else:
                scoredata.replace_int('music_rate', music_rate)
            history.replace_int('music_rate', music_rate)

        # Look up where this score was earned
        lid = self.get_machine_id()

        # Write the new score back
        self.data.local.music.put_score(
            self.game,
            self.music_version,
            userid,
            songid,
            chart,
            lid,
            points,
            scoredata,
            highscore,
            timestamp=timestamp,
        )

        # Save the history of this score too
        self.data.local.music.put_attempt(
            self.game,
            self.music_version,
            userid,
            songid,
            chart,
            lid,
            oldpoints,
            history,
            raised,
            timestamp=timestamp,
        )
예제 #9
0
    def update_score(
        self,
        userid: Optional[UserID],
        songid: int,
        chart: int,
        points: int,
        clear_type: int,
        grade: int,
        combo: int,
        stats: Optional[Dict[str, 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 SDVX series can expect
        the same attributes in a score.
        """
        # Range check clear type
        if clear_type not in [
                self.CLEAR_TYPE_NO_PLAY,
                self.CLEAR_TYPE_FAILED,
                self.CLEAR_TYPE_CLEAR,
                self.CLEAR_TYPE_HARD_CLEAR,
                self.CLEAR_TYPE_ULTIMATE_CHAIN,
                self.CLEAR_TYPE_PERFECT_ULTIMATE_CHAIN,
        ]:
            raise Exception(f"Invalid clear type value {clear_type}")

        #  Range check grade
        if grade not in [
                self.GRADE_NO_PLAY,
                self.GRADE_D,
                self.GRADE_C,
                self.GRADE_B,
                self.GRADE_A,
                self.GRADE_A_PLUS,
                self.GRADE_AA,
                self.GRADE_AA_PLUS,
                self.GRADE_AAA,
                self.GRADE_AAA_PLUS,
                self.GRADE_S,
        ]:
            raise Exception(f"Invalid clear type value {grade}")

        if userid is not None:
            oldscore = self.data.local.music.get_score(
                self.game,
                self.version,
                userid,
                songid,
                chart,
            )
        else:
            oldscore = None

        # Score history is verbatum, instead of highest score
        history = ValidatedDict({})
        oldpoints = points

        if oldscore is None:
            # If it is a new score, create a new dictionary to add to
            scoredata = ValidatedDict({})
            raised = True
            highscore = True
        else:
            # Set the score to any new record achieved
            raised = points > oldscore.points
            highscore = points >= oldscore.points
            points = max(oldscore.points, points)
            scoredata = oldscore.data

        # Replace clear type and grade
        scoredata.replace_int('clear_type',
                              max(scoredata.get_int('clear_type'), clear_type))
        history.replace_int('clear_type', clear_type)
        scoredata.replace_int('grade', max(scoredata.get_int('grade'), grade))
        history.replace_int('grade', grade)

        # If we have a combo, replace it
        scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))
        history.replace_int('combo', combo)

        # If we have play stats, replace it
        if stats is not None:
            if raised:
                # We have stats, and there's a new high score, update the stats
                scoredata.replace_dict('stats', stats)
            history.replace_dict('stats', stats)

        # 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.version,
                userid,
                songid,
                chart,
                lid,
                points,
                scoredata,
                highscore,
            )

        # Save the history of this score too
        self.data.local.music.put_attempt(
            self.game,
            self.version,
            userid,
            songid,
            chart,
            lid,
            oldpoints,
            history,
            raised,
        )
예제 #10
0
    def update_score(
        self,
        userid: Optional[UserID],
        songid: int,
        chart: int,
        points: int,
        rank: int,
        halo: int,
        combo: int,
        trace: Optional[List[int]] = None,
        ghost: Optional[str] = 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 DDR series can expect
        the same attributes in a score.
        """
        if chart not in [
                self.CHART_SINGLE_BEGINNER,
                self.CHART_SINGLE_BASIC,
                self.CHART_SINGLE_DIFFICULT,
                self.CHART_SINGLE_EXPERT,
                self.CHART_SINGLE_CHALLENGE,
                self.CHART_DOUBLE_BEGINNER,
                self.CHART_DOUBLE_BASIC,
                self.CHART_DOUBLE_DIFFICULT,
                self.CHART_DOUBLE_EXPERT,
                self.CHART_DOUBLE_CHALLENGE,
        ]:
            raise Exception('Invalid chart {}'.format(chart))
        if halo not in [
                self.HALO_NONE,
                self.HALO_GOOD_FULL_COMBO,
                self.HALO_GREAT_FULL_COMBO,
                self.HALO_PERFECT_FULL_COMBO,
                self.HALO_MARVELOUS_FULL_COMBO,
        ]:
            raise Exception('Invalid halo {}'.format(halo))
        if rank not in [
                self.RANK_E,
                self.RANK_D,
                self.RANK_D_PLUS,
                self.RANK_C_MINUS,
                self.RANK_C,
                self.RANK_C_PLUS,
                self.RANK_B_MINUS,
                self.RANK_B,
                self.RANK_B_PLUS,
                self.RANK_A_MINUS,
                self.RANK_A,
                self.RANK_A_PLUS,
                self.RANK_AA_MINUS,
                self.RANK_AA,
                self.RANK_AA_PLUS,
                self.RANK_AAA,
        ]:
            raise Exception('Invalid rank {}'.format(rank))

        if userid is not None:
            oldscore = self.data.local.music.get_score(
                self.game,
                self.music_version,
                userid,
                songid,
                chart,
            )
        else:
            oldscore = None

        # 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({})
            raised = True
            highscore = True
        else:
            # Set the score to any new record achieved
            raised = points > oldscore.points
            highscore = points >= oldscore.points
            points = max(oldscore.points, points)
            scoredata = oldscore.data

        # Save combo
        history.replace_int('combo', combo)
        scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo))

        # Save halo
        history.replace_int('halo', halo)
        scoredata.replace_int('halo', max(scoredata.get_int('halo'), halo))

        # Save rank
        history.replace_int('rank', rank)
        scoredata.replace_int('rank', max(scoredata.get_int('rank'), rank))

        # Save ghost steps
        if trace is not None:
            history.replace_int_array('trace', len(trace), trace)
            if raised:
                scoredata.replace_int_array('trace', len(trace), trace)
        if ghost is not None:
            history.replace_str('ghost', ghost)
            if raised:
                scoredata.replace_str('ghost', ghost)

        # Look up where this score was earned
        lid = self.get_machine_id()

        # DDR sometimes 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

            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,
                    points,
                    scoredata,
                    highscore,
                    timestamp=timestamp,
                )

            try:
                # Save the history of this score too
                self.data.local.music.put_attempt(
                    self.game,
                    self.music_version,
                    userid,
                    songid,
                    chart,
                    lid,
                    oldpoints,
                    history,
                    raised,
                    timestamp=timestamp,
                )
            except ScoreSaveException:
                # Try again one second in the future
                continue

            # We saved successfully
            break