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.
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/PKG-INFO +1 -1
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/pyproject.toml +1 -1
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/__init__.py +2 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/__init__.py +1 -3
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/base/__init__.py +2 -1
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/base/beatmap.py +10 -14
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/modlist.py +255 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/__init__.py +31 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod.py +90 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_approachdifferent.py +58 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_auto.py +11 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_autopilot.py +11 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_customspeed.py +27 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_difficultyadjust.py +39 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_doubletime.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_easy.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_flashlight.py +35 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_freezeframe.py +11 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_halftime.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_hardrock.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_hidden.py +25 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_mirror.py +43 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_muted.py +56 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_nightcore.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_nofail.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_perfect.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_precise.py +12 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_random.py +39 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_reallyeasy.py +13 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_relax.py +11 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_replayv6.py +9 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_scorev2.py +11 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_smallcircles.py +8 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_suddendeath.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_synesthesia.py +11 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_traceable.py +10 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_winddown.py +44 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_windup.py +44 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/settings/__init__.py +2 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/settings/setting.py +103 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/settings/settings_list.py +66 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/base/player.py +47 -41
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay.py +134 -80
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/cursordata.py +2 -5
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/cursoroccurrence.py +1 -2
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/cursoroccurrencegroup.py +19 -10
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/hitresult.py +2 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/replayobjectdata.py +13 -10
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/score.py +145 -0
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/structs/__init__.py +1 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/structs/profile.py +13 -22
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/PKG-INFO +1 -1
- osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper.egg-info/SOURCES.txt +57 -0
- osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/__init__.py +0 -2
- osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/classes/base/mods.py +0 -39
- osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/classes/score.py +0 -107
- osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper/structs/__init__.py +0 -1
- osudroid_api_wrapper-0.0.5/src/osudroid_api_wrapper.egg-info/SOURCES.txt +0 -23
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/LICENSE +0 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/setup.cfg +0 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper/classes/replay_data/movementtype.py +0 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/dependency_links.txt +0 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/requires.txt +0 -0
- {osudroid_api_wrapper-0.0.5 → osudroid_api_wrapper-0.0.6}/src/osudroid_api_wrapper.egg-info/top_level.txt +0 -0
|
@@ -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) ->
|
|
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(
|
|
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})"
|
osudroid_api_wrapper-0.0.6/src/osudroid_api_wrapper/classes/base/mods/mod_approachdifferent.py
ADDED
|
@@ -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,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,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
|