Muse Dash: Option Groups and Options Rework (#3434)
* Ensure that included/starter songs only include those within enabled dlcs. * Allow filtering traps by trap instead of by category. * Add in the currently available limited time dlcs to the dlc list. * Add the option group to the webhost and cleanup some errors. * Fix trap list. * Update tests. Add new ones to test correctness of new features. * Remove the old Just As Planned option * Make traps order alphabetically. Also adjust the title for traps. * Adjust new lines to better fit the website. * Style fixes. * Test adjustments and a fix due to test no longer having just as planned dlc. * Undo spacing changes as it breaks yaml generation. * Fix indenting in webhost. * Add the old options in as removed. Also clean up unused import. * Remove references to the old allow_just_as_planned_dlc_songs option in Muse Dash tests. * Add newline to end of file. --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
parent
f30f2d3a3f
commit
133167564c
|
@ -22,12 +22,15 @@ class MuseDashCollections:
|
||||||
]
|
]
|
||||||
|
|
||||||
MUSE_PLUS_DLC: str = "Muse Plus"
|
MUSE_PLUS_DLC: str = "Muse Plus"
|
||||||
|
|
||||||
|
# Ordering matters for webhost. Order goes: Muse Plus, Time Limited Muse Plus Dlcs, Paid Dlcs
|
||||||
DLC: List[str] = [
|
DLC: List[str] = [
|
||||||
# MUSE_PLUS_DLC, # To be included when OptionSets are rendered as part of basic settings.
|
MUSE_PLUS_DLC,
|
||||||
# "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
|
"CHUNITHM COURSE MUSE", # Part of Muse Plus. Goes away 22nd May 2027.
|
||||||
|
"maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
|
||||||
|
"MSR Anthology", # Now no longer available.
|
||||||
"Miku in Museland", # Paid DLC not included in Muse Plus
|
"Miku in Museland", # Paid DLC not included in Muse Plus
|
||||||
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
|
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
|
||||||
"MSR Anthology", # Now no longer available.
|
|
||||||
]
|
]
|
||||||
|
|
||||||
DIFF_OVERRIDES: List[str] = [
|
DIFF_OVERRIDES: List[str] = [
|
||||||
|
@ -50,7 +53,7 @@ class MuseDashCollections:
|
||||||
song_items: Dict[str, SongData] = {}
|
song_items: Dict[str, SongData] = {}
|
||||||
song_locations: Dict[str, int] = {}
|
song_locations: Dict[str, int] = {}
|
||||||
|
|
||||||
vfx_trap_items: Dict[str, int] = {
|
trap_items: Dict[str, int] = {
|
||||||
"Bad Apple Trap": STARTING_CODE + 1,
|
"Bad Apple Trap": STARTING_CODE + 1,
|
||||||
"Pixelate Trap": STARTING_CODE + 2,
|
"Pixelate Trap": STARTING_CODE + 2,
|
||||||
"Ripple Trap": STARTING_CODE + 3,
|
"Ripple Trap": STARTING_CODE + 3,
|
||||||
|
@ -58,13 +61,15 @@ class MuseDashCollections:
|
||||||
"Chromatic Aberration Trap": STARTING_CODE + 5,
|
"Chromatic Aberration Trap": STARTING_CODE + 5,
|
||||||
"Background Freeze Trap": STARTING_CODE + 6,
|
"Background Freeze Trap": STARTING_CODE + 6,
|
||||||
"Gray Scale Trap": STARTING_CODE + 7,
|
"Gray Scale Trap": STARTING_CODE + 7,
|
||||||
|
"Nyaa SFX Trap": STARTING_CODE + 8,
|
||||||
|
"Error SFX Trap": STARTING_CODE + 9,
|
||||||
"Focus Line Trap": STARTING_CODE + 10,
|
"Focus Line Trap": STARTING_CODE + 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
sfx_trap_items: Dict[str, int] = {
|
sfx_trap_items: List[str] = [
|
||||||
"Nyaa SFX Trap": STARTING_CODE + 8,
|
"Nyaa SFX Trap",
|
||||||
"Error SFX Trap": STARTING_CODE + 9,
|
"Error SFX Trap",
|
||||||
}
|
]
|
||||||
|
|
||||||
filler_items: Dict[str, int] = {
|
filler_items: Dict[str, int] = {
|
||||||
"Great To Perfect (10 Pack)": STARTING_CODE + 30,
|
"Great To Perfect (10 Pack)": STARTING_CODE + 30,
|
||||||
|
@ -78,7 +83,7 @@ class MuseDashCollections:
|
||||||
"Extra Life": 1,
|
"Extra Life": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
item_names_to_id: ChainMap = ChainMap({}, filler_items, sfx_trap_items, vfx_trap_items)
|
item_names_to_id: ChainMap = ChainMap({}, filler_items, trap_items)
|
||||||
location_names_to_id: ChainMap = ChainMap(song_locations, album_locations)
|
location_names_to_id: ChainMap = ChainMap(song_locations, album_locations)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
@ -171,6 +176,9 @@ class MuseDashCollections:
|
||||||
|
|
||||||
return filtered_list
|
return filtered_list
|
||||||
|
|
||||||
|
def filter_songs_to_dlc(self, song_list: List[str], dlc_songs: Set[str]) -> List[str]:
|
||||||
|
return [song for song in song_list if self.song_matches_dlc_filter(self.song_items[song], dlc_songs)]
|
||||||
|
|
||||||
def song_matches_dlc_filter(self, song: SongData, dlc_songs: Set[str]) -> bool:
|
def song_matches_dlc_filter(self, song: SongData, dlc_songs: Set[str]) -> bool:
|
||||||
if song.album in self.FREE_ALBUMS:
|
if song.album in self.FREE_ALBUMS:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
from typing import Dict
|
from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions, OptionGroup, Removed
|
||||||
from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from .MuseDashCollection import MuseDashCollections
|
from .MuseDashCollection import MuseDashCollections
|
||||||
|
|
||||||
|
|
||||||
class AllowJustAsPlannedDLCSongs(Toggle):
|
|
||||||
"""Whether [Muse Plus] DLC Songs, and all the albums included in it, can be chosen as randomised songs.
|
|
||||||
Note: The [Just As Planned] DLC contains all [Muse Plus] songs."""
|
|
||||||
display_name = "Allow [Muse Plus] DLC Songs"
|
|
||||||
|
|
||||||
|
|
||||||
class DLCMusicPacks(OptionSet):
|
class DLCMusicPacks(OptionSet):
|
||||||
"""Which non-[Muse Plus] DLC packs can be chosen as randomised songs."""
|
"""
|
||||||
|
Choose which DLC Packs will be included in the pool of chooseable songs.
|
||||||
|
|
||||||
|
Note: The [Just As Planned] DLC contains all [Muse Plus] songs.
|
||||||
|
"""
|
||||||
display_name = "DLC Packs"
|
display_name = "DLC Packs"
|
||||||
default = {}
|
default = {}
|
||||||
valid_keys = [dlc for dlc in MuseDashCollections.DLC]
|
valid_keys = [dlc for dlc in MuseDashCollections.DLC]
|
||||||
|
|
||||||
|
|
||||||
class StreamerModeEnabled(Toggle):
|
class StreamerModeEnabled(Toggle):
|
||||||
"""In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming.
|
"""
|
||||||
If this is enabled, only songs available under Streamer Mode will be available for randomization."""
|
In Muse Dash, an option named 'Streamer Mode' removes songs which may trigger copyright issues when streaming.
|
||||||
|
|
||||||
|
If this is enabled, only songs available under Streamer Mode will be available for randomization.
|
||||||
|
"""
|
||||||
display_name = "Streamer Mode Only Songs"
|
display_name = "Streamer Mode Only Songs"
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +33,8 @@ class StartingSongs(Range):
|
||||||
|
|
||||||
|
|
||||||
class AdditionalSongs(Range):
|
class AdditionalSongs(Range):
|
||||||
"""The total number of songs that will be placed in the randomization pool.
|
"""
|
||||||
|
The total number of songs that will be placed in the randomization pool.
|
||||||
- This does not count any starting songs or the goal song.
|
- This does not count any starting songs or the goal song.
|
||||||
- The final song count may be lower due to other settings.
|
- The final song count may be lower due to other settings.
|
||||||
"""
|
"""
|
||||||
|
@ -44,7 +45,8 @@ class AdditionalSongs(Range):
|
||||||
|
|
||||||
|
|
||||||
class DifficultyMode(Choice):
|
class DifficultyMode(Choice):
|
||||||
"""Ensures that at any chosen song has at least 1 value falling within these values.
|
"""
|
||||||
|
Ensures that at any chosen song has at least 1 value falling within these values.
|
||||||
- Any: All songs are available
|
- Any: All songs are available
|
||||||
- Easy: 1, 2 or 3
|
- Easy: 1, 2 or 3
|
||||||
- Medium: 4, 5
|
- Medium: 4, 5
|
||||||
|
@ -66,8 +68,11 @@ class DifficultyMode(Choice):
|
||||||
|
|
||||||
# Todo: Investigate options to make this non randomizable
|
# Todo: Investigate options to make this non randomizable
|
||||||
class DifficultyModeOverrideMin(Range):
|
class DifficultyModeOverrideMin(Range):
|
||||||
"""Ensures that 1 difficulty has at least 1 this value or higher per song.
|
"""
|
||||||
- Difficulty Mode must be set to Manual."""
|
Ensures that 1 difficulty has at least 1 this value or higher per song.
|
||||||
|
|
||||||
|
Note: Difficulty Mode must be set to Manual.
|
||||||
|
"""
|
||||||
display_name = "Manual Difficulty Min"
|
display_name = "Manual Difficulty Min"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 11
|
range_end = 11
|
||||||
|
@ -76,8 +81,11 @@ class DifficultyModeOverrideMin(Range):
|
||||||
|
|
||||||
# Todo: Investigate options to make this non randomizable
|
# Todo: Investigate options to make this non randomizable
|
||||||
class DifficultyModeOverrideMax(Range):
|
class DifficultyModeOverrideMax(Range):
|
||||||
"""Ensures that 1 difficulty has at least 1 this value or lower per song.
|
"""
|
||||||
- Difficulty Mode must be set to Manual."""
|
Ensures that 1 difficulty has at least 1 this value or lower per song.
|
||||||
|
|
||||||
|
Note: Difficulty Mode must be set to Manual.
|
||||||
|
"""
|
||||||
display_name = "Manual Difficulty Max"
|
display_name = "Manual Difficulty Max"
|
||||||
range_start = 1
|
range_start = 1
|
||||||
range_end = 11
|
range_end = 11
|
||||||
|
@ -85,7 +93,8 @@ class DifficultyModeOverrideMax(Range):
|
||||||
|
|
||||||
|
|
||||||
class GradeNeeded(Choice):
|
class GradeNeeded(Choice):
|
||||||
"""Completing a song will require a grade of this value or higher in order to unlock items.
|
"""
|
||||||
|
Completing a song will require a grade of this value or higher in order to unlock items.
|
||||||
The grades are as follows:
|
The grades are as follows:
|
||||||
- Silver S (SS): >= 95% accuracy
|
- Silver S (SS): >= 95% accuracy
|
||||||
- Pink S (S): >= 90% accuracy
|
- Pink S (S): >= 90% accuracy
|
||||||
|
@ -104,7 +113,9 @@ class GradeNeeded(Choice):
|
||||||
|
|
||||||
|
|
||||||
class MusicSheetCountPercentage(Range):
|
class MusicSheetCountPercentage(Range):
|
||||||
"""Controls how many music sheets are added to the pool based on the number of songs, including starting songs.
|
"""
|
||||||
|
Controls how many music sheets are added to the pool based on the number of songs, including starting songs.
|
||||||
|
|
||||||
Higher numbers leads to more consistent game lengths, but will cause individual music sheets to be less important.
|
Higher numbers leads to more consistent game lengths, but will cause individual music sheets to be less important.
|
||||||
"""
|
"""
|
||||||
range_start = 10
|
range_start = 10
|
||||||
|
@ -121,19 +132,18 @@ class MusicSheetWinCountPercentage(Range):
|
||||||
display_name = "Music Sheets Needed to Win"
|
display_name = "Music Sheets Needed to Win"
|
||||||
|
|
||||||
|
|
||||||
class TrapTypes(Choice):
|
class ChosenTraps(OptionSet):
|
||||||
"""This controls the types of traps that can be added to the pool.
|
"""
|
||||||
|
This controls the types of traps that can be added to the pool.
|
||||||
|
- Traps last the length of a song, or until you die.
|
||||||
- VFX Traps consist of visual effects that play over the song. (i.e. Grayscale.)
|
- VFX Traps consist of visual effects that play over the song. (i.e. Grayscale.)
|
||||||
- SFX Traps consist of changing your sfx setting to one possibly more annoying sfx.
|
- SFX Traps consist of changing your sfx setting to one possibly more annoying sfx.
|
||||||
Traps last the length of a song, or until you die.
|
|
||||||
Note: SFX traps are only available if [Just as Planned] DLC songs are enabled.
|
Note: SFX traps are only available if [Just as Planned] DLC songs are enabled.
|
||||||
"""
|
"""
|
||||||
display_name = "Available Trap Types"
|
display_name = "Chosen Traps"
|
||||||
option_None = 0
|
default = {}
|
||||||
option_VFX = 1
|
valid_keys = {trap for trap in MuseDashCollections.trap_items.keys()}
|
||||||
option_SFX = 2
|
|
||||||
option_All = 3
|
|
||||||
default = 3
|
|
||||||
|
|
||||||
|
|
||||||
class TrapCountPercentage(Range):
|
class TrapCountPercentage(Range):
|
||||||
|
@ -145,24 +155,49 @@ class TrapCountPercentage(Range):
|
||||||
|
|
||||||
|
|
||||||
class IncludeSongs(ItemSet):
|
class IncludeSongs(ItemSet):
|
||||||
"""Any song listed here will be guaranteed to be included as part of the seed.
|
"""
|
||||||
- Difficulty options will be skipped for these songs.
|
These songs will be guaranteed to show up within the seed.
|
||||||
- If there being too many included songs, songs will be randomly chosen without regard for difficulty.
|
- You must have the DLC enabled to play these songs.
|
||||||
- If you want these songs immediately, use start_inventory instead.
|
- Difficulty options will not affect these songs.
|
||||||
|
- If there are too many included songs, this will act as a whitelist ignoring song difficulty.
|
||||||
"""
|
"""
|
||||||
verify_item_name = True
|
verify_item_name = True
|
||||||
display_name = "Include Songs"
|
display_name = "Include Songs"
|
||||||
|
|
||||||
|
|
||||||
class ExcludeSongs(ItemSet):
|
class ExcludeSongs(ItemSet):
|
||||||
"""Any song listed here will be excluded from being a part of the seed."""
|
"""
|
||||||
|
These songs will be guaranteed to not show up within the seed.
|
||||||
|
|
||||||
|
Note: Does not affect songs within the "Include Songs" list.
|
||||||
|
"""
|
||||||
verify_item_name = True
|
verify_item_name = True
|
||||||
display_name = "Exclude Songs"
|
display_name = "Exclude Songs"
|
||||||
|
|
||||||
|
|
||||||
|
md_option_groups = [
|
||||||
|
OptionGroup("Song Choice", [
|
||||||
|
DLCMusicPacks,
|
||||||
|
StreamerModeEnabled,
|
||||||
|
IncludeSongs,
|
||||||
|
ExcludeSongs,
|
||||||
|
]),
|
||||||
|
OptionGroup("Difficulty", [
|
||||||
|
GradeNeeded,
|
||||||
|
DifficultyMode,
|
||||||
|
DifficultyModeOverrideMin,
|
||||||
|
DifficultyModeOverrideMax,
|
||||||
|
DeathLink,
|
||||||
|
]),
|
||||||
|
OptionGroup("Traps", [
|
||||||
|
ChosenTraps,
|
||||||
|
TrapCountPercentage,
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MuseDashOptions(PerGameCommonOptions):
|
class MuseDashOptions(PerGameCommonOptions):
|
||||||
allow_just_as_planned_dlc_songs: AllowJustAsPlannedDLCSongs
|
|
||||||
dlc_packs: DLCMusicPacks
|
dlc_packs: DLCMusicPacks
|
||||||
streamer_mode_enabled: StreamerModeEnabled
|
streamer_mode_enabled: StreamerModeEnabled
|
||||||
starting_song_count: StartingSongs
|
starting_song_count: StartingSongs
|
||||||
|
@ -173,8 +208,12 @@ class MuseDashOptions(PerGameCommonOptions):
|
||||||
grade_needed: GradeNeeded
|
grade_needed: GradeNeeded
|
||||||
music_sheet_count_percentage: MusicSheetCountPercentage
|
music_sheet_count_percentage: MusicSheetCountPercentage
|
||||||
music_sheet_win_count_percentage: MusicSheetWinCountPercentage
|
music_sheet_win_count_percentage: MusicSheetWinCountPercentage
|
||||||
available_trap_types: TrapTypes
|
chosen_traps: ChosenTraps
|
||||||
trap_count_percentage: TrapCountPercentage
|
trap_count_percentage: TrapCountPercentage
|
||||||
death_link: DeathLink
|
death_link: DeathLink
|
||||||
include_songs: IncludeSongs
|
include_songs: IncludeSongs
|
||||||
exclude_songs: ExcludeSongs
|
exclude_songs: ExcludeSongs
|
||||||
|
|
||||||
|
# Removed
|
||||||
|
allow_just_as_planned_dlc_songs: Removed
|
||||||
|
available_trap_types: Removed
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
||||||
# An option to support Short Sync games. 40 songs.
|
# An option to support Short Sync games. 40 songs.
|
||||||
"No DLC - Short": {
|
"No DLC - Short": {
|
||||||
"allow_just_as_planned_dlc_songs": False,
|
"dlc_packs": [],
|
||||||
"starting_song_count": 5,
|
"starting_song_count": 5,
|
||||||
"additional_song_count": 34,
|
"additional_song_count": 34,
|
||||||
"music_sheet_count_percentage": 20,
|
"music_sheet_count_percentage": 20,
|
||||||
|
@ -11,7 +11,7 @@ MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
||||||
},
|
},
|
||||||
# An option to support Short Sync games but adds variety. 40 songs.
|
# An option to support Short Sync games but adds variety. 40 songs.
|
||||||
"DLC - Short": {
|
"DLC - Short": {
|
||||||
"allow_just_as_planned_dlc_songs": True,
|
"dlc_packs": ["Muse Plus"],
|
||||||
"starting_song_count": 5,
|
"starting_song_count": 5,
|
||||||
"additional_song_count": 34,
|
"additional_song_count": 34,
|
||||||
"music_sheet_count_percentage": 20,
|
"music_sheet_count_percentage": 20,
|
||||||
|
@ -19,7 +19,7 @@ MuseDashPresets: Dict[str, Dict[str, Any]] = {
|
||||||
},
|
},
|
||||||
# An option to support Longer Sync/Async games. 100 songs.
|
# An option to support Longer Sync/Async games. 100 songs.
|
||||||
"DLC - Long": {
|
"DLC - Long": {
|
||||||
"allow_just_as_planned_dlc_songs": True,
|
"dlc_packs": ["Muse Plus"],
|
||||||
"starting_song_count": 8,
|
"starting_song_count": 8,
|
||||||
"additional_song_count": 91,
|
"additional_song_count": 91,
|
||||||
"music_sheet_count_percentage": 20,
|
"music_sheet_count_percentage": 20,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from worlds.AutoWorld import World, WebWorld
|
from worlds.AutoWorld import World, WebWorld
|
||||||
from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial
|
from BaseClasses import Region, Item, ItemClassification, Tutorial
|
||||||
from typing import List, ClassVar, Type
|
from typing import List, ClassVar, Type, Set
|
||||||
from math import floor
|
from math import floor
|
||||||
from Options import PerGameCommonOptions
|
from Options import PerGameCommonOptions
|
||||||
|
|
||||||
from .Options import MuseDashOptions
|
from .Options import MuseDashOptions, md_option_groups
|
||||||
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
|
||||||
|
@ -35,6 +35,7 @@ class MuseDashWebWorld(WebWorld):
|
||||||
|
|
||||||
tutorials = [setup_en, setup_es]
|
tutorials = [setup_en, setup_es]
|
||||||
options_presets = MuseDashPresets
|
options_presets = MuseDashPresets
|
||||||
|
option_groups = md_option_groups
|
||||||
|
|
||||||
|
|
||||||
class MuseDashWorld(World):
|
class MuseDashWorld(World):
|
||||||
|
@ -72,8 +73,6 @@ class MuseDashWorld(World):
|
||||||
|
|
||||||
def generate_early(self):
|
def generate_early(self):
|
||||||
dlc_songs = {key for key in self.options.dlc_packs.value}
|
dlc_songs = {key for key in self.options.dlc_packs.value}
|
||||||
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
|
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()
|
||||||
|
@ -88,7 +87,7 @@ class MuseDashWorld(World):
|
||||||
available_song_keys = self.md_collection.get_songs_with_settings(
|
available_song_keys = self.md_collection.get_songs_with_settings(
|
||||||
dlc_songs, bool(streamer_mode.value), lower_diff_threshold, higher_diff_threshold)
|
dlc_songs, bool(streamer_mode.value), lower_diff_threshold, higher_diff_threshold)
|
||||||
|
|
||||||
available_song_keys = self.handle_plando(available_song_keys)
|
available_song_keys = self.handle_plando(available_song_keys, dlc_songs)
|
||||||
|
|
||||||
count_needed_for_start = max(0, starter_song_count - len(self.starting_songs))
|
count_needed_for_start = max(0, starter_song_count - len(self.starting_songs))
|
||||||
if len(available_song_keys) + len(self.included_songs) >= count_needed_for_start + 11:
|
if len(available_song_keys) + len(self.included_songs) >= count_needed_for_start + 11:
|
||||||
|
@ -109,7 +108,7 @@ class MuseDashWorld(World):
|
||||||
for song in self.starting_songs:
|
for song in self.starting_songs:
|
||||||
self.multiworld.push_precollected(self.create_item(song))
|
self.multiworld.push_precollected(self.create_item(song))
|
||||||
|
|
||||||
def handle_plando(self, available_song_keys: List[str]) -> List[str]:
|
def handle_plando(self, available_song_keys: List[str], dlc_songs: Set[str]) -> List[str]:
|
||||||
song_items = self.md_collection.song_items
|
song_items = self.md_collection.song_items
|
||||||
|
|
||||||
start_items = self.options.start_inventory.value.keys()
|
start_items = self.options.start_inventory.value.keys()
|
||||||
|
@ -117,7 +116,9 @@ class MuseDashWorld(World):
|
||||||
exclude_songs = self.options.exclude_songs.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.starting_songs = self.md_collection.filter_songs_to_dlc(self.starting_songs, dlc_songs)
|
||||||
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]
|
||||||
|
self.included_songs = self.md_collection.filter_songs_to_dlc(self.included_songs, dlc_songs)
|
||||||
|
|
||||||
return [s for s in available_song_keys if s not in start_items
|
return [s for s in available_song_keys if s not in start_items
|
||||||
and s not in include_songs and s not in exclude_songs]
|
and s not in include_songs and s not in exclude_songs]
|
||||||
|
@ -148,7 +149,7 @@ class MuseDashWorld(World):
|
||||||
self.victory_song_name = available_song_keys[chosen_song - included_song_count]
|
self.victory_song_name = available_song_keys[chosen_song - included_song_count]
|
||||||
del available_song_keys[chosen_song - included_song_count]
|
del available_song_keys[chosen_song - included_song_count]
|
||||||
|
|
||||||
# Next, make sure the starting songs are fufilled
|
# Next, make sure the starting songs are fulfilled
|
||||||
if len(self.starting_songs) < starting_song_count:
|
if len(self.starting_songs) < starting_song_count:
|
||||||
for _ in range(len(self.starting_songs), starting_song_count):
|
for _ in range(len(self.starting_songs), starting_song_count):
|
||||||
if len(available_song_keys) > 0:
|
if len(available_song_keys) > 0:
|
||||||
|
@ -156,7 +157,7 @@ class MuseDashWorld(World):
|
||||||
else:
|
else:
|
||||||
self.starting_songs.append(self.included_songs.pop())
|
self.starting_songs.append(self.included_songs.pop())
|
||||||
|
|
||||||
# Then attempt to fufill any remaining songs for interim songs
|
# Then attempt to fulfill 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.options.additional_song_count):
|
for _ in range(len(self.included_songs), self.options.additional_song_count):
|
||||||
if len(available_song_keys) <= 0:
|
if len(available_song_keys) <= 0:
|
||||||
|
@ -174,11 +175,7 @@ class MuseDashWorld(World):
|
||||||
if filler:
|
if filler:
|
||||||
return MuseDashFixedItem(name, ItemClassification.filler, filler, self.player)
|
return MuseDashFixedItem(name, ItemClassification.filler, filler, self.player)
|
||||||
|
|
||||||
trap = self.md_collection.vfx_trap_items.get(name)
|
trap = self.md_collection.trap_items.get(name)
|
||||||
if trap:
|
|
||||||
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
|
|
||||||
|
|
||||||
trap = self.md_collection.sfx_trap_items.get(name)
|
|
||||||
if trap:
|
if trap:
|
||||||
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
|
return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player)
|
||||||
|
|
||||||
|
@ -286,17 +283,11 @@ 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]:
|
||||||
sfx_traps_available = self.options.allow_just_as_planned_dlc_songs.value
|
full_trap_list = self.md_collection.trap_items.keys()
|
||||||
|
if self.md_collection.MUSE_PLUS_DLC not in self.options.dlc_packs.value:
|
||||||
|
full_trap_list = [trap for trap in full_trap_list if trap not in self.md_collection.sfx_trap_items]
|
||||||
|
|
||||||
trap_list = []
|
return [trap for trap in full_trap_list if trap in self.options.chosen_traps.value]
|
||||||
if self.options.available_trap_types.value & 1 != 0:
|
|
||||||
trap_list += self.md_collection.vfx_trap_items.keys()
|
|
||||||
|
|
||||||
# SFX options are only available under Just as Planned DLC.
|
|
||||||
if sfx_traps_available and self.options.available_trap_types.value & 2 != 0:
|
|
||||||
trap_list += self.md_collection.sfx_trap_items.keys()
|
|
||||||
|
|
||||||
return trap_list
|
|
||||||
|
|
||||||
def get_trap_count(self) -> int:
|
def get_trap_count(self) -> int:
|
||||||
multiplier = self.options.trap_count_percentage.value / 100.0
|
multiplier = self.options.trap_count_percentage.value / 100.0
|
||||||
|
|
|
@ -9,25 +9,26 @@ class CollectionsTest(unittest.TestCase):
|
||||||
for name in collection.song_items.keys():
|
for name in collection.song_items.keys():
|
||||||
for c in name:
|
for c in name:
|
||||||
# This is taken directly from OoT. Represents the generally excepted characters.
|
# This is taken directly from OoT. Represents the generally excepted characters.
|
||||||
if (0x20 <= ord(c) < 0x7e):
|
if 0x20 <= ord(c) < 0x7e:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bad_names.append(name)
|
bad_names.append(name)
|
||||||
break
|
break
|
||||||
|
|
||||||
self.assertEqual(len(bad_names), 0, f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}")
|
self.assertEqual(len(bad_names), 0,
|
||||||
|
f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}")
|
||||||
|
|
||||||
def test_ids_dont_change(self) -> None:
|
def test_ids_dont_change(self) -> None:
|
||||||
collection = MuseDashCollections()
|
collection = MuseDashCollections()
|
||||||
itemsBefore = {name: code for name, code in collection.item_names_to_id.items()}
|
items_before = {name: code for name, code in collection.item_names_to_id.items()}
|
||||||
locationsBefore = {name: code for name, code in collection.location_names_to_id.items()}
|
locations_before = {name: code for name, code in collection.location_names_to_id.items()}
|
||||||
|
|
||||||
collection.__init__()
|
collection.__init__()
|
||||||
itemsAfter = {name: code for name, code in collection.item_names_to_id.items()}
|
items_after = {name: code for name, code in collection.item_names_to_id.items()}
|
||||||
locationsAfter = {name: code for name, code in collection.location_names_to_id.items()}
|
locations_after = {name: code for name, code in collection.location_names_to_id.items()}
|
||||||
|
|
||||||
self.assertDictEqual(itemsBefore, itemsAfter, "Item ID changed after secondary init.")
|
self.assertDictEqual(items_before, items_after, "Item ID changed after secondary init.")
|
||||||
self.assertDictEqual(locationsBefore, locationsAfter, "Location ID changed after secondary init.")
|
self.assertDictEqual(locations_before, locations_after, "Location ID changed after secondary init.")
|
||||||
|
|
||||||
def test_free_dlc_included_in_base_songs(self) -> None:
|
def test_free_dlc_included_in_base_songs(self) -> None:
|
||||||
collection = MuseDashCollections()
|
collection = MuseDashCollections()
|
||||||
|
|
|
@ -3,29 +3,29 @@ 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.get_world()
|
||||||
dlc_set = {x for x in muse_dash_world.md_collection.DLC}
|
dlc_set = {x for x in muse_dash_world.md_collection.DLC}
|
||||||
difficulty_choice = muse_dash_world.options.song_difficulty_mode
|
difficulty_choice = muse_dash_world.options.song_difficulty_mode
|
||||||
difficulty_min = muse_dash_world.options.song_difficulty_min
|
difficulty_min = muse_dash_world.options.song_difficulty_min
|
||||||
difficulty_max = muse_dash_world.options.song_difficulty_max
|
difficulty_max = muse_dash_world.options.song_difficulty_max
|
||||||
|
|
||||||
def test_range(inputRange, lower, upper):
|
def test_range(input_range, lower, upper):
|
||||||
self.assertEqual(inputRange[0], lower)
|
self.assertEqual(input_range[0], lower)
|
||||||
self.assertEqual(inputRange[1], upper)
|
self.assertEqual(input_range[1], upper)
|
||||||
|
|
||||||
songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, inputRange[0], inputRange[1])
|
songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, False, input_range[0], input_range[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 input_range[0] <= song.easy <= input_range[1]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (song.hard is not None and inputRange[0] <= song.hard <= inputRange[1]):
|
if song.hard is not None and input_range[0] <= song.hard <= input_range[1]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (song.master is not None and inputRange[0] <= song.master <= inputRange[1]):
|
if song.master is not None and input_range[0] <= song.master <= input_range[1]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.fail(f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'")
|
self.fail(f"Invalid song '{songKey}' was given for range '{input_range[0]} to {input_range[1]}'")
|
||||||
|
|
||||||
# auto ranges
|
# auto ranges
|
||||||
difficulty_choice.value = 0
|
difficulty_choice.value = 0
|
||||||
|
@ -61,7 +61,7 @@ class DifficultyRanges(MuseDashTestBase):
|
||||||
test_range(muse_dash_world.get_difficulty_range(), 4, 6)
|
test_range(muse_dash_world.get_difficulty_range(), 4, 6)
|
||||||
|
|
||||||
def test_songs_have_difficulty(self) -> None:
|
def test_songs_have_difficulty(self) -> None:
|
||||||
muse_dash_world = self.multiworld.worlds[1]
|
muse_dash_world = self.get_world()
|
||||||
|
|
||||||
for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES:
|
for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES:
|
||||||
song = muse_dash_world.md_collection.song_items[song_name]
|
song = muse_dash_world.md_collection.song_items[song_name]
|
||||||
|
|
|
@ -4,7 +4,32 @@ from . import MuseDashTestBase
|
||||||
class TestPlandoSettings(MuseDashTestBase):
|
class TestPlandoSettings(MuseDashTestBase):
|
||||||
options = {
|
options = {
|
||||||
"additional_song_count": 15,
|
"additional_song_count": 15,
|
||||||
"allow_just_as_planned_dlc_songs": True,
|
"dlc_packs": {"Muse Plus"},
|
||||||
|
"include_songs": [
|
||||||
|
"Lunatic",
|
||||||
|
"Out of Sense",
|
||||||
|
"Magic Knight Girl",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_included_songs_didnt_grow_item_count(self) -> None:
|
||||||
|
muse_dash_world = self.get_world()
|
||||||
|
self.assertEqual(len(muse_dash_world.included_songs), 15, "Logical songs size grew when it shouldn't.")
|
||||||
|
|
||||||
|
def test_included_songs_plando(self) -> None:
|
||||||
|
muse_dash_world = self.get_world()
|
||||||
|
songs = muse_dash_world.included_songs.copy()
|
||||||
|
songs.append(muse_dash_world.victory_song_name)
|
||||||
|
|
||||||
|
self.assertIn("Lunatic", songs, "Logical songs is missing a plando song: Lunatic")
|
||||||
|
self.assertIn("Out of Sense", songs, "Logical songs is missing a plando song: Out of Sense")
|
||||||
|
self.assertIn("Magic Knight Girl", songs, "Logical songs is missing a plando song: Magic Knight Girl")
|
||||||
|
|
||||||
|
|
||||||
|
class TestFilteredPlandoSettings(MuseDashTestBase):
|
||||||
|
options = {
|
||||||
|
"additional_song_count": 15,
|
||||||
|
"dlc_packs": {"MSR Anthology"},
|
||||||
"include_songs": [
|
"include_songs": [
|
||||||
"Operation Blade",
|
"Operation Blade",
|
||||||
"Autumn Moods",
|
"Autumn Moods",
|
||||||
|
@ -13,15 +38,15 @@ class TestPlandoSettings(MuseDashTestBase):
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_included_songs_didnt_grow_item_count(self) -> None:
|
def test_included_songs_didnt_grow_item_count(self) -> None:
|
||||||
muse_dash_world = self.multiworld.worlds[1]
|
muse_dash_world = self.get_world()
|
||||||
self.assertEqual(len(muse_dash_world.included_songs), 15,
|
self.assertEqual(len(muse_dash_world.included_songs), 15, "Logical songs size grew when it shouldn't.")
|
||||||
f"Logical songs size grew when it shouldn't. Expected 15. Got {len(muse_dash_world.included_songs)}")
|
|
||||||
|
|
||||||
def test_included_songs_plando(self) -> None:
|
# Tests for excluding included songs when the right dlc isn't enabled
|
||||||
muse_dash_world = self.multiworld.worlds[1]
|
def test_filtered_included_songs_plando(self) -> None:
|
||||||
|
muse_dash_world = self.get_world()
|
||||||
songs = muse_dash_world.included_songs.copy()
|
songs = muse_dash_world.included_songs.copy()
|
||||||
songs.append(muse_dash_world.victory_song_name)
|
songs.append(muse_dash_world.victory_song_name)
|
||||||
|
|
||||||
self.assertIn("Operation Blade", songs, "Logical songs is missing a plando song: Operation Blade")
|
self.assertIn("Operation Blade", songs, "Logical songs is missing a plando song: Operation Blade")
|
||||||
self.assertIn("Autumn Moods", songs, "Logical songs is missing a plando song: Autumn Moods")
|
self.assertIn("Autumn Moods", songs, "Logical songs is missing a plando song: Autumn Moods")
|
||||||
self.assertIn("Fireflies", songs, "Logical songs is missing a plando song: Fireflies")
|
self.assertNotIn("Fireflies", songs, "Logical songs has added a filtered a plando song: Fireflies")
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
from . import MuseDashTestBase
|
||||||
|
|
||||||
|
|
||||||
|
class TestNoTraps(MuseDashTestBase):
|
||||||
|
def test_no_traps(self) -> None:
|
||||||
|
md_world = self.get_world()
|
||||||
|
md_world.options.chosen_traps.value.clear()
|
||||||
|
self.assertEqual(len(md_world.get_available_traps()), 0, "Got an available trap when we expected none.")
|
||||||
|
|
||||||
|
def test_all_traps(self) -> None:
|
||||||
|
md_world = self.get_world()
|
||||||
|
md_world.options.dlc_packs.value.add(md_world.md_collection.MUSE_PLUS_DLC)
|
||||||
|
|
||||||
|
for trap in md_world.md_collection.trap_items.keys():
|
||||||
|
md_world.options.chosen_traps.value.add(trap)
|
||||||
|
|
||||||
|
trap_count = len(md_world.get_available_traps())
|
||||||
|
true_count = len(md_world.md_collection.trap_items.keys())
|
||||||
|
|
||||||
|
self.assertEqual(trap_count, true_count, "Got a different amount of traps than what was expected.")
|
||||||
|
|
||||||
|
def test_exclude_sfx_traps(self) -> None:
|
||||||
|
md_world = self.get_world()
|
||||||
|
if "Muse Plus" in md_world.options.dlc_packs.value:
|
||||||
|
md_world.options.dlc_packs.value.remove("Muse Plus")
|
||||||
|
|
||||||
|
for trap in md_world.md_collection.trap_items.keys():
|
||||||
|
md_world.options.chosen_traps.value.add(trap)
|
||||||
|
|
||||||
|
trap_count = len(md_world.get_available_traps())
|
||||||
|
true_count = len(md_world.md_collection.trap_items.keys()) - len(md_world.md_collection.sfx_trap_items)
|
||||||
|
|
||||||
|
self.assertEqual(trap_count, true_count, "Got a different amount of traps than what was expected.")
|
|
@ -4,30 +4,33 @@ from . import MuseDashTestBase
|
||||||
# This ends up with only 25 valid songs that can be chosen.
|
# This ends up with only 25 valid songs that can be chosen.
|
||||||
# These tests ensure that this won't fail generation
|
# These tests ensure that this won't fail generation
|
||||||
|
|
||||||
|
|
||||||
class TestWorstCaseHighDifficulty(MuseDashTestBase):
|
class TestWorstCaseHighDifficulty(MuseDashTestBase):
|
||||||
options = {
|
options = {
|
||||||
"starting_song_count": 10,
|
"starting_song_count": 10,
|
||||||
"allow_just_as_planned_dlc_songs": False,
|
"dlc_packs": [],
|
||||||
"streamer_mode_enabled": True,
|
"streamer_mode_enabled": True,
|
||||||
"song_difficulty_mode": 6,
|
"song_difficulty_mode": 6,
|
||||||
"song_difficulty_min": 11,
|
"song_difficulty_min": 11,
|
||||||
"song_difficulty_max": 11,
|
"song_difficulty_max": 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestWorstCaseMidDifficulty(MuseDashTestBase):
|
class TestWorstCaseMidDifficulty(MuseDashTestBase):
|
||||||
options = {
|
options = {
|
||||||
"starting_song_count": 10,
|
"starting_song_count": 10,
|
||||||
"allow_just_as_planned_dlc_songs": False,
|
"dlc_packs": [],
|
||||||
"streamer_mode_enabled": True,
|
"streamer_mode_enabled": True,
|
||||||
"song_difficulty_mode": 6,
|
"song_difficulty_mode": 6,
|
||||||
"song_difficulty_min": 6,
|
"song_difficulty_min": 6,
|
||||||
"song_difficulty_max": 6,
|
"song_difficulty_max": 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestWorstCaseLowDifficulty(MuseDashTestBase):
|
class TestWorstCaseLowDifficulty(MuseDashTestBase):
|
||||||
options = {
|
options = {
|
||||||
"starting_song_count": 10,
|
"starting_song_count": 10,
|
||||||
"allow_just_as_planned_dlc_songs": False,
|
"dlc_packs": [],
|
||||||
"streamer_mode_enabled": True,
|
"streamer_mode_enabled": True,
|
||||||
"song_difficulty_mode": 6,
|
"song_difficulty_mode": 6,
|
||||||
"song_difficulty_min": 1,
|
"song_difficulty_min": 1,
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
from test.bases import WorldTestBase
|
from test.bases import WorldTestBase
|
||||||
|
from .. import MuseDashWorld
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
class MuseDashTestBase(WorldTestBase):
|
class MuseDashTestBase(WorldTestBase):
|
||||||
game = "Muse Dash"
|
game = "Muse Dash"
|
||||||
|
|
||||||
|
def get_world(self) -> MuseDashWorld:
|
||||||
|
return cast(MuseDashWorld, self.multiworld.worlds[1])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue