Example #1
0
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
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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"])
Example #5
0
    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
Example #6
0
    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