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:
Justus Lind 2024-06-05 05:45:26 +10:00 committed by GitHub
parent f30f2d3a3f
commit 133167564c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 209 additions and 104 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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