diff --git a/worlds/musedash/Items.py b/worlds/musedash/Items.py index 84dd1c55..be229228 100644 --- a/worlds/musedash/Items.py +++ b/worlds/musedash/Items.py @@ -8,10 +8,9 @@ class SongData(NamedTuple): code: Optional[int] song_is_free: bool streamer_mode: bool - easy: str = Optional[int] - hard: int = Optional[int] - master: int = Optional[int] - secret: int = Optional[int] + easy: Optional[int] + hard: Optional[int] + master: Optional[int] class AlbumData(NamedTuple): diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py index 66753935..7812e28b 100644 --- a/worlds/musedash/MuseDashCollection.py +++ b/worlds/musedash/MuseDashCollection.py @@ -10,21 +10,22 @@ def load_text_file(name: str) -> str: class MuseDashCollections: """Contains all the data of Muse Dash, loaded from MuseDashData.txt.""" + STARTING_CODE = 2900000 MUSIC_SHEET_NAME: str = "Music Sheet" - MUSIC_SHEET_CODE: int + MUSIC_SHEET_CODE: int = STARTING_CODE FREE_ALBUMS = [ "Default Music", "Budget Is Burning: Nano Core", - "Budget is Burning Vol.1" + "Budget Is Burning Vol.1", ] DIFF_OVERRIDES = [ "MuseDash ka nanika hi", "Rush-Hour", "Find this Month's Featured Playlist", - "PeroPero in the Universe" + "PeroPero in the Universe", ] album_items: Dict[str, AlbumData] = {} @@ -33,47 +34,43 @@ class MuseDashCollections: song_locations: Dict[str, int] = {} vfx_trap_items: Dict[str, int] = { - "Bad Apple Trap": 1, - "Pixelate Trap": 2, - "Random Wave Trap": 3, - "Shadow Edge Trap": 4, - "Chromatic Aberration Trap": 5, - "Background Freeze Trap": 6, - "Gray Scale Trap": 7, + "Bad Apple Trap": STARTING_CODE + 1, + "Pixelate Trap": STARTING_CODE + 2, + "Random Wave Trap": STARTING_CODE + 3, + "Shadow Edge Trap": STARTING_CODE + 4, + "Chromatic Aberration Trap": STARTING_CODE + 5, + "Background Freeze Trap": STARTING_CODE + 6, + "Gray Scale Trap": STARTING_CODE + 7, } sfx_trap_items: Dict[str, int] = { - "Nyaa SFX Trap": 8, - "Error SFX Trap": 9, + "Nyaa SFX Trap": STARTING_CODE + 8, + "Error SFX Trap": STARTING_CODE + 9, } item_names_to_id = ChainMap({}, sfx_trap_items, vfx_trap_items) location_names_to_id = ChainMap(song_locations, album_locations) - def __init__(self, start_item_id: int, items_per_location: int): - self.MUSIC_SHEET_CODE = start_item_id + def __init__(self) -> None: self.item_names_to_id[self.MUSIC_SHEET_NAME] = self.MUSIC_SHEET_CODE - self.vfx_trap_items.update({k: (v + start_item_id) for (k, v) in self.vfx_trap_items.items()}) - self.sfx_trap_items.update({k: (v + start_item_id) for (k, v) in self.sfx_trap_items.items()}) - - item_id_index = start_item_id + 50 - location_id_index = start_item_id - + item_id_index = self.STARTING_CODE + 50 full_file = load_text_file("MuseDashData.txt") - + seen_albums = set() for line in full_file.splitlines(): line = line.strip() sections = line.split("|") - if sections[2] not in self.album_items: - self.album_items[sections[2]] = AlbumData(item_id_index) + album = sections[2] + if album not in seen_albums: + seen_albums.add(album) + self.album_items[album] = AlbumData(item_id_index) item_id_index += 1 # Data is in the format 'Song|UID|Album|StreamerMode|EasyDiff|HardDiff|MasterDiff|SecretDiff' song_name = sections[0] # [1] is used in the client copy to make sure item id's match. - song_is_free = sections[2] in self.FREE_ALBUMS + song_is_free = album in self.FREE_ALBUMS steamer_mode = sections[3] == "True" if song_name in self.DIFF_OVERRIDES: @@ -94,17 +91,16 @@ class MuseDashCollections: self.item_names_to_id.update({name: data.code for name, data in self.song_items.items()}) self.item_names_to_id.update({name: data.code for name, data in self.album_items.items()}) + location_id_index = self.STARTING_CODE for name in self.album_items.keys(): - for i in range(0, items_per_location): - new_name = f"{name}-{i}" - self.album_locations[new_name] = location_id_index - location_id_index += 1 + self.album_locations[f"{name}-0"] = location_id_index + self.album_locations[f"{name}-1"] = location_id_index + 1 + location_id_index += 2 for name in self.song_items.keys(): - for i in range(0, items_per_location): - new_name = f"{name}-{i}" - self.song_locations[new_name] = location_id_index - location_id_index += 1 + self.song_locations[f"{name}-0"] = location_id_index + self.song_locations[f"{name}-1"] = location_id_index + 1 + location_id_index += 2 def get_songs_with_settings(self, dlc_songs: bool, streamer_mode_active: bool, diff_lower: int, diff_higher: int) -> List[str]: diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt index a7a5e67e..8d6c3f37 100644 --- a/worlds/musedash/MuseDashData.txt +++ b/worlds/musedash/MuseDashData.txt @@ -464,4 +464,8 @@ Songs Are Judged 90% by Chorus feat. Mameko|64-3|COSMIC RADIO PEROLIST|True|6|8| Kawai Splendid Space Thief|64-4|COSMIC RADIO PEROLIST|False|6|8|10|11 Night City Runway|64-5|COSMIC RADIO PEROLIST|True|4|6|8| Chaos Shotgun feat. ChumuNote|64-6|COSMIC RADIO PEROLIST|True|6|8|10| -mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11 \ No newline at end of file +mew mew magical summer|64-7|COSMIC RADIO PEROLIST|False|5|8|10|11 +BrainDance|65-0|Neon Abyss|True|3|6|9| +My Focus!|65-1|Neon Abyss|True|5|7|10| +ABABABA BURST|65-2|Neon Abyss|True|5|7|9| +ULTRA HIGHER|65-3|Neon Abyss|True|4|7|10| \ No newline at end of file diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py index f34142a2..78b9c253 100644 --- a/worlds/musedash/__init__.py +++ b/worlds/musedash/__init__.py @@ -40,14 +40,14 @@ class MuseDashWorld(World): game = "Muse Dash" option_definitions = musedash_options topology_present = False - data_version = 8 + data_version = 9 web = MuseDashWebWorld() # Necessary Data - md_collection = MuseDashCollections(2900000, 2) + md_collection = MuseDashCollections() - item_name_to_id = md_collection.item_names_to_id - location_name_to_id = md_collection.location_names_to_id + item_name_to_id = {name: code for name, code in md_collection.item_names_to_id.items()} + location_name_to_id = {name: code for name, code in md_collection.location_names_to_id.items()} # Working Data victory_song_name: str = "" @@ -167,11 +167,12 @@ class MuseDashWorld(World): if trap: return MuseDashFixedItem(name, ItemClassification.trap, trap, self.player) - song = self.md_collection.song_items.get(name) - if song: - return MuseDashSongItem(name, self.player, song) + album = self.md_collection.album_items.get(name) + if album: + return MuseDashSongItem(name, self.player, album) - return MuseDashFixedItem(name, ItemClassification.filler, None, self.player) + song = self.md_collection.song_items.get(name) + return MuseDashSongItem(name, self.player, song) def create_items(self) -> None: song_keys_in_pool = self.included_songs.copy() diff --git a/worlds/musedash/test/TestCollection.py b/worlds/musedash/test/TestCollection.py new file mode 100644 index 00000000..23348af1 --- /dev/null +++ b/worlds/musedash/test/TestCollection.py @@ -0,0 +1,49 @@ +import unittest +from ..MuseDashCollection import MuseDashCollections + + +class CollectionsTest(unittest.TestCase): + REMOVED_SONGS = [ + "CHAOS Glitch", + "FM 17314 SUGAR RADIO", + ] + + def test_all_names_are_ascii(self) -> None: + bad_names = list() + collection = MuseDashCollections() + 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): + 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}") + + 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()} + + 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()} + + self.assertDictEqual(itemsBefore, itemsAfter, "Item ID changed after secondary init.") + self.assertDictEqual(locationsBefore, locationsAfter, "Location ID changed after secondary init.") + + def test_free_dlc_included_in_base_songs(self) -> None: + collection = MuseDashCollections() + songs = collection.get_songs_with_settings(False, False, 0, 11) + + self.assertIn("Glimmer", songs, "Budget Is Burning Vol.1 is not being included in base songs") + self.assertIn("Out of Sense", songs, "Budget Is Burning: Nano Core is not being included in base songs") + + def test_remove_songs_are_not_generated(self) -> None: + collection = MuseDashCollections() + songs = collection.get_songs_with_settings(True, False, 0, 11) + + for song_name in self.REMOVED_SONGS: + self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.") diff --git a/worlds/musedash/test/TestDifficultyRanges.py b/worlds/musedash/test/TestDifficultyRanges.py index f43b6779..58817d0f 100644 --- a/worlds/musedash/test/TestDifficultyRanges.py +++ b/worlds/musedash/test/TestDifficultyRanges.py @@ -9,8 +9,8 @@ class DifficultyRanges(MuseDashTestBase): difficulty_max = self.multiworld.song_difficulty_max[1] def test_range(inputRange, lower, upper): - assert inputRange[0] == lower and inputRange[1] == upper, \ - f"Output incorrect. Got: {inputRange[0]} to {inputRange[1]}. Expected: {lower} to {upper}" + self.assertEqual(inputRange[0], lower) + self.assertEqual(inputRange[1], upper) songs = muse_dash_world.md_collection.get_songs_with_settings(True, False, inputRange[0], inputRange[1]) for songKey in songs: @@ -24,7 +24,7 @@ class DifficultyRanges(MuseDashTestBase): if (song.master is not None and inputRange[0] <= song.master <= inputRange[1]): continue - assert False, f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'" + self.fail(f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'") #auto ranges difficulty_choice.value = 0 @@ -65,5 +65,5 @@ class DifficultyRanges(MuseDashTestBase): for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES: song = muse_dash_world.md_collection.song_items[song_name] - assert 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." + 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.") diff --git a/worlds/musedash/test/TestNames.py b/worlds/musedash/test/TestNames.py deleted file mode 100644 index 0629afc6..00000000 --- a/worlds/musedash/test/TestNames.py +++ /dev/null @@ -1,18 +0,0 @@ -import unittest -from ..MuseDashCollection import MuseDashCollections - - -class NamesTest(unittest.TestCase): - def test_all_names_are_ascii(self) -> None: - bad_names = list() - collection = MuseDashCollections(0, 1) - 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): - continue - - bad_names.append(name) - break - - assert len(bad_names) == 0, f"Muse Dash has {len(bad_names)} songs with non-ASCII characters.\n{bad_names}" diff --git a/worlds/musedash/test/TestPlandoSettings.py b/worlds/musedash/test/TestPlandoSettings.py index a6bd8ad2..4b23a4af 100644 --- a/worlds/musedash/test/TestPlandoSettings.py +++ b/worlds/musedash/test/TestPlandoSettings.py @@ -1,7 +1,7 @@ from . import MuseDashTestBase -class TestIncludedSongSizeDoesntGrow(MuseDashTestBase): +class TestPlandoSettings(MuseDashTestBase): options = { "additional_song_count": 15, "allow_just_as_planned_dlc_songs": True, @@ -14,14 +14,14 @@ class TestIncludedSongSizeDoesntGrow(MuseDashTestBase): def test_included_songs_didnt_grow_item_count(self) -> None: muse_dash_world = self.multiworld.worlds[1] - assert 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)}" + 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)}") def test_included_songs_plando(self) -> None: muse_dash_world = self.multiworld.worlds[1] songs = muse_dash_world.included_songs.copy() songs.append(muse_dash_world.victory_song_name) - assert "Operation Blade" in songs, "Logical songs is missing a plando song: Operation Blade" - assert "Autumn Moods" in songs, "Logical songs is missing a plando song: Autumn Moods" - assert "Fireflies" in songs, "Logical songs is missing a plando song: Fireflies" \ No newline at end of file + 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 diff --git a/worlds/musedash/test/TestRemovedSongs.py b/worlds/musedash/test/TestRemovedSongs.py deleted file mode 100644 index 838c64b5..00000000 --- a/worlds/musedash/test/TestRemovedSongs.py +++ /dev/null @@ -1,25 +0,0 @@ -from . import MuseDashTestBase - - -class TestRemovedSongs(MuseDashTestBase): - options = { - "starting_song_count": 10, - "allow_just_as_planned_dlc_songs": True, - "additional_song_count": 500, - } - - removed_songs = [ - "CHAOS Glitch", - "FM 17314 SUGAR RADIO" - ] - - def test_remove_songs_are_not_generated(self) -> None: - # This test is done on a world where every song should be added. - muse_dash_world = self.multiworld.worlds[1] - - for song_name in self.removed_songs: - assert song_name not in muse_dash_world.starting_songs, \ - f"Song '{song_name}' was included into the starting songs when it shouldn't." - - assert song_name not in muse_dash_world.included_songs, \ - f"Song '{song_name}' was included into the included songs when it shouldn't."