def recalc_score(score_data: Dict, no_download=False) -> Optional[score.score]: """ Recalculates pp for a score :param score_data: dict containing score and beatmap information about a score. :param no_download: if True, raise FileNotFoundError() if the map should be re-downloaded. this ensures no requests are made to osu! :return: new `score` object, with `pp` attribute set to the new value """ # Create score object and set its data s: score.score = score.score() s.setDataFromDict(score_data) s.passed = True # Create beatmap object and set its data b: beatmap.beatmap = beatmap.beatmap() b.setDataFromDict(score_data) # Abort if we are running in no_download mode and the map should be re-downloaded if no_download and mapsHelper.shouldDownloadMap( mapsHelper.cachedMapPath(b.beatmapID), b): raise FileNotFoundError("no_download mode and local map not found") # Calculate score pp s.calculatePP(b) del b return s
def calculate_pp(self): try: # Cache beatmap mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID) mapsHelper.cacheMap(mapFile, self.beatmap) # TODO: Sanizite mods # Gamemode check if self.score and self.score.gameMode != gameModes.CTB: raise exceptions.unsupportedGameModeException() # Accuracy check if self.accuracy > 1: raise ValueError("Accuracy must be between 0 and 1") # Calculate difficulty calcBeatmap = CalcBeatmap(mapFile) difficulty = Difficulty(beatmap=calcBeatmap, mods=self.mods) # Calculate pp if self.tillerino: results = [] for acc in [1, 0.99, 0.98, 0.95]: results.append(ppCalc.calculate_pp( diff=difficulty, accuracy=acc, combo=self.combo if self.combo >= 0 else calcBeatmap.max_combo, miss=self.misses )) self.pp = results else: self.pp = ppCalc.calculate_pp( diff=difficulty, accuracy=self.accuracy, combo=self.combo if self.combo >= 0 else calcBeatmap.max_combo, miss=self.misses ) except exceptions.osuApiFailException: log.error("cicciobello ~> osu!api error!") self.pp = 0 except exceptions.unsupportedGameModeException: log.error("cicciobello ~> Unsupported gamemode") self.pp = 0 except Exception as e: log.error("cicciobello ~> Unhandled exception: {}".format(str(e))) self.pp = 0 raise finally: log.debug("cicciobello ~> Shutting down, pp = {}".format(self.pp))
def calculatePP(self): """ Calculate total pp value with oppai and return it return -- total pp """ # Set variables self.pp = None try: # Build .osu map file path mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID) log.debug("oppai ~> Map file: {}".format(mapFile)) mapsHelper.cacheMap(mapFile, self.beatmap) # Use only mods supported by oppai modsFixed = self.mods & 5983 # Check gamemode if self.gameMode != gameModes.STD and self.gameMode != gameModes.TAIKO: raise exceptions.unsupportedGameModeException() command = "./pp/oppai-ng/oppai {}".format(mapFile) if not self.tillerino: # force acc only for non-tillerino calculation # acc is set for each subprocess if calculating tillerino-like pp sets if self.acc > 0: command += " {acc:.2f}%".format(acc=self.acc) if self.mods > 0: command += " +{mods}".format(mods=scoreUtils.readableMods(modsFixed)) if self.combo >= 0: command += " {combo}x".format(combo=self.combo) if self.misses > 0: command += " {misses}xm".format(misses=self.misses) if self.gameMode == gameModes.TAIKO: command += " -taiko" command += " -ojson" # Calculate pp if not self.tillerino: # self.pp, self.stars = self._runOppaiProcess(command) temp_pp, self.stars = self._runOppaiProcess(command) if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \ self.stars > 50: # Invalidate pp for bugged taiko converteds and bugged inf pp std maps self.pp = 0 else: self.pp = temp_pp else: pp_list = [] for acc in [100, 99, 98, 95]: temp_command = command temp_command += " {acc:.2f}%".format(acc=acc) pp, self.stars = self._runOppaiProcess(temp_command) # If this is a broken converted, set all pp to 0 and break the loop if self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and pp > 800: pp_list = [0, 0, 0, 0] break pp_list.append(pp) self.pp = pp_list log.debug("oppai ~> Calculated PP: {}, stars: {}".format(self.pp, self.stars)) except OppaiError: log.error("oppai ~> oppai-ng error!") self.pp = 0 except exceptions.osuApiFailException: log.error("oppai ~> osu!api error!") self.pp = 0 except exceptions.unsupportedGameModeException: log.error("oppai ~> Unsupported gamemode") self.pp = 0 except Exception as e: log.error("oppai ~> Unhandled exception: {}".format(str(e))) self.pp = 0 raise finally: log.debug("oppai ~> Shutting down, pp = {}".format(self.pp))
def mapPath(self): return mapsHelper.cachedMapPath(self.beatmap.beatmapID)
def calculatePP(self): """ Calculate total pp value with oppai and return it return -- total pp """ # Set variables self.pp = None ez = None try: # Build .osu map file path mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID) mapsHelper.cacheMap(mapFile, self.beatmap) # Use only mods supported by oppai modsFixed = self.mods & 5983 # Check gamemode if self.gameMode not in (gameModes.STD, gameModes.TAIKO): raise exceptions.unsupportedGameModeException() ez = oppai.ezpp_new() if self.misses > 0: oppai.ezpp_set_nmiss(ez, self.misses) if self.combo >= 0: oppai.ezpp_set_combo(ez, self.combo) if self.gameMode == gameModes.TAIKO: oppai.ezpp_set_mode_override(ez, gameModes.TAIKO) if not self.tillerino: if self.acc > 0: oppai.ezpp_set_accuracy_percent(ez, self.acc) if self.mods > mods.NOMOD: oppai.ezpp_set_mods(ez, modsFixed) relax = (self.mods & mods.RELAX) > 0 autopilot = (self.mods & mods.RELAX2) > 0 if relax or autopilot: oppai.ezpp_set_relax_version(ez, 1) if relax: oppai.ezpp_set_relax(ez, 1) elif autopilot: oppai.ezpp_set_autopilot(ez, 1) if not self.tillerino: oppai.ezpp_dup(ez, mapFile) temp_pp = oppai.ezpp_pp(ez) self.stars = oppai.ezpp_stars(ez) if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \ self.stars > 50: # Invalidate pp for bugged taiko converteds and bugged inf pp std maps self.pp = 0 else: self.pp = temp_pp else: with open(mapFile, "r") as f: data = f.read() oppai.ezpp_data_dup(ez, data, len(data.encode())) pp_list = [] self.stars = oppai.ezpp_stars(ez) oppai.ezpp_set_autocalc(ez, 1) for acc in (100, 99, 98, 95): oppai.ezpp_set_accuracy_percent(ez, acc) pp = oppai.ezpp_pp(ez) # If this is a broken converted, set all pp to 0 and break the loop if self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and pp > 800: pp_list = [0, 0, 0, 0] break pp_list.append(pp) self.pp = pp_list log.debug("oppai ~> Calculated PP: {}, stars: {}".format( self.pp, self.stars)) except exceptions.osuApiFailException: log.error("oppai ~> osu!api error!") self.pp = 0 except exceptions.unsupportedGameModeException: log.error("oppai ~> Unsupported gamemode") self.pp = 0 except Exception as e: log.error("oppai ~> Unhandled exception: {}".format(str(e))) self.pp = 0 raise finally: if ez is not None: oppai.ezpp_free(ez) log.debug("oppai ~> Shutting down, pp = {}".format(self.pp))
def update_beatmaps(args): beatmaps = glob.db.fetchAll( 'select beatmapset_id as sid, beatmap_id as id, beatmap_md5 as md5 from beatmaps' ) for bm in beatmaps: b = beatmap.beatmap() b.beatmapID, b.fileMD5 = bm['id'], bm['md5'] if bm['id'] > 100000000: continue fn = mapsHelper.cachedMapPath(bm['id']) if mapsHelper.os.path.isfile(fn) and mapsHelper.generalUtils.fileMd5( fn) == bm['md5']: continue try: if args.download_update: if mapsHelper.os.path.isfile(fn): if mapsHelper.generalUtils.fileMd5(fn) != bm['md5']: log.info("BID {}: Hash difference ({}) expected ({})". format( bm['id'], mapsHelper.generalUtils.fileMd5(fn)[:8], bm['md5'][:8])) else: log.info("BID {}: Not exists, downloading...".format( bm['id'])) mapsHelper.cacheMap(fn, b) if mapsHelper.os.path.isfile( fn) and mapsHelper.generalUtils.fileMd5(fn) != bm['md5']: bm['update.md5'] = mapsHelper.generalUtils.fileMd5(fn) else: bm['delete.file'] = True except Exception as e: log.warning('BID {}: ({}) {}'.format(bm['id'], type(e).__name__, e)) pass if args.download_update: time.sleep(2.5) stmts = [] bmgroup = [] def destructor(query): nonlocal bmgroup query += " end where beatmap_id in ({})".format(','.join( str(bm) for bm in bmgroup)) del bmgroup[:] return query stmt = QueryBuilder("update beatmaps set beatmap_md5=case ", '', " ") stmt.finisher = destructor for bm in beatmaps: if 'delete.file' in bm: continue if 'update.md5' not in bm: continue bmgroup.append(bm['id']) stmtline = "when beatmap_id={} then '{}'".format( bm['id'], bm['update.md5']) newQuery = stmt.appendAndBuild(stmtline) if newQuery: stmts.append(newQuery) if stmt.query: stmts.append(stmt.build()) for s in stmts: # print(s) glob.db.execute(s)
def getPP(self): try: stars = self.beatmap.starsMania if stars == 0: # This beatmap can't be converted to mania raise exceptions.invalidBeatmapException() # Cache beatmap for cono mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID) mapsHelper.cacheMap(mapFile, self.beatmap) od = self.beatmap.OD objects = self.score.c50 + self.score.c100 + self.score.c300 + self.score.cKatu + self.score.cGeki + self.score.cMiss score = self.score.score accuracy = self.score.accuracy scoreMods = self.score.mods log.debug( "[WIFIPIANO2] SCORE DATA: Stars: {stars}, OD: {od}, obj: {objects}, score: {score}, acc: {acc}, mods: {mods}" .format(stars=stars, od=od, objects=objects, score=score, acc=accuracy, mods=scoreMods)) # ---------- STRAIN PP # Scale score to mods multiplier scoreMultiplier = 1.0 # Doubles score if EZ/HT if scoreMods & mods.EASY != 0: scoreMultiplier *= 0.50 #if scoreMods & mods.HALFTIME != 0: # scoreMultiplier *= 0.50 # Calculate strain PP if scoreMultiplier <= 0: strainPP = 0 else: score *= int(1.0 / scoreMultiplier) strainPP = pow(5.0 * max(1.0, stars / 0.0825) - 4.0, 3.0) / 110000.0 strainPP *= 1 + 0.1 * min(1.0, float(objects) / 1500.0) if score <= 500000: strainPP *= (float(score) / 500000.0) * 0.1 elif score <= 600000: strainPP *= 0.1 + float(score - 500000) / 100000.0 * 0.2 elif score <= 700000: strainPP *= 0.3 + float(score - 600000) / 100000.0 * 0.35 elif score <= 800000: strainPP *= 0.65 + float(score - 700000) / 100000.0 * 0.20 elif score <= 900000: strainPP *= 0.85 + float(score - 800000) / 100000.0 * 0.1 else: strainPP *= 0.95 + float(score - 900000) / 100000.0 * 0.05 # ---------- ACC PP # Makes sure OD is in range 0-10. If this is done elsewhere, remove this. scrubbedOD = min(10.0, max(0, 10.0 - od)) # Old formula but done backwards. hitWindow300 = (34 + 3 * scrubbedOD) # Increases hitWindow if EZ is on if scoreMods & mods.EASY != 0: hitWindow300 *= 1.4 # Fiddles with DT and HT to make them match hitWindow300's ingame. if scoreMods & mods.DOUBLETIME != 0: hitWindow300 *= 1.5 elif scoreMods & mods.HALFTIME != 0: hitWindow300 *= 0.75 # makes hit window match what it is ingame. hitWindow300 = int(hitWindow300) + 0.5 if scoreMods & mods.DOUBLETIME != 0: hitWindow300 /= 1.5 elif scoreMods & mods.HALFTIME != 0: hitWindow300 /= 0.75 # Calculate accuracy PP accPP = pow((150.0 / hitWindow300) * pow(accuracy, 16), 1.8) * 2.5 accPP *= min(1.15, pow(float(objects) / 1500.0, 0.3)) # ---------- TOTAL PP multiplier = 1.1 if scoreMods & mods.NOFAIL != 0: multiplier *= 0.90 if scoreMods & mods.SPUNOUT != 0: multiplier *= 0.95 if scoreMods & mods.EASY != 0: multiplier *= 0.50 if scoreMods & mods.HARDROCK != 0: multiplier *= 1.20 if scoreMods & mods.DOUBLETIME != 0: multiplier *= 1.45 if scoreMods & mods.NIGHTCORE != 0: multiplier *= 1.45 pp = pow(pow(strainPP, 1.1) + pow(accPP, 1.1), 1.0 / 1.1) * multiplier log.debug("[WIFIPIANO2] Calculated PP: {}".format(pp)) self.pp = pp except exceptions.invalidBeatmapException: log.warning("Invalid beatmap {}".format(self.beatmap.beatmapID)) self.pp = 0 finally: return self.pp