Muse Dash: Add support for specifying specific DLCs (#2329)
This commit is contained in:
		
							parent
							
								
									fb6b66463d
								
							
						
					
					
						commit
						385803eb5c
					
				|  | @ -6,7 +6,7 @@ class SongData(NamedTuple): | ||||||
|     """Special data container to contain the metadata of each song to make filtering work.""" |     """Special data container to contain the metadata of each song to make filtering work.""" | ||||||
| 
 | 
 | ||||||
|     code: Optional[int] |     code: Optional[int] | ||||||
|     song_is_free: bool |     album: str | ||||||
|     streamer_mode: bool |     streamer_mode: bool | ||||||
|     easy: Optional[int] |     easy: Optional[int] | ||||||
|     hard: Optional[int] |     hard: Optional[int] | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| from .Items import SongData, AlbumData | from .Items import SongData, AlbumData | ||||||
| from typing import Dict, List, Optional | from typing import Dict, List, Set, Optional | ||||||
| from collections import ChainMap | from collections import ChainMap | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -15,13 +15,21 @@ class MuseDashCollections: | ||||||
|     MUSIC_SHEET_NAME: str = "Music Sheet" |     MUSIC_SHEET_NAME: str = "Music Sheet" | ||||||
|     MUSIC_SHEET_CODE: int = STARTING_CODE |     MUSIC_SHEET_CODE: int = STARTING_CODE | ||||||
| 
 | 
 | ||||||
|     FREE_ALBUMS = [ |     FREE_ALBUMS: List[str] = [ | ||||||
|         "Default Music", |         "Default Music", | ||||||
|         "Budget Is Burning: Nano Core", |         "Budget Is Burning: Nano Core", | ||||||
|         "Budget Is Burning Vol.1", |         "Budget Is Burning Vol.1", | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     DIFF_OVERRIDES = [ |     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] = [ | ||||||
|         "MuseDash ka nanika hi", |         "MuseDash ka nanika hi", | ||||||
|         "Rush-Hour", |         "Rush-Hour", | ||||||
|         "Find this Month's Featured Playlist", |         "Find this Month's Featured Playlist", | ||||||
|  | @ -48,8 +56,8 @@ class MuseDashCollections: | ||||||
|         "Error SFX Trap": STARTING_CODE + 9, |         "Error SFX Trap": STARTING_CODE + 9, | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     item_names_to_id = ChainMap({}, sfx_trap_items, vfx_trap_items) |     item_names_to_id: ChainMap = ChainMap({}, sfx_trap_items, vfx_trap_items) | ||||||
|     location_names_to_id = ChainMap(song_locations, album_locations) |     location_names_to_id: ChainMap = ChainMap(song_locations, album_locations) | ||||||
| 
 | 
 | ||||||
|     def __init__(self) -> None: |     def __init__(self) -> None: | ||||||
|         self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE |         self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE | ||||||
|  | @ -70,7 +78,6 @@ class MuseDashCollections: | ||||||
|             # Data is in the format 'Song|UID|Album|StreamerMode|EasyDiff|HardDiff|MasterDiff|SecretDiff' |             # Data is in the format 'Song|UID|Album|StreamerMode|EasyDiff|HardDiff|MasterDiff|SecretDiff' | ||||||
|             song_name = sections[0] |             song_name = sections[0] | ||||||
|             # [1] is used in the client copy to make sure item id's match. |             # [1] is used in the client copy to make sure item id's match. | ||||||
|             song_is_free = album in self.FREE_ALBUMS |  | ||||||
|             steamer_mode = sections[3] == "True" |             steamer_mode = sections[3] == "True" | ||||||
| 
 | 
 | ||||||
|             if song_name in self.DIFF_OVERRIDES: |             if song_name in self.DIFF_OVERRIDES: | ||||||
|  | @ -84,7 +91,7 @@ class MuseDashCollections: | ||||||
|                 diff_of_hard = self.parse_song_difficulty(sections[5]) |                 diff_of_hard = self.parse_song_difficulty(sections[5]) | ||||||
|                 diff_of_master = self.parse_song_difficulty(sections[6]) |                 diff_of_master = self.parse_song_difficulty(sections[6]) | ||||||
| 
 | 
 | ||||||
|             self.song_items[song_name] = SongData(item_id_index, song_is_free, steamer_mode, |             self.song_items[song_name] = SongData(item_id_index, album, steamer_mode, | ||||||
|                                                   diff_of_easy, diff_of_hard, diff_of_master) |                                                   diff_of_easy, diff_of_hard, diff_of_master) | ||||||
|             item_id_index += 1 |             item_id_index += 1 | ||||||
| 
 | 
 | ||||||
|  | @ -102,13 +109,13 @@ class MuseDashCollections: | ||||||
|             self.song_locations[f"{name}-1"] = location_id_index + 1 |             self.song_locations[f"{name}-1"] = location_id_index + 1 | ||||||
|             location_id_index += 2 |             location_id_index += 2 | ||||||
| 
 | 
 | ||||||
|     def get_songs_with_settings(self, dlc_songs: bool, streamer_mode_active: bool, |     def get_songs_with_settings(self, dlc_songs: Set[str], streamer_mode_active: bool, | ||||||
|                                 diff_lower: int, diff_higher: int) -> List[str]: |                                 diff_lower: int, diff_higher: int) -> List[str]: | ||||||
|         """Gets a list of all songs that match the filter settings. Difficulty thresholds are inclusive.""" |         """Gets a list of all songs that match the filter settings. Difficulty thresholds are inclusive.""" | ||||||
|         filtered_list = [] |         filtered_list = [] | ||||||
| 
 | 
 | ||||||
|         for songKey, songData in self.song_items.items(): |         for songKey, songData in self.song_items.items(): | ||||||
|             if not dlc_songs and not songData.song_is_free: |             if not self.song_matches_dlc_filter(songData, dlc_songs): | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|             if streamer_mode_active and not songData.streamer_mode: |             if streamer_mode_active and not songData.streamer_mode: | ||||||
|  | @ -128,6 +135,19 @@ class MuseDashCollections: | ||||||
| 
 | 
 | ||||||
|         return filtered_list |         return filtered_list | ||||||
| 
 | 
 | ||||||
|  |     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 | ||||||
|  | 
 | ||||||
|     def parse_song_difficulty(self, difficulty: str) -> Optional[int]: |     def parse_song_difficulty(self, difficulty: str) -> Optional[int]: | ||||||
|         """Attempts to parse the song difficulty.""" |         """Attempts to parse the song difficulty.""" | ||||||
|         if len(difficulty) <= 0 or difficulty == "?" or difficulty == "¿": |         if len(difficulty) <= 0 or difficulty == "?" or difficulty == "¿": | ||||||
|  |  | ||||||
|  | @ -51,42 +51,42 @@ Mujinku-Vacuum|0-28|Default Music|False|5|7|11| | ||||||
| MilK|0-36|Default Music|False|5|7|9| | MilK|0-36|Default Music|False|5|7|9| | ||||||
| umpopoff|0-41|Default Music|False|0|?|0| | umpopoff|0-41|Default Music|False|0|?|0| | ||||||
| Mopemope|0-45|Default Music|False|4|7|9|11 | Mopemope|0-45|Default Music|False|4|7|9|11 | ||||||
| The Happycore Idol|43-0|Just as Planned  Plus|True|2|5|7| | The Happycore Idol|43-0|MD Plus Project|True|2|5|7| | ||||||
| Amatsumikaboshi|43-1|Just as Planned  Plus|True|4|6|8|10 | Amatsumikaboshi|43-1|MD Plus Project|True|4|6|8|10 | ||||||
| ARIGA THESIS|43-2|Just as Planned  Plus|True|3|6|10| | ARIGA THESIS|43-2|MD Plus Project|True|3|6|10| | ||||||
| Night of Nights|43-3|Just as Planned  Plus|False|4|7|10| | Night of Nights|43-3|MD Plus Project|False|4|7|10| | ||||||
| #Psychedelic_Meguro_River|43-4|Just as Planned  Plus|False|3|6|8| | #Psychedelic_Meguro_River|43-4|MD Plus Project|False|3|6|8| | ||||||
| can you feel it|43-5|Just as Planned  Plus|False|4|6|8|9 | can you feel it|43-5|MD Plus Project|False|4|6|8|9 | ||||||
| Midnight O'clock|43-6|Just as Planned  Plus|True|3|6|8| | Midnight O'clock|43-6|MD Plus Project|True|3|6|8| | ||||||
| Rin|43-7|Just as Planned  Plus|True|5|7|10| | Rin|43-7|MD Plus Project|True|5|7|10| | ||||||
| Smile-mileS|43-8|Just as Planned  Plus|False|6|8|10| | Smile-mileS|43-8|MD Plus Project|False|6|8|10| | ||||||
| Believing and Being|43-9|Just as Planned  Plus|True|4|6|9| | Believing and Being|43-9|MD Plus Project|True|4|6|9| | ||||||
| Catalyst|43-10|Just as Planned  Plus|False|5|7|9| | Catalyst|43-10|MD Plus Project|False|5|7|9| | ||||||
| don't!stop!eroero!|43-11|Just as Planned  Plus|True|5|7|9| | don't!stop!eroero!|43-11|MD Plus Project|True|5|7|9| | ||||||
| pa pi pu pi pu pi pa|43-12|Just as Planned  Plus|False|6|8|10| | pa pi pu pi pu pi pa|43-12|MD Plus Project|False|6|8|10| | ||||||
| Sand Maze|43-13|Just as Planned  Plus|True|6|8|10|11 | Sand Maze|43-13|MD Plus Project|True|6|8|10|11 | ||||||
| Diffraction|43-14|Just as Planned  Plus|True|5|8|10| | Diffraction|43-14|MD Plus Project|True|5|8|10| | ||||||
| AKUMU|43-15|Just as Planned  Plus|False|4|6|8| | AKUMU|43-15|MD Plus Project|False|4|6|8| | ||||||
| Queen Aluett|43-16|Just as Planned  Plus|True|7|9|11| | Queen Aluett|43-16|MD Plus Project|True|7|9|11| | ||||||
| DROPS|43-17|Just as Planned  Plus|False|2|5|8| | DROPS|43-17|MD Plus Project|False|2|5|8| | ||||||
| Frightfully-insane Flan-chan's frightful song|43-18|Just as Planned  Plus|False|5|7|10| | Frightfully-insane Flan-chan's frightful song|43-18|MD Plus Project|False|5|7|10| | ||||||
| snooze|43-19|Just as Planned  Plus|False|5|7|10| | snooze|43-19|MD Plus Project|False|5|7|10| | ||||||
| Kuishinbo Hacker feat.Kuishinbo Akachan|43-20|Just as Planned  Plus|True|5|7|9| | Kuishinbo Hacker feat.Kuishinbo Akachan|43-20|MD Plus Project|True|5|7|9| | ||||||
| Inu no outa|43-21|Just as Planned  Plus|True|3|5|7| | Inu no outa|43-21|MD Plus Project|True|3|5|7| | ||||||
| Prism Fountain|43-22|Just as Planned  Plus|True|7|9|11| | Prism Fountain|43-22|MD Plus Project|True|7|9|11| | ||||||
| Gospel|43-23|Just as Planned  Plus|False|4|6|9| | Gospel|43-23|MD Plus Project|False|4|6|9| | ||||||
| East Ai Li Lovely|62-0|Happy Otaku Pack Vol.17|False|2|4|7| | East Ai Li Lovely|62-0|Happy Otaku Pack Vol.17|False|2|4|7| | ||||||
| Mori Umi no Fune|62-1|Happy Otaku Pack Vol.17|True|5|7|9| | Mori Umi no Fune|62-1|Happy Otaku Pack Vol.17|True|5|7|9| | ||||||
| Ooi|62-2|Happy Otaku Pack Vol.17|True|5|7|10| | Ooi|62-2|Happy Otaku Pack Vol.17|True|5|7|10| | ||||||
| Numatta!!|62-3|Happy Otaku Pack Vol.17|True|5|7|9| | Numatta!!|62-3|Happy Otaku Pack Vol.17|True|5|7|9| | ||||||
| SATELLITE|62-4|Happy Otaku Pack Vol.17|False|5|7|9| | SATELLITE|62-4|Happy Otaku Pack Vol.17|False|5|7|9|10 | ||||||
| Fantasia Sonata Colorful feat. V!C|62-5|Happy Otaku Pack Vol.17|True|6|8|11| | Fantasia Sonata Colorful feat. V!C|62-5|Happy Otaku Pack Vol.17|True|6|8|11| | ||||||
| MuseDash ka nanika hi|61-0|Ola Dash|True|?|?|¿| | MuseDash ka nanika hi|61-0|Ola Dash|True|?|?|¿| | ||||||
| Aleph-0|61-1|Ola Dash|True|7|9|11| | Aleph-0|61-1|Ola Dash|True|7|9|11| | ||||||
| Buttoba Supernova|61-2|Ola Dash|False|5|7|10|11 | Buttoba Supernova|61-2|Ola Dash|False|5|7|10|11 | ||||||
| Rush-Hour|61-3|Ola Dash|False|IG|Jh|a2|Eh | Rush-Hour|61-3|Ola Dash|False|IG|Jh|a2|Eh | ||||||
| 3rd Avenue|61-4|Ola Dash|False|3|5|〇| | 3rd Avenue|61-4|Ola Dash|False|3|5|〇| | ||||||
| WORLDINVADER|61-5|Ola Dash|True|5|8|10| | WORLDINVADER|61-5|Ola Dash|True|5|8|10|11 | ||||||
| N3V3R G3T OV3R|60-0|maimai DX Limited-time Suite|True|4|7|10| | N3V3R G3T OV3R|60-0|maimai DX Limited-time Suite|True|4|7|10| | ||||||
| Oshama Scramble!|60-1|maimai DX Limited-time Suite|True|5|7|10| | Oshama Scramble!|60-1|maimai DX Limited-time Suite|True|5|7|10| | ||||||
| Valsqotch|60-2|maimai DX Limited-time Suite|True|5|9|11| | Valsqotch|60-2|maimai DX Limited-time Suite|True|5|9|11| | ||||||
|  | @ -450,13 +450,13 @@ Love Patrol|63-2|MUSE RADIO FM104|True|3|5|7| | ||||||
| Mahorova|63-3|MUSE RADIO FM104|True|3|5|8| | Mahorova|63-3|MUSE RADIO FM104|True|3|5|8| | ||||||
| Yoru no machi|63-4|MUSE RADIO FM104|True|1|4|7| | Yoru no machi|63-4|MUSE RADIO FM104|True|1|4|7| | ||||||
| INTERNET YAMERO|63-5|MUSE RADIO FM104|True|6|8|10| | INTERNET YAMERO|63-5|MUSE RADIO FM104|True|6|8|10| | ||||||
| Abracadabra|43-24|Just as Planned  Plus|False|6|8|10| | Abracadabra|43-24|MD Plus Project|False|6|8|10| | ||||||
| Squalldecimator feat. EZ-Ven|43-25|Just as Planned  Plus|True|5|7|9| | Squalldecimator feat. EZ-Ven|43-25|MD Plus Project|True|5|7|9| | ||||||
| Amateras Rhythm|43-26|Just as Planned  Plus|True|6|8|11| | Amateras Rhythm|43-26|MD Plus Project|True|6|8|11| | ||||||
| Record one's Dream|43-27|Just as Planned  Plus|False|4|7|10| | Record one's Dream|43-27|MD Plus Project|False|4|7|10| | ||||||
| Lunatic|43-28|Just as Planned  Plus|True|5|8|10| | Lunatic|43-28|MD Plus Project|True|5|8|10| | ||||||
| Jiumeng|43-29|Just as Planned  Plus|True|3|6|8| | Jiumeng|43-29|MD Plus Project|True|3|6|8| | ||||||
| The Day We Become Family|43-30|Just as Planned  Plus|True|3|5|8| | The Day We Become Family|43-30|MD Plus Project|True|3|5|8| | ||||||
| Sutori ma FIRE!?!?|64-0|COSMIC RADIO PEROLIST|True|3|5|8| | Sutori ma FIRE!?!?|64-0|COSMIC RADIO PEROLIST|True|3|5|8| | ||||||
| Tanuki Step|64-1|COSMIC RADIO PEROLIST|True|5|7|10|11 | Tanuki Step|64-1|COSMIC RADIO PEROLIST|True|5|7|10|11 | ||||||
| Space Stationery|64-2|COSMIC RADIO PEROLIST|True|5|7|10| | Space Stationery|64-2|COSMIC RADIO PEROLIST|True|5|7|10| | ||||||
|  | @ -469,3 +469,23 @@ BrainDance|65-0|Neon Abyss|True|3|6|9| | ||||||
| My Focus!|65-1|NeonAbyss|True|5|7|10| | My Focus!|65-1|NeonAbyss|True|5|7|10| | ||||||
| ABABABA BURST|65-2|NeonAbyss|True|5|7|9| | ABABABA BURST|65-2|NeonAbyss|True|5|7|9| | ||||||
| ULTRA HIGHER|65-3|NeonAbyss|True|4|7|10| | ULTRA HIGHER|65-3|NeonAbyss|True|4|7|10| | ||||||
|  | Silver Bullet|43-31|MD Plus Project|True|5|7|10| | ||||||
|  | Random|43-32|MD Plus Project|True|4|7|9| | ||||||
|  | OTOGE-BOSS-KYOKU-CHAN|43-33|MD Plus Project|False|6|8|10|11 | ||||||
|  | Crow Rabbit|43-34|MD Plus Project|True|7|9|11| | ||||||
|  | SyZyGy|43-35|MD Plus Project|True|6|8|10|11 | ||||||
|  | Mermaid Radio|43-36|MD Plus Project|True|3|5|7| | ||||||
|  | Helixir|43-37|MD Plus Project|False|6|8|10| | ||||||
|  | Highway Cruisin'|43-38|MD Plus Project|False|3|5|8| | ||||||
|  | JACK PT BOSS|43-39|MD Plus Project|False|6|8|10| | ||||||
|  | Time Capsule|43-40|MD Plus Project|False|7|9|11| | ||||||
|  | 39 Music!|66-0|Miku in Museland|False|3|5|8| | ||||||
|  | Hand in Hand|66-1|Miku in Museland|False|1|3|6| | ||||||
|  | Cynical Night Plan|66-2|Miku in Museland|False|4|6|8| | ||||||
|  | God-ish|66-3|Miku in Museland|False|4|7|10| | ||||||
|  | Darling Dance|66-4|Miku in Museland|False|4|7|9| | ||||||
|  | Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10| | ||||||
|  | The Vampire|66-6|Miku in Museland|False|4|6|9| | ||||||
|  | Future Eve|66-7|Miku in Museland|False|4|8|11| | ||||||
|  | Unknown Mother Goose|66-8|Miku in Museland|False|4|8|10| | ||||||
|  | Shun-ran|66-9|Miku in Museland|False|4|7|9| | ||||||
|  | @ -1,10 +1,19 @@ | ||||||
| from typing import Dict | from typing import Dict | ||||||
| from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet | from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions | ||||||
|  | from dataclasses import dataclass | ||||||
| 
 | 
 | ||||||
|  | from .MuseDashCollection import MuseDashCollections | ||||||
| 
 | 
 | ||||||
| class AllowJustAsPlannedDLCSongs(Toggle): | class AllowJustAsPlannedDLCSongs(Toggle): | ||||||
|     """Whether [Just as Planned]/[Muse Plus] DLC Songs, and all the DLCs along with it, will be included in the randomizer.""" |     """Whether [Muse Plus] DLC Songs, and all the albums included in it, can be chosen as randomised songs. | ||||||
|     display_name = "Allow [Just as Planned]/[Muse Plus] DLC Songs" |     Note: The [Just As Planned] DLC contains all [Muse Plus] songs.""" | ||||||
|  |     display_name = "Allow [Muse Plus] DLC Songs" | ||||||
|  | 
 | ||||||
|  | class DLCMusicPacks(OptionSet): | ||||||
|  |     """Which non-[Muse Plus] DLC packs can be chosen as randomised songs.""" | ||||||
|  |     display_name = "DLC Packs" | ||||||
|  |     default = {} | ||||||
|  |     valid_keys = [dlc for dlc in MuseDashCollections.DLC] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class StreamerModeEnabled(Toggle): | class StreamerModeEnabled(Toggle): | ||||||
|  | @ -159,21 +168,22 @@ class ExcludeSongs(ItemSet): | ||||||
|     display_name = "Exclude Songs" |     display_name = "Exclude Songs" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| musedash_options: Dict[str, type(Option)] = { | @dataclass | ||||||
|     "allow_just_as_planned_dlc_songs": AllowJustAsPlannedDLCSongs, | class MuseDashOptions(PerGameCommonOptions): | ||||||
|     "streamer_mode_enabled": StreamerModeEnabled, |     allow_just_as_planned_dlc_songs: AllowJustAsPlannedDLCSongs | ||||||
|     "starting_song_count": StartingSongs, |     dlc_packs: DLCMusicPacks | ||||||
|     "additional_song_count": AdditionalSongs, |     streamer_mode_enabled: StreamerModeEnabled | ||||||
|     "additional_item_percentage": AdditionalItemPercentage, |     starting_song_count: StartingSongs | ||||||
|     "song_difficulty_mode": DifficultyMode, |     additional_song_count: AdditionalSongs | ||||||
|     "song_difficulty_min": DifficultyModeOverrideMin, |     additional_item_percentage: AdditionalItemPercentage | ||||||
|     "song_difficulty_max": DifficultyModeOverrideMax, |     song_difficulty_mode: DifficultyMode | ||||||
|     "grade_needed": GradeNeeded, |     song_difficulty_min: DifficultyModeOverrideMin | ||||||
|     "music_sheet_count_percentage": MusicSheetCountPercentage, |     song_difficulty_max: DifficultyModeOverrideMax | ||||||
|     "music_sheet_win_count_percentage": MusicSheetWinCountPercentage, |     grade_needed: GradeNeeded | ||||||
|     "available_trap_types": TrapTypes, |     music_sheet_count_percentage: MusicSheetCountPercentage | ||||||
|     "trap_count_percentage": TrapCountPercentage, |     music_sheet_win_count_percentage: MusicSheetWinCountPercentage | ||||||
|     "death_link": DeathLink, |     available_trap_types: TrapTypes | ||||||
|     "include_songs": IncludeSongs, |     trap_count_percentage: TrapCountPercentage | ||||||
|     "exclude_songs": ExcludeSongs |     death_link: DeathLink | ||||||
| } |     include_songs: IncludeSongs | ||||||
|  |     exclude_songs: ExcludeSongs | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| from worlds.AutoWorld import World, WebWorld | from worlds.AutoWorld import World, WebWorld | ||||||
| from worlds.generic.Rules import set_rule |  | ||||||
| from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial | from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial | ||||||
| from typing import List | from typing import List, ClassVar, Type | ||||||
| from math import floor | from math import floor | ||||||
|  | from Options import PerGameCommonOptions | ||||||
| 
 | 
 | ||||||
| from .Options import musedash_options | from .Options import MuseDashOptions | ||||||
| from .Items import MuseDashSongItem, MuseDashFixedItem | from .Items import MuseDashSongItem, MuseDashFixedItem | ||||||
| from .Locations import MuseDashLocation | from .Locations import MuseDashLocation | ||||||
| from .MuseDashCollection import MuseDashCollections | from .MuseDashCollection import MuseDashCollections | ||||||
|  | @ -47,9 +47,9 @@ class MuseDashWorld(World): | ||||||
| 
 | 
 | ||||||
|     # World Options |     # World Options | ||||||
|     game = "Muse Dash" |     game = "Muse Dash" | ||||||
|     option_definitions = musedash_options |     options_dataclass: ClassVar[Type[PerGameCommonOptions]] = MuseDashOptions | ||||||
|     topology_present = False |     topology_present = False | ||||||
|     data_version = 9 |     data_version = 10 | ||||||
|     web = MuseDashWebWorld() |     web = MuseDashWebWorld() | ||||||
| 
 | 
 | ||||||
|     # Necessary Data |     # Necessary Data | ||||||
|  | @ -66,14 +66,17 @@ class MuseDashWorld(World): | ||||||
|     location_count: int |     location_count: int | ||||||
| 
 | 
 | ||||||
|     def generate_early(self): |     def generate_early(self): | ||||||
|         dlc_songs = self.multiworld.allow_just_as_planned_dlc_songs[self.player] |         dlc_songs = {key for key in self.options.dlc_packs.value} | ||||||
|         streamer_mode = self.multiworld.streamer_mode_enabled[self.player] |         if (self.options.allow_just_as_planned_dlc_songs.value): | ||||||
|  |             dlc_songs.add(self.md_collection.MUSE_PLUS_DLC) | ||||||
|  | 
 | ||||||
|  |         streamer_mode = self.options.streamer_mode_enabled | ||||||
|         (lower_diff_threshold, higher_diff_threshold) = self.get_difficulty_range() |         (lower_diff_threshold, higher_diff_threshold) = self.get_difficulty_range() | ||||||
| 
 | 
 | ||||||
|         # The minimum amount of songs to make an ok rando would be Starting Songs + 10 interim songs + Goal song. |         # The minimum amount of songs to make an ok rando would be Starting Songs + 10 interim songs + Goal song. | ||||||
|         # - Interim songs being equal to max starting song count. |         # - Interim songs being equal to max starting song count. | ||||||
|         # Note: The worst settings still allow 25 songs (Streamer Mode + No DLC). |         # Note: The worst settings still allow 25 songs (Streamer Mode + No DLC). | ||||||
|         starter_song_count = self.multiworld.starting_song_count[self.player].value |         starter_song_count = self.options.starting_song_count.value | ||||||
| 
 | 
 | ||||||
|         while True: |         while True: | ||||||
|             # In most cases this should only need to run once |             # In most cases this should only need to run once | ||||||
|  | @ -104,9 +107,9 @@ class MuseDashWorld(World): | ||||||
|     def handle_plando(self, available_song_keys: List[str]) -> List[str]: |     def handle_plando(self, available_song_keys: List[str]) -> List[str]: | ||||||
|         song_items = self.md_collection.song_items |         song_items = self.md_collection.song_items | ||||||
| 
 | 
 | ||||||
|         start_items = self.multiworld.start_inventory[self.player].value.keys() |         start_items = self.options.start_inventory.value.keys() | ||||||
|         include_songs = self.multiworld.include_songs[self.player].value |         include_songs = self.options.include_songs.value | ||||||
|         exclude_songs = self.multiworld.exclude_songs[self.player].value |         exclude_songs = self.options.exclude_songs.value | ||||||
| 
 | 
 | ||||||
|         self.starting_songs = [s for s in start_items if s in song_items] |         self.starting_songs = [s for s in start_items if s in song_items] | ||||||
|         self.included_songs = [s for s in include_songs if s in song_items and s not in self.starting_songs] |         self.included_songs = [s for s in include_songs if s in song_items and s not in self.starting_songs] | ||||||
|  | @ -115,8 +118,8 @@ class MuseDashWorld(World): | ||||||
|                 and s not in include_songs and s not in exclude_songs] |                 and s not in include_songs and s not in exclude_songs] | ||||||
| 
 | 
 | ||||||
|     def create_song_pool(self, available_song_keys: List[str]): |     def create_song_pool(self, available_song_keys: List[str]): | ||||||
|         starting_song_count = self.multiworld.starting_song_count[self.player].value |         starting_song_count = self.options.starting_song_count.value | ||||||
|         additional_song_count = self.multiworld.additional_song_count[self.player].value |         additional_song_count = self.options.additional_song_count.value | ||||||
| 
 | 
 | ||||||
|         self.random.shuffle(available_song_keys) |         self.random.shuffle(available_song_keys) | ||||||
| 
 | 
 | ||||||
|  | @ -150,7 +153,7 @@ class MuseDashWorld(World): | ||||||
| 
 | 
 | ||||||
|         # Then attempt to fufill any remaining songs for interim songs |         # Then attempt to fufill any remaining songs for interim songs | ||||||
|         if len(self.included_songs) < additional_song_count: |         if len(self.included_songs) < additional_song_count: | ||||||
|             for _ in range(len(self.included_songs), self.multiworld.additional_song_count[self.player]): |             for _ in range(len(self.included_songs), self.options.additional_song_count): | ||||||
|                 if len(available_song_keys) <= 0: |                 if len(available_song_keys) <= 0: | ||||||
|                     break |                     break | ||||||
|                 self.included_songs.append(available_song_keys.pop()) |                 self.included_songs.append(available_song_keys.pop()) | ||||||
|  | @ -258,40 +261,40 @@ class MuseDashWorld(World): | ||||||
|             state.has(self.md_collection.MUSIC_SHEET_NAME, self.player, self.get_music_sheet_win_count()) |             state.has(self.md_collection.MUSIC_SHEET_NAME, self.player, self.get_music_sheet_win_count()) | ||||||
| 
 | 
 | ||||||
|     def get_available_traps(self) -> List[str]: |     def get_available_traps(self) -> List[str]: | ||||||
|         dlc_songs = self.multiworld.allow_just_as_planned_dlc_songs[self.player] |         sfx_traps_available = self.options.allow_just_as_planned_dlc_songs.value | ||||||
| 
 | 
 | ||||||
|         trap_list = [] |         trap_list = [] | ||||||
|         if self.multiworld.available_trap_types[self.player].value & 1 != 0: |         if self.options.available_trap_types.value & 1 != 0: | ||||||
|             trap_list += self.md_collection.vfx_trap_items.keys() |             trap_list += self.md_collection.vfx_trap_items.keys() | ||||||
| 
 | 
 | ||||||
|         # SFX options are only available under Just as Planned DLC. |         # SFX options are only available under Just as Planned DLC. | ||||||
|         if dlc_songs and self.multiworld.available_trap_types[self.player].value & 2 != 0: |         if sfx_traps_available and self.options.available_trap_types.value & 2 != 0: | ||||||
|             trap_list += self.md_collection.sfx_trap_items.keys() |             trap_list += self.md_collection.sfx_trap_items.keys() | ||||||
| 
 | 
 | ||||||
|         return trap_list |         return trap_list | ||||||
| 
 | 
 | ||||||
|     def get_additional_item_percentage(self) -> int: |     def get_additional_item_percentage(self) -> int: | ||||||
|         trap_count = self.multiworld.trap_count_percentage[self.player].value |         trap_count = self.options.trap_count_percentage.value | ||||||
|         song_count = self.multiworld.music_sheet_count_percentage[self.player].value |         song_count = self.options.music_sheet_count_percentage.value | ||||||
|         return max(trap_count + song_count, self.multiworld.additional_item_percentage[self.player].value) |         return max(trap_count + song_count, self.options.additional_item_percentage.value) | ||||||
| 
 | 
 | ||||||
|     def get_trap_count(self) -> int: |     def get_trap_count(self) -> int: | ||||||
|         multiplier = self.multiworld.trap_count_percentage[self.player].value / 100.0 |         multiplier = self.options.trap_count_percentage.value / 100.0 | ||||||
|         trap_count = (len(self.starting_songs) * 2) + len(self.included_songs) |         trap_count = (len(self.starting_songs) * 2) + len(self.included_songs) | ||||||
|         return max(0, floor(trap_count * multiplier)) |         return max(0, floor(trap_count * multiplier)) | ||||||
| 
 | 
 | ||||||
|     def get_music_sheet_count(self) -> int: |     def get_music_sheet_count(self) -> int: | ||||||
|         multiplier = self.multiworld.music_sheet_count_percentage[self.player].value / 100.0 |         multiplier = self.options.music_sheet_count_percentage.value / 100.0 | ||||||
|         song_count = (len(self.starting_songs) * 2) + len(self.included_songs) |         song_count = (len(self.starting_songs) * 2) + len(self.included_songs) | ||||||
|         return max(1, floor(song_count * multiplier)) |         return max(1, floor(song_count * multiplier)) | ||||||
| 
 | 
 | ||||||
|     def get_music_sheet_win_count(self) -> int: |     def get_music_sheet_win_count(self) -> int: | ||||||
|         multiplier = self.multiworld.music_sheet_win_count_percentage[self.player].value / 100.0 |         multiplier = self.options.music_sheet_win_count_percentage.value / 100.0 | ||||||
|         sheet_count = self.get_music_sheet_count() |         sheet_count = self.get_music_sheet_count() | ||||||
|         return max(1, floor(sheet_count * multiplier)) |         return max(1, floor(sheet_count * multiplier)) | ||||||
| 
 | 
 | ||||||
|     def get_difficulty_range(self) -> List[int]: |     def get_difficulty_range(self) -> List[int]: | ||||||
|         difficulty_mode = self.multiworld.song_difficulty_mode[self.player] |         difficulty_mode = self.options.song_difficulty_mode | ||||||
| 
 | 
 | ||||||
|         # Valid difficulties are between 1 and 11. But make it 0 to 12 for safety |         # Valid difficulties are between 1 and 11. But make it 0 to 12 for safety | ||||||
|         difficulty_bounds = [0, 12] |         difficulty_bounds = [0, 12] | ||||||
|  | @ -309,8 +312,8 @@ class MuseDashWorld(World): | ||||||
|         elif difficulty_mode == 5: |         elif difficulty_mode == 5: | ||||||
|             difficulty_bounds[0] = 10 |             difficulty_bounds[0] = 10 | ||||||
|         elif difficulty_mode == 6: |         elif difficulty_mode == 6: | ||||||
|             minimum_difficulty = self.multiworld.song_difficulty_min[self.player].value |             minimum_difficulty = self.options.song_difficulty_min.value | ||||||
|             maximum_difficulty = self.multiworld.song_difficulty_max[self.player].value |             maximum_difficulty = self.options.song_difficulty_max.value | ||||||
| 
 | 
 | ||||||
|             difficulty_bounds[0] = min(minimum_difficulty, maximum_difficulty) |             difficulty_bounds[0] = min(minimum_difficulty, maximum_difficulty) | ||||||
|             difficulty_bounds[1] = max(minimum_difficulty, maximum_difficulty) |             difficulty_bounds[1] = max(minimum_difficulty, maximum_difficulty) | ||||||
|  | @ -320,7 +323,7 @@ class MuseDashWorld(World): | ||||||
|     def fill_slot_data(self): |     def fill_slot_data(self): | ||||||
|         return { |         return { | ||||||
|             "victoryLocation": self.victory_song_name, |             "victoryLocation": self.victory_song_name, | ||||||
|             "deathLink": self.multiworld.death_link[self.player].value, |             "deathLink": self.options.death_link.value, | ||||||
|             "musicSheetWinCount": self.get_music_sheet_win_count(), |             "musicSheetWinCount": self.get_music_sheet_win_count(), | ||||||
|             "gradeNeeded": self.multiworld.grade_needed[self.player].value |             "gradeNeeded": self.options.grade_needed.value | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -36,14 +36,27 @@ class CollectionsTest(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     def test_free_dlc_included_in_base_songs(self) -> None: |     def test_free_dlc_included_in_base_songs(self) -> None: | ||||||
|         collection = MuseDashCollections() |         collection = MuseDashCollections() | ||||||
|         songs = collection.get_songs_with_settings(False, False, 0, 11) |         songs = collection.get_songs_with_settings(set(), False, 0, 12) | ||||||
| 
 | 
 | ||||||
|         self.assertIn("Glimmer", songs, "Budget Is Burning Vol.1 is not being included in base songs") |         self.assertIn("Glimmer", songs, "Budget Is Burning Vol.1 is not being included in base songs") | ||||||
|         self.assertIn("Out of Sense", songs, "Budget Is Burning: Nano Core is not being included in base songs") |         self.assertIn("Out of Sense", songs, "Budget Is Burning: Nano Core is not being included in base songs") | ||||||
| 
 | 
 | ||||||
|  |     def test_dlcs(self) -> None: | ||||||
|  |         collection = MuseDashCollections() | ||||||
|  |         free_song_count = len(collection.get_songs_with_settings(set(), False, 0, 12)) | ||||||
|  |         known_mp_song = "The Happycore Idol" | ||||||
|  | 
 | ||||||
|  |         for dlc in collection.DLC: | ||||||
|  |             songs_with_dlc = collection.get_songs_with_settings({dlc}, False, 0, 12) | ||||||
|  |             self.assertGreater(len(songs_with_dlc), free_song_count, f"DLC {dlc} did not include extra songs.") | ||||||
|  |             if dlc == collection.MUSE_PLUS_DLC: | ||||||
|  |                 self.assertIn(known_mp_song, songs_with_dlc, f"Muse Plus missing muse plus song.") | ||||||
|  |             else: | ||||||
|  |                 self.assertNotIn(known_mp_song, songs_with_dlc, f"DLC {dlc} includes Muse Plus songs.") | ||||||
|  | 
 | ||||||
|     def test_remove_songs_are_not_generated(self) -> None: |     def test_remove_songs_are_not_generated(self) -> None: | ||||||
|         collection = MuseDashCollections() |         collection = MuseDashCollections() | ||||||
|         songs = collection.get_songs_with_settings(True, False, 0, 11) |         songs = collection.get_songs_with_settings({x for x in collection.DLC}, False, 0, 12) | ||||||
| 
 | 
 | ||||||
|         for song_name in self.REMOVED_SONGS: |         for song_name in self.REMOVED_SONGS: | ||||||
|             self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.") |             self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.") | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ from . import MuseDashTestBase | ||||||
| class DifficultyRanges(MuseDashTestBase): | class DifficultyRanges(MuseDashTestBase): | ||||||
|     def test_all_difficulty_ranges(self) -> None: |     def test_all_difficulty_ranges(self) -> None: | ||||||
|         muse_dash_world = self.multiworld.worlds[1] |         muse_dash_world = self.multiworld.worlds[1] | ||||||
|  |         dlc_set = {x for x in muse_dash_world.md_collection.DLC} | ||||||
|         difficulty_choice = self.multiworld.song_difficulty_mode[1] |         difficulty_choice = self.multiworld.song_difficulty_mode[1] | ||||||
|         difficulty_min = self.multiworld.song_difficulty_min[1] |         difficulty_min = self.multiworld.song_difficulty_min[1] | ||||||
|         difficulty_max = self.multiworld.song_difficulty_max[1] |         difficulty_max = self.multiworld.song_difficulty_max[1] | ||||||
|  | @ -12,7 +13,7 @@ class DifficultyRanges(MuseDashTestBase): | ||||||
|             self.assertEqual(inputRange[0], lower) |             self.assertEqual(inputRange[0], lower) | ||||||
|             self.assertEqual(inputRange[1], upper) |             self.assertEqual(inputRange[1], upper) | ||||||
| 
 | 
 | ||||||
|             songs = muse_dash_world.md_collection.get_songs_with_settings(True, False, inputRange[0], inputRange[1]) |             songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, inputRange[0], inputRange[1]) | ||||||
|             for songKey in songs: |             for songKey in songs: | ||||||
|                 song = muse_dash_world.md_collection.song_items[songKey] |                 song = muse_dash_world.md_collection.song_items[songKey] | ||||||
|                 if (song.easy is not None and inputRange[0] <= song.easy <= inputRange[1]): |                 if (song.easy is not None and inputRange[0] <= song.easy <= inputRange[1]): | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue