From 32f367c60ab1b85ad0f24f215466c30ab8791379 Mon Sep 17 00:00:00 2001 From: Justus Lind Date: Wed, 5 Jun 2024 05:45:26 +1000 Subject: [PATCH] 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> --- worlds/musedash/MuseDashCollection.py | 30 +++-- worlds/musedash/Options.py | 109 ++++++++++++------ worlds/musedash/Presets.py | 6 +- worlds/musedash/__init__.py | 39 +++---- worlds/musedash/test/TestCollection.py | 17 +-- worlds/musedash/test/TestDifficultyRanges.py | 24 ++-- worlds/musedash/test/TestPlandoSettings.py | 39 +++++-- worlds/musedash/test/TestTrapOption.py | 33 ++++++ worlds/musedash/test/TestWorstCaseSettings.py | 9 +- worlds/musedash/test/__init__.py | 7 +- 10 files changed, 209 insertions(+), 104 deletions(-) create mode 100644 worlds/musedash/test/TestTrapOption.py diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py index 68e4ad5912bc..576a106df7cc 100644 --- a/worlds/musedash/MuseDashCollection.py +++ b/worlds/musedash/MuseDashCollection.py @@ -22,12 +22,15 @@ class MuseDashCollections: ] MUSE_PLUS_DLC: str = "Muse Plus" + + # Ordering matters for webhost. Order goes: Muse Plus, Time Limited Muse Plus Dlcs, Paid Dlcs 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 - "Rin Len's Mirrorland", # Paid DLC not included in Muse Plus - "MSR Anthology", # Now no longer available. + MUSE_PLUS_DLC, + "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 + "Rin Len's Mirrorland", # Paid DLC not included in Muse Plus ] DIFF_OVERRIDES: List[str] = [ @@ -50,7 +53,7 @@ class MuseDashCollections: song_items: Dict[str, SongData] = {} song_locations: Dict[str, int] = {} - vfx_trap_items: Dict[str, int] = { + trap_items: Dict[str, int] = { "Bad Apple Trap": STARTING_CODE + 1, "Pixelate Trap": STARTING_CODE + 2, "Ripple Trap": STARTING_CODE + 3, @@ -58,14 +61,16 @@ class MuseDashCollections: "Chromatic Aberration Trap": STARTING_CODE + 5, "Background Freeze Trap": STARTING_CODE + 6, "Gray Scale Trap": STARTING_CODE + 7, - "Focus Line Trap": STARTING_CODE + 10, - } - - sfx_trap_items: Dict[str, int] = { "Nyaa SFX Trap": STARTING_CODE + 8, "Error SFX Trap": STARTING_CODE + 9, + "Focus Line Trap": STARTING_CODE + 10, } + sfx_trap_items: List[str] = [ + "Nyaa SFX Trap", + "Error SFX Trap", + ] + filler_items: Dict[str, int] = { "Great To Perfect (10 Pack)": STARTING_CODE + 30, "Miss To Great (5 Pack)": STARTING_CODE + 31, @@ -78,7 +83,7 @@ class MuseDashCollections: "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) def __init__(self) -> None: @@ -171,6 +176,9 @@ def get_songs_with_settings(self, dlc_songs: Set[str], streamer_mode_active: boo 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: if song.album in self.FREE_ALBUMS: return True diff --git a/worlds/musedash/Options.py b/worlds/musedash/Options.py index 4f4f52ad2d2d..7164aa3e1362 100644 --- a/worlds/musedash/Options.py +++ b/worlds/musedash/Options.py @@ -1,26 +1,26 @@ -from typing import Dict -from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions +from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions, OptionGroup, Removed from dataclasses import dataclass 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): - """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" default = {} valid_keys = [dlc for dlc in MuseDashCollections.DLC] 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" @@ -33,7 +33,8 @@ class StartingSongs(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. - The final song count may be lower due to other settings. """ @@ -44,7 +45,8 @@ class AdditionalSongs(Range): 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 - Easy: 1, 2 or 3 - Medium: 4, 5 @@ -66,8 +68,11 @@ class DifficultyMode(Choice): # Todo: Investigate options to make this non randomizable 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" range_start = 1 range_end = 11 @@ -76,8 +81,11 @@ class DifficultyModeOverrideMin(Range): # Todo: Investigate options to make this non randomizable 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" range_start = 1 range_end = 11 @@ -85,7 +93,8 @@ class DifficultyModeOverrideMax(Range): 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: - Silver S (SS): >= 95% accuracy - Pink S (S): >= 90% accuracy @@ -104,7 +113,9 @@ class GradeNeeded(Choice): 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. """ range_start = 10 @@ -121,19 +132,18 @@ class MusicSheetWinCountPercentage(Range): display_name = "Music Sheets Needed to Win" -class TrapTypes(Choice): - """This controls the types of traps that can be added to the pool. +class ChosenTraps(OptionSet): + """ + 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.) - 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. """ - display_name = "Available Trap Types" - option_None = 0 - option_VFX = 1 - option_SFX = 2 - option_All = 3 - default = 3 + display_name = "Chosen Traps" + default = {} + valid_keys = {trap for trap in MuseDashCollections.trap_items.keys()} class TrapCountPercentage(Range): @@ -145,24 +155,49 @@ class TrapCountPercentage(Range): 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. - - If there being too many included songs, songs will be randomly chosen without regard for difficulty. - - If you want these songs immediately, use start_inventory instead. + """ + These songs will be guaranteed to show up within the seed. + - You must have the DLC enabled to play these songs. + - 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 display_name = "Include Songs" 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 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 class MuseDashOptions(PerGameCommonOptions): - allow_just_as_planned_dlc_songs: AllowJustAsPlannedDLCSongs dlc_packs: DLCMusicPacks streamer_mode_enabled: StreamerModeEnabled starting_song_count: StartingSongs @@ -173,8 +208,12 @@ class MuseDashOptions(PerGameCommonOptions): grade_needed: GradeNeeded music_sheet_count_percentage: MusicSheetCountPercentage music_sheet_win_count_percentage: MusicSheetWinCountPercentage - available_trap_types: TrapTypes + chosen_traps: ChosenTraps trap_count_percentage: TrapCountPercentage death_link: DeathLink include_songs: IncludeSongs exclude_songs: ExcludeSongs + + # Removed + allow_just_as_planned_dlc_songs: Removed + available_trap_types: Removed diff --git a/worlds/musedash/Presets.py b/worlds/musedash/Presets.py index 8dd8507d9b7f..fe314edbc9b5 100644 --- a/worlds/musedash/Presets.py +++ b/worlds/musedash/Presets.py @@ -3,7 +3,7 @@ MuseDashPresets: Dict[str, Dict[str, Any]] = { # An option to support Short Sync games. 40 songs. "No DLC - Short": { - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "starting_song_count": 5, "additional_song_count": 34, "music_sheet_count_percentage": 20, @@ -11,7 +11,7 @@ }, # An option to support Short Sync games but adds variety. 40 songs. "DLC - Short": { - "allow_just_as_planned_dlc_songs": True, + "dlc_packs": ["Muse Plus"], "starting_song_count": 5, "additional_song_count": 34, "music_sheet_count_percentage": 20, @@ -19,7 +19,7 @@ }, # An option to support Longer Sync/Async games. 100 songs. "DLC - Long": { - "allow_just_as_planned_dlc_songs": True, + "dlc_packs": ["Muse Plus"], "starting_song_count": 8, "additional_song_count": 91, "music_sheet_count_percentage": 20, diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py index 1c009bfaee45..a9eacbbcf82c 100644 --- a/worlds/musedash/__init__.py +++ b/worlds/musedash/__init__.py @@ -1,10 +1,10 @@ from worlds.AutoWorld import World, WebWorld -from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial -from typing import List, ClassVar, Type +from BaseClasses import Region, Item, ItemClassification, Tutorial +from typing import List, ClassVar, Type, Set from math import floor from Options import PerGameCommonOptions -from .Options import MuseDashOptions +from .Options import MuseDashOptions, md_option_groups from .Items import MuseDashSongItem, MuseDashFixedItem from .Locations import MuseDashLocation from .MuseDashCollection import MuseDashCollections @@ -35,6 +35,7 @@ class MuseDashWebWorld(WebWorld): tutorials = [setup_en, setup_es] options_presets = MuseDashPresets + option_groups = md_option_groups class MuseDashWorld(World): @@ -72,8 +73,6 @@ class MuseDashWorld(World): def generate_early(self): 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 (lower_diff_threshold, higher_diff_threshold) = self.get_difficulty_range() @@ -88,7 +87,7 @@ def generate_early(self): available_song_keys = self.md_collection.get_songs_with_settings( 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)) if len(available_song_keys) + len(self.included_songs) >= count_needed_for_start + 11: @@ -109,7 +108,7 @@ def generate_early(self): for song in self.starting_songs: 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 start_items = self.options.start_inventory.value.keys() @@ -117,7 +116,9 @@ def handle_plando(self, available_song_keys: List[str]) -> List[str]: exclude_songs = self.options.exclude_songs.value 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 = 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 and s not in include_songs and s not in exclude_songs] @@ -148,7 +149,7 @@ def create_song_pool(self, available_song_keys: List[str]): self.victory_song_name = 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: for _ in range(len(self.starting_songs), starting_song_count): if len(available_song_keys) > 0: @@ -156,7 +157,7 @@ def create_song_pool(self, available_song_keys: List[str]): else: 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: for _ in range(len(self.included_songs), self.options.additional_song_count): if len(available_song_keys) <= 0: @@ -174,11 +175,7 @@ def create_item(self, name: str) -> Item: if filler: return MuseDashFixedItem(name, ItemClassification.filler, filler, self.player) - trap = self.md_collection.vfx_trap_items.get(name) - if trap: - return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player) - - trap = self.md_collection.sfx_trap_items.get(name) + trap = self.md_collection.trap_items.get(name) if trap: return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player) @@ -286,17 +283,11 @@ def set_rules(self) -> None: state.has(self.md_collection.MUSIC_SHEET_NAME, self.player, self.get_music_sheet_win_count()) def get_available_traps(self) -> List[str]: - sfx_traps_available = self.options.allow_just_as_planned_dlc_songs.value - - trap_list = [] - 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() + 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] - return trap_list + return [trap for trap in full_trap_list if trap in self.options.chosen_traps.value] def get_trap_count(self) -> int: multiplier = self.options.trap_count_percentage.value / 100.0 diff --git a/worlds/musedash/test/TestCollection.py b/worlds/musedash/test/TestCollection.py index 48cb69e403ad..c8c2b39acb4d 100644 --- a/worlds/musedash/test/TestCollection.py +++ b/worlds/musedash/test/TestCollection.py @@ -9,25 +9,26 @@ def test_all_names_are_ascii(self) -> None: for name in collection.song_items.keys(): for c in name: # This is taken directly from OoT. Represents the generally excepted characters. - if (0x20 <= ord(c) < 0x7e): + if 0x20 <= ord(c) < 0x7e: continue bad_names.append(name) 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: collection = MuseDashCollections() - itemsBefore = {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()} + items_before = {name: code for name, code in collection.item_names_to_id.items()} + locations_before = {name: code for name, code in collection.location_names_to_id.items()} collection.__init__() - itemsAfter = {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()} + items_after = {name: code for name, code in collection.item_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(locationsBefore, locationsAfter, "Location ID changed after secondary init.") + self.assertDictEqual(items_before, items_after, "Item 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: collection = MuseDashCollections() diff --git a/worlds/musedash/test/TestDifficultyRanges.py b/worlds/musedash/test/TestDifficultyRanges.py index 89214d3f0f88..a9c36985afae 100644 --- a/worlds/musedash/test/TestDifficultyRanges.py +++ b/worlds/musedash/test/TestDifficultyRanges.py @@ -3,31 +3,31 @@ class DifficultyRanges(MuseDashTestBase): 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} difficulty_choice = muse_dash_world.options.song_difficulty_mode difficulty_min = muse_dash_world.options.song_difficulty_min difficulty_max = muse_dash_world.options.song_difficulty_max - def test_range(inputRange, lower, upper): - self.assertEqual(inputRange[0], lower) - self.assertEqual(inputRange[1], upper) + def test_range(input_range, lower, upper): + self.assertEqual(input_range[0], lower) + 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: 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 - 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 - 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 - 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 test_range(muse_dash_world.get_difficulty_range(), 0, 12) difficulty_choice.value = 1 @@ -61,7 +61,7 @@ def test_range(inputRange, lower, upper): test_range(muse_dash_world.get_difficulty_range(), 4, 6) 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: song = muse_dash_world.md_collection.song_items[song_name] @@ -73,4 +73,4 @@ def test_songs_have_difficulty(self) -> None: f"Song '{song_name}' difficulty not set when it should be.") else: self.assertTrue(song.easy is not None and song.hard is not None and song.master is not None, - f"Song '{song_name}' difficulty not set when it should be.") + f"Song '{song_name}' difficulty not set when it should be.") diff --git a/worlds/musedash/test/TestPlandoSettings.py b/worlds/musedash/test/TestPlandoSettings.py index 4b23a4afa90a..2617b7a4e02c 100644 --- a/worlds/musedash/test/TestPlandoSettings.py +++ b/worlds/musedash/test/TestPlandoSettings.py @@ -4,7 +4,32 @@ class TestPlandoSettings(MuseDashTestBase): options = { "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": [ "Operation Blade", "Autumn Moods", @@ -13,15 +38,15 @@ class TestPlandoSettings(MuseDashTestBase): } def test_included_songs_didnt_grow_item_count(self) -> None: - muse_dash_world = self.multiworld.worlds[1] - self.assertEqual(len(muse_dash_world.included_songs), 15, - f"Logical songs size grew when it shouldn't. Expected 15. Got {len(muse_dash_world.included_songs)}") + 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.multiworld.worlds[1] + # Tests for excluding included songs when the right dlc isn't enabled + def test_filtered_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("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("Fireflies", songs, "Logical songs is missing a plando song: Fireflies") \ No newline at end of file + self.assertNotIn("Fireflies", songs, "Logical songs has added a filtered a plando song: Fireflies") diff --git a/worlds/musedash/test/TestTrapOption.py b/worlds/musedash/test/TestTrapOption.py new file mode 100644 index 000000000000..ca0579c1f66c --- /dev/null +++ b/worlds/musedash/test/TestTrapOption.py @@ -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.") diff --git a/worlds/musedash/test/TestWorstCaseSettings.py b/worlds/musedash/test/TestWorstCaseSettings.py index eeedfa5c3a5f..fd39651d1203 100644 --- a/worlds/musedash/test/TestWorstCaseSettings.py +++ b/worlds/musedash/test/TestWorstCaseSettings.py @@ -4,30 +4,33 @@ # This ends up with only 25 valid songs that can be chosen. # These tests ensure that this won't fail generation + class TestWorstCaseHighDifficulty(MuseDashTestBase): options = { "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "streamer_mode_enabled": True, "song_difficulty_mode": 6, "song_difficulty_min": 11, "song_difficulty_max": 11, } + class TestWorstCaseMidDifficulty(MuseDashTestBase): options = { "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "streamer_mode_enabled": True, "song_difficulty_mode": 6, "song_difficulty_min": 6, "song_difficulty_max": 6, } + class TestWorstCaseLowDifficulty(MuseDashTestBase): options = { "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": False, + "dlc_packs": [], "streamer_mode_enabled": True, "song_difficulty_mode": 6, "song_difficulty_min": 1, diff --git a/worlds/musedash/test/__init__.py b/worlds/musedash/test/__init__.py index c77f9f6a06b8..ff9d988c65c2 100644 --- a/worlds/musedash/test/__init__.py +++ b/worlds/musedash/test/__init__.py @@ -1,5 +1,10 @@ from test.bases import WorldTestBase - +from .. import MuseDashWorld +from typing import cast class MuseDashTestBase(WorldTestBase): game = "Muse Dash" + + def get_world(self) -> MuseDashWorld: + return cast(MuseDashWorld, self.multiworld.worlds[1]) +