osudroid-api-wrapper 0.0.5__tar.gz → 0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/PKG-INFO +1 -1
  2. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/pyproject.toml +1 -1
  3. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/__init__.py +2 -0
  4. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/__init__.py +1 -3
  5. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/base/__init__.py +2 -1
  6. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/base/beatmap.py +10 -14
  7. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/modlist.py +255 -0
  8. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/__init__.py +31 -0
  9. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod.py +90 -0
  10. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_approachdifferent.py +58 -0
  11. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_auto.py +11 -0
  12. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_autopilot.py +11 -0
  13. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_customspeed.py +27 -0
  14. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_difficultyadjust.py +39 -0
  15. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_doubletime.py +10 -0
  16. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_easy.py +10 -0
  17. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_flashlight.py +35 -0
  18. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_freezeframe.py +11 -0
  19. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_halftime.py +10 -0
  20. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_hardrock.py +10 -0
  21. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_hidden.py +25 -0
  22. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_mirror.py +43 -0
  23. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_muted.py +56 -0
  24. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_nightcore.py +10 -0
  25. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_nofail.py +10 -0
  26. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_perfect.py +10 -0
  27. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_precise.py +12 -0
  28. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_random.py +39 -0
  29. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_reallyeasy.py +13 -0
  30. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_relax.py +11 -0
  31. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_replayv6.py +9 -0
  32. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_scorev2.py +11 -0
  33. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_smallcircles.py +8 -0
  34. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_suddendeath.py +10 -0
  35. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_synesthesia.py +11 -0
  36. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_traceable.py +10 -0
  37. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_winddown.py +44 -0
  38. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_windup.py +44 -0
  39. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/settings/__init__.py +2 -0
  40. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/settings/setting.py +103 -0
  41. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/settings/settings_list.py +66 -0
  42. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/base/player.py +47 -41
  43. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay.py +134 -80
  44. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/cursordata.py +2 -5
  45. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/cursoroccurrence.py +1 -2
  46. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/cursoroccurrencegroup.py +19 -10
  47. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/hitresult.py +2 -0
  48. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/replayobjectdata.py +13 -10
  49. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/score.py +145 -0
  50. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/structs/__init__.py +1 -0
  51. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/structs/profile.py +13 -22
  52. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/PKG-INFO +1 -1
  53. osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper.egg-info/SOURCES.txt +57 -0
  54. osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/__init__.py +0 -2
  55. osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/classes/base/mods.py +0 -39
  56. osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/classes/score.py +0 -107
  57. osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/structs/__init__.py +0 -1
  58. osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper.egg-info/SOURCES.txt +0 -23
  59. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/LICENSE +0 -0
  60. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/setup.cfg +0 -0
  61. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/movementtype.py +0 -0
  62. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/dependency_links.txt +0 -0
  63. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/requires.txt +0 -0
  64. {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osudroid-api-wrapper
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: Python api wrapper for osu!droid
5
5
  Author-email: unclem <mannringyt11@gmail.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "osudroid-api-wrapper"
7
- version = "0.0.5"
7
+ version = "0.0.6"
8
8
  authors = [
9
9
  { name="unclem", email="mannringyt11@gmail.com" }
10
10
  ]
@@ -0,0 +1,2 @@
1
+ from .structs import Profile
2
+ from .classes import Replay, Score, ModList, Player, Beatmap, mods
@@ -1,5 +1,3 @@
1
1
  from .replay import Replay
2
2
  from .score import Score
3
- from .base import Mods, Player, Beatmap
4
-
5
-
3
+ from .base import ModList, Player, Beatmap, mods
@@ -1,3 +1,4 @@
1
1
  from .beatmap import Beatmap
2
2
  from .player import Player
3
- from .mods import Mods
3
+ from .modlist import ModList
4
+
@@ -29,7 +29,6 @@ import os
29
29
  import requests
30
30
 
31
31
  dotenv.load_dotenv()
32
- key = os.getenv("OSU_API_KEY")
33
32
 
34
33
 
35
34
  class Beatmap:
@@ -51,16 +50,17 @@ class Beatmap:
51
50
  self.length: int = None
52
51
 
53
52
  @classmethod
54
- def get_beatmap(cls, beatmap_id: int = None, md5: str = None) -> 'Beatmap':
53
+ def get_beatmap(cls, beatmap_id: int = None, md5: str = None) -> "Beatmap":
54
+ key = os.getenv("OSU_API_KEY")
55
+
55
56
  beatmap = cls()
56
57
  url = "https://old.ppy.sh/api/get_beatmaps"
57
58
  if key is "":
58
- raise ValueError("API key is not set. Please set the OSU_API_KEY environment variable.")
59
+ raise ValueError(
60
+ "API key is not set. Please set the OSU_API_KEY environment variable."
61
+ )
59
62
  if beatmap_id is not None:
60
- params = {
61
- "k": key,
62
- "b": beatmap_id
63
- }
63
+ params = {"k": key, "b": beatmap_id}
64
64
  response = requests.get(url, params=params)
65
65
  data = response.json()
66
66
  if len(data) == 0:
@@ -68,10 +68,7 @@ class Beatmap:
68
68
  data = data[0]
69
69
 
70
70
  elif md5 is not None:
71
- params = {
72
- "k": key,
73
- "h": md5
74
- }
71
+ params = {"k": key, "h": md5}
75
72
  response = requests.get(url, params=params)
76
73
  data = response.json()
77
74
  if len(data) == 0:
@@ -95,9 +92,8 @@ class Beatmap:
95
92
  beatmap.star = float(data["difficultyrating"])
96
93
  beatmap.length = int(data["total_length"])
97
94
  beatmap.combo = int(data["max_combo"])
98
-
99
- return beatmap
100
95
 
96
+ return beatmap
101
97
 
102
98
  @property
103
99
  def to_dict(self):
@@ -116,5 +112,5 @@ class Beatmap:
116
112
  "bpm": self.bpm,
117
113
  "star": self.star,
118
114
  "length": self.length,
119
- "combo": self.combo
115
+ "combo": self.combo,
120
116
  }
