| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | from .Items import SongData, AlbumData | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  | from typing import Dict, List, Set, Optional | 
					
						
							| 
									
										
										
										
											2023-08-11 19:02:35 +10:00
										 |  |  |  | from collections import ChainMap | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | def load_text_file(name: str) -> str: | 
					
						
							|  |  |  |  |     import pkgutil | 
					
						
							|  |  |  |  |     return pkgutil.get_data(__name__, name).decode() | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class MuseDashCollections: | 
					
						
							|  |  |  |  |     """Contains all the data of Muse Dash, loaded from MuseDashData.txt.""" | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |     STARTING_CODE = 2900000 | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-11 19:02:35 +10:00
										 |  |  |  |     MUSIC_SHEET_NAME: str = "Music Sheet" | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |     MUSIC_SHEET_CODE: int = STARTING_CODE | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |     FREE_ALBUMS: List[str] = [ | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |         "Default Music", | 
					
						
							|  |  |  |  |         "Budget Is Burning: Nano Core", | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         "Budget Is Burning Vol.1", | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |     ] | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |     MUSE_PLUS_DLC: str = "Muse Plus" | 
					
						
							|  |  |  |  |     DLC: List[str] = [ | 
					
						
							|  |  |  |  |         # MUSE_PLUS_DLC, # To be included when OptionSets are rendered as part of basic settings. | 
					
						
							|  |  |  |  |         # "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026. | 
					
						
							|  |  |  |  |         "Miku in Museland", # Paid DLC not included in Muse Plus | 
					
						
							|  |  |  |  |         "MSR Anthology", # Part of Muse Plus. Goes away 20th Jan 2024. | 
					
						
							|  |  |  |  |     ] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     DIFF_OVERRIDES: List[str] = [ | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |         "MuseDash ka nanika hi", | 
					
						
							|  |  |  |  |         "Rush-Hour", | 
					
						
							|  |  |  |  |         "Find this Month's Featured Playlist", | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         "PeroPero in the Universe", | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |     ] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     album_items: Dict[str, AlbumData] = {} | 
					
						
							|  |  |  |  |     album_locations: Dict[str, int] = {} | 
					
						
							|  |  |  |  |     song_items: Dict[str, SongData] = {} | 
					
						
							|  |  |  |  |     song_locations: Dict[str, int] = {} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     vfx_trap_items: Dict[str, int] = { | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         "Bad Apple Trap": STARTING_CODE + 1, | 
					
						
							|  |  |  |  |         "Pixelate Trap": STARTING_CODE + 2, | 
					
						
							| 
									
										
										
										
											2023-11-16 20:33:56 +10:00
										 |  |  |  |         "Ripple Trap": STARTING_CODE + 3, | 
					
						
							|  |  |  |  |         "Vignette Trap": STARTING_CODE + 4, | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         "Chromatic Aberration Trap": STARTING_CODE + 5, | 
					
						
							|  |  |  |  |         "Background Freeze Trap": STARTING_CODE + 6, | 
					
						
							|  |  |  |  |         "Gray Scale Trap": STARTING_CODE + 7, | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     sfx_trap_items: Dict[str, int] = { | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         "Nyaa SFX Trap": STARTING_CODE + 8, | 
					
						
							|  |  |  |  |         "Error SFX Trap": STARTING_CODE + 9, | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |     item_names_to_id: ChainMap = ChainMap({}, sfx_trap_items, vfx_trap_items) | 
					
						
							|  |  |  |  |     location_names_to_id: ChainMap = ChainMap(song_locations, album_locations) | 
					
						
							| 
									
										
										
										
											2023-08-11 19:02:35 +10:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |     def __init__(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-08-11 19:02:35 +10:00
										 |  |  |  |         self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         item_id_index = self.STARTING_CODE + 50 | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |         full_file = load_text_file("MuseDashData.txt") | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         seen_albums = set() | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |         for line in full_file.splitlines(): | 
					
						
							|  |  |  |  |             line = line.strip() | 
					
						
							|  |  |  |  |             sections = line.split("|") | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |             album = sections[2] | 
					
						
							|  |  |  |  |             if album not in seen_albums: | 
					
						
							|  |  |  |  |                 seen_albums.add(album) | 
					
						
							|  |  |  |  |                 self.album_items[album] = AlbumData(item_id_index) | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |                 item_id_index += 1 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             # Data is in the format 'Song|UID|Album|StreamerMode|EasyDiff|HardDiff|MasterDiff|SecretDiff' | 
					
						
							|  |  |  |  |             song_name = sections[0] | 
					
						
							|  |  |  |  |             # [1] is used in the client copy to make sure item id's match. | 
					
						
							|  |  |  |  |             steamer_mode = sections[3] == "True" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if song_name in self.DIFF_OVERRIDES: | 
					
						
							|  |  |  |  |                 # Note: These difficulties may not actually be representative of these songs. | 
					
						
							|  |  |  |  |                 # The game does not provide these difficulties so they have to be filled in. | 
					
						
							|  |  |  |  |                 diff_of_easy = 4 | 
					
						
							|  |  |  |  |                 diff_of_hard = 7 | 
					
						
							|  |  |  |  |                 diff_of_master = 10 | 
					
						
							|  |  |  |  |             else: | 
					
						
							|  |  |  |  |                 diff_of_easy = self.parse_song_difficulty(sections[4]) | 
					
						
							|  |  |  |  |                 diff_of_hard = self.parse_song_difficulty(sections[5]) | 
					
						
							|  |  |  |  |                 diff_of_master = self.parse_song_difficulty(sections[6]) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |             self.song_items[song_name] = SongData(item_id_index, album, steamer_mode, | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |                                                   diff_of_easy, diff_of_hard, diff_of_master) | 
					
						
							|  |  |  |  |             item_id_index += 1 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-11 19:02:35 +10:00
										 |  |  |  |         self.item_names_to_id.update({name: data.code for name, data in self.song_items.items()}) | 
					
						
							|  |  |  |  |         self.item_names_to_id.update({name: data.code for name, data in self.album_items.items()}) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |         location_id_index = self.STARTING_CODE | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |         for name in self.album_items.keys(): | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |             self.album_locations[f"{name}-0"] = location_id_index | 
					
						
							|  |  |  |  |             self.album_locations[f"{name}-1"] = location_id_index + 1 | 
					
						
							|  |  |  |  |             location_id_index += 2 | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         for name in self.song_items.keys(): | 
					
						
							| 
									
										
										
										
											2023-08-30 04:58:34 +10:00
										 |  |  |  |             self.song_locations[f"{name}-0"] = location_id_index | 
					
						
							|  |  |  |  |             self.song_locations[f"{name}-1"] = location_id_index + 1 | 
					
						
							|  |  |  |  |             location_id_index += 2 | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |     def get_songs_with_settings(self, dlc_songs: Set[str], streamer_mode_active: bool, | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |                                 diff_lower: int, diff_higher: int) -> List[str]: | 
					
						
							|  |  |  |  |         """Gets a list of all songs that match the filter settings. Difficulty thresholds are inclusive.""" | 
					
						
							|  |  |  |  |         filtered_list = [] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         for songKey, songData in self.song_items.items(): | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |             if not self.song_matches_dlc_filter(songData, dlc_songs): | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |                 continue | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if streamer_mode_active and not songData.streamer_mode: | 
					
						
							|  |  |  |  |                 continue | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if songData.easy is not None and diff_lower <= songData.easy <= diff_higher: | 
					
						
							|  |  |  |  |                 filtered_list.append(songKey) | 
					
						
							|  |  |  |  |                 continue | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if songData.hard is not None and diff_lower <= songData.hard <= diff_higher: | 
					
						
							|  |  |  |  |                 filtered_list.append(songKey) | 
					
						
							|  |  |  |  |                 continue | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if songData.master is not None and diff_lower <= songData.master <= diff_higher: | 
					
						
							|  |  |  |  |                 filtered_list.append(songKey) | 
					
						
							|  |  |  |  |                 continue | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return filtered_list | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:13:17 +10:00
										 |  |  |  |     def song_matches_dlc_filter(self, song: SongData, dlc_songs: Set[str]) -> bool: | 
					
						
							|  |  |  |  |         if song.album in self.FREE_ALBUMS: | 
					
						
							|  |  |  |  |             return True | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         if song.album in dlc_songs: | 
					
						
							|  |  |  |  |             return True | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # Muse Plus provides access to any DLC not included as a seperate pack | 
					
						
							|  |  |  |  |         if song.album not in self.DLC and self.MUSE_PLUS_DLC in dlc_songs: | 
					
						
							|  |  |  |  |             return True | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return False | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |     def parse_song_difficulty(self, difficulty: str) -> Optional[int]: | 
					
						
							|  |  |  |  |         """Attempts to parse the song difficulty.""" | 
					
						
							|  |  |  |  |         if len(difficulty) <= 0 or difficulty == "?" or difficulty == "¿": | 
					
						
							|  |  |  |  |             return None | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-30 23:10:58 +10:00
										 |  |  |  |         # 0 is used as a filler and no songs actually have a 0 difficulty song. | 
					
						
							|  |  |  |  |         if difficulty == "0": | 
					
						
							|  |  |  |  |             return None | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-29 22:36:39 +10:00
										 |  |  |  |         # Curse the 2023 april fools update. Used on 3rd Avenue. | 
					
						
							|  |  |  |  |         if difficulty == "〇": | 
					
						
							|  |  |  |  |             return 10 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return int(difficulty) |