diff --git a/handlers/generalHelper.py b/handlers/generalHelper.py new file mode 100644 index 0000000..065ddb0 --- /dev/null +++ b/handlers/generalHelper.py @@ -0,0 +1,8 @@ +def zingonify(d): + """ + Zingonifies a string + :param d: input dict + :return: zingonified dict as str + """ + + return "|".join(f"{k}:{v}" for k, v in d.items()) diff --git a/handlers/submitModularHandler.pyx b/handlers/submitModularHandler.pyx index 194950b..822eb48 100644 --- a/handlers/submitModularHandler.pyx +++ b/handlers/submitModularHandler.pyx @@ -30,6 +30,8 @@ from objects import score from objects import scoreboard from objects import relaxboard from objects import rxscore +from helpers.generalHelper import zingonify +from objects.charts import BeatmapChart, OverallChart from common import generalUtils MODULE_NAME = "submit_modular" @@ -215,6 +217,19 @@ class handler(requestsManager.asyncRequestHandler): log.warning("**{}** ({}) has been restricted due to notepad hack".format(username, userID), "cm") return + # Right before submitting the score, get the personal best score object (we need it for charts) + if s.passed and s.oldPersonalBest > 0: + oldPersonalBestRank = glob.personalBestCache.get(userID, s.fileMd5) + if oldPersonalBestRank == 0: + oldScoreboard = scoreboard.scoreboard(username, s.gameMode, beatmapInfo, False) + oldScoreboard.setPersonalBestRank() + oldPersonalBestRank = max(oldScoreboard.personalBestRank, 0) + oldPersonalBest = score.score(s.oldPersonalBest, oldPersonalBestRank) + else: + oldPersonalBestRank = 0 + oldPersonalBest = None + + # Save score in db s.saveScoreInDB() @@ -331,17 +346,6 @@ class handler(requestsManager.asyncRequestHandler): oldUserData = glob.userStatsCache.get(userID, s.gameMode) oldRank = userUtils.getGameRank(userID, s.gameMode) - # Try to get oldPersonalBestRank from cache - oldPersonalBestRank = glob.personalBestCache.get(userID, s.fileMd5) - if oldPersonalBestRank == 0: - # oldPersonalBestRank not found in cache, get it from db - if isRelaxing: - oldScoreboard = relaxboard.scoreboard(username, s.gameMode, beatmapInfo, False) - else: - oldScoreboard = scoreboard.scoreboard(username, s.gameMode, beatmapInfo, False) - - oldScoreboard.setPersonalBest() - oldPersonalBestRank = oldScoreboard.personalBestRank if oldScoreboard.personalBestRank > 0 else 0 # Always update users stats (total/ranked score, playcount, level, acc and pp) # even if not passed @@ -401,8 +405,11 @@ class handler(requestsManager.asyncRequestHandler): # Trigger bancho stats cache update glob.redis.publish("peppy:update_rxcached_stats", userID) - # Get personal best after submitting the score - newScoreboard = relaxboard.scoreboard(username, s.gameMode, beatmapInfo, True) + newScoreboard = relaxboard.scoreboard(username, s.gameMode, beatmapInfo, False) + newScoreboard.setPersonalBestRank() + personalBestID = newScoreboard.getPersonalBestID() + assert personalBestID is not None + currentPersonalBest = rxscore.score(personalBestID, newScoreboard.personalBestRank) # Get rank info (current rank, pp/score to next rank, user who is 1 rank above us) rankInfo = leaderboardHelper.rxgetRankInfo(userID, s.gameMode) @@ -411,53 +418,70 @@ class handler(requestsManager.asyncRequestHandler): # Trigger bancho stats cache update glob.redis.publish("peppy:update_cached_stats", userID) - # Get personal best after submitting the score - newScoreboard = scoreboard.scoreboard(username, s.gameMode, beatmapInfo, True) + newScoreboard = scoreboard.scoreboard(username, s.gameMode, beatmapInfo, False) + newScoreboard.setPersonalBestRank() + personalBestID = newScoreboard.getPersonalBestID() + assert personalBestID is not None + currentPersonalBest = score.score(personalBestID, newScoreboard.personalBestRank) # Get rank info (current rank, pp/score to next rank, user who is 1 rank above us) rankInfo = leaderboardHelper.getRankInfo(userID, s.gameMode) - # Output dictionary - output = collections.OrderedDict() - output["beatmapId"] = beatmapInfo.beatmapID - output["beatmapSetId"] = beatmapInfo.beatmapSetID - output["beatmapPlaycount"] = beatmapInfo.playcount - output["beatmapPasscount"] = beatmapInfo.passcount - #output["approvedDate"] = "2015-07-09 23:20:14\n" - output["approvedDate"] = "\n" - output["chartId"] = "overall" - output["chartName"] = "Overall Ranking" - output["chartEndDate"] = "" - output["beatmapRankingBefore"] = oldPersonalBestRank - output["beatmapRankingAfter"] = newScoreboard.personalBestRank - output["rankedScoreBefore"] = oldUserData["rankedScore"] - output["rankedScoreAfter"] = newUserData["rankedScore"] - output["totalScoreBefore"] = oldUserData["totalScore"] - output["totalScoreAfter"] = newUserData["totalScore"] - output["playCountBefore"] = newUserData["playcount"] - output["accuracyBefore"] = float(oldUserData["accuracy"])/100 - output["accuracyAfter"] = float(newUserData["accuracy"])/100 - output["rankBefore"] = oldRank - output["rankAfter"] = rankInfo["currentRank"] - output["toNextRank"] = rankInfo["difference"] - output["toNextRankUser"] = rankInfo["nextUsername"] - output["achievements"] = "" - output["achievements-new"] = secret.achievements.utils.achievements_response(new_achievements) - output["onlineScoreId"] = s.scoreID + if newCharts: + log.debug("Using new charts") + dicts = [ + collections.OrderedDict([ + ("beatmapId", beatmapInfo.beatmapID), + ("beatmapSetId", beatmapInfo.beatmapSetID), + ("beatmapPlaycount", beatmapInfo.playcount + 1), + ("beatmapPasscount", beatmapInfo.passcount + (s.completed == 3)), + ("approvedDate", beatmapInfo.rankingDate) + ]), + BeatmapChart( + oldPersonalBest if s.completed == 3 else currentPersonalBest, + currentPersonalBest if s.completed == 3 else s, + beatmapInfo.beatmapID, + ), + OverallChart( + userID, oldUserData, newUserData, beatmapInfo, s, new_achievements, oldRank, rankInfo["currentRank"] + ) + ] + else: + log.debug("Using old charts") + dicts = [ + collections.OrderedDict([ + ("beatmapId", beatmapInfo.beatmapID), + ("beatmapSetId", beatmapInfo.beatmapSetID), + ("beatmapPlaycount", beatmapInfo.playcount), + ("beatmapPasscount", beatmapInfo.passcount), + ("approvedDate", beatmapInfo.rankingDate) + ]), + collections.OrderedDict([ + ("chartId", "overall"), + ("chartName", "Overall Ranking"), + ("chartEndDate", ""), + ("beatmapRankingBefore", oldPersonalBestRank), + ("beatmapRankingAfter", newScoreboard.personalBestRank), + ("rankedScoreBefore", oldUserData["rankedScore"]), + ("rankedScoreAfter", newUserData["rankedScore"]), + ("totalScoreBefore", oldUserData["totalScore"]), + ("totalScoreAfter", newUserData["totalScore"]), + ("playCountBefore", newUserData["playcount"]), + ("accuracyBefore", float(oldUserData["accuracy"])/100), + ("accuracyAfter", float(newUserData["accuracy"])/100), + ("rankBefore", oldRank), + ("rankAfter", rankInfo["currentRank"]), + ("toNextRank", rankInfo["difference"]), + ("toNextRankUser", rankInfo["nextUsername"]), + ("achievements", ""), + ("achievements-new", secret.achievements.utils.achievements_response(new_achievements)), + ("onlineScoreId", s.scoreID) + ]) + ] + output = "\n".join(zingonify(x) for x in dicts) - # Build final string - msg = "" - for line, val in output.items(): - msg += "{}:{}".format(line, val) - if val != "\n": - if (len(output) - 1) != list(output.keys()).index(line): - msg += "|" - else: - msg += "\n" - - # Some debug messages log.debug("Generated output for online ranking screen!") - log.debug(msg) + log.debug(output) # Send message to #announce if we're rank #1 if newScoreboard.personalBestRank == 1 and s.completed == 3 and not restricted: diff --git a/objects/charts.py b/objects/charts.py new file mode 100644 index 0000000..1a4d63e --- /dev/null +++ b/objects/charts.py @@ -0,0 +1,131 @@ +from secret.achievements.utils import achievements_response + + +class Chart: + """ + Chart base class + """ + + + def __init__(self, id_, url, name): + """ + Initializes a new chart. + + :param id_: chart id. Currently known values are 'beatmap' and 'overall' + :param url: URL to open when clicking on the chart title. + :param name: chart name displayed in the game client + """ + self.id_ = id_ + self.url = url + self.name = name + + def items(self): + """ + `items()` method that allows this class to be used as a iterable dict + + :return: + """ + return self.output_attrs.items() + + @property + def output_attrs(self): + """ + An unzingonified dict containing the stuff that will be sent to the game client + + :return: dict + """ + return { + "chartId": self.id_, + "chartUrl": self.url, + "chartName": self.name + } + + @staticmethod + def before_after_dict(name, values, none_value="0"): + """ + Turns a tuple with two elements in a dict with two elements. + + :param name: prefix of the keys + :param values: (value_before, value_after). value_before and value_after can be None. + :param none_value: value to use instead of None (None, when zingonified, is not recognized by the game client) + :return: { XXXBefore -> first element, XXXAfter -> second element }, where XXX is `name` + """ + return { + f"{name}{'Before' if i == 0 else 'After'}": x if x is not None else none_value for i, x in enumerate(values) + } + + +class BeatmapChart(Chart): + """ + Beatmap ranking chart + """ + def __init__(self, old_score, new_score, beatmap_id): + """ + Initializes a new BeatmapChart object. + + :param old_score: score object of the old score + :param new_score: score object of the currently submitted score + :param beatmap_id: beatmap id, for the clickable link + """ + super(BeatmapChart, self).__init__("beatmap", f"https://akatsuki.pw/b/{beatmap_id}", "Beatmap Ranking") + self.rank = (old_score.rank if old_score is not None else None, new_score.rank) + self.max_combo = (old_score.maxCombo if old_score is not None else None, new_score.maxCombo) + self.accuracy = (old_score.accuracy * 100 if old_score is not None else None, new_score.accuracy * 100) + self.ranked_score = (old_score.score if old_score is not None else None, new_score.score) + self.pp = (old_score.pp if old_score is not None else None, new_score.pp) + self.score_id = new_score.scoreID + + @property + def output_attrs(self): + return { + **super(BeatmapChart, self).output_attrs, + **self.before_after_dict("rank", self.rank, none_value=""), + **self.before_after_dict("maxCombo", self.max_combo), + **self.before_after_dict("accuracy", self.accuracy), + **self.before_after_dict("rankedScore", self.ranked_score), + **self.before_after_dict("totalScore", self.ranked_score), + **self.before_after_dict("pp", self.pp), + "onlineScoreId": self.score_id + } + + +class OverallChart(Chart): + """ + Overall ranking chart achievements + """ + def __init__(self, user_id, old_user_stats, new_user_stats, bi, score, new_achievements, old_rank, new_rank): + """ + Initializes a new OverallChart object. + This constructor sucks because LETS itself sucks. + + :param user_id: id of the user + :param old_user_stats: user stats dict before submitting the score + :param new_user_stats: user stats dict after submitting the score + :param score: score object of the scores that has just been submitted + :param new_achievements: achievements unlocked list + :param old_rank: global rank before submitting the scpre + :param new_rank: global rank after submitting the score + """ + super(OverallChart, self).__init__("overall", f"https://akatsuki.pw/u/{user_id}", "Overall Ranking") + self.rank = (old_rank, new_rank) + self.ranked_score = (old_user_stats["rankedScore"], new_user_stats["rankedScore"]) + self.total_score = (old_user_stats["totalScore"], new_user_stats["totalScore"]) + self.max_combo = (bi.maxCombo, bi.maxCombo) + self.accuracy = (old_user_stats["accuracy"], new_user_stats["accuracy"]) + self.pp = (old_user_stats["pp"], new_user_stats["pp"]) + self.new_achievements = new_achievements + self.score_id = score.scoreID + + @property + def output_attrs(self): + return { + **super(OverallChart, self).output_attrs, + **self.before_after_dict("rank", self.rank), + **self.before_after_dict("rankedScore", self.ranked_score), + **self.before_after_dict("totalScore", self.total_score), + **self.before_after_dict("maxCombo", self.max_combo), + **self.before_after_dict("accuracy", self.accuracy), + **self.before_after_dict("pp", self.pp), + "achievements-new": achievements_response(self.new_achievements), + "onlineScoreId": self.score_id + } diff --git a/objects/relaxboard.pyx b/objects/relaxboard.pyx index 6fa2d76..b608780 100644 --- a/objects/relaxboard.pyx +++ b/objects/relaxboard.pyx @@ -1,16 +1,15 @@ -from objects import rxscore +from objects import score from common.ripple import userUtils from constants import rankedStatuses from common.constants import mods as modsEnum -from common.constants import privileges from objects import glob +from common.constants import privileges class scoreboard: def __init__(self, username, gameMode, beatmap, setScores = True, country = False, friends = False, mods = -1): """ Initialize a leaderboard object - username -- username of who's requesting the scoreboard. None if not known gameMode -- requested gameMode beatmap -- beatmap objecy relative to this leaderboard @@ -29,15 +28,48 @@ class scoreboard: if setScores: self.setScores() + @staticmethod + def buildQuery(params): + return "{select} {joins} {country} {mods} {friends} {order} {limit}".format(**params) + + def getPersonalBestID(self): + if self.userID == 0: + return None + + # Query parts + cdef str select = "" + cdef str joins = "" + cdef str country = "" + cdef str mods = "" + cdef str friends = "" + cdef str order = "" + cdef str limit = "" + select = "SELECT id FROM scores_relax WHERE userid = %(userid)s AND beatmap_md5 = %(md5)s AND play_mode = %(mode)s AND completed = 3" + + # Mods + if self.mods > -1: + mods = "AND mods = %(mods)s" + + # Friends ranking + if self.friends: + friends = "AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)" + + # Sort and limit at the end + order = "ORDER BY pp DESC" + limit = "LIMIT 1" + + # Build query, get params and run query + query = self.buildQuery(locals()) + params = {"userid": self.userID, "md5": self.beatmap.fileMD5, "mode": self.gameMode, "mods": self.mods} + id_ = glob.db.fetch(query, params) + if id_ is None: + return None + return id_["id"] + def setScores(self): """ Set scores list """ - - isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM - - def buildQuery(params): - return "{select} {joins} {country} {mods} {friends} {order} {limit}".format(**params) # Reset score list self.scores = [] self.scores.append(-1) @@ -56,38 +88,11 @@ class scoreboard: cdef str limit = "" # Find personal best score - if self.userID != 0: - # Query parts - select = "SELECT id FROM scores_relax WHERE userid = %(userid)s AND beatmap_md5 = %(md5)s AND play_mode = %(mode)s AND completed = 3" - - # Mods - if self.mods > -1: - mods = "AND mods = %(mods)s" - - # Friends ranking - if self.friends: - friends = "AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)" - else: - friends = "" - - # Sort and limit at the end - if self.beatmap.rankedStatus == rankedStatuses.LOVED: - order = "ORDER BY score DESC" - else: - order = "ORDER BY pp DESC" - - limit = "LIMIT 1" - - # Build query, get params and run query - query = buildQuery(locals()) - params = {"userid": self.userID, "md5": self.beatmap.fileMD5, "mode": self.gameMode, "mods": self.mods} - personalBestScore = glob.db.fetch(query, params) - else: - personalBestScore = None - + personalBestScoreID = self.getPersonalBestID() + isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM # Output our personal best if found - if personalBestScore is not None: - s = rxscore.score(personalBestScore["id"]) + if personalBestScoreID is not None: + s = score.score(personalBestScoreID) self.scores[0] = s else: # No personal best @@ -99,17 +104,12 @@ class scoreboard: # Country ranking if self.country: - """ Honestly this is more of a preference thing than something that should be premium only? - if isPremium: - country = "AND user_clans.clan = (SELECT clan FROM user_clans WHERE user = %(userid)s LIMIT 1)" - else: - """ country = "AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)" else: country = "" # Mods ranking (ignore auto, since we use it for pp sorting) - if self.mods > -1: + if self.mods > -1 and self.mods & modsEnum.AUTOPLAY == 0: mods = "AND scores_relax.mods = %(mods)s" else: mods = "" @@ -119,19 +119,16 @@ class scoreboard: friends = "AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)" else: friends = "" - - if self.beatmap.rankedStatus == rankedStatuses.LOVED: - order = "ORDER BY score DESC" - else: - order = "ORDER BY pp DESC" - + + order = "ORDER BY pp DESC" + if isPremium: # Premium members can see up to 100 scores on leaderboards limit = "LIMIT 100" else: limit = "LIMIT 50" # Build query, get params and run query - query = buildQuery(locals()) + query = self.buildQuery(locals()) params = {"beatmap_md5": self.beatmap.fileMD5, "play_mode": self.gameMode, "userid": self.userID, "mods": self.mods} topScores = glob.db.fetchAll(query, params) @@ -141,11 +138,11 @@ class scoreboard: if topScores is not None: for topScore in topScores: # Create score object - s = rxscore.score(topScore["id"], setData=False) + s = score.score(topScore["id"], setData=False) # Set data and rank from topScores's row s.setDataFromDict(topScore) - s.setRank(c) + s.rank = c # Check if this top 50 score is our personal best if s.playerName == self.username: @@ -160,9 +157,8 @@ class scoreboard: # Count all scores on this map select = "SELECT COUNT(*) AS count" limit = "LIMIT 1" - # Build query, get params and run query - query = buildQuery(locals()) + query = self.buildQuery(locals()) count = glob.db.fetch(query, params) if count == None: self.totalScores = 0 @@ -172,19 +168,19 @@ class scoreboard: self.totalScores = c-1''' # If personal best score was not in top 50, try to get it from cache - if personalBestScore is not None and self.personalBestRank < 1: + if personalBestScoreID is not None and self.personalBestRank < 1: self.personalBestRank = glob.personalBestCache.get(self.userID, self.beatmap.fileMD5, self.country, self.friends, self.mods) # It's not even in cache, get it from db - if personalBestScore is not None and self.personalBestRank < 1: - self.setPersonalBest() + if personalBestScoreID is not None and self.personalBestRank < 1: + self.setPersonalBestRank() # Cache our personal best rank so we can eventually use it later as # before personal best rank" in submit modular when building ranking panel if self.personalBestRank >= 1: glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5) - def setPersonalBest(self): + def setPersonalBestRank(self): """ Set personal best rank ONLY Ikr, that query is HUGE but xd @@ -203,12 +199,15 @@ class scoreboard: if hasScore is None: return - # We have a score, run the huge query # Base query - query = """SELECT COUNT(*) AS rank FROM scores_relax STRAIGHT_JOIN users ON scores_relax.userid = users.id STRAIGHT_JOIN users_stats ON users.id = users_stats.id WHERE scores_relax.{PPorScore} >= ( - SELECT {PPorScore} FROM scores_relax WHERE beatmap_md5 = %(md5)s AND play_mode = %(mode)s AND completed = 3 AND userid = %(userid)s LIMIT 1 - ) AND scores_relax.beatmap_md5 = %(md5)s AND scores_relax.play_mode = %(mode)s AND scores_relax.completed = 3 AND users.privileges & 1 > 0""".format(PPorScore="score" if self.beatmap.rankedStatus == rankedStatuses.LOVED else "pp") + + overwrite = "pp" + # We have a score, run the huge query + # Base query + query = """SELECT COUNT(*) AS rank FROM scores_relax STRAIGHT_JOIN users ON scores_relax.userid = users.id STRAIGHT_JOIN users_stats ON users.id = users_stats.id WHERE scores_relax.{0} >= ( + SELECT {0} FROM scores_relax WHERE beatmap_md5 = %(md5)s AND play_mode = %(mode)s AND completed = 3 AND userid = %(userid)s LIMIT 1 + ) AND scores_relax.beatmap_md5 = %(md5)s AND scores_relax.play_mode = %(mode)s AND scores_relax.completed = 3 AND users.privileges & 1 > 0""".format(overwrite) # Country if self.country: query += " AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)" @@ -219,12 +218,7 @@ class scoreboard: if self.friends: query += " AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)" # Sort and limit at the end - - if self.beatmap.rankedStatus == rankedStatuses.LOVED: - query += " ORDER BY score DESC LIMIT 1" - else: - query += " ORDER BY pp DESC LIMIT 1" - + query += " ORDER BY pp DESC LIMIT 1".format(overwrite) result = glob.db.fetch(query, {"md5": self.beatmap.fileMD5, "userid": self.userID, "mode": self.gameMode, "mods": self.mods}) if result is not None: self.personalBestRank = result["rank"] @@ -232,7 +226,6 @@ class scoreboard: def getScoresData(self): """ Return scores data for getscores - return -- score data in getscores format """ data = "" @@ -243,11 +236,12 @@ class scoreboard: data += "\n" else: # Set personal best score rank - self.setPersonalBest() # sets self.personalBestRank with the huge query - self.scores[0].setRank(self.personalBestRank) - data += self.scores[0].getData() + self.setPersonalBestRank() # sets self.personalBestRank with the huge query + self.scores[0].rank = self.personalBestRank + data += self.scores[0].getData(pp=True) # Output top 50 scores for i in self.scores[1:]: - data += i.getData(pp=self.beatmap.rankedStatus != rankedStatuses.LOVED) + data += i.getData(pp=True) + return data diff --git a/objects/scoreboard.pyx b/objects/scoreboard.pyx index 0aac8b5..45fa59b 100644 --- a/objects/scoreboard.pyx +++ b/objects/scoreboard.pyx @@ -2,15 +2,14 @@ from objects import score from common.ripple import userUtils from constants import rankedStatuses from common.constants import mods as modsEnum -from common.constants import privileges from objects import glob +from common.constants import privileges class scoreboard: def __init__(self, username, gameMode, beatmap, setScores = True, country = False, friends = False, mods = -1): """ Initialize a leaderboard object - username -- username of who's requesting the scoreboard. None if not known gameMode -- requested gameMode beatmap -- beatmap objecy relative to this leaderboard @@ -29,15 +28,48 @@ class scoreboard: if setScores: self.setScores() + @staticmethod + def buildQuery(params): + return "{select} {joins} {country} {mods} {friends} {order} {limit}".format(**params) + + def getPersonalBestID(self): + if self.userID == 0: + return None + + # Query parts + cdef str select = "" + cdef str joins = "" + cdef str country = "" + cdef str mods = "" + cdef str friends = "" + cdef str order = "" + cdef str limit = "" + select = "SELECT id FROM scores WHERE userid = %(userid)s AND beatmap_md5 = %(md5)s AND play_mode = %(mode)s AND completed = 3" + + # Mods + if self.mods > -1: + mods = "AND mods = %(mods)s" + + # Friends ranking + if self.friends: + friends = "AND (scores.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores.userid = %(userid)s)" + + # Sort and limit at the end + order = "ORDER BY score DESC" + limit = "LIMIT 1" + + # Build query, get params and run query + query = self.buildQuery(locals()) + params = {"userid": self.userID, "md5": self.beatmap.fileMD5, "mode": self.gameMode, "mods": self.mods} + id_ = glob.db.fetch(query, params) + if id_ is None: + return None + return id_["id"] + def setScores(self): """ Set scores list """ - - isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM - - def buildQuery(params): - return "{select} {joins} {country} {mods} {friends} {order} {limit}".format(**params) # Reset score list self.scores = [] self.scores.append(-1) @@ -56,32 +88,11 @@ class scoreboard: cdef str limit = "" # Find personal best score - if self.userID != 0: - # Query parts - select = "SELECT id FROM scores WHERE userid = %(userid)s AND beatmap_md5 = %(md5)s AND play_mode = %(mode)s AND completed = 3" - - # Mods - if self.mods > -1: - mods = "AND mods = %(mods)s" - - # Friends ranking - if self.friends: - friends = "AND (scores.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores.userid = %(userid)s)" - - # Sort and limit at the end - order = "ORDER BY score DESC" - limit = "LIMIT 1" - - # Build query, get params and run query - query = buildQuery(locals()) - params = {"userid": self.userID, "md5": self.beatmap.fileMD5, "mode": self.gameMode, "mods": self.mods} - personalBestScore = glob.db.fetch(query, params) - else: - personalBestScore = None - + personalBestScoreID = self.getPersonalBestID() + isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM # Output our personal best if found - if personalBestScore is not None: - s = score.score(personalBestScore["id"]) + if personalBestScoreID is not None: + s = score.score(personalBestScoreID) self.scores[0] = s else: # No personal best @@ -93,17 +104,12 @@ class scoreboard: # Country ranking if self.country: - """ Honestly this is more of a preference thing than something that should be premium only? - if isPremium: - country = "AND user_clans.clan = (SELECT clan FROM user_clans WHERE user = %(userid)s LIMIT 1)" - else: - """ country = "AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)" else: country = "" # Mods ranking (ignore auto, since we use it for pp sorting) - if self.mods > -1: + if self.mods > -1 and self.mods & modsEnum.AUTOPLAY == 0: mods = "AND scores.mods = %(mods)s" else: mods = "" @@ -113,7 +119,7 @@ class scoreboard: friends = "AND (scores.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores.userid = %(userid)s)" else: friends = "" - + order = "ORDER BY score DESC" if isPremium: # Premium members can see up to 100 scores on leaderboards @@ -122,7 +128,7 @@ class scoreboard: limit = "LIMIT 50" # Build query, get params and run query - query = buildQuery(locals()) + query = self.buildQuery(locals()) params = {"beatmap_md5": self.beatmap.fileMD5, "play_mode": self.gameMode, "userid": self.userID, "mods": self.mods} topScores = glob.db.fetchAll(query, params) @@ -136,7 +142,7 @@ class scoreboard: # Set data and rank from topScores's row s.setDataFromDict(topScore) - s.setRank(c) + s.rank = c # Check if this top 50 score is our personal best if s.playerName == self.username: @@ -151,9 +157,8 @@ class scoreboard: # Count all scores on this map select = "SELECT COUNT(*) AS count" limit = "LIMIT 1" - # Build query, get params and run query - query = buildQuery(locals()) + query = self.buildQuery(locals()) count = glob.db.fetch(query, params) if count == None: self.totalScores = 0 @@ -163,19 +168,19 @@ class scoreboard: self.totalScores = c-1''' # If personal best score was not in top 50, try to get it from cache - if personalBestScore is not None and self.personalBestRank < 1: + if personalBestScoreID is not None and self.personalBestRank < 1: self.personalBestRank = glob.personalBestCache.get(self.userID, self.beatmap.fileMD5, self.country, self.friends, self.mods) # It's not even in cache, get it from db - if personalBestScore is not None and self.personalBestRank < 1: - self.setPersonalBest() + if personalBestScoreID is not None and self.personalBestRank < 1: + self.setPersonalBestRank() # Cache our personal best rank so we can eventually use it later as # before personal best rank" in submit modular when building ranking panel if self.personalBestRank >= 1: glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5) - def setPersonalBest(self): + def setPersonalBestRank(self): """ Set personal best rank ONLY Ikr, that query is HUGE but xd @@ -217,7 +222,6 @@ class scoreboard: def getScoresData(self): """ Return scores data for getscores - return -- score data in getscores format """ data = "" @@ -228,8 +232,8 @@ class scoreboard: data += "\n" else: # Set personal best score rank - self.setPersonalBest() # sets self.personalBestRank with the huge query - self.scores[0].setRank(self.personalBestRank) + self.setPersonalBestRank() # sets self.personalBestRank with the huge query + self.scores[0].rank = self.personalBestRank data += self.scores[0].getData() # Output top 50 scores