Merge pull request #6 from depreciate/master

scoreboard shit
This commit is contained in:
Josh Smith 2019-02-11 06:36:31 -05:00 committed by GitHub
commit fc5a025bf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 337 additions and 173 deletions

View File

@ -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())

View File

@ -30,6 +30,8 @@ from objects import score
from objects import scoreboard from objects import scoreboard
from objects import relaxboard from objects import relaxboard
from objects import rxscore from objects import rxscore
from helpers.generalHelper import zingonify
from objects.charts import BeatmapChart, OverallChart
from common import generalUtils from common import generalUtils
MODULE_NAME = "submit_modular" 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") log.warning("**{}** ({}) has been restricted due to notepad hack".format(username, userID), "cm")
return 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 # Save score in db
s.saveScoreInDB() s.saveScoreInDB()
@ -331,17 +346,6 @@ class handler(requestsManager.asyncRequestHandler):
oldUserData = glob.userStatsCache.get(userID, s.gameMode) oldUserData = glob.userStatsCache.get(userID, s.gameMode)
oldRank = userUtils.getGameRank(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) # Always update users stats (total/ranked score, playcount, level, acc and pp)
# even if not passed # even if not passed
@ -401,8 +405,11 @@ class handler(requestsManager.asyncRequestHandler):
# Trigger bancho stats cache update # Trigger bancho stats cache update
glob.redis.publish("peppy:update_rxcached_stats", userID) glob.redis.publish("peppy:update_rxcached_stats", userID)
# Get personal best after submitting the score newScoreboard = relaxboard.scoreboard(username, s.gameMode, beatmapInfo, False)
newScoreboard = relaxboard.scoreboard(username, s.gameMode, beatmapInfo, True) 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) # Get rank info (current rank, pp/score to next rank, user who is 1 rank above us)
rankInfo = leaderboardHelper.rxgetRankInfo(userID, s.gameMode) rankInfo = leaderboardHelper.rxgetRankInfo(userID, s.gameMode)
@ -411,53 +418,70 @@ class handler(requestsManager.asyncRequestHandler):
# Trigger bancho stats cache update # Trigger bancho stats cache update
glob.redis.publish("peppy:update_cached_stats", userID) glob.redis.publish("peppy:update_cached_stats", userID)
# Get personal best after submitting the score newScoreboard = scoreboard.scoreboard(username, s.gameMode, beatmapInfo, False)
newScoreboard = scoreboard.scoreboard(username, s.gameMode, beatmapInfo, True) 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) # Get rank info (current rank, pp/score to next rank, user who is 1 rank above us)
rankInfo = leaderboardHelper.getRankInfo(userID, s.gameMode) rankInfo = leaderboardHelper.getRankInfo(userID, s.gameMode)
# Output dictionary if newCharts:
output = collections.OrderedDict() log.debug("Using new charts")
output["beatmapId"] = beatmapInfo.beatmapID dicts = [
output["beatmapSetId"] = beatmapInfo.beatmapSetID collections.OrderedDict([
output["beatmapPlaycount"] = beatmapInfo.playcount ("beatmapId", beatmapInfo.beatmapID),
output["beatmapPasscount"] = beatmapInfo.passcount ("beatmapSetId", beatmapInfo.beatmapSetID),
#output["approvedDate"] = "2015-07-09 23:20:14\n" ("beatmapPlaycount", beatmapInfo.playcount + 1),
output["approvedDate"] = "\n" ("beatmapPasscount", beatmapInfo.passcount + (s.completed == 3)),
output["chartId"] = "overall" ("approvedDate", beatmapInfo.rankingDate)
output["chartName"] = "Overall Ranking" ]),
output["chartEndDate"] = "" BeatmapChart(
output["beatmapRankingBefore"] = oldPersonalBestRank oldPersonalBest if s.completed == 3 else currentPersonalBest,
output["beatmapRankingAfter"] = newScoreboard.personalBestRank currentPersonalBest if s.completed == 3 else s,
output["rankedScoreBefore"] = oldUserData["rankedScore"] beatmapInfo.beatmapID,
output["rankedScoreAfter"] = newUserData["rankedScore"] ),
output["totalScoreBefore"] = oldUserData["totalScore"] OverallChart(
output["totalScoreAfter"] = newUserData["totalScore"] userID, oldUserData, newUserData, beatmapInfo, s, new_achievements, oldRank, rankInfo["currentRank"]
output["playCountBefore"] = newUserData["playcount"] )
output["accuracyBefore"] = float(oldUserData["accuracy"])/100 ]
output["accuracyAfter"] = float(newUserData["accuracy"])/100 else:
output["rankBefore"] = oldRank log.debug("Using old charts")
output["rankAfter"] = rankInfo["currentRank"] dicts = [
output["toNextRank"] = rankInfo["difference"] collections.OrderedDict([
output["toNextRankUser"] = rankInfo["nextUsername"] ("beatmapId", beatmapInfo.beatmapID),
output["achievements"] = "" ("beatmapSetId", beatmapInfo.beatmapSetID),
output["achievements-new"] = secret.achievements.utils.achievements_response(new_achievements) ("beatmapPlaycount", beatmapInfo.playcount),
output["onlineScoreId"] = s.scoreID ("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("Generated output for online ranking screen!")
log.debug(msg) log.debug(output)
# Send message to #announce if we're rank #1 # Send message to #announce if we're rank #1
if newScoreboard.personalBestRank == 1 and s.completed == 3 and not restricted: if newScoreboard.personalBestRank == 1 and s.completed == 3 and not restricted:

131
objects/charts.py Normal file
View File

@ -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
}

View File

@ -2,15 +2,14 @@ from objects import rxscore
from common.ripple import userUtils from common.ripple import userUtils
from constants import rankedStatuses from constants import rankedStatuses
from common.constants import mods as modsEnum from common.constants import mods as modsEnum
from common.constants import privileges
from objects import glob from objects import glob
from common.constants import privileges
class scoreboard: class scoreboard:
def __init__(self, username, gameMode, beatmap, setScores = True, country = False, friends = False, mods = -1): def __init__(self, username, gameMode, beatmap, setScores = True, country = False, friends = False, mods = -1):
""" """
Initialize a leaderboard object Initialize a leaderboard object
username -- username of who's requesting the scoreboard. None if not known username -- username of who's requesting the scoreboard. None if not known
gameMode -- requested gameMode gameMode -- requested gameMode
beatmap -- beatmap objecy relative to this leaderboard beatmap -- beatmap objecy relative to this leaderboard
@ -29,15 +28,48 @@ class scoreboard:
if setScores: if setScores:
self.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): def setScores(self):
""" """
Set scores list 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 # Reset score list
self.scores = [] self.scores = []
self.scores.append(-1) self.scores.append(-1)
@ -56,38 +88,11 @@ class scoreboard:
cdef str limit = "" cdef str limit = ""
# Find personal best score # Find personal best score
if self.userID != 0: personalBestScoreID = self.getPersonalBestID()
# Query parts isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM
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
# Output our personal best if found # Output our personal best if found
if personalBestScore is not None: if personalBestScoreID is not None:
s = rxscore.score(personalBestScore["id"]) s = rxscore.score(personalBestScoreID)
self.scores[0] = s self.scores[0] = s
else: else:
# No personal best # No personal best
@ -99,17 +104,12 @@ class scoreboard:
# Country ranking # Country ranking
if self.country: 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)" country = "AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)"
else: else:
country = "" country = ""
# Mods ranking (ignore auto, since we use it for pp sorting) # 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" mods = "AND scores_relax.mods = %(mods)s"
else: else:
mods = "" mods = ""
@ -119,19 +119,19 @@ class scoreboard:
friends = "AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)" friends = "AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)"
else: else:
friends = "" friends = ""
if self.beatmap.rankedStatus == rankedStatuses.LOVED: if self.beatmap.rankedStatus == rankedStatuses.LOVED:
order = "ORDER BY score DESC" order = "ORDER BY score DESC"
else: else:
order = "ORDER BY pp DESC" order = "ORDER BY pp DESC"
if isPremium: # Premium members can see up to 100 scores on leaderboards if isPremium: # Premium members can see up to 100 scores on leaderboards
limit = "LIMIT 100" limit = "LIMIT 100"
else: else:
limit = "LIMIT 50" limit = "LIMIT 50"
# Build query, get params and run query # 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} params = {"beatmap_md5": self.beatmap.fileMD5, "play_mode": self.gameMode, "userid": self.userID, "mods": self.mods}
topScores = glob.db.fetchAll(query, params) topScores = glob.db.fetchAll(query, params)
@ -145,7 +145,7 @@ class scoreboard:
# Set data and rank from topScores's row # Set data and rank from topScores's row
s.setDataFromDict(topScore) s.setDataFromDict(topScore)
s.setRank(c) s.rank = c
# Check if this top 50 score is our personal best # Check if this top 50 score is our personal best
if s.playerName == self.username: if s.playerName == self.username:
@ -160,9 +160,8 @@ class scoreboard:
# Count all scores on this map # Count all scores on this map
select = "SELECT COUNT(*) AS count" select = "SELECT COUNT(*) AS count"
limit = "LIMIT 1" limit = "LIMIT 1"
# Build query, get params and run query # Build query, get params and run query
query = buildQuery(locals()) query = self.buildQuery(locals())
count = glob.db.fetch(query, params) count = glob.db.fetch(query, params)
if count == None: if count == None:
self.totalScores = 0 self.totalScores = 0
@ -172,19 +171,19 @@ class scoreboard:
self.totalScores = c-1''' self.totalScores = c-1'''
# If personal best score was not in top 50, try to get it from cache # 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) 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 # It's not even in cache, get it from db
if personalBestScore is not None and self.personalBestRank < 1: if personalBestScoreID is not None and self.personalBestRank < 1:
self.setPersonalBest() self.setPersonalBestRank()
# Cache our personal best rank so we can eventually use it later as # Cache our personal best rank so we can eventually use it later as
# before personal best rank" in submit modular when building ranking panel # before personal best rank" in submit modular when building ranking panel
if self.personalBestRank >= 1: if self.personalBestRank >= 1:
glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5) glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5)
def setPersonalBest(self): def setPersonalBestRank(self):
""" """
Set personal best rank ONLY Set personal best rank ONLY
Ikr, that query is HUGE but xd Ikr, that query is HUGE but xd
@ -203,12 +202,15 @@ class scoreboard:
if hasScore is None: if hasScore is None:
return return
# We have a score, run the huge query # We have a score, run the huge query
# Base 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 overwrite = "pp"
) 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") # 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 # Country
if self.country: if self.country:
query += " AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)" query += " AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)"
@ -219,12 +221,7 @@ class scoreboard:
if self.friends: if self.friends:
query += " AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)" 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 # Sort and limit at the end
query += " ORDER BY pp DESC LIMIT 1".format(overwrite)
if self.beatmap.rankedStatus == rankedStatuses.LOVED:
query += " ORDER BY score DESC LIMIT 1"
else:
query += " ORDER BY pp DESC LIMIT 1"
result = glob.db.fetch(query, {"md5": self.beatmap.fileMD5, "userid": self.userID, "mode": self.gameMode, "mods": self.mods}) result = glob.db.fetch(query, {"md5": self.beatmap.fileMD5, "userid": self.userID, "mode": self.gameMode, "mods": self.mods})
if result is not None: if result is not None:
self.personalBestRank = result["rank"] self.personalBestRank = result["rank"]
@ -232,7 +229,6 @@ class scoreboard:
def getScoresData(self): def getScoresData(self):
""" """
Return scores data for getscores Return scores data for getscores
return -- score data in getscores format return -- score data in getscores format
""" """
data = "" data = ""
@ -243,11 +239,12 @@ class scoreboard:
data += "\n" data += "\n"
else: else:
# Set personal best score rank # Set personal best score rank
self.setPersonalBest() # sets self.personalBestRank with the huge query self.setPersonalBestRank() # sets self.personalBestRank with the huge query
self.scores[0].setRank(self.personalBestRank) self.scores[0].rank = self.personalBestRank
data += self.scores[0].getData() data += self.scores[0].getData(pp=True)
# Output top 50 scores # Output top 50 scores
for i in self.scores[1:]: for i in self.scores[1:]:
data += i.getData(pp=self.beatmap.rankedStatus != rankedStatuses.LOVED) data += i.getData(pp=True)
return data return data

