diff --git a/worlds/musedash/Items.py b/worlds/musedash/Items.py new file mode 100644 index 000000000000..84dd1c555df5 --- /dev/null +++ b/worlds/musedash/Items.py @@ -0,0 +1,34 @@ +from typing import NamedTuple, Optional, Union +from BaseClasses import Item, ItemClassification + + +class SongData(NamedTuple): + """Special data container to contain the metadata of each song to make filtering work.""" + + 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] + + +class AlbumData(NamedTuple): + """Special data container to contain the metadata of each album to make filtering work. Currently not used.""" + + code: Optional[int] + + +class MuseDashSongItem(Item): + game: str = "Muse Dash" + + def __init__(self, name: str, player: int, data: Union[SongData, AlbumData]) -> None: + super().__init__(name, ItemClassification.progression, data.code, player) + + +class MuseDashFixedItem(Item): + game: str = "Muse Dash" + + def __init__(self, name: str, classification: ItemClassification, code: Optional[int], player: int) -> None: + super().__init__(name, classification, code, player) diff --git a/worlds/musedash/Locations.py b/worlds/musedash/Locations.py new file mode 100644 index 000000000000..3b505df110c7 --- /dev/null +++ b/worlds/musedash/Locations.py @@ -0,0 +1,5 @@ +from BaseClasses import Location + + +class MuseDashLocation(Location): + game: str = "Muse Dash" diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py new file mode 100644 index 000000000000..2be14863299f --- /dev/null +++ b/worlds/musedash/MuseDashCollection.py @@ -0,0 +1,136 @@ +from .Items import SongData, AlbumData +from typing import Dict, List, Optional + + +def load_text_file(name: str) -> str: + import pkgutil + return pkgutil.get_data(__name__, name).decode() + + +class MuseDashCollections: + """Contains all the data of Muse Dash, loaded from MuseDashData.txt.""" + + MUSIC_SHEET_CODE: int + + FREE_ALBUMS = [ + "Default Music", + "Budget Is Burning: Nano Core", + "Budget is Burning Vol.1" + ] + + DIFF_OVERRIDES = [ + "MuseDash ka nanika hi", + "Rush-Hour", + "Find this Month's Featured Playlist", + "PeroPero in the Universe", + "CHAOS Glitch" + ] + + album_items: Dict[str, AlbumData] = {} + album_locations: Dict[str, int] = {} + song_items: Dict[str, SongData] = {} + 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, + } + + sfx_trap_items: Dict[str, int] = { + "Nyaa SFX Trap": 8, + "Error SFX Trap": 9, + } + + def __init__(self, start_item_id: int, items_per_location: int): + self.MUSIC_SHEET_CODE = start_item_id + + self.vfx_trap_items = {k: (v + start_item_id) for (k, v) in self.vfx_trap_items.items()} + self.sfx_trap_items = {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 + + full_file = load_text_file("MuseDashData.txt") + + 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) + 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 + steamer_mode = sections[3] == "True" + + if song_name in self.DIFF_OVERRIDES: + # Note: These difficulties may not actually be representative of these songs. + # The game does not provide these difficulties so they have to be filled in. + diff_of_easy = 4 + diff_of_hard = 7 + diff_of_master = 10 + else: + diff_of_easy = self.parse_song_difficulty(sections[4]) + diff_of_hard = self.parse_song_difficulty(sections[5]) + diff_of_master = self.parse_song_difficulty(sections[6]) + + self.song_items[song_name] = SongData(item_id_index, song_is_free, steamer_mode, + diff_of_easy, diff_of_hard, diff_of_master) + item_id_index += 1 + + 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 + + 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 + + def get_songs_with_settings(self, dlc_songs: bool, streamer_mode_active: bool, + diff_lower: int, diff_higher: int) -> List[str]: + """Gets a list of all songs that match the filter settings. Difficulty thresholds are inclusive.""" + filtered_list = [] + + for songKey, songData in self.song_items.items(): + if not dlc_songs and not songData.song_is_free: + continue + + if streamer_mode_active and not songData.streamer_mode: + continue + + if songData.easy is not None and diff_lower <= songData.easy <= diff_higher: + filtered_list.append(songKey) + continue + + if songData.hard is not None and diff_lower <= songData.hard <= diff_higher: + filtered_list.append(songKey) + continue + + if songData.master is not None and diff_lower <= songData.master <= diff_higher: + filtered_list.append(songKey) + continue + + return filtered_list + + def parse_song_difficulty(self, difficulty: str) -> Optional[int]: + """Attempts to parse the song difficulty.""" + if len(difficulty) <= 0 or difficulty == "?" or difficulty == "¿": + return None + + # Curse the 2023 april fools update. Used on 3rd Avenue. + if difficulty == "〇": + return 10 + + return int(difficulty) diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt new file mode 100644 index 000000000000..91a5bc795dca --- /dev/null +++ b/worlds/musedash/MuseDashData.txt @@ -0,0 +1,452 @@ +Magical Wonderland|0-48|Default Music|True|1|3|0| +Iyaiya|0-0|Default Music|True|1|4|0| +Wonderful Pain|0-2|Default Music|False|1|3|0| +Breaking Dawn|0-3|Default Music|True|2|4|0| +One-Way Subway|0-4|Default Music|True|1|4|0| +Frost Land|0-1|Default Music|False|1|3|6| +Heart-Pounding Flight|0-5|Default Music|True|2|5|0| +Pancake is Love|0-29|Default Music|True|2|4|7| +Shiguang Tuya|0-6|Default Music|True|2|5|0| +Evolution|0-37|Default Music|False|2|4|7| +Dolphin and Broadcast|0-7|Default Music|True|2|5|0| +Yuki no Shizuku Ame no Oto|0-8|Default Music|True|2|4|6| +Best One feat.tooko|0-43|Default Music|False|3|5|0| +Candy-coloured Love Theory|0-31|Default Music|False|2|4|6| +Night Wander|0-38|Default Music|False|3|5|7| +Dohna Dohna no Uta|0-46|Default Music|False|2|4|6| +Spring Carnival|0-9|Default Music|False|2|4|7| +DISCO NIGHT|0-30|Default Music|True|2|4|7| +Koi no Moonlight|0-49|Default Music|False|2|5|8| +Lian Ai Audio Navigation|0-10|Default Music|False|3|5|7| +Lights of Muse|0-11|Default Music|True|4|6|8|10 +midstream jam|0-12|Default Music|False|2|5|8| +Nihao|0-40|Default Music|False|3|5|7| +Confession|0-13|Default Music|False|3|5|8| +Galaxy Striker|0-32|Default Music|False|4|7|9| +Departure Road|0-14|Default Music|True|2|5|8| +Bass Telekinesis|0-15|Default Music|False|2|5|8| +Cage of Almeria|0-16|Default Music|True|3|5|7| +Ira|0-17|Default Music|True|4|6|8| +Blackest Luxury Car|0-18|Default Music|True|3|6|8| +Medicine of Sing|0-19|Default Music|False|3|6|8| +irregulyze|0-20|Default Music|True|3|6|8| +I don't care about Christmas though|0-47|Default Music|False|4|6|8| +Imaginary World|0-21|Default Music|True|4|6|8| +Dysthymia|0-22|Default Music|True|4|7|9| +From the New World|0-42|Default Music|False|2|5|7| +NISEGAO|0-33|Default Music|True|4|7|9| +Say! Fanfare!|0-44|Default Music|False|4|6|9| +Star Driver|0-34|Default Music|True|5|7|9| +Formation|0-23|Default Music|True|4|6|9| +Shinsou Masui|0-24|Default Music|True|4|6|10| +Mezame Eurythmics|0-50|Default Music|False|4|6|9| +Shenri Kuaira -repeat-|0-51|Default Music|False|5|7|9| +Latitude|0-25|Default Music|True|3|6|9| +Aqua Stars|0-39|Default Music|False|5|7|10| +Funkotsu Saishin Casino|0-26|Default Music|False|5|7|10| +Clock Room & Spiritual World|0-27|Default Music|True|4|6|9| +INTERNET OVERDOSE|0-52|Default Music|False|3|6|9| +Tu Hua|0-35|Default Music|True|4|7|9| +Mujinku-Vacuum|0-28|Default Music|False|5|7|11| +MilK|0-36|Default Music|False|5|7|9| +umpopoff|0-41|Default Music|False|0|?|0| +Mopemope|0-45|Default Music|False|4|7|9|11 +The Happycore Idol|43-0|Just as Planned Plus|True|2|5|7| +Amatsumikaboshi|43-1|Just as Planned Plus|True|4|6|8|10 +ARIGA THESIS|43-2|Just as Planned Plus|True|3|6|10| +Night of Nights|43-3|Just as Planned Plus|False|4|7|10| +#Psychedelic_Meguro_River|43-4|Just as Planned Plus|False|3|6|8| +can you feel it|43-5|Just as Planned Plus|False|4|6|8|9 +Midnight O'clock|43-6|Just as Planned Plus|True|3|6|8| +Rin|43-7|Just as Planned Plus|True|5|7|10| +Smile-mileS|43-8|Just as Planned Plus|False|6|8|10| +Believing and Being|43-9|Just as Planned Plus|True|4|6|9| +Catalyst|43-10|Just as Planned Plus|False|5|7|9| +don't!stop!eroero!|43-11|Just as Planned Plus|True|5|7|9| +pa pi pu pi pu pi pa|43-12|Just as Planned Plus|False|6|8|10| +Sand Maze|43-13|Just as Planned Plus|True|6|8|10|11 +Diffraction|43-14|Just as Planned Plus|True|5|8|10| +AKUMU|43-15|Just as Planned Plus|False|4|6|8| +Queen Aluett|43-16|Just as Planned Plus|True|7|9|11| +DROPS|43-17|Just as Planned Plus|False|2|5|8| +Frightfully-insane Flan-chan's frightful song|43-18|Just as Planned Plus|False|5|7|10| +snooze|43-19|Just as Planned Plus|False|5|7|10| +Kuishinbo Hacker feat.Kuishinbo Akachan|43-20|Just as Planned Plus|True|5|7|9| +Inu no outa|43-21|Just as Planned Plus|True|3|5|7| +Prism Fountain|43-22|Just as Planned Plus|True|7|9|11| +Gospel|43-23|Just as Planned Plus|False|4|6|10| +East Ai Li Lovely|62-0|Happy Otaku Pack Vol.17|False|2|4|7| +Mori Umi no Fune|62-1|Happy Otaku Pack Vol.17|True|5|7|9| +Ooi|62-2|Happy Otaku Pack Vol.17|True|5|7|10| +Numatta!!|62-3|Happy Otaku Pack Vol.17|True|5|7|9| +SATELLITE|62-4|Happy Otaku Pack Vol.17|False|5|7|9| +Fantasia Sonata Colorful feat. V!C|62-5|Happy Otaku Pack Vol.17|True|6|8|11| +MuseDash ka nanika hi|61-0|Ola Dash|True|?|?|¿| +Aleph-0|61-1|Ola Dash|True|7|9|11| +Buttoba Supernova|61-2|Ola Dash|False|5|7|10|11 +Rush-Hour|61-3|Ola Dash|False|IG|Jh|a2|Eh +3rd Avenue|61-4|Ola Dash|False|3|5|〇| +WORLDINVADER|61-5|Ola Dash|True|5|8|10| +N3V3R G3T OV3R|60-0|maimai DX Limited-time Suite|True|4|7|10| +Oshama Scramble!|60-1|maimai DX Limited-time Suite|True|5|7|10| +Valsqotch|60-2|maimai DX Limited-time Suite|True|5|9|11| +Paranormal My Mind|60-3|maimai DX Limited-time Suite|True|5|7|9| +Flower, snow and Drum'n'bass.|60-4|maimai DX Limited-time Suite|True|5|8|10|? +Amenohoakari|60-5|maimai DX Limited-time Suite|True|6|8|10| +Boiling Blood|59-0|MSR Anthology|True|5|8|10| +ManiFesto|59-1|MSR Anthology|True|4|6|9| +Operation Blade|59-2|MSR Anthology|True|3|5|7| +Radiant|59-3|MSR Anthology|True|3|5|8| +Renegade|59-4|MSR Anthology|True|3|5|8| +Speed of Light|59-5|MSR Anthology|False|1|4|7| +Dossoles Holiday|59-6|MSR Anthology|True|5|7|9| +Autumn Moods|59-7|MSR Anthology|True|3|5|7| +People People|58-0|Nanahira Paradise|True|5|7|9|11 +Endless Error Loop|58-1|Nanahira Paradise|True|4|7|9| +Forbidden Pizza!|58-2|Nanahira Paradise|True|5|7|9| +Don't Make the Vocalist do Anything Insane|58-3|Nanahira Paradise|True|5|8|9| +Tokimeki*Meteostrike|57-0|Happy Otaku Pack Vol.16|True|3|6|8| +Down Low|57-1|Happy Otaku Pack Vol.16|True|4|6|8| +LOUDER MACHINE|57-2|Happy Otaku Pack Vol.16|True|5|7|9| +Sorewa mo Lovechu|57-3|Happy Otaku Pack Vol.16|True|5|7|10| +Rave_Tech|57-4|Happy Otaku Pack Vol.16|True|5|8|10| +Brilliant & Shining!|57-5|Happy Otaku Pack Vol.16|False|5|8|10| +Psyched Fevereiro|56-0|Give Up TREATMENT Vol.11|False|5|8|10| +Inferno City|56-1|Give Up TREATMENT Vol.11|False|6|8|10| +Paradigm Shift|56-2|Give Up TREATMENT Vol.11|False|4|7|10| +Snapdragon|56-3|Give Up TREATMENT Vol.11|False|5|7|10| +Prestige and Vestige|56-4|Give Up TREATMENT Vol.11|True|6|8|11| +Tiny Fate|56-5|Give Up TREATMENT Vol.11|False|7|9|11| +Tsuki ni Murakumo Hana ni Kaze|55-0|Touhou Mugakudan -2-|False|3|5|7| +Patchouli's - Best Hit GSK|55-1|Touhou Mugakudan -2-|False|3|5|8| +Monosugoi Space Shuttle de Koishi ga Monosugoi uta|55-2|Touhou Mugakudan -2-|False|3|5|7| +Kakoinaki Yo wa Ichigo no Tsukikage|55-3|Touhou Mugakudan -2-|False|3|6|8| +Psychedelic Kizakura Doumei|55-4|Touhou Mugakudan -2-|False|4|7|10| +Mischievous Sensation|55-5|Touhou Mugakudan -2-|False|5|7|9| +White Canvas|54-0|MEGAREX THE FUTURE|False|3|6|8| +Gloomy Flash|54-1|MEGAREX THE FUTURE|False|5|8|10| +Find this Month's Featured Playlist|54-2|MEGAREX THE FUTURE|False|?|?|¿| +Sunday Night|54-3|MEGAREX THE FUTURE|False|3|6|9| +Goodbye Goodnight|54-4|MEGAREX THE FUTURE|False|4|6|9| +ENDLESS CIDER|54-5|MEGAREX THE FUTURE|False|4|6|8| +On And On!!|53-0|Happy Otaku Pack Vol.15|True|4|7|9|11 +Trip!|53-1|Happy Otaku Pack Vol.15|True|3|5|7| +Hoshi no otoshimono|53-2|Happy Otaku Pack Vol.15|False|5|7|9| +Plucky Race|53-3|Happy Otaku Pack Vol.15|True|5|8|10|11 +Fantasia Sonata Destiny|53-4|Happy Otaku Pack Vol.15|True|3|7|10| +Run through|53-5|Happy Otaku Pack Vol.15|False|5|8|10| +marooned night|52-0|MUSE RADIO FM103|False|2|4|6| +daydream girl|52-1|MUSE RADIO FM103|False|3|6|8| +Not Ornament|52-2|MUSE RADIO FM103|True|3|5|8| +Baby Pink|52-3|MUSE RADIO FM103|False|3|5|8| +I'm Here|52-4|MUSE RADIO FM103|False|4|6|8| +Masquerade Diary|51-0|Virtual Idol Production|True|2|5|8| +Reminiscence|51-1|Virtual Idol Production|True|5|7|9| +DarakuDatenshi|51-2|Virtual Idol Production|True|3|6|9| +D.I.Y.|51-3|Virtual Idol Production|False|4|6|9| +Boys in Virtual Land|51-4|Virtual Idol Production|False|4|7|9| +kui|51-5|Virtual Idol Production|True|5|7|9|11 +Nyan Cat|50-0|Nyanya Universe!|False|4|7|9| +PeroPero in the Universe|50-1|Nyanya Universe!|True|?|?|¿| +In-kya Yo-kya Onmyoji|50-2|Nyanya Universe!|False|6|8|10| +KABOOOOOM!!!!|50-3|Nyanya Universe!|True|4|6|8| +Doppelganger|50-4|Nyanya Universe!|True|5|7|9|12 +Pray a LOVE|49-0|DokiDoki! Valentine!|False|2|5|8| +Love-Avoidance Addiction|49-1|DokiDoki! Valentine!|False|3|5|7| +Daisuki Dayo feat.Wotoha|49-2|DokiDoki! Valentine!|False|5|7|10| +glory day|48-0|DJMAX Reflect|False|2|5|7| +Bright Dream|48-1|DJMAX Reflect|False|2|4|7| +Groovin Up|48-2|DJMAX Reflect|False|4|6|8| +I Want You|48-3|DJMAX Reflect|False|3|6|8| +OBLIVION|48-4|DJMAX Reflect|False|3|6|9| +Elastic STAR|48-5|DJMAX Reflect|False|4|6|8| +U.A.D|48-6|DJMAX Reflect|False|4|6|8|10 +Jealousy|48-7|DJMAX Reflect|False|3|5|7| +Memory of Beach|48-8|DJMAX Reflect|False|3|6|8| +Don't Die|48-9|DJMAX Reflect|False|6|8|10| +Y CE Ver.|48-10|DJMAX Reflect|False|4|6|9| +Fancy Night|48-11|DJMAX Reflect|False|4|6|8| +Can We Talk|48-12|DJMAX Reflect|False|4|6|8| +Give Me 5|48-13|DJMAX Reflect|False|2|6|8| +Nightmare|48-14|DJMAX Reflect|False|7|9|11| +Haze of Autumn|47-0|Arcaea|True|3|6|9| +GIMME DA BLOOD|47-1|Arcaea|False|3|6|9| +Libertas|47-2|Arcaea|False|4|7|10| +Cyaegha|47-3|Arcaea|False|5|7|9|11 +Bang!!|46-0|Happy Otaku Pack Vol.14|False|4|6|8| +Paradise 2|46-1|Happy Otaku Pack Vol.14|False|4|6|8| +Symbol|46-2|Happy Otaku Pack Vol.14|False|5|7|9| +Nekojarashi|46-3|Happy Otaku Pack Vol.14|False|5|8|10|11 +A Philosophical Wanderer|46-4|Happy Otaku Pack Vol.14|False|4|6|10| +Isouten|46-5|Happy Otaku Pack Vol.14|True|6|8|10|11 +ONOMATO Pairing!!!|45-0|WACCA Horizon|False|4|6|9| +with U|45-1|WACCA Horizon|False|6|8|10|11 +Chariot|45-2|WACCA Horizon|False|3|6|9| +GASHATT|45-3|WACCA Horizon|False|5|7|10| +LIN NE KRO NE feat. lasah|45-4|WACCA Horizon|False|6|8|10| +ANGEL HALO|45-5|WACCA Horizon|False|5|8|11| +Party in the HOLLOWood|44-0|Happy Otaku Pack Vol.13|False|3|6|8| +Ying Ying da Zuozhan|44-1|Happy Otaku Pack Vol.13|True|5|7|9| +Howlin' Pumpkin|44-2|Happy Otaku Pack Vol.13|True|4|6|8| +Bad Apple!! feat. Nomico|42-0|Touhou Mugakudan -1-|False|1|3|6|8 +Iro wa Nioedo, Chirinuru wo|42-1|Touhou Mugakudan -1-|False|2|4|7| +Cirno's Perfect Math Class|42-2|Touhou Mugakudan -1-|False|4|7|9| +Hiiro Gekka Kyousai no Zetsu|42-3|Touhou Mugakudan -1-|False|4|6|8| +Flowery Moonlit Night|42-4|Touhou Mugakudan -1-|False|3|6|8| +Unconscious Requiem|42-5|Touhou Mugakudan -1-|False|3|6|8| +Super Battleworn Insomniac|41-0|7th Beat Games|True|4|7|9|? +Bomb-Sniffing Pomeranian|41-1|7th Beat Games|True|4|6|8| +Rollerdisco Rumble|41-2|7th Beat Games|True|4|6|9| +Rose Garden|41-3|7th Beat Games|False|5|8|9| +EMOMOMO|41-4|7th Beat Games|True|4|7|10| +Heracles|41-5|7th Beat Games|False|6|8|10|? +Rush-More|40-0|Happy Otaku Pack Vol.12|False|4|7|9| +Kill My Fortune|40-1|Happy Otaku Pack Vol.12|False|5|7|10| +Yosari Tsukibotaru Suminoborite|40-2|Happy Otaku Pack Vol.12|False|5|7|9| +JUMP! HardCandy|40-3|Happy Otaku Pack Vol.12|False|3|6|8| +Hibari|40-4|Happy Otaku Pack Vol.12|False|3|5|8| +OCCHOCO-REST-LESS|40-5|Happy Otaku Pack Vol.12|True|4|7|9| +See-Saw Day|39-0|MUSE RADIO FM102|True|1|3|6| +happy hour|39-1|MUSE RADIO FM102|True|2|4|7| +Seikimatsu no Natsu|39-2|MUSE RADIO FM102|True|4|6|8| +twinkle night|39-3|MUSE RADIO FM102|False|3|6|8| +ARUYA HARERUYA|39-4|MUSE RADIO FM102|False|2|5|7| +Blush|39-5|MUSE RADIO FM102|False|2|4|7| +Naked Summer|39-6|MUSE RADIO FM102|True|4|6|8| +BLESS ME|39-7|MUSE RADIO FM102|True|2|5|7| +FM 17314 SUGAR RADIO|39-8|MUSE RADIO FM102|True|?|?|?| +NO ONE YES MAN|38-0|Phigros|False|5|7|9| +Snowfall, Merry Christmas|38-1|Phigros|False|5|8|10| +Igallta|38-2|Phigros|False|6|8|10|11 +Colored Glass|37-0|Cute Is Everything Vol.7|False|1|4|7| +Neonlights|37-1|Cute Is Everything Vol.7|False|4|7|9| +Hope for the flowers|37-2|Cute Is Everything Vol.7|False|4|7|9| +Seaside Cycling on May 30|37-3|Cute Is Everything Vol.7|False|3|6|8| +SKY HIGH|37-4|Cute Is Everything Vol.7|False|2|4|6| +Mousou Chu!!|37-5|Cute Is Everything Vol.7|False|4|7|8| +NightTheater|36-0|Give Up TREATMENT Vol.10|True|6|8|11| +Cutter|36-1|Give Up TREATMENT Vol.10|False|4|7|10| +bamboo|36-2|Give Up TREATMENT Vol.10|False|6|8|10|11 +enchanted love|36-3|Give Up TREATMENT Vol.10|False|2|6|9| +c.s.q.n.|36-4|Give Up TREATMENT Vol.10|False|5|8|11| +Booouncing!!|36-5|Give Up TREATMENT Vol.10|False|5|7|10| +PeroPeroGames goes Bankrupt|35-0|Happy Otaku Pack SP|True|6|8|10| +MARENOL|35-1|Happy Otaku Pack SP|False|4|7|10| +I am really good at Japanese style|35-2|Happy Otaku Pack SP|True|6|8|10| +Rush B|35-3|Happy Otaku Pack SP|True|4|7|9| +DataErr0r|35-4|Happy Otaku Pack SP|False|5|7|9|? +Burn|35-5|Happy Otaku Pack SP|True|4|7|9| +ALiVE|34-0|HARDCORE TANO*C|False|5|7|10| +BATTLE NO.1|34-1|HARDCORE TANO*C|False|5|8|10|11 +Cthugha|34-2|HARDCORE TANO*C|False|6|8|10|11 +TWINKLE*MAGIC|34-3|HARDCORE TANO*C|False|4|7|10|11 +Comet Coaster|34-4|HARDCORE TANO*C|False|6|8|10|11 +XODUS|34-5|HARDCORE TANO*C|False|7|9|11|12 +Fireflies|33-0|cyTus|True|1|4|7| +Light up my love!!|33-1|cyTus|True|3|5|7| +Happiness Breeze|33-2|cyTus|True|4|6|8|9 +Chrome VOX|33-3|cyTus|True|6|8|10|11 +CHAOS|33-4|cyTus|True|3|6|9| +Saika|33-5|cyTus|True|3|5|8| +Standby for Action|33-6|cyTus|True|4|6|8| +Hydrangea|33-7|cyTus|True|5|7|9| +Amenemhat|33-8|cyTus|True|6|8|10| +Santouka|33-9|cyTus|True|2|5|8| +HEXENNACHTROCK-katashihaya-|33-10|cyTus|True|4|8|10| +Blah!!|33-11|cyTus|True|5|8|11| +CHAOS Glitch|33-12|cyTus|True|0|?|0| +Preparara|32-0|Let's Do Bad Things Together|False|1|4|6| +Whatcha;Whatcha Doin'|32-1|Let's Do Bad Things Together|False|3|6|9| +Madara|32-2|Let's Do Bad Things Together|False|4|6|9| +pICARESq|32-3|Let's Do Bad Things Together|False|4|6|8| +Desastre|32-4|Let's Do Bad Things Together|False|4|6|8| +Shoot for the Moon|32-5|Let's Do Bad Things Together|False|2|5|8| +The 90's Decision|31-0|Happy Otaku Pack Vol.11|True|5|7|9| +Medusa|31-1|Happy Otaku Pack Vol.11|False|4|6|8|10 +Final Step!|31-2|Happy Otaku Pack Vol.11|False|5|7|10| +MAGENTA POTION|31-3|Happy Otaku Pack Vol.11|False|4|7|9| +Cross Ray|31-4|Happy Otaku Pack Vol.11|False|3|6|9| +Square Lake|31-5|Happy Otaku Pack Vol.11|True|6|8|9|11 +Girly Cupid|30-0|Cute Is Everything Vol.6|False|3|6|8| +sheep in the light|30-1|Cute Is Everything Vol.6|False|2|5|8| +Breaker city|30-2|Cute Is Everything Vol.6|False|4|6|9| +heterodoxy|30-3|Cute Is Everything Vol.6|False|4|6|8| +Computer Music Girl|30-4|Cute Is Everything Vol.6|False|3|5|7| +Focus Point|30-5|Cute Is Everything Vol.6|True|2|5|7| +Groove Prayer|29-0|Let' s GROOVE!|True|3|5|7| +FUJIN Rumble|29-1|Let' s GROOVE!|True|5|7|10|11 +Marry me, Nightmare|29-2|Let' s GROOVE!|False|6|8|11| +HG Makaizou Polyvinyl Shounen|29-3|Let' s GROOVE!|True|4|7|9|10 +Seizya no Ibuki|29-4|Let' s GROOVE!|True|6|8|10| +ouroboros -twin stroke of the end-|29-5|Let' s GROOVE!|True|4|6|9|12 +Heisha Onsha|28-0|Happy Otaku Pack Vol.10|False|4|6|8| +Ginevra|28-1|Happy Otaku Pack Vol.10|True|5|7|10|10 +Paracelestia|28-2|Happy Otaku Pack Vol.10|False|5|8|10| +un secret|28-3|Happy Otaku Pack Vol.10|False|2|4|6| +Good Life|28-4|Happy Otaku Pack Vol.10|False|4|6|8| +nini-nini-|28-5|Happy Otaku Pack Vol.10|False|4|7|9| +Can I friend you on Bassbook? lol|27-0|Nanahira Festival|False|3|6|8| +Gaming*Everything|27-1|Nanahira Festival|False|5|8|11| +Renji de haochi|27-2|Nanahira Festival|False|5|7|9| +You Make My Life 1UP|27-3|Nanahira Festival|False|4|6|8| +Newbies, Geeks, Internets|27-4|Nanahira Festival|False|6|8|10| +Onegai!Kon kon Oinarisama|27-5|Nanahira Festival|False|3|6|9| +Legend of Eastern Rabbit -SKY DEFENDER-|26-0|Give Up TREATMENT Vol.9|False|4|6|9| +ENERGY SYNERGY MATRIX|26-1|Give Up TREATMENT Vol.9|False|6|8|10| +Punai Punai Genso|26-2|Give Up TREATMENT Vol.9|False|2|7|11| +Better Graphic Animation|26-3|Give Up TREATMENT Vol.9|False|5|8|11| +Variant Cross|26-4|Give Up TREATMENT Vol.9|False|4|7|10| +Ultra Happy Miracle Bazoooooka!!|26-5|Give Up TREATMENT Vol.9|False|7|9|11| +tape/stop/night|25-0|MUSE RADIO FM101|True|3|5|7| +Pixel Galaxy|25-1|MUSE RADIO FM101|False|2|5|8| +Notice|25-2|MUSE RADIO FM101|False|4|7|10| +Strawberry Godzilla|25-3|MUSE RADIO FM101|True|2|5|7| +OKIMOCHI EXPRESSION|25-4|MUSE RADIO FM101|False|4|6|10| +Kimi to pool disco|25-5|MUSE RADIO FM101|False|4|6|8| +The Last Page|24-0|Happy Otaku Pack Vol.9|False|3|5|7| +IKAROS|24-1|Happy Otaku Pack Vol.9|False|4|7|10| +Tsukuyomi|24-2|Happy Otaku Pack Vol.9|False|3|6|9| +Future Stream|24-3|Happy Otaku Pack Vol.9|False|4|6|8| +FULi AUTO SHOOTER|24-4|Happy Otaku Pack Vol.9|True|4|7|9| +GOODFORTUNE|24-5|Happy Otaku Pack Vol.9|False|5|7|9| +The Dessert After Rain|23-0|Cute Is Everything Vol.5|True|2|4|6| +Confession Support Formula|23-1|Cute Is Everything Vol.5|False|3|5|7| +Omatsuri|23-2|Cute Is Everything Vol.5|False|1|3|6| +FUTUREPOP|23-3|Cute Is Everything Vol.5|True|2|5|7| +The Breeze|23-4|Cute Is Everything Vol.5|False|1|4|6| +I LOVE LETTUCE FRIED RICE!!|23-5|Cute Is Everything Vol.5|False|3|7|9| +The NightScape|22-0|Give Up TREATMENT Vol.8|False|4|7|9| +FREEDOM DiVE|22-1|Give Up TREATMENT Vol.8|False|6|8|10|12 +Phi|22-2|Give Up TREATMENT Vol.8|False|5|8|10| +Lueur de la nuit|22-3|Give Up TREATMENT Vol.8|False|6|8|11| +Creamy Sugary OVERDRIVE!!!|22-4|Give Up TREATMENT Vol.8|True|4|7|10| +Disorder|22-5|Give Up TREATMENT Vol.8|False|5|7|11| +Glimmer|21-0|Budget Is Burning: Nano Core|False|2|5|8| +EXIST|21-1|Budget Is Burning: Nano Core|False|3|5|8| +Irreplaceable|21-2|Budget Is Burning: Nano Core|False|4|6|8| +Moonlight Banquet|20-0|Happy Otaku Pack Vol.8|True|2|5|8| +Flashdance|20-1|Happy Otaku Pack Vol.8|False|3|6|9| +INFiNiTE ENERZY -Overdoze-|20-2|Happy Otaku Pack Vol.8|False|4|7|9|10 +One Way Street|20-3|Happy Otaku Pack Vol.8|False|3|6|10| +This Club is Not 4 U|20-4|Happy Otaku Pack Vol.8|False|4|7|9| +ULTRA MEGA HAPPY PARTY!!!|20-5|Happy Otaku Pack Vol.8|False|5|7|10| +INFINITY|19-0|Give Up TREATMENT Vol.7|True|5|8|10| +Punai Punai Senso|19-1|Give Up TREATMENT Vol.7|False|2|7|11| +Maxi|19-2|Give Up TREATMENT Vol.7|False|5|8|10| +YInMn Blue|19-3|Give Up TREATMENT Vol.7|False|6|8|10| +Plumage|19-4|Give Up TREATMENT Vol.7|False|4|7|10| +Dr.Techro|19-5|Give Up TREATMENT Vol.7|False|7|9|11| +SWEETSWEETSWEET|18-0|Cute Is Everything Vol.4|True|2|5|7| +Deep Blue and the Breaths of the Night|18-1|Cute Is Everything Vol.4|True|2|4|6| +Joy Connection|18-2|Cute Is Everything Vol.4|False|3|6|8| +Self Willed Girl Ver.B|18-3|Cute Is Everything Vol.4|True|4|6|8| +Just Disobedient|18-4|Cute Is Everything Vol.4|False|3|6|8| +Holy Sh*t Grass Snake|18-5|Cute Is Everything Vol.4|False|2|6|9| +Cotton Candy Wonderland|17-0|Happy Otaku Pack Vol.7|False|2|5|8| +Punai Punai Taiso|17-1|Happy Otaku Pack Vol.7|False|2|7|10| +Fly High|17-2|Happy Otaku Pack Vol.7|False|3|5|7| +prejudice|17-3|Happy Otaku Pack Vol.7|True|4|6|9| +The 89's Momentum|17-4|Happy Otaku Pack Vol.7|True|5|7|9| +energy night|17-5|Happy Otaku Pack Vol.7|True|5|7|10| +Future Dive|16-0|Give Up TREATMENT Vol.6|True|4|6|9| +Re End of a Dream|16-1|Give Up TREATMENT Vol.6|False|5|8|11| +Etude -Storm-|16-2|Give Up TREATMENT Vol.6|True|6|8|10| +Unlimited Katharsis|16-3|Give Up TREATMENT Vol.6|False|4|6|10| +Magic Knight Girl|16-4|Give Up TREATMENT Vol.6|False|4|7|9| +Eeliaas|16-5|Give Up TREATMENT Vol.6|True|6|9|11| +Magic Spell|15-0|Cute Is Everything Vol.3|True|2|5|7| +Colorful Star, Colored Drawing, Travel Poem|15-1|Cute Is Everything Vol.3|False|3|4|6| +Satell Knight|15-2|Cute Is Everything Vol.3|False|3|6|8| +Black River Feat.Mes|15-3|Cute Is Everything Vol.3|True|1|4|6| +I am sorry|15-4|Cute Is Everything Vol.3|False|2|5|8| +Ueta Tori Tachi|15-5|Cute Is Everything Vol.3|False|3|6|8| +Elysion's Old Mans|14-0|Happy Otaku Pack Vol.6|False|3|5|8| +AXION|14-1|Happy Otaku Pack Vol.6|False|4|5|8| +Amnesia|14-2|Happy Otaku Pack Vol.6|True|3|6|9| +Onsen Dai Sakusen|14-3|Happy Otaku Pack Vol.6|True|4|6|8| +Gleam stone|14-4|Happy Otaku Pack Vol.6|False|4|7|9| +GOODWORLD|14-5|Happy Otaku Pack Vol.6|False|4|7|10| +Instant Soluble Neon|13-0|Cute Is Everything Vol.2|True|2|4|7| +Retrospective Poem on the Planet|13-1|Cute Is Everything Vol.2|False|3|5|7| +I'm Gonna Buy! Buy! Buy!|13-2|Cute Is Everything Vol.2|True|4|6|8| +Dating Manifesto|13-3|Cute Is Everything Vol.2|True|2|4|6| +First Snow|13-4|Cute Is Everything Vol.2|True|2|3|6| +Xin Shang Huahai|13-5|Cute Is Everything Vol.2|False|3|6|8| +Gaikan Chrysalis|12-0|Give Up TREATMENT Vol.5|False|4|6|8| +Sterelogue|12-1|Give Up TREATMENT Vol.5|True|5|7|10| +Cheshire's Dance|12-2|Give Up TREATMENT Vol.5|True|4|7|10| +Skrik|12-3|Give Up TREATMENT Vol.5|True|5|7|11| +Soda Pop Canva5!|12-4|Give Up TREATMENT Vol.5|False|5|8|10| +RUBY LINTe|12-5|Give Up TREATMENT Vol.5|False|5|8|11| +Brave My Heart|11-0|Happy Otaku Pack Vol.5|True|3|5|7| +Sakura Fubuki|11-1|Happy Otaku Pack Vol.5|False|4|7|10| +8bit Adventurer|11-2|Happy Otaku Pack Vol.5|False|6|8|10| +Suffering of screw|11-3|Happy Otaku Pack Vol.5|False|3|5|8| +tiny lady|11-4|Happy Otaku Pack Vol.5|True|4|6|9| +Power Attack|11-5|Happy Otaku Pack Vol.5|False|5|7|10| +Destr0yer|10-0|Give Up TREATMENT Vol.4|False|4|7|9| +Noel|10-1|Give Up TREATMENT Vol.4|False|5|8|10| +Kyoukiranbu|10-2|Give Up TREATMENT Vol.4|False|7|9|11| +Two Phace|10-3|Give Up TREATMENT Vol.4|True|4|7|10| +Fly Again|10-4|Give Up TREATMENT Vol.4|False|5|7|10| +ouroVoros|10-5|Give Up TREATMENT Vol.4|False|7|9|11| +Leave It Alone|9-0|Happy Otaku Pack Vol.4|True|2|5|8| +Tsubasa no Oreta Tenshitachi no Requiem|9-1|Happy Otaku Pack Vol.4|False|4|7|9| +Chronomia|9-2|Happy Otaku Pack Vol.4|False|5|7|10| +Dandelion's Daydream|9-3|Happy Otaku Pack Vol.4|True|5|7|8| +Lorikeet Flat design|9-4|Happy Otaku Pack Vol.4|True|5|7|10| +GOODRAGE|9-5|Happy Otaku Pack Vol.4|False|6|9|11| +Altale|8-0|Give Up TREATMENT Vol.3|False|3|5|7| +Brain Power|8-1|Give Up TREATMENT Vol.3|False|4|7|10| +Berry Go!!|8-2|Give Up TREATMENT Vol.3|False|3|6|9| +Sweet* Witch* Girl*|8-3|Give Up TREATMENT Vol.3|False|6|8|10|? +trippers feeling!|8-4|Give Up TREATMENT Vol.3|True|5|7|9|11 +Lilith ambivalence lovers|8-5|Give Up TREATMENT Vol.3|False|5|8|10| +Brave My Soul|7-0|Give Up TREATMENT Vol.2|False|4|6|8| +Halcyon|7-1|Give Up TREATMENT Vol.2|False|4|7|10| +Crimson Nightingle|7-2|Give Up TREATMENT Vol.2|True|4|7|10| +Invader|7-3|Give Up TREATMENT Vol.2|True|3|7|11| +Lyrith|7-4|Give Up TREATMENT Vol.2|False|5|7|10| +GOODBOUNCE|7-5|Give Up TREATMENT Vol.2|False|4|6|9| +Out of Sense|6-0|Budget Is Burning Vol.1|False|3|5|8| +My Life Is For You|6-1|Budget Is Burning Vol.1|False|2|4|7| +Etude -Sunset-|6-2|Budget Is Burning Vol.1|True|5|7|9| +Goodbye Boss|6-3|Budget Is Burning Vol.1|False|4|6|8| +Stargazer|6-4|Budget Is Burning Vol.1|True|2|5|8|9 +Lys Tourbillon|6-5|Budget Is Burning Vol.1|True|4|6|8| +Thirty Million Persona|5-0|Happy Otaku Pack Vol.3|False|2|4|6| +conflict|5-1|Happy Otaku Pack Vol.3|False|2|6|9|10 +Enka Dance Music|5-2|Happy Otaku Pack Vol.3|False|3|5|7| +XING|5-3|Happy Otaku Pack Vol.3|True|4|6|8|9 +Amakakeru Soukyuu no Serenade|5-4|Happy Otaku Pack Vol.3|False|3|6|9| +Gift box|5-5|Happy Otaku Pack Vol.3|False|5|7|10| +MUSEDASH!!!!|4-0|Happy Otaku Pack Vol.2|False|2|6|9|0 +Imprinting|4-1|Happy Otaku Pack Vol.2|False|3|6|9|0 +Skyward|4-2|Happy Otaku Pack Vol.2|True|4|7|10|0 +La nuit de vif|4-3|Happy Otaku Pack Vol.2|True|2|5|8|0 +Bit-alize|4-4|Happy Otaku Pack Vol.2|False|3|6|8|0 +GOODTEK|4-5|Happy Otaku Pack Vol.2|False|4|6|9|? +Maharajah|3-0|Happy Otaku Pack Vol.1|False|1|3|6| +keep on running|3-1|Happy Otaku Pack Vol.1|False|5|7|9| +Kafig|3-2|Happy Otaku Pack Vol.1|True|4|6|8| +-+|3-3|Happy Otaku Pack Vol.1|True|4|6|8| +Tenri Kaku Jou|3-4|Happy Otaku Pack Vol.1|True|3|6|9| +Adjudicatorz-DanZai-|3-5|Happy Otaku Pack Vol.1|False|3|7|10| +Oriens|2-0|Give Up TREATMENT Vol.1|True|3|7|9| +PUPA|2-1|Give Up TREATMENT Vol.1|False|6|8|11| +Luna Express 2032|2-2|Give Up TREATMENT Vol.1|False|4|6|8| +Ukiyoe Yokochou|2-3|Give Up TREATMENT Vol.1|False|6|7|9| +Alice in Misanthrope|2-4|Give Up TREATMENT Vol.1|False|5|7|10| +GOODMEN|2-5|Give Up TREATMENT Vol.1|False|5|7|10| +Sunshine and Rainbow after August Rain|1-0|Cute Is Everything Vol.1|False|2|5|8| +Magical Number|1-1|Cute Is Everything Vol.1|False|2|5|8| +Dreaming Girl|1-2|Cute Is Everything Vol.1|False|2|5|6| +Daruma-san Fell Over|1-3|Cute Is Everything Vol.1|False|3|4|6| +Different|1-4|Cute Is Everything Vol.1|False|1|3|6| +The Future of the Phantom|1-5|Cute Is Everything Vol.1|False|1|3|5| +Doki Doki Jump!|63-0|MUSE RADIO FM104|True|3|5|7| +Centennial Streamers High|63-1|MUSE RADIO FM104|False|4|7|9| +Love Patrol|63-2|MUSE RADIO FM104|True|3|5|7| +Mahorova|63-3|MUSE RADIO FM104|True|3|5|8| +Yoru no machi|63-4|MUSE RADIO FM104|True|1|4|7| +INTERNET YAMERO|63-5|MUSE RADIO FM104|True|6|8|10| \ No newline at end of file diff --git a/worlds/musedash/Options.py b/worlds/musedash/Options.py new file mode 100644 index 000000000000..cc9f5d705684 --- /dev/null +++ b/worlds/musedash/Options.py @@ -0,0 +1,179 @@ +from typing import Dict +from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet + + +class AllowJustAsPlannedDLCSongs(Toggle): + """Whether 'Just as Planned DLC' songs, and all the DLCs along with it, will be included in the randomizer.""" + display_name = "Allow Just As Planned DLC Songs" + + +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.""" + display_name = "Streamer Mode Only Songs" + + +class StartingSongs(Range): + """The number of songs that will be automatically unlocked at the start of a run.""" + range_start = 3 + range_end = 10 + default = 5 + display_name = "Starting Song Count" + + +class AdditionalSongs(Range): + """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. + """ + range_start = 15 + range_end = 500 # Note will probably not reach this high if any other settings are done. + default = 40 + display_name = "Additional Song Count" + + +class DifficultyMode(Choice): + """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 + - Hard: 6, 7 + - Expert: 8, 9 + - Master: 10+ + - Manual: Uses the provided minimum and maximum range. + """ + display_name = "Song Difficulty" + option_Any = 0 + option_Easy = 1 + option_Medium = 2 + option_Hard = 3 + option_Expert = 4 + option_Master = 5 + option_Manual = 6 + default = 0 + + +# 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.""" + display_name = "Manual Difficulty Min" + range_start = 1 + range_end = 11 + default = 4 + + +# 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.""" + display_name = "Manual Difficulty Max" + range_start = 1 + range_end = 11 + default = 8 + + +class GradeNeeded(Choice): + """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 + - A: >= 80% or a Full Combo + - B: >= 70% + - C: >= 60% + """ + display_name = "Grade Needed" + option_Any = 0 + option_C = 1 + option_B = 2 + option_A = 3 + option_PinkS = 4 + option_SilverS = 5 + default = 0 + + +class AdditionalItemPercentage(Range): + """The percentage of songs that will have 2 items instead of 1 when completing them. + - Starting Songs will always have 2 items. + - Locations will be filled with duplicate songs if there are not enough items. + """ + display_name = "Additional Item %" + range_start = 50 + default = 80 + range_end = 100 + + +class MusicSheetCountPercentage(Range): + """Collecting enough Music Sheets will unlock the goal song needed for completion. + This option controls how many are in the item pool, based on the total number of songs.""" + range_start = 10 + range_end = 40 + default = 20 + display_name = "Music Sheet Percentage" + + +class MusicSheetWinCountPercentage(Range): + """The percentage of Music Sheets in the item pool that are needed to unlock the winning song.""" + range_start = 50 + range_end = 100 + default = 80 + display_name = "Music Sheets Needed to Win" + + +class TrapTypes(Choice): + """This controls the types of traps that can be added to the pool. + - 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 with Just As Planned dlc songs. + """ + display_name = "Available Trap Types" + option_None = 0 + option_VFX = 1 + option_SFX = 2 + option_All = 3 + default = 3 + + +class TrapCountPercentage(Range): + """This controls how many traps to add into the pool, based the total number of songs.""" + range_start = 0 + range_end = 35 + default = 15 + display_name = "Trap Percentage" + + +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. + """ + 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.""" + verify_item_name = True + display_name = "Exclude Songs" + + +musedash_options: Dict[str, type(Option)] = { + "allow_just_as_planned_dlc_songs": AllowJustAsPlannedDLCSongs, + "streamer_mode_enabled": StreamerModeEnabled, + "starting_song_count": StartingSongs, + "additional_song_count": AdditionalSongs, + "additional_item_percentage": AdditionalItemPercentage, + "song_difficulty_mode": DifficultyMode, + "song_difficulty_min": DifficultyModeOverrideMin, + "song_difficulty_max": DifficultyModeOverrideMax, + "grade_needed": GradeNeeded, + "music_sheet_count_percentage": MusicSheetCountPercentage, + "music_sheet_win_count_percentage": MusicSheetWinCountPercentage, + "available_trap_types": TrapTypes, + "trap_count_percentage": TrapCountPercentage, + "death_link": DeathLink, + "include_songs": IncludeSongs, + "exclude_songs": ExcludeSongs +} diff --git a/worlds/musedash/__init__.py b/worlds/musedash/__init__.py new file mode 100644 index 000000000000..b76b0f6a4d30 --- /dev/null +++ b/worlds/musedash/__init__.py @@ -0,0 +1,349 @@ +from worlds.AutoWorld import World, WebWorld +from worlds.generic.Rules import set_rule +from BaseClasses import Region, Item, ItemClassification, Entrance, Tutorial +from typing import List +from math import floor + +from .Options import musedash_options +from .Items import MuseDashSongItem, MuseDashFixedItem +from .Locations import MuseDashLocation +from .MuseDashCollection import MuseDashCollections + + +class MuseDashWebWorld(WebWorld): + theme = "partyTime" + + bug_report_page = "https://github.com/DeamonHunter/ArchipelagoMuseDash/issues" + setup_en = Tutorial( + "Mod Setup and Use Guide", + "A guide to setting up the Muse Dash Archipelago Mod on your computer.", + "English", + "setup_en.md", + "setup/en", + ["DeamonHunter"] + ) + + tutorials = [setup_en] + + +class MuseDashWorld(World): + """Muse Dash is a rhythm game where you hit objects to the beat of one of 400+ songs. + Play through a selection of randomly chosen songs, collecting music sheets + until you have enough to play and complete the goal song!""" + + # FUTURE OPTIONS + # - Album Rando. + # - Added items for characters/elfin/portraits. + # - Support for blacklisting/plando-ing certain songs. + + # World Options + game = "Muse Dash" + option_definitions = musedash_options + topology_present = False + data_version = 6 + web = MuseDashWebWorld() + + music_sheet_name: str = "Music Sheet" + + # Necessary Data + md_collection = MuseDashCollections(2900000, 2) + + item_name_to_id = { + name: data.code for name, data in md_collection.album_items.items() | md_collection.song_items.items() + } + item_name_to_id[music_sheet_name] = md_collection.MUSIC_SHEET_CODE + for item in md_collection.sfx_trap_items.items() | md_collection.vfx_trap_items.items(): + item_name_to_id[item[0]] = item[1] + + location_name_to_id = { + name: id for name, id in md_collection.album_locations.items() | md_collection.song_locations.items() + } + + # Working Data + victory_song_name: str = "" + starting_songs: List[str] + included_songs: List[str] + needed_token_count: int + location_count: int + + def generate_early(self): + dlc_songs = self.multiworld.allow_just_as_planned_dlc_songs[self.player] + streamer_mode = self.multiworld.streamer_mode_enabled[self.player] + (lower_diff_threshold, higher_diff_threshold) = self.get_difficulty_range() + + # The minimum amount of songs to make an ok rando would be Starting Songs + 10 interim songs + Goal song. + # - Interim songs being equal to max starting song count. + # Note: The worst settings still allow 25 songs (Streamer Mode + No DLC). + starter_song_count = self.multiworld.starting_song_count[self.player].value + + while True: + # In most cases this should only need to run once + available_song_keys = self.md_collection.get_songs_with_settings( + dlc_songs, streamer_mode, lower_diff_threshold, higher_diff_threshold) + + available_song_keys = self.handle_plando(available_song_keys) + + 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: + final_song_list = available_song_keys + break + + # If the above fails, we want to adjust the difficulty thresholds. + # Easier first, then harder + if lower_diff_threshold <= 1 and higher_diff_threshold >= 11: + raise Exception("Failed to find enough songs, even with maximum difficulty thresholds.") + elif lower_diff_threshold <= 1: + higher_diff_threshold += 1 + else: + lower_diff_threshold -= 1 + + self.create_song_pool(final_song_list) + + 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]: + song_items = self.md_collection.song_items + + start_items = self.multiworld.start_inventory[self.player].value.keys() + include_songs = self.multiworld.include_songs[self.player].value + exclude_songs = self.multiworld.exclude_songs[self.player].value + + self.starting_songs = [s for s in start_items if s in song_items] + self.included_songs = [s for s in include_songs if s in song_items and s not in self.starting_songs] + + 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] + + def create_song_pool(self, available_song_keys: List[str]): + starting_song_count = self.multiworld.starting_song_count[self.player].value + additional_song_count = self.multiworld.additional_song_count[self.player].value + + self.multiworld.random.shuffle(available_song_keys) + + # First, we must double check if the player has included too many guaranteed songs + included_song_count = len(self.included_songs) + if included_song_count > additional_song_count: + # If so, we want to thin the list, thus let's get the goal song and starter songs while we are at it. + self.multiworld.random.shuffle(self.included_songs) + self.victory_song_name = self.included_songs.pop() + while len(self.included_songs) > additional_song_count: + next_song = self.included_songs.pop() + if len(self.starting_songs) < starting_song_count: + self.starting_songs.append(next_song) + else: + # If not, choose a random victory song from the available songs + chosen_song = self.multiworld.random.randrange(0, len(available_song_keys) + included_song_count) + if chosen_song < included_song_count: + self.victory_song_name = self.included_songs[chosen_song] + del self.included_songs[chosen_song] + else: + 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 + if len(self.starting_songs) < starting_song_count: + for _ in range(len(self.starting_songs), starting_song_count): + if len(available_song_keys) > 0: + self.starting_songs.append(available_song_keys.pop()) + else: + self.starting_songs.append(self.included_songs.pop()) + + # Then attempt to fufill any remaining songs for interim songs + if len(self.included_songs) < additional_song_count: + for _ in range(len(self.included_songs), self.multiworld.additional_song_count[self.player]): + if len(available_song_keys) <= 0: + break + self.included_songs.append(available_song_keys.pop()) + + self.location_count = len(self.starting_songs) + len(self.included_songs) + location_multiplier = 1 + (self.get_additional_item_percentage() / 100.0) + self.location_count = floor(self.location_count * location_multiplier) + + minimum_location_count = len(self.included_songs) + self.get_music_sheet_count() + if self.location_count < minimum_location_count: + self.location_count = minimum_location_count + + def create_item(self, name: str) -> Item: + if name == self.music_sheet_name: + return MuseDashFixedItem(name, ItemClassification.progression_skip_balancing, + self.md_collection.MUSIC_SHEET_CODE, 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) + 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) + + return MuseDashFixedItem(name, ItemClassification.filler, None, self.player) + + def create_items(self) -> None: + song_keys_in_pool = self.included_songs.copy() + + # Note: Item count will be off if plando is involved. + item_count = self.get_music_sheet_count() + + # First add all goal song tokens + for _ in range(0, item_count): + self.multiworld.itempool.append(self.create_item(self.music_sheet_name)) + + # Then add all traps + trap_count = self.get_trap_count() + trap_list = self.get_available_traps() + if len(trap_list) > 0 and trap_count > 0: + for _ in range(0, trap_count): + index = self.multiworld.random.randrange(0, len(trap_list)) + self.multiworld.itempool.append(self.create_item(trap_list[index])) + + item_count += trap_count + + # Next fill all remaining slots with song items + needed_item_count = self.location_count + while item_count < needed_item_count: + # If we have more items needed than keys, just iterate the list and add them all + if len(song_keys_in_pool) <= needed_item_count - item_count: + for key in song_keys_in_pool: + self.multiworld.itempool.append(self.create_item(key)) + + item_count += len(song_keys_in_pool) + continue + + # Otherwise add a random assortment of songs + self.multiworld.random.shuffle(song_keys_in_pool) + for i in range(0, needed_item_count - item_count): + self.multiworld.itempool.append(self.create_item(song_keys_in_pool[i])) + + item_count = needed_item_count + + def create_regions(self) -> None: + # Basic Region Setup: Menu -> Song Select -> Songs + menu_region = Region("Menu", self.player, self.multiworld) + song_select_region = Region("Song Select", self.player, self.multiworld) + + song_select_entrance = Entrance(self.player, "Song Select Entrance", menu_region) + + menu_region.exits.append(song_select_entrance) + song_select_entrance.connect(song_select_region) + + self.multiworld.regions.append(menu_region) + self.multiworld.regions.append(song_select_region) + + # Make a collection of all songs available for this rando. + # 1. All starting songs + # 2. All other songs shuffled + # Doing it in this order ensures that starting songs are first in line to getting 2 locations. + # Final song is excluded as for the purpose of this rando, it doesn't matter. + + all_selected_locations = self.starting_songs.copy() + included_song_copy = self.included_songs.copy() + + self.multiworld.random.shuffle(included_song_copy) + all_selected_locations.extend(included_song_copy) + + two_item_location_count = self.location_count - len(all_selected_locations) + + # Make a region per song/album, then adds 1-2 item locations to them + for i in range(0, len(all_selected_locations)): + name = all_selected_locations[i] + region = Region(name, self.player, self.multiworld) + + # 2 Locations are defined per song + location_name = name + "-0" + region.locations.append(MuseDashLocation(self.player, location_name, + self.md_collection.song_locations[location_name], region)) + + if i < two_item_location_count: + location_name = name + "-1" + region.locations.append(MuseDashLocation(self.player, location_name, + self.md_collection.song_locations[location_name], region)) + + region_exit = Entrance(self.player, name, song_select_region) + song_select_region.exits.append(region_exit) + region_exit.connect(region) + self.multiworld.regions.append(region) + + def set_rules(self) -> None: + self.multiworld.completion_condition[self.player] = lambda state: \ + state.has(self.music_sheet_name, self.player, self.get_music_sheet_win_count()) + + for location in self.multiworld.get_locations(self.player): + item_name = location.name[0:(len(location.name) - 2)] + if item_name == self.victory_song_name: + set_rule(location, lambda state: + state.has(self.music_sheet_name, self.player, self.get_music_sheet_win_count())) + else: + set_rule(location, lambda state, place=item_name: state.has(place, self.player)) + + def get_available_traps(self) -> List[str]: + dlc_songs = self.multiworld.allow_just_as_planned_dlc_songs[self.player] + + trap_list = [] + if self.multiworld.available_trap_types[self.player].value & 1 != 0: + trap_list += self.md_collection.vfx_trap_items.keys() + + # SFX options are only available under Just as Planned DLC. + if dlc_songs and self.multiworld.available_trap_types[self.player].value & 2 != 0: + trap_list += self.md_collection.sfx_trap_items.keys() + + return trap_list + + def get_additional_item_percentage(self) -> int: + trap_count = self.multiworld.trap_count_percentage[self.player].value + song_count = self.multiworld.music_sheet_count_percentage[self.player].value + return max(trap_count + song_count, self.multiworld.additional_item_percentage[self.player].value) + + def get_trap_count(self) -> int: + multiplier = self.multiworld.trap_count_percentage[self.player].value / 100.0 + trap_count = (len(self.starting_songs) * 2) + len(self.included_songs) + return max(0, floor(trap_count * multiplier)) + + def get_music_sheet_count(self) -> int: + multiplier = self.multiworld.music_sheet_count_percentage[self.player].value / 100.0 + song_count = (len(self.starting_songs) * 2) + len(self.included_songs) + return max(1, floor(song_count * multiplier)) + + def get_music_sheet_win_count(self) -> int: + multiplier = self.multiworld.music_sheet_win_count_percentage[self.player].value / 100.0 + sheet_count = self.get_music_sheet_count() + return max(1, floor(sheet_count * multiplier)) + + def get_difficulty_range(self) -> List[int]: + difficulty_mode = self.multiworld.song_difficulty_mode[self.player] + + # Valid difficulties are between 1 and 11. But make it 0 to 12 for safety + difficulty_bounds = [0, 12] + if difficulty_mode == 1: + difficulty_bounds[1] = 3 + elif difficulty_mode == 2: + difficulty_bounds[0] = 4 + difficulty_bounds[1] = 5 + elif difficulty_mode == 3: + difficulty_bounds[0] = 6 + difficulty_bounds[1] = 7 + elif difficulty_mode == 4: + difficulty_bounds[0] = 8 + difficulty_bounds[1] = 9 + elif difficulty_mode == 5: + difficulty_bounds[0] = 10 + elif difficulty_mode == 6: + minimum_difficulty = self.multiworld.song_difficulty_min[self.player].value + maximum_difficulty = self.multiworld.song_difficulty_max[self.player].value + + difficulty_bounds[0] = min(minimum_difficulty, maximum_difficulty) + difficulty_bounds[1] = max(minimum_difficulty, maximum_difficulty) + + return difficulty_bounds + + def fill_slot_data(self): + return { + "victoryLocation": self.victory_song_name, + "deathLink": self.multiworld.death_link[self.player].value, + "musicSheetWinCount": self.get_music_sheet_win_count(), + "gradeNeeded": self.multiworld.grade_needed[self.player].value + } diff --git a/worlds/musedash/docs/en_Muse Dash.md b/worlds/musedash/docs/en_Muse Dash.md new file mode 100644 index 000000000000..5f4673d256a8 --- /dev/null +++ b/worlds/musedash/docs/en_Muse Dash.md @@ -0,0 +1,27 @@ +# Muse Dash + +## Quick Links +- [Setup Guide](../../../tutorial/Muse%20Dash/setup/en) +- [Settings Page](../player-settings) + +## What Does Randomization do to this Game? +- You will be given a number of starting songs. The number of which depends on your settings. +- Completing any song will give you 1 or 2 rewards. +- The rewards for completing songs will range from songs to traps and **Music Sheets**. + +## What is the Goal of Muse Dash in Archipelago + +The goal of Muse Dash is to collect a number of **Music Sheets**. Once you've collected enough Music Sheets, the goal song will be unlocked. Completing the goal song will complete your seed. + +## What is Required to Play the Game in Archipelago? + +Only the base Muse Dash game is required in order to play this game. + +However, the **Just as Planned DLC** is recommended as the number of possible songs increases from 60+ to 400+ songs, which adds to the variety and increases replayability. + +## What Other Adjustments have been made to the Base Game? +- Several song select filters have been added to make finding songs to play easy. +- Selecting Random while playing a seed will give you a random song you haven't finished yet. +- Master difficulties are always unlocked while playing a seed. +- Hints are shown when viewing a song in song select. +- You can hint for songs and music sheets from song select. \ No newline at end of file diff --git a/worlds/musedash/docs/setup_en.md b/worlds/musedash/docs/setup_en.md new file mode 100644 index 000000000000..7ad701829735 --- /dev/null +++ b/worlds/musedash/docs/setup_en.md @@ -0,0 +1,48 @@ +# Muse Dash Randomizer Setup Guide + +## Quick Links +- [Main Page](../../../../games/Muse%20Dash/info/en) +- [Settings Page](../../../../games/Muse%20Dash/player-settings) + +## Required Software + +- Windows 8 or Newer. +- Muse Dash: [Available on Steam](https://store.steampowered.com/app/774171/Muse_Dash/) + - \[Optional\] Just As Planned DLC: [Also Available on Steam](https://store.steampowered.com/app/1055810/Muse_Dash__Just_as_planned/) +- Melon Loader: [GitHub](https://github.com/LavaGang/MelonLoader/releases/latest) + - .Net Framework 4.8 may be needed for the installer: [Download](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48) +- .Net 6.0 (If not already installed): [Download](https://dotnet.microsoft.com/en-us/download/dotnet/6.0#runtime-6.0.15) +- Muse Dash Archipelago Mod: [GitHub](https://github.com/DeamonHunter/ArchipelagoMuseDash/releases/latest) + +## Installing the Archipelago mod to Muse Dash + +1. Download [MelonLoader.Installer.exe](https://github.com/LavaGang/MelonLoader/releases/latest) and run it. +2. Choose the automated tab, click the select button and browse to `MuseDash.exe`. Then click install. + - You can find the folder in steam by finding the game in your library, right clicking it and choosing *Manage→Browse Local Files*. + - If you click the bar at the top telling you your current folder, this will give you a path you can copy. If you paste that into the window popped up by **MelonLoader**, it will automatically go to the same folder. +3. Run the game once, and wait until you get to the Muse Dash start screen before exiting. +4. Download the latest [Muse Dash Archipelago Mod](https://github.com/DeamonHunter/ArchipelagoMuseDash/releases/latest) and then extract that into the newly created `/Mods/` folder in MuseDash's install location. + - All files must be under the `/Mods/` folder and not within a sub folder inside of `/Mods/` + +If you've successfully installed everything, a button will appear in the bottom right which will allow you to log into an Archipelago server. + +## Generating a MultiWorld Game +1. Visit the [Player Settings](/games/Muse%20Dash/player-settings) page and configure the game-specific settings to your taste. +2. Export your yaml file and use it to generate a new randomized game + - (For instructions on how to generate an Archipelago game, refer to the [Archipelago Web Guide](/tutorial/Archipelago/setup/en)) + +## Joining a MultiWorld Game + +1. Launch Muse Dash and get past the intro screen. Click on the button in the bottom right. +2. Enter in the details for the archipelago game, such as the server address with port (e.g. archipelago.gg:38381), username and password. +3. If entered correctly, the pop-up should disappear and the usual main menu will show. When entering the song select, you should see a limited number of songs. + +## Troubleshooting + +### No Support Module Loaded + +This error occurs when Melon Loader cannot find needed files in order to run mods. There are generally two main sources of this error: a failure to generate the files when the game was first run with Melon Loader, or by a virus scanner is removing the files after generation. + +To fix this, first you should remove Melon Loader from Muse Dash. You can do this by deleting the Melon Loader folder within Muse Dash's folder. Afterwards you can follow the installation steps again. + +If you continue to run into issues, and are using a virus scanner, you may want to either temporarily turn it off when first running Muse Dash, or whitelist the Muse Dash folder. \ No newline at end of file diff --git a/worlds/musedash/test/TestDifficultyRanges.py b/worlds/musedash/test/TestDifficultyRanges.py new file mode 100644 index 000000000000..f43b67793520 --- /dev/null +++ b/worlds/musedash/test/TestDifficultyRanges.py @@ -0,0 +1,69 @@ +from . import MuseDashTestBase + + +class DifficultyRanges(MuseDashTestBase): + def test_all_difficulty_ranges(self) -> None: + muse_dash_world = self.multiworld.worlds[1] + difficulty_choice = self.multiworld.song_difficulty_mode[1] + difficulty_min = self.multiworld.song_difficulty_min[1] + 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}" + + songs = muse_dash_world.md_collection.get_songs_with_settings(True, False, inputRange[0], inputRange[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]): + continue + + if (song.hard is not None and inputRange[0] <= song.hard <= inputRange[1]): + continue + + 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]}'" + + #auto ranges + difficulty_choice.value = 0 + test_range(muse_dash_world.get_difficulty_range(), 0, 12) + difficulty_choice.value = 1 + test_range(muse_dash_world.get_difficulty_range(), 0, 3) + difficulty_choice.value = 2 + test_range(muse_dash_world.get_difficulty_range(), 4, 5) + difficulty_choice.value = 3 + test_range(muse_dash_world.get_difficulty_range(), 6, 7) + difficulty_choice.value = 4 + test_range(muse_dash_world.get_difficulty_range(), 8, 9) + difficulty_choice.value = 5 + test_range(muse_dash_world.get_difficulty_range(), 10, 12) + + # Test the Manual ranges + difficulty_choice.value = 6 + + difficulty_min.value = 1 + difficulty_max.value = 11 + test_range(muse_dash_world.get_difficulty_range(), 1, 11) + + difficulty_min.value = 1 + difficulty_max.value = 1 + test_range(muse_dash_world.get_difficulty_range(), 1, 1) + + difficulty_min.value = 11 + difficulty_max.value = 11 + test_range(muse_dash_world.get_difficulty_range(), 11, 11) + + difficulty_min.value = 4 + difficulty_max.value = 6 + test_range(muse_dash_world.get_difficulty_range(), 4, 6) + + def test_songs_have_difficulty(self) -> None: + muse_dash_world = self.multiworld.worlds[1] + + 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." diff --git a/worlds/musedash/test/TestNames.py b/worlds/musedash/test/TestNames.py new file mode 100644 index 000000000000..0629afc62a39 --- /dev/null +++ b/worlds/musedash/test/TestNames.py @@ -0,0 +1,18 @@ +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 new file mode 100644 index 000000000000..af2bb5f0971f --- /dev/null +++ b/worlds/musedash/test/TestPlandoSettings.py @@ -0,0 +1,25 @@ +from . import MuseDashTestBase + + +class TestIncludedSongSizeDoesntGrow(MuseDashTestBase): + options = { + "additional_song_count": 15, + "allow_just_as_planned_dlc_songs": True, + "include_songs": [ + "Operation Blade", + "Autumn Moods", + "Fireflies", + ] + } + + 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)}" + + def test_included_songs_plando(self) -> None: + muse_dash_world = self.multiworld.worlds[1] + + assert "Operation Blade" in muse_dash_world.included_songs, f"Logical songs is missing a plando song" + assert "Autumn Moods" in muse_dash_world.included_songs, f"Logical songs is missing a plando song" + assert "Fireflies" in muse_dash_world.included_songs, f"Logical songs is missing a plando song" \ No newline at end of file diff --git a/worlds/musedash/test/TestWorstCaseSettings.py b/worlds/musedash/test/TestWorstCaseSettings.py new file mode 100644 index 000000000000..eeedfa5c3a5f --- /dev/null +++ b/worlds/musedash/test/TestWorstCaseSettings.py @@ -0,0 +1,35 @@ +from . import MuseDashTestBase + +# The worst case settings are DLC songs off, and enabling streamer mode. +# 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, + "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, + "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, + "streamer_mode_enabled": True, + "song_difficulty_mode": 6, + "song_difficulty_min": 1, + "song_difficulty_max": 1, + } diff --git a/worlds/musedash/test/__init__.py b/worlds/musedash/test/__init__.py new file mode 100644 index 000000000000..818fd357cd97 --- /dev/null +++ b/worlds/musedash/test/__init__.py @@ -0,0 +1,5 @@ +from test.TestBase import WorldTestBase + + +class MuseDashTestBase(WorldTestBase): + game = "Muse Dash"