Exemplo n.º 1
0
    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))
Exemplo n.º 2
0
    def asyncGet(self):
        statusCode = 400
        data = {"message": "unknown error"}
        try:
            # Check arguments
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["b"]):
                raise exceptions.invalidArgumentsException(self.MODULE_NAME)

            # Get beatmap ID and make sure it's a valid number
            beatmapId = self.get_argument("b")
            if not beatmapId.isdigit():
                raise exceptions.invalidArgumentsException(self.MODULE_NAME)

            # Get mods
            if "m" in self.request.arguments:
                modsEnum = self.get_argument("m")
                if not modsEnum.isdigit():
                    raise exceptions.invalidArgumentsException(
                        self.MODULE_NAME)
                modsEnum = int(modsEnum)
            else:
                modsEnum = 0

            # Get game mode
            if "g" in self.request.arguments:
                gameMode = self.get_argument("g")
                if not gameMode.isdigit():
                    raise exceptions.invalidArgumentsException(
                        self.MODULE_NAME)
                gameMode = int(gameMode)
            else:
                gameMode = 0

            # Get acc
            if "a" in self.request.arguments:
                accuracy = self.get_argument("a")
                try:
                    accuracy = float(accuracy)
                except ValueError:
                    raise exceptions.invalidArgumentsException(
                        self.MODULE_NAME)
            else:
                accuracy = None

            # Print message
            log.info("Requested pp for beatmap {}".format(beatmapId))

            # Get beatmap md5 from osuapi
            # TODO: Move this to beatmap object
            osuapiData = osuapiHelper.osuApiRequest("get_beatmaps",
                                                    "b={}".format(beatmapId))
            if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData:
                raise exceptions.invalidBeatmapException(self.MODULE_NAME)
            beatmapMd5 = osuapiData["file_md5"]
            beatmapSetID = osuapiData["beatmapset_id"]

            # Create beatmap object
            bmap = beatmap.beatmap(beatmapMd5, beatmapSetID)

            # Check beatmap length
            # TODO: Why do we do this?
            if bmap.hit_length > 900:
                raise exceptions.beatmapTooLongException(self.MODULE_NAME)

            if gameMode == gameModes.STD and bmap.starsStd == 0:
                # Mode Specific beatmap, auto detect game mode
                if bmap.starsTaiko > 0:
                    gameMode = gameModes.TAIKO
                if bmap.starsCtb > 0:
                    gameMode = gameModes.CTB
                if bmap.starsMania > 0:
                    gameMode = gameModes.MANIA

            # Calculate pp
            if gameMode in (gameModes.STD, gameModes.TAIKO):
                # osu!standard and osu!taiko
                oppai = ez.Ez(bmap,
                              mods_=modsEnum,
                              tillerino=accuracy is None,
                              acc=accuracy,
                              gameMode=gameMode)
                bmap.starsStd = oppai.stars
                returnPP = oppai.pp
            elif gameMode == gameModes.CTB:
                # osu!catch
                ciccio = cicciobello.Cicciobello(bmap,
                                                 mods_=modsEnum,
                                                 tillerino=accuracy is None,
                                                 accuracy=accuracy)
                bmap.starsStd = ciccio.stars
                returnPP = ciccio.pp
            else:
                raise exceptions.unsupportedGameModeException()

            # Data to return
            data = {
                "song_name": bmap.songName,
                "pp":
                [x for x in returnPP] if type(returnPP) is list else returnPP,
                "game_mode": gameMode,
                "length": bmap.hit_length,
                "stars": bmap.starsStd
                if bmap.starsStd is not None else bmap.difficultyrating,
                "ar": bmap.diff_approach,
                "bpm": bmap.bpm,
            }

            # Set status code and message
            statusCode = 200
            data["message"] = "ok"
        except exceptions.invalidArgumentsException:
            # Set error and message
            statusCode = 400
            data["message"] = "missing required arguments"
        except exceptions.invalidBeatmapException:
            statusCode = 400
            data["message"] = "beatmap not found"
        except exceptions.beatmapTooLongException:
            statusCode = 400
            data["message"] = "requested beatmap is too long"
        except exceptions.unsupportedGameModeException:
            statusCode = 400
            data["message"] = "Unsupported gamemode"
        finally:
            # Add status code to data
            data["status"] = statusCode

            # Debug output
            log.debug(str(data))

            # Send response
            self.write(json.dumps(data))
            self.set_header("Content-Type", "application/json")
            self.set_status(statusCode)