@@ -0,0 +1,255 @@
1
+ from .mods import (
2
+ Mod,
3
+ ModApproachDifferent,
4
+ ModAuto,
5
+ ModAutopilot,
6
+ ModCustomSpeed,
7
+ ModDifficultyAdjust,
8
+ ModDoubleTime,
9
+ ModEasy,
10
+ ModFlashlight,
11
+ ModHardRock,
12
+ ModHalfTime,
13
+ ModHidden,
14
+ ModMuted,
15
+ ModNightcore,
16
+ ModNoFail,
17
+ ModPerfect,
18
+ ModPrecise,
19
+ ModReallyEasy,
20
+ ModRelax,
21
+ ModScoreV2,
22
+ ModSuddenDeath,
23
+ ModSynesthesia,
24
+ ModTraceable,
25
+ ModWindDown,
26
+ ModWindUp,
27
+ ModRandom,
28
+ ModMirror,
29
+ ModFreezeFrame,
30
+ ModSmallCircles,
31
+ ModReplayV6,
32
+ )
33
+ from typing import List
34
+ import re
35
+ import json
36
+
37
+ # TODO: Fix mod order, remove repeated mapping declarations, check acronyms, add missing mods
38
+
39
+
40
+ class ModList:
41
+
42
+ def __init__(self, mods=None):
43
+ self.__mods: List[Mod] = mods
44
+
45
+ @classmethod
46
+ def from_droid_site(cls, mods: str):
47
+
48
+ mod_abbreviations: dict[str, type[Mod]] = {
49
+ "None": "",
50
+ "Easy": ModEasy,
51
+ "HalfTime": ModHalfTime,
52
+ "NoFail": ModNoFail,
53
+ "ReallyEasy": ModReallyEasy,
54
+ "DoubleTime": ModDoubleTime,
55
+ "Flashlight": ModFlashlight,
56
+ "HardRock": ModHardRock,
57
+ "Hidden": ModHidden,
58
+ "Nightcore": ModNightcore,
59
+ "Perfect": ModPerfect,
60
+ "Precise": ModPrecise,
61
+ "SuddenDeath": ModSuddenDeath,
62
+ "Traceable": ModTraceable,
63
+ }
64
+ mod_list = []
65
+ for mod in mods.split(","):
66
+ mod = mod.strip()
67
+ if mod.startswith("CustomSpeed"):
68
+ mod_list.append(ModCustomSpeed(float(mod.split("(")[1][:-2])))
69
+ continue
70
+
71
+ if mod in mod_abbreviations:
72
+ if mod == "None":
73
+ continue
74
+ mod_list.append(mod_abbreviations[mod]())
75
+
76
+ else:
77
+ raise ValueError(f"Unknown mod: {mod}")
78
+
79
+ return cls(mod_list)
80
+
81
+ @classmethod
82
+ def from_droid_replay_v6(cls, mods: list):
83
+ mod_mapping = {
84
+ "MOD_NOFAIL": ModNoFail,
85
+ "MOD_EASY": ModEasy,
86
+ "MOD_HIDDEN": ModHidden,
87
+ "MOD_HARDROCK": ModHardRock,
88
+ "MOD_SUDDENDEATH": ModSuddenDeath,
89
+ "MOD_DOUBLETIME": ModDoubleTime,
90
+ "MOD_RELAX": ModRelax,
91
+ "MOD_HALFTIME": ModHalfTime,
92
+ "MOD_NIGHTCORE": ModNightcore,
93
+ "MOD_FLASHLIGHT": ModFlashlight,
94
+ "MOD_SCOREV2": ModScoreV2,
95
+ "MOD_AUTOPILOT": ModAutopilot,
96
+ "MOD_AUTO": ModAuto,
97
+ "MOD_PRECISE": ModPrecise,
98
+ "MOD_REALLYEASY": ModReallyEasy,
99
+ "MOD_SMALLCIRCLES": ModSmallCircles,
100
+ "MOD_PERFECT": ModPerfect,
101
+ "MOD_SUDDENDEATH": ModSuddenDeath,
102
+ }
103
+ mod_list = []
104
+ for mod in mods:
105
+ if mod in mod_mapping:
106
+ mod_list.append(mod_mapping[mod]())
107
+ else:
108
+ raise ValueError(f"Unknown mod: {mod}")
109
+
110
+ return cls(mod_list)
111
+
112
+ @classmethod
113
+ def from_dict(cls, mods: list):
114
+ mod_classes: List[type[Mod]] = [
115
+ ModEasy,
116
+ ModHalfTime,
117
+ ModNoFail,
118
+ ModReallyEasy,
119
+ ModDoubleTime,
120
+ ModFlashlight,
121
+ ModHardRock,
122
+ ModHidden,
123
+ ModNightcore,
124
+ ModPerfect,
125
+ ModPrecise,
126
+ ModSuddenDeath,
127
+ ModTraceable,
128
+ ModAuto,
129
+ ModAutopilot,
130
+ ModRelax,
131
+ ModCustomSpeed,
132
+ ModDifficultyAdjust,
133
+ ModMirror,
134
+ ModRandom,
135
+ ModScoreV2,
136
+ ModApproachDifferent,
137
+ ModFreezeFrame,
138
+ ModMuted,
139
+ ModSynesthesia,
140
+ ModWindDown,
141
+ ModWindUp,
142
+ ]
143
+
144
+ mod_mapping = {cls_().acronym: cls_() for cls_ in mod_classes}
145
+
146
+ mod_list = []
147
+ for mod in mods:
148
+ acronym = mod["acronym"]
149
+ if acronym in mod_mapping:
150
+ mod_instance = mod_mapping[acronym]
151
+ settings = mod.get("settings", {})
152
+ for key, value in settings.items():
153
+ try:
154
+ mod_instance.settings.set_value(key, value)
155
+ except ValueError as e:
156
+ raise ValueError(
157
+ f"Invalid setting '{key}' for mod '{acronym}': {e}"
158
+ )
159
+ mod_list.append(mod_instance)
160
+
161
+ return cls(mod_list)
162
+
163
+ @classmethod
164
+ def from_droid_letters(cls, mods: str):
165
+ mod_mapping: dict[str, type[Mod]] = {
166
+ "n": ModNoFail,
167
+ "e": ModEasy,
168
+ "h": ModHidden,
169
+ "r": ModHardRock,
170
+ "u": ModSuddenDeath,
171
+ "d": ModDoubleTime,
172
+ "x": ModCustomSpeed,
173
+ "t": ModHalfTime,
174
+ "c": ModNightcore,
175
+ "i": ModFlashlight,
176
+ "v": ModScoreV2,
177
+ "p": ModAutopilot,
178
+ "a": ModApproachDifferent,
179
+ "s": ModPrecise,
180
+ "l": ModReallyEasy,
181
+ "m": ModSynesthesia,
182
+ "f": ModPerfect,
183
+ "b": ModTraceable,
184
+ }
185
+ mod_list = []
186
+ mod_chars = re.sub(r"\bx\d+\.\d+\b|[^a-z]", "", mods)
187
+ for char in mod_chars:
188
+ if char in mod_mapping:
189
+ mod_list.append(mod_mapping[char]())
190
+ else:
191
+ raise ValueError(f"Unknown mod character: {char}")
192
+
193
+ matchar = re.search(r"\bAR(\d+\.\d+)\b", mods)
194
+ matchcs = re.search(r"\bCS(\d+\.\d+)\b", mods)
195
+ matchod = re.search(r"\bOD(\d+\.\d+)\b", mods)
196
+ matchhp = re.search(r"\bHP(\d+\.\d+)\b", mods)
197
+ matchsm = re.search(r"\bx(\d+\.\d+)\b", mods, re.IGNORECASE)
198
+ matchfld = re.search(r"\bFLD(\d+\.\d+)\b", mods)
199
+
200
+ if matchar or matchcs or matchod or matchhp:
201
+ da = ModDifficultyAdjust(
202
+ ar=float(matchar.group(1)) if matchar else None,
203
+ cs=float(matchcs.group(1)) if matchcs else None,
204
+ od=float(matchod.group(1)) if matchod else None,
205
+ hp=float(matchhp.group(1)) if matchhp else None,
206
+ )
207
+ mod_list.append(da)
208
+ if matchsm:
209
+ mod_list.append(ModCustomSpeed(float(matchsm.group(1))))
210
+ if matchfld:
211
+ if mod_list.count(ModFlashlight) == 0:
212
+ mod_list.append(ModFlashlight(matchfld.group(1)))
213
+ return cls(mod_list)
214
+
215
+ @property
216
+ def mods(self) -> List[Mod]:
217
+ """List of mods."""
218
+ return self.__mods
219
+
220
+ def get_mod(self, acronym: str) -> Mod | None:
221
+ """Get a mod by its acronym."""
222
+ for mod in self.__mods:
223
+ if mod.acronym == acronym:
224
+ return mod
225
+ return None
226
+
227
+ def add_mod(self, mod: Mod):
228
+ """Add a new mod to the list."""
229
+ if not isinstance(mod, Mod):
230
+ raise TypeError("mod must be an instance of Mod.")
231
+ self.__mods.append(mod)
232
+
233
+ @property
234
+ def as_json(self) -> List[dict]:
235
+ """Return the mod list as a JSON serializable list with full mod details."""
236
+ return [mod.as_json for mod in self.__mods]
237
+
238
+ @property
239
+ def as_standard_mods(self) -> str:
240
+ """Return the mod list as a string of mod acronyms and settings."""
241
+ return "".join(mod.as_standard_mod for mod in self.__mods if mod.acronym)
242
+
243
+ @property
244
+ def as_calculatable_mods(self) -> list:
245
+ """Return the mod list as a string of mods suitable for pp calculation."""
246
+ return [mod.as_calculatable for mod in self.__mods]
247
+
248
+ @property
249
+ def as_json_string(self) -> str:
250
+ """Return the mod list as a JSON string."""
251
+ return json.dumps(self.as_calculatable_mods, separators=(",", ":"))
252
+
253
+ def __iter__(self):
254
+ """Iterate over the mods."""
255
+ return iter(self.__mods)
@@ -0,0 +1,31 @@
1
+ from .mod import Mod
2
+ from .mod_easy import ModEasy
3
+ from .mod_reallyeasy import ModReallyEasy
4
+ from .mod_hardrock import ModHardRock
5
+ from .mod_hidden import ModHidden
6
+ from .mod_flashlight import ModFlashlight
7
+ from .mod_precise import ModPrecise
8
+ from .mod_relax import ModRelax
9
+ from .mod_autopilot import ModAutopilot
10
+ from .mod_auto import ModAuto
11
+ from .mod_nofail import ModNoFail
12
+ from .mod_scorev2 import ModScoreV2
13
+ from .mod_suddendeath import ModSuddenDeath
14
+ from .mod_perfect import ModPerfect
15
+ from .mod_doubletime import ModDoubleTime
16
+ from .mod_nightcore import ModNightcore
17
+ from .mod_halftime import ModHalfTime
18
+ from .mod_windup import ModWindUp
19
+ from .mod_winddown import ModWindDown
20
+ from .mod_mirror import ModMirror
21
+ from .mod_traceable import ModTraceable
22
+ from .mod_muted import ModMuted
23
+ from .mod_approachdifferent import ModApproachDifferent
24
+ from .mod_random import ModRandom
25
+ from .mod_freezeframe import ModFreezeFrame
26
+ from .mod_synesthesia import ModSynesthesia
27
+ from .mod_customspeed import ModCustomSpeed
28
+ from .mod_difficultyadjust import ModDifficultyAdjust
29
+ from .settings import SettingsList, Setting
30
+ from .mod_smallcircles import ModSmallCircles
31
+ from .mod_replayv6 import ModReplayV6
@@ -0,0 +1,90 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List
3
+ from .settings.settings_list import SettingsList
4
+
5
+
6
+ class Mod(ABC):
7
+ """Base class for all mods."""
8
+
9
+ def __init__(self):
10
+ self.__name: str = None
11
+ self.__acronym: str = None
12
+ self.__settings: SettingsList = SettingsList()
13
+ self.__is_ranked: bool = True
14
+
15
+ @property
16
+ def name(self) -> str:
17
+ """Name of the mod."""
18
+ return self.__name
19
+
20
+ @name.setter
21
+ def name(self, new_name: str):
22
+ """Set the name of the mod."""
23
+ if self.__name is not None:
24
+ raise ValueError("Name can only be set once.")
25
+ self.__name = new_name
26
+
27
+ @property
28
+ def acronym(self) -> str:
29
+ """Acronym of the mod."""
30
+ return self.__acronym
31
+
32
+ @acronym.setter
33
+ def acronym(self, new_acronym: str):
34
+ """Set the acronym of the mod."""
35
+ if self.__acronym is not None:
36
+ raise ValueError("Acronym can only be set once.")
37
+ self.__acronym = new_acronym
38
+
39
+ @property
40
+ def is_ranked(self) -> bool:
41
+ """Submit status of the mod"""
42
+ return self.__is_ranked
43
+
44
+ @is_ranked.setter
45
+ def is_ranked(self, new_is_ranked: bool):
46
+ """Set the ranked status of the mod."""
47
+ self.__is_ranked = new_is_ranked
48
+
49
+ @property
50
+ def as_json(self) -> dict:
51
+ """Return the mod as a JSON serializable dictionary."""
52
+ return {
53
+ "name": self.__name,
54
+ "acronym": self.__acronym,
55
+ "settings": self.__settings.as_json,
56
+ "is_ranked": self.__is_ranked,
57
+ }
58
+
59
+ @property
60
+ def as_standard_mod(self) -> str:
61
+ """Return the mod as a standard mod instance."""
62
+ return self.__acronym
63
+
64
+ @property
65
+ def as_calculatable(self) -> dict:
66
+ """Return the mod in a format suitable for pp calculation."""
67
+ ret = {
68
+ "acronym": self.__acronym,
69
+ }
70
+ if self.__settings.as_calculatable:
71
+ ret["settings"] = self.__settings.as_calculatable
72
+ return ret
73
+
74
+ @property
75
+ def settings(self) -> SettingsList:
76
+ """Settings of the mod."""
77
+ return self.__settings
78
+
79
+ @settings.setter
80
+ def settings(self, new_settings: SettingsList):
81
+ """Set the settings of the mod."""
82
+ if not isinstance(new_settings, SettingsList):
83
+ raise TypeError("Settings must be an instance of SettingsList.")
84
+ self.__settings = new_settings
85
+
86
+ def __repr__(self):
87
+ return f"{self.__name}({self.settings})"
88
+
89
+ def __str__(self):
90
+ return f"{self.__name}({self.settings})"
@@ -0,0 +1,58 @@
1
+ from .mod import Mod
2
+ from .settings import Setting
3
+ from enum import Enum
4
+ from typing import override
5
+
6
+
7
+ class ApproachStyle(Enum):
8
+ """Enum for approach styles in the approach different mod."""
9
+
10
+ LINEAR = 0
11
+ GRAVITY = 1
12
+ IN_OUT_1 = 2
13
+ IN_OUT_2 = 3
14
+ ACCELERATE_1 = 4
15
+ ACCELERATE_2 = 5
16
+ ACCELERATE_3 = 6
17
+ DECELERATE_1 = 7
18
+ DECELERATE_2 = 8
19
+ DECELERATE_3 = 9
20
+ BOUNCE_IN = 10
21
+ BOUNCE_OUT = 11
22
+ BOUNCE_IN_OUT = 12
23
+
24
+
25
+ class ModApproachDifferent(Mod):
26
+ """ModApproachDifferent class represents the approach different mod in osu!droid."""
27
+
28
+ def __init__(self, scale: float = None, style: ApproachStyle = None):
29
+ super().__init__()
30
+ self.name = "Approach Different"
31
+ self.acronym = "AD"
32
+ self.is_ranked = False
33
+ self.settings.add_setting(
34
+ Setting(
35
+ name="scale",
36
+ default_value=3.0,
37
+ value=scale,
38
+ min_value=1.5,
39
+ max_value=10.0,
40
+ step=0.1,
41
+ )
42
+ )
43
+ self.settings.add_setting(
44
+ Setting(
45
+ name="style",
46
+ default_value=ApproachStyle.LINEAR,
47
+ value=style,
48
+ min_value=0,
49
+ max_value=len(ApproachStyle) - 1,
50
+ step=1,
51
+ )
52
+ )
53
+
54
+ @property
55
+ @override
56
+ def as_standard_mod(self):
57
+ # This mod have options, but for some reason it is not displayed in the game
58
+ return self.__acronym
@@ -0,0 +1,11 @@
1
+ from .mod import Mod
2
+
3
+
4
+ class ModAuto(Mod):
5
+ """ModAutoplay class represents the autoplay mod in osu!droid."""
6
+
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.name = "Auto"
10
+ self.acronym = "AT"
11
+ self.is_ranked = False
@@ -0,0 +1,11 @@
1
+ from .mod import Mod
2
+
3
+
4
+ class ModAutopilot(Mod):
5
+ """ModAutopilot class represents the autopilot mod in osu!droid."""
6
+
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.name = "Autopilot"
10
+ self.acronym = "AP"
11
+ self.is_ranked = False
@@ -0,0 +1,27 @@
1
+ from .mod import Mod
2
+ from .settings import Setting
3
+ from typing import override
4
+
5
+
6
+ class ModCustomSpeed(Mod):
7
+ """ModCustomSpeed class represents the custom speed mod in osu!droid."""
8
+
9
+ def __init__(self, rateMultiplier: float = None):
10
+ super().__init__()
11
+ self.name = "Custom Speed"
12
+ self.acronym = "CS"
13
+ self.settings.add_setting(
14
+ Setting(
15
+ name="rateMultiplier",
16
+ default_value=1.0,
17
+ value=rateMultiplier,
18
+ min_value=0.5,
19
+ max_value=2.0,
20
+ step=0.05,
21
+ )
22
+ )
23
+
24
+ @property
25
+ @override
26
+ def as_standard_mod(self):
27
+ return f"{self.acronym}(x{self.settings.get_setting('rateMultiplier').value})"
@@ -0,0 +1,39 @@
1
+ from .mod import Mod
2
+ from .settings import Setting
3
+ from typing import override
4
+
5
+
6
+ class ModDifficultyAdjust(Mod):
7
+ """ModDifficultyAdjust class represents the difficulty adjustment mod in osu!droid."""
8
+
9
+ def __init__(
10
+ self, ar: float = None, cs: float = None, od: float = None, hp: float = None
11
+ ):
12
+
13
+ super().__init__()
14
+ self.name = "Difficulty Adjust"
15
+ self.acronym = "DA"
16
+ self.settings.add_setting(
17
+ Setting(name="ar", value=ar, min_value=0.0, max_value=12.5, step=0.1)
18
+ )
19
+ self.settings.add_setting(
20
+ Setting(name="cs", value=cs, min_value=0.0, max_value=15.0, step=0.1)
21
+ )
22
+ self.settings.add_setting(
23
+ Setting(name="od", value=od, min_value=0.0, max_value=11.0, step=0.1)
24
+ )
25
+ self.settings.add_setting(
26
+ Setting(name="hp", value=hp, min_value=0.0, max_value=11.0, step=0.1)
27
+ )
28
+ self.is_ranked = False
29
+
30
+ @property
31
+ @override
32
+ def as_standard_mod(self) -> str:
33
+ string = f"{self.acronym}"
34
+ stats = ", ".join(
35
+ f"{setting.name.upper()}{setting.value}"
36
+ for setting in self.settings
37
+ if (setting.value is not None and setting.value != setting.default_value)
38
+ )
39
+ return f"{string}({stats})" if stats else string
@@ -0,0 +1,10 @@
1
+ from .mod import Mod
2
+
3
+
4
+ class ModDoubleTime(Mod):
5
+ """ModDoubleTime class represents the doubletime mod in osu!droid."""
6
+
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.name = "Double Time"
10
+ self.acronym = "DT"
@@ -0,0 +1,10 @@
1
+ from .mod import Mod
2
+
3
+
4
+ class ModEasy(Mod):
5
+ """ModEasy class represents the easy mod in osu!droid."""
6
+
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.name = "Easy"
10
+ self.acronym = "EZ"
@@ -0,0 +1,35 @@
1
+ from .mod import Mod
2
+ from .settings import SettingsList, Setting
3
+ from typing import override
4
+
5
+
6
+ class ModFlashlight(Mod):
7
+ """ModFlashlight class represents the flashlight mod in osu!droid."""
8
+
9
+ def __init__(self, areaFollowDelay: float = None):
10
+ super().__init__()
11
+ self.name = "Flashlight"
12
+ self.acronym = "FL"
13
+ self.settings.add_setting(
14
+ Setting(
15
+ name="areaFollowDelay",
16
+ min_value=0.12,
17
+ max_value=1.2,
18
+ default_value=0.12,
19
+ value=areaFollowDelay,
20
+ step=0.12,
21
+ )
22
+ )
23
+
24
+ @property
25
+ @override
26
+ def as_standard_mod(self) -> str:
27
+ if (
28
+ self.settings.get_setting("areaFollowDelay").value is not None
29
+ and self.settings.get_setting("areaFollowDelay").value
30
+ != self.settings.get_setting("areaFollowDelay").default_value
31
+ ):
32
+ return (
33
+ f"{self.acronym}({self.settings.get_setting('areaFollowDelay').value}s)"
34
+ )
35
+ return self.acronym