parsecore 1.0.0__py3-none-any.whl

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 (77) hide show
  1. parsecore/Beatmap/__init__.py +50 -0
  2. parsecore/Beatmap/beatmap.py +316 -0
  3. parsecore/Beatmap/encode.py +653 -0
  4. parsecore/Beatmap/reader.py +71 -0
  5. parsecore/Beatmap/section/__init__.py +84 -0
  6. parsecore/Beatmap/section/colors.py +109 -0
  7. parsecore/Beatmap/section/difficulty.py +111 -0
  8. parsecore/Beatmap/section/editor.py +100 -0
  9. parsecore/Beatmap/section/enums.py +147 -0
  10. parsecore/Beatmap/section/events.py +143 -0
  11. parsecore/Beatmap/section/general.py +153 -0
  12. parsecore/Beatmap/section/hit_objects/__init__.py +61 -0
  13. parsecore/Beatmap/section/hit_objects/hit_objects.py +434 -0
  14. parsecore/Beatmap/section/hit_objects/slider.py +482 -0
  15. parsecore/Beatmap/section/metadata.py +115 -0
  16. parsecore/Beatmap/section/timing_points.py +402 -0
  17. parsecore/Beatmap/utils.py +196 -0
  18. parsecore/Mods/__init__.py +331 -0
  19. parsecore/Mods/_patches.py +322 -0
  20. parsecore/Mods/acronym.py +64 -0
  21. parsecore/Mods/game_mod.py +639 -0
  22. parsecore/Mods/game_mod_intermode.py +639 -0
  23. parsecore/Mods/game_mod_kind.py +49 -0
  24. parsecore/Mods/game_mod_simple.py +90 -0
  25. parsecore/Mods/game_mode.py +65 -0
  26. parsecore/Mods/game_mods.py +362 -0
  27. parsecore/Mods/game_mods_intermode.py +241 -0
  28. parsecore/Mods/game_mods_legacy.py +419 -0
  29. parsecore/Mods/generated_mods.py +1737 -0
  30. parsecore/Performance/__init__.py +50 -0
  31. parsecore/Performance/api.py +352 -0
  32. parsecore/Performance/data/__init__.py +47 -0
  33. parsecore/Performance/data/attributes.py +289 -0
  34. parsecore/Performance/data/beatmap.py +399 -0
  35. parsecore/Performance/data/hit_objects.py +75 -0
  36. parsecore/Performance/data/mode.py +31 -0
  37. parsecore/Performance/data/mods.py +214 -0
  38. parsecore/Performance/data/score_state.py +103 -0
  39. parsecore/Performance/rulesets/__init__.py +23 -0
  40. parsecore/Performance/rulesets/catch/__init__.py +23 -0
  41. parsecore/Performance/rulesets/catch/convert.py +220 -0
  42. parsecore/Performance/rulesets/catch/difficulty.py +152 -0
  43. parsecore/Performance/rulesets/catch/gradual.py +200 -0
  44. parsecore/Performance/rulesets/catch/hit_objects.py +350 -0
  45. parsecore/Performance/rulesets/catch/hitresult_generator.py +337 -0
  46. parsecore/Performance/rulesets/catch/performance.py +170 -0
  47. parsecore/Performance/rulesets/catch/skills.py +249 -0
  48. parsecore/Performance/rulesets/mania/__init__.py +23 -0
  49. parsecore/Performance/rulesets/mania/convert.py +1240 -0
  50. parsecore/Performance/rulesets/mania/difficulty.py +124 -0
  51. parsecore/Performance/rulesets/mania/gradual.py +24 -0
  52. parsecore/Performance/rulesets/mania/hit_objects.py +47 -0
  53. parsecore/Performance/rulesets/mania/hitresult_generator.py +192 -0
  54. parsecore/Performance/rulesets/mania/performance.py +117 -0
  55. parsecore/Performance/rulesets/mania/skills.py +346 -0
  56. parsecore/Performance/rulesets/osu/__init__.py +23 -0
  57. parsecore/Performance/rulesets/osu/convert.py +211 -0
  58. parsecore/Performance/rulesets/osu/difficulty.py +533 -0
  59. parsecore/Performance/rulesets/osu/gradual.py +24 -0
  60. parsecore/Performance/rulesets/osu/hit_objects.py +711 -0
  61. parsecore/Performance/rulesets/osu/hitresult_generator.py +218 -0
  62. parsecore/Performance/rulesets/osu/legacy_score.py +311 -0
  63. parsecore/Performance/rulesets/osu/performance.py +924 -0
  64. parsecore/Performance/rulesets/osu/skills.py +1813 -0
  65. parsecore/Performance/rulesets/taiko/__init__.py +23 -0
  66. parsecore/Performance/rulesets/taiko/convert.py +191 -0
  67. parsecore/Performance/rulesets/taiko/difficulty.py +146 -0
  68. parsecore/Performance/rulesets/taiko/gradual.py +24 -0
  69. parsecore/Performance/rulesets/taiko/hit_objects.py +62 -0
  70. parsecore/Performance/rulesets/taiko/hitresult_generator.py +105 -0
  71. parsecore/Performance/rulesets/taiko/performance.py +257 -0
  72. parsecore/Performance/rulesets/taiko/skills.py +1262 -0
  73. parsecore/Performance/utils.py +527 -0
  74. parsecore-1.0.0.dist-info/METADATA +293 -0
  75. parsecore-1.0.0.dist-info/RECORD +77 -0
  76. parsecore-1.0.0.dist-info/WHEEL +4 -0
  77. parsecore-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,50 @@