Exemplo n.º 3
0
	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))
Exemplo n.º 4
0
    def asyncGet(self):
        statusCode = 400
        data = {"message": "unknown error"}
        try:
            # Check arguments
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["b"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get beatmap ID and make sure it's a valid number
            beatmapID = self.get_argument("b")
            if not beatmapID.isdigit():
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get mods
            if "m" in self.request.arguments:
                modsEnum = self.get_argument("m")
                if not modsEnum.isdigit():
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
                modsEnum = int(modsEnum)
            else:
                modsEnum = 0

            # Get game mode
            if "g" in self.request.arguments:
                gameMode = self.get_argument("g")
                if not gameMode.isdigit():
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
                gameMode = int(gameMode)
            else:
                gameMode = 0

            # Get acc
            if "a" in self.request.arguments:
                accuracy = self.get_argument("a")
                try:
                    accuracy = float(accuracy)
                except ValueError:
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
            else:
                accuracy = None

            # Print message
            log.info("Requested pp for beatmap {}".format(beatmapID))

            # Get beatmap md5 from osuapi
            # TODO: Move this to beatmap object
            osuapiData = osuapiHelper.osuApiRequest("get_beatmaps",
                                                    "b={}".format(beatmapID))
            if int(beatmapID) > 100000000:
                raise exceptions.ppCustomBeatmap(MODULE_NAME)
            if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData:
                raise exceptions.invalidBeatmapException(MODULE_NAME)
            beatmapMd5 = osuapiData["file_md5"]
            beatmapSetID = osuapiData["beatmapset_id"]

            # Create beatmap object
            bmap = beatmap.beatmap(beatmapMd5, beatmapSetID)

            # Check beatmap length
            if bmap.hitLength > 900:
                raise exceptions.beatmapTooLongException(MODULE_NAME)

            returnPP = []
            if gameMode == gameModes.STD and bmap.starsStd == 0:
                # Mode Specific beatmap, auto detect game mode
                if bmap.starsTaiko > 0:
                    gameMode = gameModes.TAIKO
                if bmap.starsCtb > 0:
                    gameMode = gameModes.CTB
                if bmap.starsMania > 0:
                    gameMode = gameModes.MANIA

            # Calculate pp
            if gameMode in (gameModes.STD, gameModes.TAIKO):
                # Std pp
                if accuracy is None and modsEnum == 0:
                    # Generic acc/nomod
                    # Get cached pp values
                    cachedPP = bmap.getCachedTillerinoPP()
                    if (modsEnum & mods.RELAX):
                        cachedPP = [0, 0, 0, 0]
                    elif (modsEnum & mods.RELAX2):
                        cachedPP = [0, 0, 0, 0]

                    if cachedPP != [0, 0, 0, 0]:
                        log.debug("Got cached pp.")
                        returnPP = cachedPP
                    else:
                        log.debug(
                            "Cached pp not found. Calculating pp with oppai..."
                        )
                        # Cached pp not found, calculate them
                        if gameMode == gameModes.STD and (modsEnum
                                                          & mods.RELAX):
                            oppai = relaxoppai.oppai(bmap,
                                                     mods=modsEnum,
                                                     tillerino=True)
                        elif gameMode == gameModes.STD and (modsEnum
                                                            & mods.RELAX2):
                            oppai = relax2oppai.oppai(bmap,
                                                      mods=modsEnum,
                                                      tillerino=True)
                        else:
                            oppai = rippoppai.oppai(bmap,
                                                    mods=modsEnum,
                                                    tillerino=True)
                        returnPP = oppai.pp
                        bmap.starsStd = oppai.stars

                        if not (modsEnum & mods.RELAX) or (modsEnum
                                                           & mods.RELAX2):
                            # Cache values in DB
                            log.debug("Saving cached pp...")
                            if type(returnPP) == list and len(returnPP) == 4:
                                bmap.saveCachedTillerinoPP(returnPP)
                else:
                    # Specific accuracy/mods, calculate pp
                    # Create oppai instance
                    log.debug(
                        "Specific request ({}%/{}). Calculating pp with oppai..."
                        .format(accuracy, modsEnum))
                    if gameMode == gameModes.STD and (modsEnum & mods.RELAX):
                        oppai = relaxoppai.oppai(bmap,
                                                 mods=modsEnum,
                                                 tillerino=True)
                    elif gameMode == gameModes.STD and (modsEnum
                                                        & mods.RELAX2):
                        oppai = relax2oppai.oppai(bmap,
                                                  mods=modsEnum,
                                                  tillerino=True)
                    else:
                        oppai = rippoppai.oppai(bmap,
                                                mods=modsEnum,
                                                tillerino=True)
                    bmap.starsStd = oppai.stars
                    if accuracy is not None:
                        returnPP = calculatePPFromAcc(oppai, accuracy)
                    else:
                        returnPP = oppai.pp
            else:
                raise exceptions.unsupportedGameModeException()

            # Data to return
            data = {
                "song_name": bmap.songName,
                "pp":
                [x for x in returnPP] if type(returnPP) is list else returnPP,
                "length": bmap.hitLength,
                "stars": bmap.starsStd,
                "ar": bmap.AR,
                "bpm": bmap.bpm,
            }

            # Set status code and message
            statusCode = 200
            data["message"] = "ok"
        except exceptions.invalidArgumentsException:
            # Set error and message
            statusCode = 400
            data["message"] = "missing required arguments"
        except exceptions.ppCustomBeatmap:
            statusCode = 400
            data[
                "message"] = "Custom map does not supported pp calculating yet"
        except exceptions.invalidBeatmapException:
            statusCode = 400
            data["message"] = "beatmap not found"
        except exceptions.beatmapTooLongException:
            statusCode = 400
            data["message"] = "requested beatmap is too long"
        except exceptions.unsupportedGameModeException:
            statusCode = 400
            data["message"] = "Unsupported gamemode"
        finally:
            # Add status code to data
            data["status"] = statusCode

            # Debug output
            log.debug(str(data))

            # Send response
            #self.clear()
            self.write(json.dumps(data))
            self.set_header("Content-Type", "application/json")
            self.set_status(statusCode)
Exemplo n.º 5
0
    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))
