Muse Dash: Add support for specifying specific DLCs (#2329)

This commit is contained in:
Justus Lind 2023-10-20 10:13:17 +10:00 committed by GitHub
parent fb6b66463d
commit 385803eb5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 166 additions and 99 deletions

View File

@ -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]

View File

@ -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 == "¿":

View File

@ -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|

View File

@ -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

View File

@ -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
} }

View File

@ -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.")

View File

@ -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]):