def update_memberships(user_id, gamemode=Gamemode.STANDARD): """ Updates all the memberships for a given user and gamemode """ memberships = Membership.objects.select_related( "leaderboard", "leaderboard__score_filter").filter(user_id=user_id, leaderboard__gamemode=gamemode) user_stats = UserStats.objects.get(user_id=user_id, gamemode=gamemode) for membership in memberships: leaderboard = membership.leaderboard if leaderboard.score_filter: scores = user_stats.scores.apply_score_filter( leaderboard.score_filter) else: scores = user_stats.scores.all() scores = scores.get_score_set(score_set=leaderboard.score_set) membership.scores.set(scores) if leaderboard.score_set == ScoreSet.NORMAL: membership.pp = calculate_pp_total(score.pp for score in scores) elif leaderboard.score_set == ScoreSet.NEVER_CHOKE: membership.pp = calculate_pp_total( score.nochoke_pp if score.result & ScoreResult.CHOKE else score.pp for score in scores) elif leaderboard.score_set == ScoreSet.ALWAYS_FULL_COMBO: membership.pp = calculate_pp_total(score.nochoke_pp for score in scores) membership.save() return memberships
def __process_scores(self, *new_scores): """ Calculates pp totals (extra pp, nochoke pp) and scores style using unique maps, and returns all scores for UserStats and the scores that need to be added """ # Fetch all scores currently in database and add to new_scores ensuring no duplicate scores database_scores = self.scores.select_related("beatmap").all() database_score_dates = [score.date for score in database_scores] scores_to_create = [ score for score in new_scores if score.date not in database_score_dates ] scores = [*scores_to_create, *database_scores] # Sort all scores by pp scores.sort(key=lambda s: s.pp, reverse=True) # Filter to be unique on maps (cant use .unique_maps() because duplicate maps might come from new scores) unique_map_scores = [ score for score in scores if score == next( s for s in scores if s.beatmap_id == score.beatmap_id) ] # Calculate bonus pp (+ pp from non-top100 scores) self.extra_pp = self.pp - utils.calculate_pp_total( score.pp for score in unique_map_scores[:100]) # Calculate nochoke pp and mod pp if self.gamemode == Gamemode.STANDARD: self.nochoke_pp = utils.calculate_pp_total( sorted((score.nochoke_pp if score.result & ScoreResult.CHOKE else score.pp for score in unique_map_scores), reverse=True)) + self.extra_pp # Calculate score style top_100_scores = unique_map_scores[: 100] # score style limited to top 100 scores weighting_value = sum(0.95**i for i in range(100)) self.score_style_accuracy = sum( score.accuracy * (0.95**i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_bpm = sum( score.bpm * (0.95**i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_length = sum( score.length * (0.95**i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_cs = sum( score.circle_size * (0.95**i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_ar = sum( score.approach_rate * (0.95**i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_od = sum( score.overall_difficulty * (0.95**i) for i, score in enumerate(top_100_scores)) / weighting_value return scores, scores_to_create
def update_membership(self, user_id): """ Update a membership for a user_id ensuring all scores that fit the criteria are added """ # Get or create Membership model try: membership = self.memberships.select_for_update().get(user_id=user_id) # Clear all currently added scores membership.scores.clear() join_date = membership.join_date except Membership.DoesNotExist: if self.access_type in (LeaderboardAccessType.PUBLIC_INVITE_ONLY, LeaderboardAccessType.PRIVATE) and self.owner_id != user_id: # Check if user has been invited try: invitees = self.invitees.filter(id=user_id) except OsuUser.DoesNotExist: raise PermissionDenied("You must be invited to join this leaderboard.") # Invite is being accepted self.invitees.remove(*invitees) # Create new membership membership = Membership(user_id=user_id, leaderboard=self) join_date = datetime.now() # Get scores scores = Score.objects.filter( user_stats__user_id=user_id, gamemode=self.gamemode ) if not self.allow_past_scores: scores = scores.filter(date__gte=join_date) if self.score_filter: scores = scores.apply_score_filter(self.score_filter) scores = scores.get_score_set(score_set=self.score_set) # Add scores to membership if self.score_set == ScoreSet.NORMAL: membership.pp = calculate_pp_total(score.pp for score in scores) elif self.score_set == ScoreSet.NEVER_CHOKE: membership.pp = calculate_pp_total(score.nochoke_pp if score.result & ScoreResult.CHOKE else score.pp for score in scores) elif self.score_set == ScoreSet.ALWAYS_FULL_COMBO: membership.pp = calculate_pp_total(score.nochoke_pp for score in scores) membership.save() membership.scores.add(*scores) return membership
def __update_memberships(self, *scores): """ Updates memberships this UserStats' OsuUser has with Leaderboards with the scores passed """ memberships = self.user.memberships.select_for_update().select_related( "leaderboard").filter(leaderboard__gamemode=self.gamemode) # Using through model so we can bulk add all scores for all memberships at once membership_score_model = Score.membership_set.through membership_scores_to_add = [] for membership in memberships: # Find all scores matching criteria allowed_scores = [ score for score in scores if membership.score_is_allowed(score) ] # Filter for unique maps unique_map_scores = [] beatmap_ids = [] for score in allowed_scores: if score.beatmap_id not in beatmap_ids: unique_map_scores.append(score) beatmap_ids.append(score.beatmap_id) # Clear current scores so we can refresh them membership.scores.clear() # Create relations membership_scores = [ membership_score_model(score=score, membership=membership) for score in unique_map_scores ] membership_scores_to_add.extend(membership_scores) membership.pp = utils.calculate_pp_total( ms.score.pp for ms in membership_scores) # Bulk add scores to memberships and update memberships pp membership_score_model.objects.bulk_create(membership_scores_to_add, ignore_conflicts=True) self.user.memberships.bulk_update(memberships, ["pp"])
def __process_scores(self, *new_scores): """ Calculates pp totals (extra pp, nochoke pp) and scores style using unique maps, and returns all scores for UserStats and the scores that need to be added """ # Fetch all scores currently in database and add to new_scores ensuring no duplicate scores database_scores = self.scores.select_related("beatmap").filter(beatmap__status__in=[BeatmapStatus.RANKED, BeatmapStatus.APPROVED, BeatmapStatus.LOVED]) database_score_dates = [score.date for score in database_scores] scores_to_create = [score for score in new_scores if score.date not in database_score_dates] scores = [*[score for score in scores_to_create if score.beatmap.status in [BeatmapStatus.RANKED, BeatmapStatus.APPROVED, BeatmapStatus.LOVED]], *database_scores] # Sort all scores by pp scores.sort(key=lambda s: s.pp, reverse=True) # Filter to be unique on maps (cant use .unique_maps() because duplicate maps might come from new scores) # (also this 1 liner is really inefficient for some reason so lets do it the standard way) # unique_map_scores = [score for score in scores if score == next(s for s in scores if s.beatmap_id == score.beatmap_id)] unique_map_scores = [] beatmap_ids = [] for score in scores: if score.beatmap_id not in beatmap_ids: unique_map_scores.append(score) beatmap_ids.append(score.beatmap_id) # Calculate bonus pp (+ pp from non-top100 scores) self.extra_pp = self.pp - utils.calculate_pp_total(score.pp for score in unique_map_scores[:100]) # Calculate score style top_100_scores = unique_map_scores[:100] # score style limited to top 100 scores weighting_value = sum(0.95 ** i for i in range(100)) self.score_style_accuracy = sum(score.accuracy * (0.95 ** i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_bpm = sum(score.bpm * (0.95 ** i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_length = sum(score.length * (0.95 ** i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_cs = sum(score.circle_size * (0.95 ** i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_ar = sum(score.approach_rate * (0.95 ** i) for i, score in enumerate(top_100_scores)) / weighting_value self.score_style_od = sum(score.overall_difficulty * (0.95 ** i) for i, score in enumerate(top_100_scores)) / weighting_value return scores, scores_to_create
def update_membership(self, user_id): """ Update a membership for a user_id ensuring all scores that fit the criteria are added """ # Get or create Membership model try: membership = self.memberships.select_for_update().get( user_id=user_id) # Clear all currently added scores membership.scores.clear() join_date = membership.join_date except Membership.DoesNotExist: if self.access_type in (LeaderboardAccessType.PUBLIC_INVITE_ONLY, LeaderboardAccessType.PRIVATE ) and self.owner_id != user_id: # Check if user has been invited try: invite = self.invitees.get(id=user_id) except OsuUser.DoesNotExist: raise PermissionDenied( "You must be invited to join this leaderboard.") # Invite is being accepted self.invitees.remove(invite) # Create new membership membership = Membership(user_id=user_id, leaderboard=self) join_date = datetime.now() # Get scores scores = Score.objects.filter(user_stats__user_id=user_id, beatmap__gamemode=self.gamemode, mods__allbits=self.required_mods, mods__nobits=self.disqualified_mods) if not self.allow_past_scores: scores = scores.filter(date__gte=join_date) if self.allowed_beatmap_status == AllowedBeatmapStatus.LOVED_ONLY: scores = scores.filter(beatmap__status=BeatmapStatus.LOVED) elif self.allowed_beatmap_status == AllowedBeatmapStatus.RANKED_ONLY: scores = scores.filter(beatmap__status__in=[ BeatmapStatus.RANKED, BeatmapStatus.APPROVED ]) # optional filters if self.oldest_beatmap_date: scores = scores.filter( beatmap__approval_date__gte=self.oldest_beatmap_date) if self.newest_beatmap_date: scores = scores.filter( beatmap__approval_date__lte=self.newest_beatmap_date) if self.oldest_score_date: scores = scores.filter(date__gte=self.oldest_score_date) if self.newest_score_date: scores = scores.filter(date__lte=self.newest_score_date) if self.lowest_ar: scores = scores.filter(approach_rate__gte=self.lowest_ar) if self.highest_ar: scores = scores.filter(approach_rate__lte=self.highest_ar) if self.lowest_od: scores = scores.filter(overall_difficulty__gte=self.lowest_od) if self.highest_od: scores = scores.filter(overall_difficulty__lte=self.highest_od) if self.lowest_cs: scores = scores.filter(circle_size__gte=self.lowest_cs) if self.highest_cs: scores = scores.filter(circle_size__lte=self.highest_cs) if self.lowest_accuracy: scores = scores.filter(accuracy__gte=self.lowest_accuracy) if self.highest_accuracy: scores = scores.filter(accuracy__lte=self.highest_accuracy) scores = scores.unique_maps() # Add scores to membership membership.pp = calculate_pp_total(score.pp for score in scores.order_by("-pp")) membership.save() membership.scores.add(*scores) return membership