Exemplo n.º 6
0
    def _calculatePP(self):
        log.debug(f"ezPeace ~> object dict: {self.__dict__}")
        """
        Calculate total pp value with peace and return it

        return -- total pp
        """
        # Set variables
        self.pp = None
        peace = None
        try:
            # Check gamemode
            if self.gameMode not in (gameModes.STD, gameModes.TAIKO,
                                     gameModes.CTB, gameModes.MANIA):
                raise exceptions.unsupportedGameModeException()

            # Build .osu map file path
            mapFile = "{path}/{map}".format(
                path=self.GAME_FOLDER[self.gameMode], map=self.map)
            mapsHelper.cacheMap(mapFile, self.beatmap)

            beatmap = Beatmap(mapFile)
            peace = Calculator()

            # Not so readeable part starts here...
            peace.set_mode(self.gameMode)
            if self.misses > 0:
                peace.set_miss(self.misses)
            if self.combo >= 0:
                peace.set_combo(self.combo)
            if not self.tillerino:
                if self.acc > 0:
                    peace.set_acc(self.acc)
            if self.score and self.gameMode == gameModes.MANIA:
                peace.set_score(self.score.score)

            if self.mods > mods.NOMOD:
                peace.set_mods(self.mods)

            if not self.tillerino:
                peace_calculations = peace.calculate(beatmap)
                temp_pp = round(peace_calculations.pp, 5)
                self.stars = peace_calculations.attrs_dict['stars']
                if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \
                        self.stars > 50 or \
                            self.gameMode == gameModes.MANIA and self.mods & mods.SCOREV2 > 0:
                    # Invalidate pp for bugged taiko converteds and bugged inf pp std maps
                    self.pp = 0
                else:
                    self.pp = temp_pp
            else:
                pp_list = []
                peace_calculations = peace.calculate(beatmap)
                self.stars = peace_calculations.attrs_dict['stars']

                if self.acc and self.acc > 0:
                    peace.set_acc(self.acc)
                    peace_calculations = peace.calculate(beatmap)

                    self.pp = peace_calculations.pp
                else:
                    for acc in (100, 99, 98, 95):
                        peace.set_acc(acc)
                        peace_calculations = peace.calculate(beatmap)
                        pp = round(peace_calculations.pp, 5)
                        # 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 or \
                            self.gameMode == gameModes.MANIA and self.mods & mods.SCOREV2 > 0:
                            pp_list = [0, 0, 0, 0]
                            break

                        pp_list.append(pp)

                    self.pp = pp_list

            log.debug("peace ~> Calculated PP: {}, stars: {}".format(
                self.pp, self.stars))
        except exceptions.osuApiFailException:
            log.error("peace ~> osu!api error!")
            self.pp = 0
        except exceptions.unsupportedGameModeException:
            log.error("peace ~> Unsupported gamemode")
            self.pp = 0
        except Exception as e:
            log.error("peace ~> Unhandled exception: {}".format(str(e)))
            self.pp = 0
            raise
        finally:
            log.debug("peace ~> Shutting down, pp = {}".format(self.pp))
