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, )
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