1
+ """
2
+ MIT License
3
+
4
+ Copyright (c) 2026-Present O!Lib Contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+
25
+ import os
26
+ import sys
27
+
28
+ _pkg_dir = os.path.dirname(__file__)
29
+ if _pkg_dir not in sys.path:
30
+ sys.path.insert(0, _pkg_dir)
31
+
32
+ from .beatmap import Beatmap
33
+ from .utils import (
34
+ KeyValue, MAX_PARSE_VALUE, ParseNumberError, parse_with_limits, parse_int, parse_float, Pos, trim_comment,
35
+ to_standardized_path, clean_filename
36
+ )
37
+
38
+ __all__ = [
39
+ "Beatmap",
40
+ "KeyValue",
41
+ "MAX_PARSE_VALUE",
42
+ "ParseNumberError",
43
+ "parse_with_limits",
44
+ "parse_int",
45
+ "parse_float",
46
+ "Pos",
47
+ "trim_comment",
48
+ "to_standardized_path",
49
+ "clean_filename",
50
+ ]
@@ -0,0 +1,316 @@
1
+ """
2
+ MIT License
3
+
4
+ Copyright (c) 2026-Present O!Lib Contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import io
28
+
29
+ from dataclasses import dataclass
30
+ from .encode import encode_beatmap
31
+ from .reader import Decoder
32
+ from .section import (
33
+ Colors, Difficulty, DifficultyState, Editor, GameMode, SampleBank, Section, Events, General, Metadata,
34
+ ControlPoints, TimingPointsState
35
+ )
36
+ from .section.hit_objects import HitObjectsState
37
+
38
+ LATEST_FORMAT_VERSION = 14
39
+
40
+
41
+ class ParseBeatmapError(Exception):
42
+ pass
43
+
44
+
45
+ class UnknownFileFormatError(ParseBeatmapError):
46
+ pass
47
+
48
+
49
+ def try_version_from_line(line: str) -> int | None:
50
+ if not line.startswith("osu file format v"):
51
+ if not line:
52
+ return None
53
+ raise UnknownFileFormatError("unknown file format")
54
+
55
+ parts = line.split("v", 1)
56
+ if len(parts) > 1:
57
+ try:
58
+ return int(parts[-1])
59
+ except ValueError:
60
+ raise ParseBeatmapError("failed to parse number in format version")
61
+
62
+ return LATEST_FORMAT_VERSION
63
+
64
+
65
+ @dataclass(slots=True, eq=True)
66
+ class Beatmap:
67
+ format_version: int
68
+ general: General
69
+ editor: Editor
70
+ metadata: Metadata
71
+ difficulty: Difficulty
72
+ events: Events
73
+ timing_points: TimingPointsState
74
+ colors: Colors
75
+ hit_objects: HitObjectsState
76
+
77
+ @property
78
+ def control_points(self) -> ControlPoints:
79
+ return self.timing_points.control_points
80
+
81
+ @property
82
+ def mode(self) -> GameMode:
83
+ return self.general.mode
84
+
85
+ @property
86
+ def audio_filename(self) -> str:
87
+ return self.general.audio_filename
88
+
89
+ @property
90
+ def audio_lead_in(self) -> int:
91
+ return self.general.audio_lead_in
92
+
93
+ @property
94
+ def preview_time(self) -> int:
95
+ return self.general.preview_time
96
+
97
+ @property
98
+ def stack_leniency(self) -> float:
99
+ return self.general.stack_leniency
100
+
101
+ @property
102
+ def letterbox_in_breaks(self) -> bool:
103
+ return self.general.letterbox_in_breaks
104
+
105
+ @property
106
+ def widescreen_storyboard(self) -> bool:
107
+ return self.general.widescreen_storyboard
108
+
109
+ @property
110
+ def epilepsy_warning(self) -> bool:
111
+ return self.general.epilepsy_warning
112
+
113
+ @property
114
+ def special_style(self) -> bool:
115
+ return self.general.special_style
116
+
117
+ @property
118
+ def samples_match_playback_rate(self) -> bool:
119
+ return self.general.samples_match_playback_rate
120
+
121
+ @property
122
+ def title(self) -> str:
123
+ return self.metadata.title
124
+
125
+ @property
126
+ def title_unicode(self) -> str:
127
+ return self.metadata.title_unicode
128
+
129
+ @property
130
+ def artist(self) -> str:
131
+ return self.metadata.artist
132
+
133
+ @property
134
+ def artist_unicode(self) -> str:
135
+ return self.metadata.artist_unicode
136
+
137
+ @property
138
+ def creator(self) -> str:
139
+ return self.metadata.creator
140
+
141
+ @property
142
+ def version(self) -> str:
143
+ return self.metadata.version
144
+
145
+ @property
146
+ def source(self) -> str:
147
+ return self.metadata.source
148
+
149
+ @property
150
+ def tags(self) -> str:
151
+ return self.metadata.tags
152
+
153
+ @property
154
+ def beatmap_id(self) -> int:
155
+ return self.metadata.beatmap_id
156
+
157
+ @property
158
+ def beatmap_set_id(self) -> int:
159
+ return self.metadata.beatmap_set_id
160
+
161
+ @property
162
+ def hp_drain_rate(self) -> float:
163
+ return self.difficulty.hp_drain_rate
164
+
165
+ @property
166
+ def circle_size(self) -> float:
167
+ return self.difficulty.circle_size
168
+
169
+ @property
170
+ def overall_difficulty(self) -> float:
171
+ return self.difficulty.overall_difficulty
172
+
173
+ @property
174
+ def approach_rate(self) -> float:
175
+ return self.difficulty.approach_rate
176
+
177
+ @property
178
+ def slider_multiplier(self) -> float:
179
+ return self.difficulty.slider_multiplier
180
+
181
+ @property
182
+ def slider_tick_rate(self) -> float:
183
+ return self.difficulty.slider_tick_rate
184
+
185
+ @classmethod
186
+ def from_path(cls, path: str) -> Beatmap:
187
+ with open(path, "rb") as f:
188
+ decoder = Decoder(f)
189
+ return cls._decode(decoder)
190
+
191
+ @classmethod
192
+ def from_bytes(cls, data: bytes) -> Beatmap:
193
+ reader = io.BytesIO(data)
194
+ decoder = Decoder(reader)
195
+ return cls._decode(decoder)
196
+
197
+ @classmethod
198
+ def _decode(cls, decoder: Decoder) -> Beatmap:
199
+ format_version = LATEST_FORMAT_VERSION
200
+ use_current_line = False
201
+ current_line_content = ""
202
+
203
+ while True:
204
+ line = decoder.read_line()
205
+ if line is None:
206
+ break
207
+ try:
208
+ version = try_version_from_line(line)
209
+ if version is not None:
210
+ format_version = int(version)
211
+ break
212
+ except Exception:
213
+ use_current_line = True
214
+ current_line_content = line
215
+ break
216
+
217
+ general = General()
218
+ editor = Editor()
219
+ metadata = Metadata()
220
+ difficulty = DifficultyState()
221
+ events = Events()
222
+ colors = Colors()
223
+ hit_objects = HitObjectsState()
224
+ timing_points = TimingPointsState(
225
+ general.mode, general.sample_bank, 100
226
+ )
227
+
228
+ current_section: Section | None = None
229
+
230
+ if use_current_line:
231
+ sec = Section.try_from_line(current_line_content)
232
+ if sec is not None:
233
+ current_section = sec
234
+
235
+ if current_section is None:
236
+ while True:
237
+ line = decoder.read_line()
238
+ if line is None:
239
+ break
240
+ sec = Section.try_from_line(line)
241
+ if sec is not None:
242
+ current_section = sec
243
+ break
244
+
245
+ while True:
246
+ line = decoder.read_line()
247
+ if line is None:
248
+ break
249
+
250
+ if not line or line.lstrip().startswith("//"):
251
+ continue
252
+
253
+ next_section = Section.try_from_line(line)
254
+ if next_section is not None:
255
+ current_section = next_section
256
+ continue
257
+
258
+ if current_section is None:
259
+ continue
260
+
261
+ try:
262
+ if current_section == Section.General:
263
+ general.parse_general(line)
264
+ timing_points.general_mode = general.mode
265
+ timing_points.general_default_sample_bank = general.sample_bank
266
+ elif current_section == Section.Editor:
267
+ editor.parse_editor(line)
268
+ elif current_section == Section.Metadata:
269
+ metadata.parse_metadata(line)
270
+ elif current_section == Section.Difficulty:
271
+ difficulty.parse_difficulty(line)
272
+ elif current_section == Section.Events:
273
+ events.parse_events(line)
274
+ elif current_section == Section.TimingPoints:
275
+ timing_points.parse_timing_points(line)
276
+ elif current_section == Section.Colors:
277
+ colors.parse_colors(line)
278
+ elif current_section == Section.HitObjects:
279
+ hit_objects.parse_hit_object(line)
280
+ except Exception:
281
+ pass
282
+
283
+ timing_points.flush_pending()
284
+
285
+ for break_period in events.breaks:
286
+ if not break_period.has_effect():
287
+ continue
288
+
289
+ for obj in hit_objects.hit_objects:
290
+ if obj.start_time > break_period.end_time and hasattr(obj.kind, "new_combo"):
291
+ obj.kind.new_combo = True
292
+ break
293
+
294
+ return cls(
295
+ format_version=format_version,
296
+ general=general,
297
+ editor=editor,
298
+ metadata=metadata,
299
+ difficulty=difficulty.difficulty,
300
+ events=events,
301
+ timing_points=timing_points,
302
+ colors=colors,
303
+ hit_objects=hit_objects,
304
+ )
305
+
306
+ def to_bytes(self, *, lazer_compatible: bool = False) -> bytes:
307
+ return self.encode_to_string(lazer_compatible=lazer_compatible).encode("utf-8")
308
+
309
+ def encode_to_path(self, path: str, *, lazer_compatible: bool = False) -> None:
310
+ with open(path, "w", encoding="utf-8") as f:
311
+ encode_beatmap(self, f, lazer_compatible=lazer_compatible)
312
+
313
+ def encode_to_string(self, *, lazer_compatible: bool = False) -> str:
314
+ writer = io.StringIO()
315
+ encode_beatmap(self, writer, lazer_compatible=lazer_compatible)
316
+ return writer.getvalue()