Exemplo n.º 7
0
    def _calculatePP(self):
        """
        Calculate total pp value with peace and return it
        return -- total pp
        """
        # Set variables
        self.pp = None
        peace = None
        try:
            # Check gamemode
            if self.gameMode not in (gameModes.STD, gameModes.TAIKO,
                                     gameModes.CTB, gameModes.MANIA):
                raise exceptions.unsupportedGameModeException()

            # Build .osu map file path
            mapFile = "{path}/maps/{map}".format(
                path=self.GAME_FOLDER[self.gameMode], map=self.map)
            mapsHelper.cacheMap(mapFile, self.beatmap)

            rosu = Calculator(mapFile)
            score_params = ScoreParams()

            # Not so readeable part starts here...
            if self.misses > 0:
                score_params.nMisses = self.misses
            if self.combo >= 0:
                score_params.combo = self.combo
            if not self.tillerino:
                if self.acc > 0:
                    score_params.acc = self.acc
            if self.score and self.gameMode == gameModes.MANIA:
                score_params.score = self.score.score

            if self.mods > mods.NOMOD:
                score_params.mods = self.mods

            [rosu_calculations] = rosu.calculate(score_params)
            if not self.tillerino:
                if rosu_calculations.mode != self.score.gameMode:
                    self.pp = 0
                else:
                    temp_pp = round(rosu_calculations.pp, 5)
                    self.stars = rosu_calculations.stars
                    if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \
                            self.stars > 50 or \
                                self.gameMode == gameModes.MANIA and self.mods & mods.SCOREV2 > 0:
                        # Invalidate pp for bugged taiko converteds and bugged inf pp std maps
                        self.pp = 0
                    else:
                        self.pp = temp_pp
            else:
                if rosu_calculations.mode != self.score.gameMode:
                    self.pp = [0, 0, 0, 0]
                else:
                    pp_list = []
                    self.stars = rosu_calculations.stars

                    if self.acc and self.acc > 0:
                        score_params.acc = self.acc
                        [rosu_calculations] = rosu.calculate(score_params)

                        self.pp = rosu_calculations.pp
                    else:
                        # ik that can be a better way, but i don't wanna do something "NICE" to this odd code
                        for acc in (100, 99, 98, 95):
                            score_params.acc = self.acc
                            [rosu_calculations] = rosu.calculate(score_params)
                            pp = round(rosu_calculations.pp, 5)
                            # 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 or \
                                self.gameMode == gameModes.MANIA and self.mods & mods.SCOREV2 > 0:
                                pp_list = [0, 0, 0, 0]
                                break

                            pp_list.append(pp)

                        self.pp = pp_list

            log.debug("rosu ~> Calculated PP: {}, stars: {}".format(
                self.pp, self.stars))
        except exceptions.osuApiFailException:
            log.error("rosu ~> osu!api error!")
            self.pp = 0
        except exceptions.unsupportedGameModeException:
            log.error("rosu ~> Unsupported gamemode")
            self.pp = 0
        except Exception as e:
            log.error("rosu ~> Unhandled exception: {}".format(str(e)))
            self.pp = 0
            raise
        finally:
            log.debug("rosu ~> Shutting down, pp = {}".format(self.pp))