2023-06-29 12:36:39 +00:00
|
|
|
|
from .Items import SongData, AlbumData
|
2023-10-20 00:13:17 +00:00
|
|
|
|
from typing import Dict, List, Set, Optional
|
2023-08-11 09:02:35 +00:00
|
|
|
|
from collections import ChainMap
|
2023-06-29 12:36:39 +00: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-29 18:58:34 +00:00
|
|
|
|
STARTING_CODE = 2900000
|
2023-06-29 12:36:39 +00:00
|
|
|
|
|
2023-08-11 09:02:35 +00:00
|
|
|
|
MUSIC_SHEET_NAME: str = "Music Sheet"
|
2023-08-29 18:58:34 +00:00
|
|
|
|
MUSIC_SHEET_CODE: int = STARTING_CODE
|
2023-06-29 12:36:39 +00:00
|
|
|
|
|
2023-10-20 00:13:17 +00:00
|
|
|
|
FREE_ALBUMS: List[str] = [
|
2023-06-29 12:36:39 +00:00
|
|
|
|
"Default Music",
|
|
|
|
|
"Budget Is Burning: Nano Core",
|
2023-08-29 18:58:34 +00:00
|
|
|
|
"Budget Is Burning Vol.1",
|
2023-06-29 12:36:39 +00:00
|
|
|
|
]
|
|
|
|
|
|
2023-10-20 00:13:17 +00:00
|
|
|
|
MUSE_PLUS_DLC: str = "Muse Plus"
|
2024-06-04 19:45:26 +00:00
|
|
|
|
|
|
|
|
|
# Ordering matters for webhost. Order goes: Muse Plus, Time Limited Muse Plus Dlcs, Paid Dlcs
|
2023-10-20 00:13:17 +00:00
|
|
|
|
DLC: List[str] = [
|
2024-06-04 19:45:26 +00:00
|
|
|
|
MUSE_PLUS_DLC,
|
|
|
|
|
"CHUNITHM COURSE MUSE", # Part of Muse Plus. Goes away 22nd May 2027.
|
|
|
|
|
"maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
|
|
|
|
|
"MSR Anthology", # Now no longer available.
|
|
|
|
|
"Miku in Museland", # Paid DLC not included in Muse Plus
|
|
|
|
|
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
|
2023-10-20 00:13:17 +00:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
DIFF_OVERRIDES: List[str] = [
|
2023-06-29 12:36:39 +00:00
|
|
|
|
"MuseDash ka nanika hi",
|
|
|
|
|
"Rush-Hour",
|
|
|
|
|
"Find this Month's Featured Playlist",
|
2023-08-29 18:58:34 +00:00
|
|
|
|
"PeroPero in the Universe",
|
2024-04-11 22:44:16 +00:00
|
|
|
|
"umpopoff",
|
|
|
|
|
"P E R O P E R O Brother Dance",
|
2023-06-29 12:36:39 +00:00
|
|
|
|
]
|
2024-03-05 08:33:15 +00:00
|
|
|
|
|
|
|
|
|
REMOVED_SONGS = [
|
|
|
|
|
"CHAOS Glitch",
|
|
|
|
|
"FM 17314 SUGAR RADIO",
|
2024-04-11 22:44:16 +00:00
|
|
|
|
"Yume Ou Mono Yo Secret",
|
2024-03-05 08:33:15 +00:00
|
|
|
|
]
|
2023-06-29 12:36:39 +00:00
|
|
|
|
|
|
|
|
|
album_items: Dict[str, AlbumData] = {}
|
|
|
|
|
album_locations: Dict[str, int] = {}
|
|
|
|
|
song_items: Dict[str, SongData] = {}
|
|
|
|
|
song_locations: Dict[str, int] = {}
|
|
|
|
|
|
2024-06-04 19:45:26 +00:00
|
|
|
|
trap_items: Dict[str, int] = {
|
2023-08-29 18:58:34 +00:00
|
|
|
|
"Bad Apple Trap": STARTING_CODE + 1,
|
|
|
|
|
"Pixelate Trap": STARTING_CODE + 2,
|
2023-11-16 10:33:56 +00:00
|
|
|
|
"Ripple Trap": STARTING_CODE + 3,
|
|
|
|
|
"Vignette Trap": STARTING_CODE + 4,
|
2023-08-29 18:58:34 +00:00
|
|
|
|
"Chromatic Aberration Trap": STARTING_CODE + 5,
|
|
|
|
|
"Background Freeze Trap": STARTING_CODE + 6,
|
|
|
|
|
"Gray Scale Trap": STARTING_CODE + 7,
|
|
|
|
|
"Nyaa SFX Trap": STARTING_CODE + 8,
|
|
|
|
|
"Error SFX Trap": STARTING_CODE + 9,
|
2024-06-04 19:45:26 +00:00
|
|
|
|
"Focus Line Trap": STARTING_CODE + 10,
|
2023-06-29 12:36:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-04 19:45:26 +00:00
|
|
|
|
sfx_trap_items: List[str] = [
|
|
|
|
|
"Nyaa SFX Trap",
|
|
|
|
|
"Error SFX Trap",
|
|
|
|
|
]
|
|
|
|
|
|
2024-04-14 18:23:13 +00:00
|
|
|
|
filler_items: Dict[str, int] = {
|
|
|
|
|
"Great To Perfect (10 Pack)": STARTING_CODE + 30,
|
|
|
|
|
"Miss To Great (5 Pack)": STARTING_CODE + 31,
|
|
|
|
|
"Extra Life": STARTING_CODE + 32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filler_item_weights: Dict[str, int] = {
|
|
|
|
|
"Great To Perfect (10 Pack)": 10,
|
|
|
|
|
"Miss To Great (5 Pack)": 3,
|
|
|
|
|
"Extra Life": 1,
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-04 19:45:26 +00:00
|
|
|
|
item_names_to_id: ChainMap = ChainMap({}, filler_items, trap_items)
|
2023-10-20 00:13:17 +00:00
|
|
|
|
location_names_to_id: ChainMap = ChainMap(song_locations, album_locations)
|
2023-08-11 09:02:35 +00:00
|
|
|
|
|
2023-08-29 18:58:34 +00:00
|
|
|
|
def __init__(self) -> None:
|
2023-08-11 09:02:35 +00:00
|
|
|
|
self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE
|
2023-06-29 12:36:39 +00:00
|
|
|
|
|
2023-08-29 18:58:34 +00:00
|
|
|
|
item_id_index = self.STARTING_CODE + 50
|
2023-06-29 12:36:39 +00:00
|
|
|
|
full_file = load_text_file("MuseDashData.txt")
|
2023-08-29 18:58:34 +00:00
|
|
|
|
seen_albums = set()
|
2023-06-29 12:36:39 +00:00
|
|
|
|
for line in full_file.splitlines():
|
|
|
|
|
line = line.strip()
|
|
|
|
|
sections = line.split("|")
|
|
|
|
|
|
2023-08-29 18:58:34 +00: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 12:36:39 +00: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:
|
2024-01-11 22:13:39 +00:00
|
|
|
|
# These songs use non-standard difficulty values. Which are being overriden with standard values.
|
|
|
|
|
# But also avoid filling any missing difficulties (i.e. 0s) with a difficulty value.
|
|
|
|
|
if sections[4] != '0':
|
|
|
|
|
diff_of_easy = 4
|
|
|
|
|
else:
|
|
|
|
|
diff_of_easy = None
|
|
|
|
|
|
|
|
|
|
if sections[5] != '0':
|
|
|
|
|
diff_of_hard = 7
|
|
|
|
|
else:
|
|
|
|
|
diff_of_hard = None
|
|
|
|
|
|
|
|
|
|
if sections[6] != '0':
|
|
|
|
|
diff_of_master = 10
|
|
|
|
|
else:
|
|
|
|
|
diff_of_master = None
|
2023-06-29 12:36:39 +00:00
|
|
|
|
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 00:13:17 +00:00
|
|
|
|
self.song_items[song_name] = SongData(item_id_index, album, steamer_mode,
|
2023-06-29 12:36:39 +00:00
|
|
|
|
diff_of_easy, diff_of_hard, diff_of_master)
|
|
|
|
|
item_id_index += 1
|
|
|
|
|
|
2023-08-11 09:02:35 +00: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-29 18:58:34 +00:00
|
|
|
|
location_id_index = self.STARTING_CODE
|
2023-06-29 12:36:39 +00:00
|
|
|
|
for name in self.album_items.keys():
|
2023-08-29 18:58:34 +00: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 12:36:39 +00:00
|
|
|
|
|
|
|
|
|
for name in self.song_items.keys():
|
2023-08-29 18:58:34 +00: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 12:36:39 +00:00
|
|
|
|
|
2023-10-20 00:13:17 +00:00
|
|
|
|
def get_songs_with_settings(self, dlc_songs: Set[str], streamer_mode_active: bool,
|
2023-06-29 12:36:39 +00: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 00:13:17 +00:00
|
|
|
|
if not self.song_matches_dlc_filter(songData, dlc_songs):
|
2023-06-29 12:36:39 +00:00
|
|
|
|
continue
|
2024-03-05 08:33:15 +00:00
|
|
|
|
|
|
|
|
|
if songKey in self.REMOVED_SONGS:
|
|
|
|
|
continue
|
2023-06-29 12:36:39 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2024-06-04 19:45:26 +00:00
|
|
|
|
def filter_songs_to_dlc(self, song_list: List[str], dlc_songs: Set[str]) -> List[str]:
|
|
|
|
|
return [song for song in song_list if self.song_matches_dlc_filter(self.song_items[song], dlc_songs)]
|
|
|
|
|
|
2023-10-20 00:13:17 +00: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 12:36:39 +00: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 13:10:58 +00:00
|
|
|
|
# 0 is used as a filler and no songs actually have a 0 difficulty song.
|
|
|
|
|
if difficulty == "0":
|
|
|
|
|
return None
|
|
|
|
|
|
2023-06-29 12:36:39 +00:00
|
|
|
|
# Curse the 2023 april fools update. Used on 3rd Avenue.
|
|
|
|
|
if difficulty == "〇":
|
|
|
|
|
return 10
|
|
|
|
|
|
|
|
|
|
return int(difficulty)
|