Ripple's score server https://ripple.moe
python
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rippoppai.py 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """
  2. oppai interface for ripple 2 / LETS
  3. """
  4. import json
  5. import os
  6. import subprocess
  7. from common.constants import gameModes
  8. from common.log import logUtils as log
  9. from common.ripple import scoreUtils
  10. from constants import exceptions
  11. from helpers import mapsHelper
  12. # constants
  13. MODULE_NAME = "rippoppai"
  14. UNIX = True if os.name == "posix" else False
  15. def fixPath(command):
  16. """
  17. Replace / with \ if running under WIN32
  18. commnd -- command to fix
  19. return -- command with fixed paths
  20. """
  21. if UNIX:
  22. return command
  23. return command.replace("/", "\\")
  24. class OppaiError(Exception):
  25. def __init__(self, error):
  26. self.error = error
  27. class oppai:
  28. """
  29. Oppai cacalculator
  30. """
  31. # __slots__ = ["pp", "score", "acc", "mods", "combo", "misses", "stars", "beatmap", "map"]
  32. def __init__(self, __beatmap, __score = None, acc = 0, mods = 0, tillerino = False):
  33. """
  34. Set oppai params.
  35. __beatmap -- beatmap object
  36. __score -- score object
  37. acc -- manual acc. Used in tillerino-like bot. You don't need this if you pass __score object
  38. mods -- manual mods. Used in tillerino-like bot. You don't need this if you pass __score object
  39. tillerino -- If True, self.pp will be a list with pp values for 100%, 99%, 98% and 95% acc. Optional.
  40. """
  41. # Default values
  42. self.pp = None
  43. self.score = None
  44. self.acc = 0
  45. self.mods = 0
  46. self.combo = 0
  47. self.misses = 0
  48. self.stars = 0
  49. self.tillerino = tillerino
  50. # Beatmap object
  51. self.beatmap = __beatmap
  52. # If passed, set everything from score object
  53. if __score is not None:
  54. self.score = __score
  55. self.acc = self.score.accuracy * 100
  56. self.mods = self.score.mods
  57. self.combo = self.score.maxCombo
  58. self.misses = self.score.cMiss
  59. self.gameMode = self.score.gameMode
  60. else:
  61. # Otherwise, set acc and mods from params (tillerino)
  62. self.acc = acc
  63. self.mods = mods
  64. if self.beatmap.starsStd > 0:
  65. self.gameMode = gameModes.STD
  66. elif self.beatmap.starsTaiko > 0:
  67. self.gameMode = gameModes.TAIKO
  68. else:
  69. self.gameMode = None
  70. # Calculate pp
  71. log.debug("oppai ~> Initialized oppai diffcalc")
  72. self.calculatePP()
  73. @staticmethod
  74. def _runOppaiProcess(command):
  75. log.debug("oppai ~> running {}".format(command))
  76. process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  77. try:
  78. output = json.loads(process.stdout.decode("utf-8", errors="ignore"))
  79. if "code" not in output or "errstr" not in output:
  80. raise OppaiError("No code in json output")
  81. if output["code"] != 200:
  82. raise OppaiError("oppai error {}: {}".format(output["code"], output["errstr"]))
  83. if "pp" not in output or "stars" not in output:
  84. raise OppaiError("No pp/stars entry in oppai json output")
  85. pp = output["pp"]
  86. stars = output["stars"]
  87. log.debug("oppai ~> full output: {}".format(output))
  88. log.debug("oppai ~> pp: {}, stars: {}".format(pp, stars))
  89. except (json.JSONDecodeError, IndexError, OppaiError) as e:
  90. raise OppaiError(e)
  91. return pp, stars
  92. def calculatePP(self):
  93. """
  94. Calculate total pp value with oppai and return it
  95. return -- total pp
  96. """
  97. # Set variables
  98. self.pp = None
  99. try:
  100. # Build .osu map file path
  101. mapFile = mapsHelper.cachedMapPath(self.beatmap.beatmapID)
  102. log.debug("oppai ~> Map file: {}".format(mapFile))
  103. mapsHelper.cacheMap(mapFile, self.beatmap)
  104. # Use only mods supported by oppai
  105. modsFixed = self.mods & 5983
  106. # Check gamemode
  107. if self.gameMode != gameModes.STD and self.gameMode != gameModes.TAIKO:
  108. raise exceptions.unsupportedGameModeException()
  109. command = "./pp/oppai-ng/oppai {}".format(mapFile)
  110. if not self.tillerino:
  111. # force acc only for non-tillerino calculation
  112. # acc is set for each subprocess if calculating tillerino-like pp sets
  113. if self.acc > 0:
  114. command += " {acc:.2f}%".format(acc=self.acc)
  115. if self.mods > 0:
  116. command += " +{mods}".format(mods=scoreUtils.readableMods(modsFixed))
  117. if self.combo > 0:
  118. command += " {combo}x".format(combo=self.combo)
  119. if self.misses > 0:
  120. command += " {misses}xm".format(misses=self.misses)
  121. if self.gameMode == gameModes.TAIKO:
  122. command += " -taiko"
  123. command += " -ojson"
  124. # Calculate pp
  125. if not self.tillerino:
  126. # self.pp, self.stars = self._runOppaiProcess(command)
  127. temp_pp, self.stars = self._runOppaiProcess(command)
  128. if (self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and temp_pp > 800) or \
  129. self.stars > 50:
  130. # Invalidate pp for bugged taiko converteds and bugged inf pp std maps
  131. self.pp = 0
  132. else:
  133. self.pp = temp_pp
  134. else:
  135. pp_list = []
  136. for acc in [100, 99, 98, 95]:
  137. temp_command = command
  138. temp_command += " {acc:.2f}%".format(acc=acc)
  139. pp, self.stars = self._runOppaiProcess(temp_command)
  140. # If this is a broken converted, set all pp to 0 and break the loop
  141. if self.gameMode == gameModes.TAIKO and self.beatmap.starsStd > 0 and pp > 800:
  142. pp_list = [0, 0, 0, 0]
  143. break
  144. pp_list.append(pp)
  145. self.pp = pp_list
  146. log.debug("oppai ~> Calculated PP: {}, stars: {}".format(self.pp, self.stars))
  147. except OppaiError:
  148. log.error("oppai ~> oppai-ng error!")
  149. self.pp = 0
  150. except exceptions.osuApiFailException:
  151. log.error("oppai ~> osu!api error!")
  152. self.pp = 0
  153. except exceptions.unsupportedGameModeException:
  154. log.error("oppai ~> Unsupported gamemode")
  155. self.pp = 0
  156. except Exception as e:
  157. log.error("oppai ~> Unhandled exception: {}".format(str(e)))
  158. self.pp = 0
  159. raise
  160. finally:
  161. log.debug("oppai ~> Shutting down, pp = {}".format(self.pp))