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|
|
||||||
|
@ -465,7 +465,27 @@ Kawai Splendid Space Thief|64-4|COSMIC RADIO PEROLIST|False|6|8|10|11
|
||||||
Night City Runway|64-5|COSMIC RADIO PEROLIST|True|4|6|8|
|
Night City Runway|64-5|COSMIC RADIO PEROLIST|True|4|6|8|
|
||||||
Chaos Shotgun feat. ChumuNote|64-6|COSMIC RADIO PEROLIST|True|6|8|10|
|
Chaos Shotgun feat. ChumuNote|64-6|COSMIC RADIO PEROLIST|True|6|8|10|
|
||||||
mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11
|
mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11
|
||||||
BrainDance|65-0|Neon Abyss|True|3|6|9|
|
BrainDance|65-0|NeonAbyss|True|3|6|9|
|
||||||
My Focus!|65-1|Neon Abyss|True|5|7|10|
|
My Focus!|65-1|NeonAbyss|True|5|7|10|
|
||||||
ABABABA BURST|65-2|Neon Abyss|True|5|7|9|
|
ABABABA BURST|65-2|NeonAbyss|True|5|7|9|
|
||||||
ULTRA HIGHER|65-3|Neon Abyss|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