View File

@ -2,15 +2,14 @@ from objects import score
from common.ripple import userUtils from common.ripple import userUtils
from constants import rankedStatuses from constants import rankedStatuses
from common.constants import mods as modsEnum from common.constants import mods as modsEnum
from common.constants import privileges
from objects import glob from objects import glob
from common.constants import privileges
class scoreboard: class scoreboard:
def __init__(self, username, gameMode, beatmap, setScores = True, country = False, friends = False, mods = -1): def __init__(self, username, gameMode, beatmap, setScores = True, country = False, friends = False, mods = -1):
""" """
Initialize a leaderboard object Initialize a leaderboard object
username -- username of who's requesting the scoreboard. None if not known username -- username of who's requesting the scoreboard. None if not known
gameMode -- requested gameMode gameMode -- requested gameMode
beatmap -- beatmap objecy relative to this leaderboard beatmap -- beatmap objecy relative to this leaderboard
@ -29,15 +28,48 @@ class scoreboard:
if setScores: if setScores:
self.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): def setScores(self):
""" """
Set scores list 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 # Reset score list
self.scores = [] self.scores = []
self.scores.append(-1) self.scores.append(-1)
@ -56,32 +88,11 @@ class scoreboard:
cdef str limit = "" cdef str limit = ""
# Find personal best score # Find personal best score
if self.userID != 0: personalBestScoreID = self.getPersonalBestID()
# Query parts isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM
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
# Output our personal best if found # Output our personal best if found
if personalBestScore is not None: if personalBestScoreID is not None:
s = score.score(personalBestScore["id"]) s = score.score(personalBestScoreID)
self.scores[0] = s self.scores[0] = s
else: else:
# No personal best # No personal best
@ -93,17 +104,12 @@ class scoreboard:
# Country ranking # Country ranking
if self.country: 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)" country = "AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)"
else: else:
country = "" country = ""
# Mods ranking (ignore auto, since we use it for pp sorting) # 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" mods = "AND scores.mods = %(mods)s"
else: else:
mods = "" 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)" friends = "AND (scores.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores.userid = %(userid)s)"
else: else:
friends = "" friends = ""
order = "ORDER BY score DESC" order = "ORDER BY score DESC"
if isPremium: # Premium members can see up to 100 scores on leaderboards if isPremium: # Premium members can see up to 100 scores on leaderboards
@ -122,7 +128,7 @@ class scoreboard:
limit = "LIMIT 50" limit = "LIMIT 50"
# Build query, get params and run query # 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} params = {"beatmap_md5": self.beatmap.fileMD5, "play_mode": self.gameMode, "userid": self.userID, "mods": self.mods}
topScores = glob.db.fetchAll(query, params) topScores = glob.db.fetchAll(query, params)
@ -136,7 +142,7 @@ class scoreboard:
# Set data and rank from topScores's row # Set data and rank from topScores's row
s.setDataFromDict(topScore) s.setDataFromDict(topScore)
s.setRank(c) s.rank = c
# Check if this top 50 score is our personal best # Check if this top 50 score is our personal best
if s.playerName == self.username: if s.playerName == self.username:
@ -151,9 +157,8 @@ class scoreboard:
# Count all scores on this map # Count all scores on this map
select = "SELECT COUNT(*) AS count" select = "SELECT COUNT(*) AS count"
limit = "LIMIT 1" limit = "LIMIT 1"
# Build query, get params and run query # Build query, get params and run query
query = buildQuery(locals()) query = self.buildQuery(locals())
count = glob.db.fetch(query, params) count = glob.db.fetch(query, params)
if count == None: if count == None:
self.totalScores = 0 self.totalScores = 0
@ -163,19 +168,19 @@ class scoreboard:
self.totalScores = c-1''' self.totalScores = c-1'''
# If personal best score was not in top 50, try to get it from cache # 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) 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 # It's not even in cache, get it from db
if personalBestScore is not None and self.personalBestRank < 1: if personalBestScoreID is not None and self.personalBestRank < 1:
self.setPersonalBest() self.setPersonalBestRank()
# Cache our personal best rank so we can eventually use it later as # Cache our personal best rank so we can eventually use it later as
# before personal best rank" in submit modular when building ranking panel # before personal best rank" in submit modular when building ranking panel
if self.personalBestRank >= 1: if self.personalBestRank >= 1:
glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5) glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5)
def setPersonalBest(self): def setPersonalBestRank(self):
""" """
Set personal best rank ONLY Set personal best rank ONLY
Ikr, that query is HUGE but xd Ikr, that query is HUGE but xd
@ -217,7 +222,6 @@ class scoreboard:
def getScoresData(self): def getScoresData(self):
""" """
Return scores data for getscores Return scores data for getscores
return -- score data in getscores format return -- score data in getscores format
""" """
data = "" data = ""
@ -228,8 +232,8 @@ class scoreboard:
data += "\n" data += "\n"
else: else:
# Set personal best score rank # Set personal best score rank
self.setPersonalBest() # sets self.personalBestRank with the huge query self.setPersonalBestRank() # sets self.personalBestRank with the huge query
self.scores[0].setRank(self.personalBestRank) self.scores[0].rank = self.personalBestRank
data += self.scores[0].getData() data += self.scores[0].getData()
# Output top 50 scores # Output top 50 scores