Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Muse Dash: Option Groups and Options Rework #3434

Merged
merged 18 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
252b603
Ensure that included/starter songs only include those within enabled …
DeamonHunter Jun 2, 2024
39c2a21
Allow filtering traps by trap instead of by category.
DeamonHunter Jun 2, 2024
badbbf3
Add in the currently available limited time dlcs to the dlc list.
DeamonHunter Jun 2, 2024
430e006
Add the option group to the webhost and cleanup some errors.
DeamonHunter Jun 2, 2024
417b059
Fix trap list.
DeamonHunter Jun 2, 2024
2524fbe
Update tests. Add new ones to test correctness of new features.
DeamonHunter Jun 2, 2024
f050fd7
Merge remote-tracking branch 'ArchipelagoMW/main' into Deamon/Options…
DeamonHunter Jun 3, 2024
2fa6939
Remove the old Just As Planned option
DeamonHunter Jun 3, 2024
0c83a47
Make traps order alphabetically. Also adjust the title for traps.
DeamonHunter Jun 3, 2024
f83703e
Adjust new lines to better fit the website.
DeamonHunter Jun 3, 2024
ca01da0
Style fixes.
DeamonHunter Jun 3, 2024
2cff656
Test adjustments and a fix due to test no longer having just as plann…
DeamonHunter Jun 3, 2024
0376e82
Undo spacing changes as it breaks yaml generation.
DeamonHunter Jun 3, 2024
85ff90f
Fix indenting in webhost.
DeamonHunter Jun 3, 2024
1c04a08
Add the old options in as removed. Also clean up unused import.
DeamonHunter Jun 3, 2024
278b7fe
Remove references to the old allow_just_as_planned_dlc_songs option i…
DeamonHunter Jun 3, 2024
dfb2d3c
Add newline to end of file.
DeamonHunter Jun 4, 2024
5c05d00
Merge branch 'main' into Deamon/OptionsUpdate
NewSoupVi Jun 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions worlds/musedash/MuseDashCollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
# MUSE_PLUS_DLC, # To be included once option sets can have preset values
"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
"MSR Anthology", # Now no longer available.
]

DIFF_OVERRIDES: List[str] = [
Expand All @@ -50,22 +53,24 @@ 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,
"Vignette Trap": STARTING_CODE + 4,
"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,
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
57 changes: 39 additions & 18 deletions worlds/musedash/Options.py
Copy link
Member

@Exempt-Medic Exempt-Medic Jun 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking that the way the tooltips display on the new WebHost are how you want them to display. Most games have changed their docstrings to display in a different format

Copy link
Collaborator Author

@DeamonHunter DeamonHunter Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reminding me. Its now set up so there isn't as much white space at the front of them. And added an extra line in front of the paragraphs so they read a bit easier.

Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from typing import Dict
from Options import Toggle, Option, Range, Choice, DeathLink, ItemSet, OptionSet, PerGameCommonOptions
from Options import Toggle, Range, Choice, DeathLink, ItemSet, OptionSet, OptionList, PerGameCommonOptions, OptionGroup, \
Visibility
from dataclasses import dataclass

from .MuseDashCollection import MuseDashCollections


# Note: Kept around because presets can't set values for sets yet.
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 that will be included in the pool of chooseable songs."""
display_name = "DLC Packs"
DeamonHunter marked this conversation as resolved.
Show resolved Hide resolved
default = {}
valid_keys = [dlc for dlc in MuseDashCollections.DLC]
Expand Down Expand Up @@ -121,19 +122,16 @@ 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 = "Trap Types"
default = {}
valid_keys = [trap for trap in MuseDashCollections.trap_items.keys()]

DeamonHunter marked this conversation as resolved.
Show resolved Hide resolved

class TrapCountPercentage(Range):
Expand All @@ -145,21 +143,44 @@ 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 being too many included songs, this will act as a whitelist ignoring song difficulty.
"""
DeamonHunter marked this conversation as resolved.
Show resolved Hide resolved
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", [
AllowJustAsPlannedDLCSongs,
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
Expand All @@ -173,7 +194,7 @@ 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
Expand Down
37 changes: 15 additions & 22 deletions worlds/musedash/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -35,6 +35,7 @@ class MuseDashWebWorld(WebWorld):

tutorials = [setup_en, setup_es]
options_presets = MuseDashPresets
option_groups = md_option_groups


class MuseDashWorld(World):
Expand Down Expand Up @@ -88,7 +89,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:
Expand All @@ -109,15 +110,17 @@ 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()
include_songs = self.options.include_songs.value
exclude_songs = self.options.exclude_songs.value

self.starting_songs = [s for s in start_items if s in song_items]
self.starting_songs = 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]
Expand Down Expand Up @@ -148,15 +151,15 @@ 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:
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
# 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:
Expand All @@ -174,11 +177,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)

Expand Down Expand Up @@ -286,17 +285,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 not self.options.allow_just_as_planned_dlc_songs.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
Expand Down
19 changes: 10 additions & 9 deletions worlds/musedash/test/TestCollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -54,4 +55,4 @@ def test_remove_songs_are_not_generated(self) -> None:
songs = collection.get_songs_with_settings({x for x in collection.DLC}, False, 0, 12)

for song_name in collection.REMOVED_SONGS:
self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.")
self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.")
DeamonHunter marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 5 additions & 5 deletions worlds/musedash/test/TestDifficultyRanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

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
Expand All @@ -16,13 +16,13 @@ def test_range(inputRange, lower, upper):
songs = muse_dash_world.md_collection.get_songs_with_settings(dlc_set, 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]):
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]):
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]):
if song.master is not None and inputRange[0] <= song.master <= inputRange[1]:
continue

self.fail(f"Invalid song '{songKey}' was given for range '{inputRange[0]} to {inputRange[1]}'")
Expand Down Expand Up @@ -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]
Expand Down
Loading
Loading