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], }
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)
def update_course( self, userid: UserID, coursetype: str, courseid: int, chart: int, clear_status: int, pgreats: int, greats: int, ) -> None: # Range check course type if coursetype not in [ self.COURSE_TYPE_SECRET, self.COURSE_TYPE_INTERNET_RANKING, self.COURSE_TYPE_CLASSIC, ]: raise Exception(f"Invalid course type value {coursetype}") # Range check medals if clear_status not in [ self.CLEAR_STATUS_NO_PLAY, self.CLEAR_STATUS_FAILED, self.CLEAR_STATUS_ASSIST_CLEAR, self.CLEAR_STATUS_EASY_CLEAR, self.CLEAR_STATUS_CLEAR, self.CLEAR_STATUS_HARD_CLEAR, self.CLEAR_STATUS_EX_HARD_CLEAR, self.CLEAR_STATUS_FULL_COMBO, ]: raise Exception(f"Invalid clear status value {clear_status}") # Update achievement to track course statistics course_score = self.data.local.user.get_achievement( self.game, self.version, userid, courseid * 6 + chart, coursetype, ) if course_score is None: course_score = ValidatedDict() course_score.replace_int( 'clear_status', max(clear_status, course_score.get_int('clear_status'))) old_ex_score = (course_score.get_int('pgnum') * 2) + course_score.get_int('gnum') if old_ex_score < ((pgreats * 2) + greats): course_score.replace_int('pgnum', pgreats) course_score.replace_int('gnum', greats) self.data.local.user.put_achievement( self.game, self.version, userid, courseid * 6 + chart, coursetype, course_score, )
def update_rank( self, userid: UserID, dantype: str, rank: int, percent: int, cleared: bool, stages_cleared: int, ) -> None: # Range check type if dantype not in [ self.DAN_RANKING_SINGLE, self.DAN_RANKING_DOUBLE, ]: raise Exception(f"Invalid dan rank type value {dantype}") # Range check rank if rank not in [ self.DAN_RANK_7_KYU, self.DAN_RANK_6_KYU, self.DAN_RANK_5_KYU, self.DAN_RANK_4_KYU, self.DAN_RANK_3_KYU, self.DAN_RANK_2_KYU, self.DAN_RANK_1_KYU, self.DAN_RANK_1_DAN, self.DAN_RANK_2_DAN, self.DAN_RANK_3_DAN, self.DAN_RANK_4_DAN, self.DAN_RANK_5_DAN, self.DAN_RANK_6_DAN, self.DAN_RANK_7_DAN, self.DAN_RANK_8_DAN, self.DAN_RANK_9_DAN, self.DAN_RANK_10_DAN, self.DAN_RANK_CHUDEN, self.DAN_RANK_KAIDEN, ]: raise Exception(f"Invalid dan rank {rank}") if cleared: # Update profile if needed profile = self.get_profile(userid) if profile is None: profile = ValidatedDict() profile.replace_int(dantype, max(rank, profile.get_int(dantype, -1))) self.put_profile(userid, profile) # Update achievement to track pass rate dan_score = self.data.local.user.get_achievement( self.game, self.version, userid, rank, dantype, ) if dan_score is None: dan_score = ValidatedDict() dan_score.replace_int('percent', max(percent, dan_score.get_int('percent'))) dan_score.replace_int( 'stages_cleared', max(stages_cleared, dan_score.get_int('stages_cleared'))) self.data.local.user.put_achievement(self.game, self.version, userid, rank, dantype, dan_score)
def update_score( self, userid: Optional[UserID], songid: int, chart: int, clear_status: int, pgreats: int, greats: int, miss_count: int, ghost: Optional[bytes], shop: Optional[int], ) -> None: """ Given various pieces of a score, update the user's high score and score history in a controlled manner, so all games in IIDX series can expect the same attributes in a score. Note that the medals passed here are expected to be converted from game identifier to our internal identifier, so that any game in the series may convert them back. In this way, a song played on Pendual that exists in Tricoro will still have scores/medals going back all versions. """ # Range check medals if clear_status not in [ self.CLEAR_STATUS_NO_PLAY, self.CLEAR_STATUS_FAILED, self.CLEAR_STATUS_ASSIST_CLEAR, self.CLEAR_STATUS_EASY_CLEAR, self.CLEAR_STATUS_CLEAR, self.CLEAR_STATUS_HARD_CLEAR, self.CLEAR_STATUS_EX_HARD_CLEAR, self.CLEAR_STATUS_FULL_COMBO, ]: raise Exception(f"Invalid clear status value {clear_status}") # Calculate ex score ex_score = (2 * pgreats) + greats if userid is not None: if ghost is None: raise Exception("Expected a ghost for user score save!") oldscore = self.data.local.music.get_score( self.game, self.music_version, userid, songid, chart, ) else: # Storing an anonymous attempt if ghost is not None: raise Exception("Expected no ghost for anonymous score save!") oldscore = None # Score history is verbatum, instead of highest score history = ValidatedDict({ 'clear_status': clear_status, 'miss_count': miss_count, }) old_ex_score = ex_score if ghost is not None: history['ghost'] = ghost if oldscore is None: # If it is a new score, create a new dictionary to add to scoredata = ValidatedDict({ 'clear_status': clear_status, 'miss_count': miss_count, 'pgreats': pgreats, 'greats': greats, }) if ghost is not None: scoredata['ghost'] = ghost raised = True highscore = True else: # Set the score to any new record achieved raised = ex_score > oldscore.points highscore = ex_score >= oldscore.points ex_score = max(ex_score, oldscore.points) scoredata = oldscore.data scoredata.replace_int( 'clear_status', max(scoredata.get_int('clear_status'), clear_status)) if raised: scoredata.replace_int('miss_count', miss_count) scoredata.replace_int('pgreats', pgreats) scoredata.replace_int('greats', greats) if ghost is not None: scoredata.replace_bytes('ghost', ghost) if shop is not None: history.replace_int('shop', shop) scoredata.replace_int('shop', shop) # Look up where this score was earned lid = self.get_machine_id() if userid is not None: # Write the new score back self.data.local.music.put_score( self.game, self.music_version, userid, songid, chart, lid, ex_score, scoredata, highscore, ) # Save the history of this score too self.data.local.music.put_attempt( self.game, self.music_version, userid, songid, chart, lid, old_ex_score, history, raised, )
def update_score( self, userid: UserID, songid: int, chart: int, points: int, achievement_rate: int, clear_type: int, combo_type: int, miss_count: int, combo: Optional[int] = None, stats: Optional[Dict[str, int]] = None, param: Optional[int] = None, kflag: Optional[int] = None, ) -> None: """ Given various pieces of a score, update the user's high score and score history in a controlled manner, so all games in Reflec series can expect the same attributes in a score. Note that the clear_types passed here are expected to be converted from game identifier to our internal identifier, so that any game in the series may convert them back. """ # Range check clear type if clear_type not in [ self.CLEAR_TYPE_NO_PLAY, self.CLEAR_TYPE_FAILED, self.CLEAR_TYPE_CLEARED, self.CLEAR_TYPE_HARD_CLEARED, self.CLEAR_TYPE_S_HARD_CLEARED, ]: raise Exception(f"Invalid clear_type value {clear_type}") # Range check combo type if combo_type not in [ self.COMBO_TYPE_NONE, self.COMBO_TYPE_ALMOST_COMBO, self.COMBO_TYPE_FULL_COMBO, self.COMBO_TYPE_FULL_COMBO_ALL_JUST, ]: raise Exception(f"Invalid combo_type value {combo_type}") oldscore = self.data.local.music.get_score( self.game, self.version, userid, songid, chart, ) # Score history is verbatum, instead of highest score now = Time.now() history = ValidatedDict({}) oldpoints = points if oldscore is None: # If it is a new score, create a new dictionary to add to scoredata = ValidatedDict({}) highscore = True else: # Set the score to any new record achieved highscore = points >= oldscore.points points = max(points, oldscore.points) scoredata = oldscore.data # Update the last played time scoredata.replace_int('last_played_time', now) # Replace clear type with highest value and timestamps if clear_type >= scoredata.get_int('clear_type'): scoredata.replace_int( 'clear_type', max(scoredata.get_int('clear_type'), clear_type)) scoredata.replace_int('best_clear_type_time', now) history.replace_int('clear_type', clear_type) # Replace combo type with highest value and timestamps if combo_type >= scoredata.get_int('combo_type'): scoredata.replace_int( 'combo_type', max(scoredata.get_int('combo_type'), combo_type)) scoredata.replace_int('best_clear_type_time', now) history.replace_int('combo_type', combo_type) # Update the combo for this song if combo is not None: scoredata.replace_int('combo', max(scoredata.get_int('combo'), combo)) history.replace_int('combo', combo) # Update the param for this song if param is not None: scoredata.replace_int('param', max(scoredata.get_int('param'), param)) history.replace_int('param', param) # Update the kflag for this song if kflag is not None: scoredata.replace_int('kflag', max(scoredata.get_int('kflag'), kflag)) history.replace_int('kflag', kflag) # Update win/lost/draw stats for this song if stats is not None: scoredata.replace_dict('stats', stats) history.replace_dict('stats', stats) # Update the achievement rate with timestamps if achievement_rate >= scoredata.get_int('achievement_rate'): scoredata.replace_int( 'achievement_rate', max(scoredata.get_int('achievement_rate'), achievement_rate)) scoredata.replace_int('best_achievement_rate_time', now) history.replace_int('achievement_rate', achievement_rate) # Update the miss count with timestamps, either if it was lowered, or if the old value was blank. # If the new value is -1 (we didn't get a miss count this time), never update the old value. if miss_count >= 0: if miss_count <= scoredata.get_int( 'miss_count', 999999) or scoredata.get_int('miss_count') == -1: scoredata.replace_int( 'miss_count', min(scoredata.get_int('miss_count', 999999), miss_count)) scoredata.replace_int('best_miss_count_time', now) history.replace_int('miss_count', miss_count) # Look up where this score was earned lid = self.get_machine_id() # Reflec Beat happens to send all songs that were played by a player # at the end of the round. It sends timestamps for the songs, but as of # Colette they were identical for each song in the round. So, if a user # plays the same song/chart# more than once in a round, we will end up # failing to store the attempt since we don't allow two of the same # attempt at the same time for the same user and song/chart. So, bump # the timestamp by one second and retry well past the maximum number of # songs. for bump in range(10): timestamp = now + bump # Write the new score back self.data.local.music.put_score( self.game, self.version, userid, songid, chart, lid, points, scoredata, highscore, timestamp=timestamp, ) try: # Save the history of this score too self.data.local.music.put_attempt( self.game, self.version, userid, songid, chart, lid, oldpoints, history, highscore, timestamp=timestamp, ) except ScoreSaveException: # Try again one second in the future continue # We saved successfully break
def 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
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, 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, )
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