diff --git a/BaseClasses.py b/BaseClasses.py index 94eb10de43e..ada18f1e1d0 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1453,14 +1453,6 @@ class Tutorial(NamedTuple): authors: List[str] -class OptionGroup(NamedTuple): - """Define a grouping of options""" - name: str - """Name of the group to categorize this option in for display on the WebHost and in generated YAMLS.""" - options: List[Type[Options.Option]] - """Options to be in the defined group. """ - - class PlandoOptions(IntFlag): none = 0b0000 items = 0b0001 diff --git a/Options.py b/Options.py index 6b4db10ac4e..7f833d5aff5 100644 --- a/Options.py +++ b/Options.py @@ -1123,6 +1123,14 @@ class DeathLinkMixin: death_link: DeathLink +class OptionGroup(typing.NamedTuple): + """Define a grouping of options.""" + name: str + """Name of the group to categorize these options in for display on the WebHost and in generated YAMLS.""" + options: typing.List[typing.Type[Option[typing.Any]]] + """Options to be in the defined group.""" + + def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True): import os diff --git a/docs/options api.md b/docs/options api.md index dbf37df7ae9..798e97781a8 100644 --- a/docs/options api.md +++ b/docs/options api.md @@ -92,7 +92,7 @@ group. ```python from worlds.AutoWorld import WebWorld -from BaseClasses import OptionGroup +from Options import OptionGroup class MyWorldWeb(WebWorld): option_groups = [ diff --git a/inno_setup.iss b/inno_setup.iss index 529a96a33ac..4b37279e8de 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -169,6 +169,11 @@ Root: HKCR; Subkey: "{#MyAppName}pkmnepatch"; ValueData: "Ar Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apmlss"; ValueData: "{#MyAppName}mlsspatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}mlsspatch"; ValueData: "Archipelago Mario & Luigi Superstar Saga Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}mlsspatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}mlsspatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + Root: HKCR; Subkey: ".apcv64"; ValueData: "{#MyAppName}cv64patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}cv64patch"; ValueData: "Archipelago Castlevania 64 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}cv64patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; diff --git a/test/general/test_implemented.py b/test/general/test_implemented.py index 624be710185..e76d539451e 100644 --- a/test/general/test_implemented.py +++ b/test/general/test_implemented.py @@ -3,6 +3,7 @@ from Fill import distribute_items_restrictive from NetUtils import encode from worlds.AutoWorld import AutoWorldRegister, call_all +from worlds import failed_world_loads from . import setup_solo_multiworld @@ -47,3 +48,7 @@ def test_slot_data(self): for key, data in multiworld.worlds[1].fill_slot_data().items(): self.assertIsInstance(key, str, "keys in slot data must be a string") self.assertIsInstance(encode(data), str, f"object {type(data).__name__} not serializable.") + + def test_no_failed_world_loads(self): + if failed_world_loads: + self.fail(f"The following worlds failed to load: {failed_world_loads}") diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index d269ee104cd..b564932eb9b 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -11,11 +11,11 @@ from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union) -from Options import ExcludeLocations, ItemLinks, LocalItems, NonLocalItems, PerGameCommonOptions, \ - PriorityLocations, \ - StartHints, \ - StartInventory, StartInventoryPool, StartLocationHints -from BaseClasses import CollectionState, OptionGroup +from Options import ( + ExcludeLocations, ItemLinks, LocalItems, NonLocalItems, OptionGroup, PerGameCommonOptions, + PriorityLocations, StartHints, StartInventory, StartInventoryPool, StartLocationHints +) +from BaseClasses import CollectionState if TYPE_CHECKING: from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance diff --git a/worlds/mlss/Client.py b/worlds/mlss/Client.py index 322f243ceb0..a1cd43afbae 100644 --- a/worlds/mlss/Client.py +++ b/worlds/mlss/Client.py @@ -47,19 +47,10 @@ async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: # Check ROM name/patch version rom_name_bytes = await bizhawk.read(ctx.bizhawk_ctx, [(0xA0, 14, "ROM")]) rom_name = bytes([byte for byte in rom_name_bytes[0] if byte != 0]).decode("UTF-8") - if not rom_name.startswith("MARIO&LUIGIU"): - return False - if rom_name == "MARIO&LUIGIUA8": - logger.info( - "ERROR: You appear to be running an unpatched version of Mario & Luigi Superstar Saga. " - "You need to generate a patch file and use it to create a patched ROM." - ) - return False - if rom_name != "MARIO&LUIGIUAP": + if not rom_name.startswith("MARIO&LUIGIUA8"): logger.info( - "ERROR: The patch file used to create this ROM is not compatible with " - "this client. Double check your client version against the version being " - "used by the generator." + "ERROR: You have opened a game that is not Mario & Luigi Superstar Saga. " + "Please make sure you are opening the correct ROM." ) return False except UnicodeDecodeError: diff --git a/worlds/mlss/Regions.py b/worlds/mlss/Regions.py index be1c5d1063a..992e99e2c7f 100644 --- a/worlds/mlss/Regions.py +++ b/worlds/mlss/Regions.py @@ -299,7 +299,7 @@ def connect( target: str, rule: typing.Optional[typing.Callable] = None, reach: typing.Optional[bool] = False, -) -> Entrance | None: +) -> typing.Optional[Entrance]: source_region = world.multiworld.get_region(source, world.player) target_region = world.multiworld.get_region(target, world.player) @@ -317,7 +317,4 @@ def connect( source_region.exits.append(connection) connection.connect(target_region) - if reach: - return connection - else: - return None + return connection if reach else None diff --git a/worlds/mlss/Rom.py b/worlds/mlss/Rom.py index 1b8a182509a..08921500dac 100644 --- a/worlds/mlss/Rom.py +++ b/worlds/mlss/Rom.py @@ -272,9 +272,6 @@ def write_tokens(world: "MLSSWorld", patch: MLSSProcedurePatch) -> None: # Bake seed name into ROM patch.write_token(APTokenTypes.WRITE, 0xDF00A0, world.multiworld.seed_name.encode("UTF-8")) - # Bake patch into header - patch.write_token(APTokenTypes.WRITE, 0xAD, "P".encode("UTF-8")) - # Intro Skip patch.write_token( APTokenTypes.WRITE, diff --git a/worlds/mlss/data/basepatch.bsdiff b/worlds/mlss/data/basepatch.bsdiff index 45ed6a6b724..156d28f346e 100644 Binary files a/worlds/mlss/data/basepatch.bsdiff and b/worlds/mlss/data/basepatch.bsdiff differ diff --git a/worlds/yugioh06/__init__.py b/worlds/yugioh06/__init__.py index ec7e769f2c4..2640b13aca4 100644 --- a/worlds/yugioh06/__init__.py +++ b/worlds/yugioh06/__init__.py @@ -193,7 +193,7 @@ def generate_early(self): self.multiworld.push_precollected(self.create_item(Banlist_Items[banlist])) if not self.removed_challenges: - challenge = list((Limited_Duels | Theme_Duels).keys()) + challenge = list(({**Limited_Duels, **Theme_Duels}).keys()) noc = len(challenge) - max( self.options.third_tier_5_campaign_boss_challenges.value if self.options.third_tier_5_campaign_boss_unlock_condition == "challenges" @@ -238,9 +238,9 @@ def create_regions(self): structure_deck = self.options.structure_deck.current_key self.multiworld.regions += [ self.create_region("Menu", None, ["to Deck Edit", "to Campaign", "to Challenges", "to Card Shop"]), - self.create_region("Campaign", Bonuses | Campaign_Opponents), + self.create_region("Campaign", {**Bonuses, **Campaign_Opponents}), self.create_region("Challenges"), - self.create_region("Card Shop", Required_Cards | collection_events), + self.create_region("Card Shop", {**Required_Cards, **collection_events}), self.create_region("Structure Deck", get_deck_content_locations(structure_deck)), ] @@ -308,7 +308,7 @@ def create_regions(self): challenge_region = self.get_region("Challenges") # Challenges - for challenge, lid in (Limited_Duels | Theme_Duels).items(): + for challenge, lid in ({**Limited_Duels, **Theme_Duels}).items(): if challenge in self.removed_challenges: continue region = self.create_region(challenge, {challenge: lid, challenge + " Complete": None}) diff --git a/worlds/yugioh06/structure_deck.py b/worlds/yugioh06/structure_deck.py index 8454c55ee17..d58223f2e21 100644 --- a/worlds/yugioh06/structure_deck.py +++ b/worlds/yugioh06/structure_deck.py @@ -1,4 +1,6 @@ -structure_contents: dict[str, set] = { +from typing import Dict, Set + +structure_contents: Dict[str, Set] = { "dragons_roar": { "Luster Dragon", "Armed Dragon LV3", @@ -77,5 +79,5 @@ } -def get_deck_content_locations(deck: str) -> dict[str, str]: +def get_deck_content_locations(deck: str) -> Dict[str, str]: return {f"{deck} {i}": content for i, content in enumerate(structure_contents[deck])}