diff --git a/BaseClasses.py b/BaseClasses.py index 15470f82a09..4002800173e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -18,11 +18,14 @@ import Options import Utils +if typing.TYPE_CHECKING: + from worlds import AutoWorld + class Group(TypedDict, total=False): name: str game: str - world: auto_world + world: "AutoWorld.World" players: Set[int] item_pool: Set[str] replacement_items: Dict[int, Optional[str]] @@ -55,7 +58,7 @@ class MultiWorld(): plando_texts: List[Dict[str, str]] plando_items: List[List[Dict[str, Any]]] plando_connections: List - worlds: Dict[int, auto_world] + worlds: Dict[int, "AutoWorld.World"] groups: Dict[int, Group] regions: RegionManager itempool: List[Item] @@ -219,6 +222,8 @@ def get_all_ids(self) -> Tuple[int, ...]: def add_group(self, name: str, game: str, players: Set[int] = frozenset()) -> Tuple[int, Group]: """Create a group with name and return the assigned player ID and group. If a group of this name already exists, the set of players is extended instead of creating a new one.""" + from worlds import AutoWorld + for group_id, group in self.groups.items(): if group["name"] == name: group["players"] |= players @@ -253,6 +258,8 @@ def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optio def set_options(self, args: Namespace) -> None: # TODO - remove this section once all worlds use options dataclasses + from worlds import AutoWorld + all_keys: Set[str] = {key for player in self.player_ids for key in AutoWorld.AutoWorldRegister.world_types[self.game[player]].options_dataclass.type_hints} for option_key in all_keys: @@ -270,6 +277,8 @@ def set_options(self, args: Namespace) -> None: for option_key in options_dataclass.type_hints}) def set_item_links(self): + from worlds import AutoWorld + item_links = {} replacement_prio = [False, True, None] for player in self.player_ids: @@ -1327,6 +1336,8 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st get_path(state, multiworld.get_region('Inverted Big Bomb Shop', player)) def to_file(self, filename: str) -> None: + from worlds import AutoWorld + def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None: res = getattr(self.multiworld.worlds[player].options, option_key) display_name = getattr(option_obj, "display_name", option_key) @@ -1450,8 +1461,3 @@ def get_seed(seed: Optional[int] = None) -> int: random.seed(None) return random.randint(0, pow(10, seeddigits) - 1) return seed - - -from worlds import AutoWorld - -auto_world = AutoWorld.World diff --git a/Patch.py b/Patch.py index 113d0658c6b..09154570005 100644 --- a/Patch.py +++ b/Patch.py @@ -8,7 +8,7 @@ import ModuleUpdate ModuleUpdate.update() -from worlds.Files import AutoPatchRegister, APDeltaPatch +from worlds.Files import AutoPatchRegister, APPatch class RomMeta(TypedDict): @@ -20,7 +20,7 @@ class RomMeta(TypedDict): def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]: auto_handler = AutoPatchRegister.get_handler(patch_file) if auto_handler: - handler: APDeltaPatch = auto_handler(patch_file) + handler: APPatch = auto_handler(patch_file) target = os.path.splitext(patch_file)[0]+handler.result_file_ending handler.patch(target) return {"server": handler.server, diff --git a/test/bases.py b/test/bases.py index 7ce12cc7b78..2d4111d1935 100644 --- a/test/bases.py +++ b/test/bases.py @@ -7,7 +7,7 @@ from Generate import get_seed_name from test.general import gen_steps from worlds import AutoWorld -from worlds.AutoWorld import call_all +from worlds.AutoWorld import World, call_all from BaseClasses import Location, MultiWorld, CollectionState, ItemClassification, Item from worlds.alttp.Items import ItemFactory @@ -105,9 +105,15 @@ def _get_items_partial(self, item_pool, missing_item): class WorldTestBase(unittest.TestCase): options: typing.Dict[str, typing.Any] = {} + """Define options that should be used when setting up this TestBase.""" multiworld: MultiWorld + """The constructed MultiWorld instance after setup.""" + world: World + """The constructed World instance after setup.""" + player: typing.ClassVar[int] = 1 - game: typing.ClassVar[str] # define game name in subclass, example "Secret of Evermore" + game: typing.ClassVar[str] + """Define game name in subclass, example "Secret of Evermore".""" auto_construct: typing.ClassVar[bool] = True """ automatically set up a world for each test in this class """ memory_leak_tested: typing.ClassVar[bool] = False @@ -150,8 +156,8 @@ def world_setup(self, seed: typing.Optional[int] = None) -> None: if not hasattr(self, "game"): raise NotImplementedError("didn't define game name") self.multiworld = MultiWorld(1) - self.multiworld.game[1] = self.game - self.multiworld.player_name = {1: "Tester"} + self.multiworld.game[self.player] = self.game + self.multiworld.player_name = {self.player: "Tester"} self.multiworld.set_seed(seed) self.multiworld.state = CollectionState(self.multiworld) random.seed(self.multiworld.seed) @@ -159,9 +165,10 @@ def world_setup(self, seed: typing.Optional[int] = None) -> None: args = Namespace() for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): setattr(args, name, { - 1: option.from_any(self.options.get(name, getattr(option, "default"))) + 1: option.from_any(self.options.get(name, option.default)) }) self.multiworld.set_options(args) + self.world = self.multiworld.worlds[self.player] for step in gen_steps: call_all(self.multiworld, step) @@ -220,19 +227,19 @@ def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None: def can_reach_location(self, location: str) -> bool: """Determines if the current state can reach the provided location name""" - return self.multiworld.state.can_reach(location, "Location", 1) + return self.multiworld.state.can_reach(location, "Location", self.player) def can_reach_entrance(self, entrance: str) -> bool: """Determines if the current state can reach the provided entrance name""" - return self.multiworld.state.can_reach(entrance, "Entrance", 1) + return self.multiworld.state.can_reach(entrance, "Entrance", self.player) def can_reach_region(self, region: str) -> bool: """Determines if the current state can reach the provided region name""" - return self.multiworld.state.can_reach(region, "Region", 1) + return self.multiworld.state.can_reach(region, "Region", self.player) def count(self, item_name: str) -> int: """Returns the amount of an item currently in state""" - return self.multiworld.state.count(item_name, 1) + return self.multiworld.state.count(item_name, self.player) def assertAccessDependency(self, locations: typing.List[str], @@ -246,10 +253,11 @@ def assertAccessDependency(self, self.collect_all_but(all_items, state) if only_check_listed: for location in locations: - self.assertFalse(state.can_reach(location, "Location", 1), f"{location} is reachable without {all_items}") + self.assertFalse(state.can_reach(location, "Location", self.player), + f"{location} is reachable without {all_items}") else: for location in self.multiworld.get_locations(): - loc_reachable = state.can_reach(location, "Location", 1) + loc_reachable = state.can_reach(location, "Location", self.player) self.assertEqual(loc_reachable, location.name not in locations, f"{location.name} is reachable without {all_items}" if loc_reachable else f"{location.name} is not reachable without {all_items}") @@ -258,7 +266,7 @@ def assertAccessDependency(self, for item in items: state.collect(item) for location in locations: - self.assertTrue(state.can_reach(location, "Location", 1), + self.assertTrue(state.can_reach(location, "Location", self.player), f"{location} not reachable with {item_names}") for item in items: state.remove(item) @@ -285,7 +293,7 @@ def test_all_state_can_reach_everything(self): if not (self.run_default_tests and self.constructed): return with self.subTest("Game", game=self.game): - excluded = self.multiworld.worlds[1].options.exclude_locations.value + excluded = self.multiworld.worlds[self.player].options.exclude_locations.value state = self.multiworld.get_all_state(False) for location in self.multiworld.get_locations(): if location.name not in excluded: @@ -302,7 +310,7 @@ def test_empty_state_can_reach_something(self): return with self.subTest("Game", game=self.game): state = CollectionState(self.multiworld) - locations = self.multiworld.get_reachable_locations(state, 1) + locations = self.multiworld.get_reachable_locations(state, self.player) self.assertGreater(len(locations), 0, "Need to be able to reach at least one location to get started.") @@ -328,7 +336,7 @@ def fulfills_accessibility() -> bool: for location in sphere: if location.item: state.collect(location.item, True, location) - return self.multiworld.has_beaten_game(state, 1) + return self.multiworld.has_beaten_game(state, self.player) with self.subTest("Game", game=self.game, seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index fdc50acc558..e8d48df58c5 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -438,7 +438,7 @@ def collect_item(self, state: "CollectionState", item: "Item", remove: bool = Fa def get_pre_fill_items(self) -> List["Item"]: return [] - # following methods should not need to be overridden. + # these two methods can be extended for pseudo-items on state def collect(self, state: "CollectionState", item: "Item") -> bool: name = self.collect_item(state, item) if name: diff --git a/worlds/Files.py b/worlds/Files.py index 52d3c7da1d3..336a3090937 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -1,5 +1,6 @@ from __future__ import annotations +import abc import json import zipfile import os @@ -15,7 +16,7 @@ del os -class AutoPatchRegister(type): +class AutoPatchRegister(abc.ABCMeta): patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {} file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {} @@ -112,14 +113,25 @@ def get_manifest(self) -> Dict[str, Any]: } -class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): - """An APContainer that additionally has delta.bsdiff4 +class APPatch(APContainer, abc.ABC, metaclass=AutoPatchRegister): + """ + An abstract `APContainer` that defines the requirements for an object + to be used by the `Patch.create_rom_file` function. + """ + result_file_ending: str = ".sfc" + + @abc.abstractmethod + def patch(self, target: str) -> None: + """ create the output file with the file name `target` """ + + +class APDeltaPatch(APPatch): + """An APPatch that additionally has delta.bsdiff4 containing a delta patch to get the desired file, often a rom.""" hash: Optional[str] # base checksum of source file patch_file_ending: str = "" delta: Optional[bytes] = None - result_file_ending: str = ".sfc" source_data: bytes def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index da78a5123df..23afb2b4450 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -278,10 +278,10 @@ tag: forbid check: True achievement: The Seeker - BEAR: + BEAR (1): id: Heteronym Room/Panel_bear_bear tag: midwhite - MINE: + MINE (1): id: Heteronym Room/Panel_mine_mine tag: double midwhite subtag: left @@ -297,7 +297,7 @@ DOES: id: Heteronym Room/Panel_does_does tag: midwhite - MOBILE: + MOBILE (1): id: Heteronym Room/Panel_mobile_mobile tag: double midwhite subtag: left @@ -399,8 +399,7 @@ door: Crossroads Entrance The Tenacious: door: Tenacious Entrance - Warts Straw Area: - door: Symmetry Door + Near Far Area: True Hedge Maze: door: Shortcut to Hedge Maze Orange Tower First Floor: @@ -427,14 +426,6 @@ id: Palindrome Room/Panel_slaughter_laughter colors: red tag: midred - NEAR: - id: Symmetry Room/Panel_near_far - colors: black - tag: botblack - FAR: - id: Symmetry Room/Panel_far_near - colors: black - tag: botblack TRACE: id: Maze Room/Panel_trace_trace tag: midwhite @@ -477,14 +468,6 @@ group: Entrances to The Tenacious panels: - SLAUGHTER - Symmetry Door: - id: - - Symmetry Room Area Doors/Door_near_far - - Symmetry Room Area Doors/Door_far_near - group: Symmetry Doors - panels: - - NEAR - - FAR Shortcut to Hedge Maze: id: Maze Area Doors/Door_trace_trace group: Hedge Maze Doors @@ -548,7 +531,7 @@ id: Lingo Room/Panel_shortcut colors: yellow tag: midyellow - PILGRIMAGE: + PILGRIM: id: Lingo Room/Panel_pilgrim colors: blue tag: midblue @@ -569,7 +552,7 @@ Exit: event: True panels: - - PILGRIMAGE + - PILGRIM Pilgrim Room: entrances: The Seeker: @@ -755,7 +738,7 @@ panels: - TURN - room: Orange Tower Fourth Floor - panel: RUNT + panel: RUNT (1) Words Sword Door: id: - Shuffle Room Area Doors/Door_words_shuffle_3 @@ -962,11 +945,36 @@ - LEVEL (White) - RACECAR (White) - SOLOS (White) + Near Far Area: + entrances: + Hub Room: True + Warts Straw Area: + door: Door + panels: + NEAR: + id: Symmetry Room/Panel_near_far + colors: black + tag: botblack + FAR: + id: Symmetry Room/Panel_far_near + colors: black + tag: botblack + doors: + Door: + id: + - Symmetry Room Area Doors/Door_near_far + - Symmetry Room Area Doors/Door_far_near + group: Symmetry Doors + item_name: Symmetry Room - Near Far Door + location_name: Symmetry Room - NEAR, FAR + panels: + - NEAR + - FAR Warts Straw Area: entrances: - Hub Room: - room: Hub Room - door: Symmetry Door + Near Far Area: + room: Near Far Area + door: Door Leaf Feel Area: door: Door panels: @@ -984,6 +992,8 @@ - Symmetry Room Area Doors/Door_warts_straw - Symmetry Room Area Doors/Door_straw_warts group: Symmetry Doors + item_name: Symmetry Room - Warts Straw Door + location_name: Symmetry Room - WARTS, STRAW panels: - WARTS - STRAW @@ -1009,6 +1019,8 @@ - Symmetry Room Area Doors/Door_leaf_feel - Symmetry Room Area Doors/Door_feel_leaf group: Symmetry Doors + item_name: Symmetry Room - Leaf Feel Door + location_name: Symmetry Room - LEAF, FEEL panels: - LEAF - FEEL @@ -1120,7 +1132,7 @@ tag: forbid required_panel: - room: Outside The Bold - panel: MOUTH + panel: SOUND - room: Outside The Bold panel: YEAST - room: Outside The Bold @@ -1166,7 +1178,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: PURPLE Hallway Door: id: Red Blue Purple Room Area Doors/Door_room_2 @@ -1436,7 +1448,7 @@ entrances: The Perceptive: True panels: - NAPS: + SPAN: id: Naps Room/Panel_naps_span colors: black tag: midblack @@ -1462,7 +1474,7 @@ location_name: The Fearless - First Floor Puzzles group: Fearless Doors panels: - - NAPS + - SPAN - TEAM - TEEM - IMPATIENT @@ -1564,11 +1576,11 @@ required_door: door: Stairs achievement: The Observant - BACK: + FOUR (1): id: Look Room/Panel_four_back colors: green tag: forbid - SIDE: + FOUR (2): id: Look Room/Panel_four_side colors: green tag: forbid @@ -1578,87 +1590,87 @@ hunt: True required_door: door: Backside Door - STAIRS: + SIX: id: Look Room/Panel_six_stairs colors: green tag: forbid - WAYS: + FOUR (3): id: Look Room/Panel_four_ways colors: green tag: forbid - "ON": + TWO (1): id: Look Room/Panel_two_on colors: green tag: forbid - UP: + TWO (2): id: Look Room/Panel_two_up colors: green tag: forbid - SWIMS: + FIVE: id: Look Room/Panel_five_swims colors: green tag: forbid - UPSTAIRS: + BELOW (1): id: Look Room/Panel_eight_upstairs colors: green tag: forbid required_door: door: Stairs - TOIL: + BLUE: id: Look Room/Panel_blue_toil colors: green tag: forbid required_door: door: Stairs - STOP: + BELOW (2): id: Look Room/Panel_four_stop colors: green tag: forbid required_door: door: Stairs - TOP: + MINT (1): id: Look Room/Panel_aqua_top colors: green tag: forbid required_door: door: Stairs - HI: + ESACREWOL: id: Look Room/Panel_blue_hi colors: green tag: forbid required_door: door: Stairs - HI (2): + EULB: id: Look Room/Panel_blue_hi2 colors: green tag: forbid required_door: door: Stairs - "31": + NUMBERS (1): id: Look Room/Panel_numbers_31 colors: green tag: forbid required_door: door: Stairs - "52": + NUMBERS (2): id: Look Room/Panel_numbers_52 colors: green tag: forbid required_door: door: Stairs - OIL: + MINT (2): id: Look Room/Panel_aqua_oil colors: green tag: forbid required_door: door: Stairs - BACKSIDE (GREEN): + GREEN (1): id: Look Room/Panel_eight_backside colors: green tag: forbid required_door: door: Stairs - SIDEWAYS: + GREEN (2): id: Look Room/Panel_eight_sideways colors: green tag: forbid @@ -1669,13 +1681,13 @@ id: Maze Area Doors/Door_backside group: Backside Doors panels: - - BACK - - SIDE + - FOUR (1) + - FOUR (2) Stairs: id: Maze Area Doors/Door_stairs group: Observant Doors panels: - - STAIRS + - SIX The Incomparable: entrances: The Observant: True # Assuming that access to The Observant includes access to the right entrance @@ -1957,7 +1969,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: RED Rhyme Room Entrance: id: Double Room Area Doors/Door_room_entry_stairs2 @@ -1975,9 +1987,9 @@ - Color Arrow Room Doors/Door_orange_hider_1 - Color Arrow Room Doors/Door_orange_hider_2 - Color Arrow Room Doors/Door_orange_hider_3 - location_name: Color Hunt - RED and YELLOW - group: Champion's Rest - Color Barriers - item_name: Champion's Rest - Orange Barrier + location_name: Color Barriers - RED and YELLOW + group: Color Hunt Barriers + item_name: Color Hunt - Orange Barrier panels: - RED - room: Directional Gallery @@ -2005,7 +2017,7 @@ Courtyard: True Roof: True # through the sunwarp panels: - RUNT: + RUNT (1): id: Shuffle Room/Panel_turn_runt2 colors: yellow tag: midyellow @@ -2219,6 +2231,7 @@ - Master Room Doors/Door_master_down - Master Room Doors/Door_master_down2 skip_location: True + item_name: Mastery panels: - THE MASTER Mastery Panels: @@ -2235,25 +2248,25 @@ panel: MASTERY - room: Hedge Maze panel: MASTERY (1) - - room: Roof - panel: MASTERY (1) - - room: Roof - panel: MASTERY (2) + - room: Behind A Smile + panel: MASTERY + - room: Sixteen Colorful Squares + panel: MASTERY - MASTERY - room: Hedge Maze panel: MASTERY (2) - - room: Roof - panel: MASTERY (3) - - room: Roof - panel: MASTERY (4) - - room: Roof - panel: MASTERY (5) + - room: Among Treetops + panel: MASTERY + - room: Horizon's Edge + panel: MASTERY + - room: Beneath The Lookout + panel: MASTERY - room: Elements Area panel: MASTERY - room: Pilgrim Antechamber panel: MASTERY - - room: Roof - panel: MASTERY (6) + - room: Rooftop Staircase + panel: MASTERY paintings: - id: map_painting2 orientation: north @@ -2265,52 +2278,75 @@ Crossroads: room: Crossroads door: Roof Access + Behind A Smile: + entrances: + Roof: True panels: - MASTERY (1): + MASTERY: id: Master Room/Panel_mastery_mastery6 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - MASTERY (2): + STAIRCASE: + id: Open Areas/Panel_staircase + tag: midwhite + Sixteen Colorful Squares: + entrances: + Roof: True + panels: + MASTERY: id: Master Room/Panel_mastery_mastery7 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - MASTERY (3): + Among Treetops: + entrances: + Roof: True + panels: + MASTERY: id: Master Room/Panel_mastery_mastery10 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - MASTERY (4): + Horizon's Edge: + entrances: + Roof: True + panels: + MASTERY: id: Master Room/Panel_mastery_mastery11 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - MASTERY (5): + Beneath The Lookout: + entrances: + Roof: True + panels: + MASTERY: id: Master Room/Panel_mastery_mastery12 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - MASTERY (6): + Rooftop Staircase: + entrances: + Roof: True + panels: + MASTERY: id: Master Room/Panel_mastery_mastery15 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - STAIRCASE: - id: Open Areas/Panel_staircase - tag: midwhite Orange Tower Basement: entrances: Orange Tower Sixth Floor: @@ -2382,7 +2418,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: GREEN paintings: - id: flower_painting_7 @@ -2632,9 +2668,7 @@ tag: forbid doors: Progress Door: - id: - - Doorway Room Doors/Door_gray - - Doorway Room Doors/Door_gray2 # See comment below + id: Doorway Room Doors/Door_gray item_name: The Colorful - Gray Door location_name: The Colorful - Gray group: Colorful Doors @@ -2784,6 +2818,7 @@ door: Exit Eight Alcove: door: Eight Door + The Optimistic: True panels: SEVEN (1): id: Backside Room/Panel_seven_seven_5 @@ -2833,21 +2868,11 @@ id: Rhyme Room/Panel_locked_knocked colors: purple tag: midpurp - BACKSIDE: - id: Backside Room/Panel_backside_1 - tag: midwhite - The Optimistic: - id: Countdown Panels/Panel_optimistic_optimistic - check: True - tag: forbid - required_door: - door: Backsides - achievement: The Optimistic - PAST: + PAST (1): id: Shuffle Room/Panel_past_present colors: brown tag: botbrown - FUTURE: + FUTURE (1): id: Shuffle Room/Panel_future_present colors: - brown @@ -2893,14 +2918,14 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: BLUE Orange Barrier: id: Color Arrow Room Doors/Door_orange_3 group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: ORANGE Initiated Entrance: id: Red Blue Purple Room Area Doors/Door_locked_knocked @@ -2912,9 +2937,9 @@ # containing region. Green Barrier: id: Color Arrow Room Doors/Door_green_hider_1 - location_name: Color Hunt - BLUE and YELLOW - item_name: Champion's Rest - Green Barrier - group: Champion's Rest - Color Barriers + location_name: Color Barriers - BLUE and YELLOW + item_name: Color Hunt - Green Barrier + group: Color Hunt Barriers panels: - BLUE - room: Directional Gallery @@ -2924,9 +2949,9 @@ - Color Arrow Room Doors/Door_purple_hider_1 - Color Arrow Room Doors/Door_purple_hider_2 - Color Arrow Room Doors/Door_purple_hider_3 - location_name: Color Hunt - RED and BLUE - item_name: Champion's Rest - Purple Barrier - group: Champion's Rest - Color Barriers + location_name: Color Barriers - RED and BLUE + item_name: Color Hunt - Purple Barrier + group: Color Hunt Barriers panels: - BLUE - room: Orange Tower Third Floor @@ -2936,7 +2961,7 @@ - Color Arrow Room Doors/Door_all_hider_1 - Color Arrow Room Doors/Door_all_hider_2 - Color Arrow Room Doors/Door_all_hider_3 - location_name: Color Hunt - GREEN, ORANGE and PURPLE + location_name: Color Barriers - GREEN, ORANGE and PURPLE item_name: Champion's Rest - Entrance panels: - ORANGE @@ -2944,17 +2969,6 @@ panel: GREEN - room: Outside The Agreeable panel: PURPLE - Backsides: - event: True - panels: - - room: The Observant - panel: BACKSIDE - - room: Yellow Backside Area - panel: BACKSIDE - - room: Directional Gallery - panel: BACKSIDE - - room: The Bearer - panel: BACKSIDE Eight Door: id: Red Blue Purple Room Area Doors/Door_a_strands2 skip_location: True @@ -3064,6 +3078,28 @@ id: Rhyme Room/Panel_bed_dead colors: purple tag: toppurp + The Optimistic: + entrances: + Outside The Initiated: True + panels: + BACKSIDE: + id: Backside Room/Panel_backside_1 + tag: midwhite + Achievement: + id: Countdown Panels/Panel_optimistic_optimistic + check: True + tag: forbid + required_panel: + - panel: BACKSIDE + - room: The Observant + panel: BACKSIDE + - room: Yellow Backside Area + panel: BACKSIDE + - room: Directional Gallery + panel: BACKSIDE + - room: The Bearer + panel: BACKSIDE + achievement: The Optimistic The Traveled: entrances: Hub Room: @@ -3164,7 +3200,7 @@ Outside The Undeterred: True Crossroads: True Hedge Maze: True - Outside The Initiated: True # backside + The Optimistic: True # backside Directional Gallery: True # backside Yellow Backside Area: True The Bearer: @@ -3176,12 +3212,12 @@ Outside The Bold: entrances: Color Hallways: True - Champion's Rest: - room: Champion's Rest + Color Hunt: + room: Color Hunt door: Shortcut to The Steady The Bearer: room: The Bearer - door: Shortcut to The Bold + door: Entrance Directional Gallery: # There is a painting warp here from the Directional Gallery, but it # only appears when the sixes are revealed. It could be its own item if @@ -3252,7 +3288,7 @@ tag: midwhite required_door: door: Stargazer Door - MOUTH: + SOUND: id: Cross Room/Panel_mouth_south colors: purple tag: midpurp @@ -3596,7 +3632,7 @@ id: Blue Room/Panel_bone_skeleton colors: blue tag: botblue - EYE: + EYE (1): id: Blue Room/Panel_mouth_face colors: blue tag: double botblue @@ -4002,7 +4038,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: YELLOW paintings: - id: smile_painting_7 @@ -4020,12 +4056,15 @@ orientation: south - id: cherry_painting orientation: east - Champion's Rest: + Color Hunt: entrances: Outside The Bold: door: Shortcut to The Steady Orange Tower Fourth Floor: True # sunwarp Roof: True # through ceiling of sunwarp + Champion's Rest: + room: Outside The Initiated + door: Entrance panels: EXIT: id: Rock Room/Panel_red_red @@ -4066,11 +4105,28 @@ required_door: room: Orange Tower Third Floor door: Orange Barrier - YOU: - id: Color Arrow Room/Panel_you + doors: + Shortcut to The Steady: + id: Rock Room Doors/Door_hint + panels: + - EXIT + paintings: + - id: arrows_painting_7 + orientation: east + - id: fruitbowl_painting3 + orientation: west + enter_only: True required_door: room: Outside The Initiated door: Entrance + Champion's Rest: + entrances: + Color Hunt: + room: Outside The Initiated + door: Entrance + panels: + YOU: + id: Color Arrow Room/Panel_you check: True colors: gray tag: forbid @@ -4078,53 +4134,24 @@ id: Color Arrow Room/Panel_me colors: gray tag: forbid - required_door: - room: Outside The Initiated - door: Entrance SECRET BLUE: # Pretend this and the other two are white, because they are snipes. # TODO: Extract them and randomize them? id: Color Arrow Room/Panel_secret_blue tag: forbid - required_door: - room: Outside The Initiated - door: Entrance SECRET YELLOW: id: Color Arrow Room/Panel_secret_yellow tag: forbid - required_door: - room: Outside The Initiated - door: Entrance SECRET RED: id: Color Arrow Room/Panel_secret_red tag: forbid - required_door: - room: Outside The Initiated - door: Entrance - doors: - Shortcut to The Steady: - id: Rock Room Doors/Door_hint - panels: - - EXIT paintings: - - id: arrows_painting_7 - orientation: east - - id: fruitbowl_painting3 - orientation: west - enter_only: True - required_door: - room: Outside The Initiated - door: Entrance - id: colors_painting orientation: south - enter_only: True - required_door: - room: Outside The Initiated - door: Entrance The Bearer: entrances: Outside The Bold: - door: Shortcut to The Bold + door: Entrance Orange Tower Fifth Floor: room: Art Gallery door: Exit @@ -4201,7 +4228,7 @@ - yellow tag: mid red yellow doors: - Shortcut to The Bold: + Entrance: id: Red Blue Purple Room Area Doors/Door_middle_middle panels: - MIDDLE @@ -4330,21 +4357,21 @@ door: Side Area Shortcut Roof: True panels: - SNOW: + SMILE: id: Cross Room/Panel_smile_lime colors: - red - yellow tag: mid yellow red - SMILE: + required_panel: + room: The Bearer (North) + panel: WARTS + SNOW: id: Cross Room/Panel_snow_won colors: - red - yellow tag: mid red yellow - required_panel: - room: The Bearer (North) - panel: WARTS doors: Side Area Shortcut: event: True @@ -4423,7 +4450,7 @@ - room: The Bearer (West) panel: SMILE - room: Outside The Bold - panel: MOUTH + panel: SOUND - room: Outside The Bold panel: YEAST - room: Outside The Bold @@ -6138,7 +6165,7 @@ id: Painting Room/Panel_our_four colors: blue tag: midblue - ONE ROAD MANY TURNS: + ORDER: id: Painting Room/Panel_order_onepathmanyturns tag: forbid colors: @@ -6193,8 +6220,9 @@ Exit: id: Tower Room Area Doors/Door_painting_exit include_reduce: True + item_name: Orange Tower Fifth Floor - Quadruple Intersection panels: - - ONE ROAD MANY TURNS + - ORDER paintings: - id: smile_painting_3 orientation: west @@ -6212,7 +6240,6 @@ - Third Floor - Fourth Floor - Fifth Floor - - Exit Art Gallery (Second Floor): entrances: Art Gallery: @@ -6687,7 +6714,7 @@ - room: Rhyme Room (Target) panel: PISTOL - room: Rhyme Room (Target) - panel: QUARTZ + panel: GEM Rhyme Room (Target): entrances: Rhyme Room (Smiley): # one-way @@ -6715,7 +6742,7 @@ tag: syn rhyme subtag: top link: rhyme CRYSTAL - QUARTZ: + GEM: id: Double Room/Panel_crystal_syn colors: purple tag: syn rhyme @@ -6740,7 +6767,7 @@ group: Rhyme Room Doors panels: - PISTOL - - QUARTZ + - GEM - INNOVATIVE (Top) - INNOVATIVE (Bottom) paintings: @@ -6798,22 +6825,18 @@ id: Panel Room/Panel_room_floor_5 colors: gray tag: forbid - FLOOR (7): + FLOOR (6): id: Panel Room/Panel_room_floor_7 colors: gray tag: forbid - FLOOR (8): + FLOOR (7): id: Panel Room/Panel_room_floor_8 colors: gray tag: forbid - FLOOR (9): + FLOOR (8): id: Panel Room/Panel_room_floor_9 colors: gray tag: forbid - FLOOR (10): - id: Panel Room/Panel_room_floor_10 - colors: gray - tag: forbid CEILING (1): id: Panel Room/Panel_room_ceiling_1 colors: gray @@ -6834,6 +6857,10 @@ id: Panel Room/Panel_room_ceiling_5 colors: gray tag: forbid + CEILING (6): + id: Panel Room/Panel_room_floor_10 + colors: gray + tag: forbid WALL (1): id: Panel Room/Panel_room_wall_1 colors: gray @@ -7092,7 +7119,7 @@ id: Hangry Room/Panel_red_top_3 colors: red tag: topred - FLUMMOXED: + FLUSTERED: id: Hangry Room/Panel_red_top_4 colors: red tag: topred @@ -7595,7 +7622,7 @@ - black - blue tag: chain mid black blue - BREAD: + CHEESE: id: Challenge Room/Panel_bread_mold colors: brown tag: double botbrown @@ -7642,7 +7669,7 @@ id: Challenge Room/Panel_double_anagram_5 colors: yellow tag: midyellow - FACTS: + FACTS (Chain): id: Challenge Room/Panel_facts colors: - red @@ -7652,18 +7679,18 @@ id: Challenge Room/Panel_facts2 colors: red tag: forbid - FACTS (3): + FACTS (2): id: Challenge Room/Panel_facts3 tag: forbid - FACTS (4): + FACTS (3): id: Challenge Room/Panel_facts4 colors: blue tag: forbid - FACTS (5): + FACTS (4): id: Challenge Room/Panel_facts5 colors: blue tag: forbid - FACTS (6): + FACTS (5): id: Challenge Room/Panel_facts6 colors: blue tag: forbid diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 2b9e7f3d8ca..4cad9485551 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -31,12 +31,12 @@ panels: LIES: 444408 The Seeker: Achievement: 444409 - BEAR: 444410 - MINE: 444411 + BEAR (1): 444410 + MINE (1): 444411 MINE (2): 444412 BOW: 444413 DOES: 444414 - MOBILE: 444415 + MOBILE (1): 444415 MOBILE (2): 444416 DESERT: 444417 DESSERT: 444418 @@ -57,8 +57,6 @@ panels: Hub Room: ORDER: 444432 SLAUGHTER: 444433 - NEAR: 444434 - FAR: 444435 TRACE: 444436 RAT: 444437 OPEN: 444438 @@ -72,7 +70,7 @@ panels: EIGHT: 444445 Pilgrim Antechamber: HOT CRUST: 444446 - PILGRIMAGE: 444447 + PILGRIM: 444447 MASTERY: 444448 Pilgrim Room: THIS: 444449 @@ -123,6 +121,9 @@ panels: RACECAR (White): 444489 SOLOS (White): 444490 Achievement: 444491 + Near Far Area: + NEAR: 444434 + FAR: 444435 Warts Straw Area: WARTS: 444492 STRAW: 444493 @@ -187,7 +188,7 @@ panels: Achievement: 444546 GAZE: 444547 The Fearless (First Floor): - NAPS: 444548 + SPAN: 444548 TEAM: 444549 TEEM: 444550 IMPATIENT: 444551 @@ -208,25 +209,25 @@ panels: EVEN: 444564 The Observant: Achievement: 444565 - BACK: 444566 - SIDE: 444567 + FOUR (1): 444566 + FOUR (2): 444567 BACKSIDE: 444568 - STAIRS: 444569 - WAYS: 444570 - 'ON': 444571 - UP: 444572 - SWIMS: 444573 - UPSTAIRS: 444574 - TOIL: 444575 - STOP: 444576 - TOP: 444577 - HI: 444578 - HI (2): 444579 - '31': 444580 - '52': 444581 - OIL: 444582 - BACKSIDE (GREEN): 444583 - SIDEWAYS: 444584 + SIX: 444569 + FOUR (3): 444570 + TWO (1): 444571 + TWO (2): 444572 + FIVE: 444573 + BELOW (1): 444574 + BLUE: 444575 + BELOW (2): 444576 + MINT (1): 444577 + ESACREWOL: 444578 + EULB: 444579 + NUMBERS (1): 444580 + NUMBERS (2): 444581 + MINT (2): 444582 + GREEN (1): 444583 + GREEN (2): 444584 The Incomparable: Achievement: 444585 A (One): 444586 @@ -254,7 +255,7 @@ panels: RED: 444605 DEER + WREN: 444606 Orange Tower Fourth Floor: - RUNT: 444607 + RUNT (1): 444607 RUNT (2): 444608 LEARNS + UNSEW: 444609 HOT CRUSTS: 444610 @@ -279,14 +280,19 @@ panels: THE END: 444620 THE MASTER: 444621 MASTERY: 444622 - Roof: - MASTERY (1): 444623 - MASTERY (2): 444624 - MASTERY (3): 444625 - MASTERY (4): 444626 - MASTERY (5): 444627 - MASTERY (6): 444628 + Behind A Smile: + MASTERY: 444623 STAIRCASE: 444629 + Sixteen Colorful Squares: + MASTERY: 444624 + Among Treetops: + MASTERY: 444625 + Horizon's Edge: + MASTERY: 444626 + Beneath The Lookout: + MASTERY: 444627 + Rooftop Staircase: + MASTERY: 444628 Orange Tower Basement: MASTERY: 444630 THE LIBRARY: 444631 @@ -341,16 +347,17 @@ panels: ORANGE: 444663 UNCOVER: 444664 OXEN: 444665 - BACKSIDE: 444666 - The Optimistic: 444667 - PAST: 444668 - FUTURE: 444669 + PAST (1): 444668 + FUTURE (1): 444669 FUTURE (2): 444670 PAST (2): 444671 PRESENT: 444672 SMILE: 444673 ANGERED: 444674 VOTE: 444675 + The Optimistic: + BACKSIDE: 444666 + Achievement: 444667 The Initiated: Achievement: 444676 DAUGHTER: 444677 @@ -400,7 +407,7 @@ panels: ZEN: 444719 SON: 444720 STARGAZER: 444721 - MOUTH: 444722 + SOUND: 444722 YEAST: 444723 WET: 444724 The Bold: @@ -442,7 +449,7 @@ panels: The Undeterred: Achievement: 444759 BONE: 444760 - EYE: 444761 + EYE (1): 444761 MOUTH: 444762 IRIS: 444763 EYE (2): 444764 @@ -489,7 +496,7 @@ panels: WINDWARD: 444803 LIGHT: 444804 REWIND: 444805 - Champion's Rest: + Color Hunt: EXIT: 444806 HUES: 444807 RED: 444808 @@ -498,6 +505,7 @@ panels: GREEN: 444811 PURPLE: 444812 ORANGE: 444813 + Champion's Rest: YOU: 444814 ME: 444815 SECRET BLUE: 444816 @@ -523,8 +531,8 @@ panels: TENT: 444832 BOWL: 444833 The Bearer (West): - SNOW: 444834 - SMILE: 444835 + SMILE: 444834 + SNOW: 444835 Bearer Side Area: SHORTCUT: 444836 POTS: 444837 @@ -719,7 +727,7 @@ panels: TRUSTWORTHY: 444978 FREE: 444979 OUR: 444980 - ONE ROAD MANY TURNS: 444981 + ORDER: 444981 Art Gallery (Second Floor): HOUSE: 444982 PATH: 444983 @@ -777,7 +785,7 @@ panels: WILD: 445028 KID: 445029 PISTOL: 445030 - QUARTZ: 445031 + GEM: 445031 INNOVATIVE (Top): 445032 INNOVATIVE (Bottom): 445033 Room Room: @@ -791,15 +799,15 @@ panels: FLOOR (3): 445041 FLOOR (4): 445042 FLOOR (5): 445043 - FLOOR (7): 445044 - FLOOR (8): 445045 - FLOOR (9): 445046 - FLOOR (10): 445047 + FLOOR (6): 445044 + FLOOR (7): 445045 + FLOOR (8): 445046 CEILING (1): 445048 CEILING (2): 445049 CEILING (3): 445050 CEILING (4): 445051 CEILING (5): 445052 + CEILING (6): 445047 WALL (1): 445053 WALL (2): 445054 WALL (3): 445055 @@ -847,7 +855,7 @@ panels: PANDEMIC (1): 445100 TRINITY: 445101 CHEMISTRY: 445102 - FLUMMOXED: 445103 + FLUSTERED: 445103 PANDEMIC (2): 445104 COUNTERCLOCKWISE: 445105 FEARLESS: 445106 @@ -933,7 +941,7 @@ panels: CORNER: 445182 STRAWBERRIES: 445183 GRUB: 445184 - BREAD: 445185 + CHEESE: 445185 COLOR: 445186 WRITER: 445187 '02759': 445188 @@ -944,12 +952,12 @@ panels: DUCK LOGO: 445193 AVIAN GREEN: 445194 FEVER TEAR: 445195 - FACTS: 445196 + FACTS (Chain): 445196 FACTS (1): 445197 - FACTS (3): 445198 - FACTS (4): 445199 - FACTS (5): 445200 - FACTS (6): 445201 + FACTS (2): 445198 + FACTS (3): 445199 + FACTS (4): 445200 + FACTS (5): 445201 LAPEL SHEEP: 445202 doors: Starting Room: @@ -979,9 +987,6 @@ doors: Tenacious Entrance: item: 444426 location: 444433 - Symmetry Door: - item: 444428 - location: 445204 Shortcut to Hedge Maze: item: 444430 location: 444436 @@ -1038,6 +1043,10 @@ doors: location: 445210 White Palindromes: location: 445211 + Near Far Area: + Door: + item: 444428 + location: 445204 Warts Straw Area: Door: item: 444451 @@ -1286,12 +1295,12 @@ doors: location: 445246 Yellow Barrier: item: 444538 - Champion's Rest: + Color Hunt: Shortcut to The Steady: item: 444539 location: 444806 The Bearer: - Shortcut to The Bold: + Entrance: item: 444540 location: 444820 Backside Door: @@ -1442,7 +1451,6 @@ door_groups: Fearless Doors: 444469 Backside Doors: 444473 Orange Tower First Floor - Shortcuts: 444484 - Champion's Rest - Color Barriers: 444489 Welcome Back Doors: 444492 Colorful Doors: 444498 Directional Gallery Doors: 444531 diff --git a/worlds/lingo/items.py b/worlds/lingo/items.py index 7b1a6505617..9f8bf561559 100644 --- a/worlds/lingo/items.py +++ b/worlds/lingo/items.py @@ -24,14 +24,6 @@ def should_include(self, world: "LingoWorld") -> bool: return world.options.shuffle_colors > 0 elif self.mode == "doors": return world.options.shuffle_doors != ShuffleDoors.option_none - elif self.mode == "orange tower": - # door shuffle is on and tower isn't progressive - return world.options.shuffle_doors != ShuffleDoors.option_none \ - and not world.options.progressive_orange_tower - elif self.mode == "the colorful": - # complex door shuffle is on and colorful isn't progressive - return world.options.shuffle_doors == ShuffleDoors.option_complex \ - and not world.options.progressive_colorful elif self.mode == "complex door": return world.options.shuffle_doors == ShuffleDoors.option_complex elif self.mode == "door group": @@ -72,12 +64,7 @@ def load_item_data(): door_groups.setdefault(door.group, []).extend(door.door_ids) if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]: - if room_name == "Orange Tower": - door_mode = "orange tower" - elif room_name == "The Colorful": - door_mode = "the colorful" - else: - door_mode = "special" + door_mode = "special" ALL_ITEM_TABLE[door.item_name] = \ ItemData(get_door_item_id(room_name, door_name), diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index f3efc2914c3..0ae303518cf 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING from .items import ALL_ITEM_TABLE @@ -36,6 +37,27 @@ class PlayerLocation(NamedTuple): access: AccessRequirements +class ProgressiveItemBehavior(Enum): + DISABLE = 1 + SPLIT = 2 + PROGRESSIVE = 3 + + +def should_split_progression(progression_name: str, world: "LingoWorld") -> ProgressiveItemBehavior: + if progression_name == "Progressive Orange Tower": + if world.options.progressive_orange_tower: + return ProgressiveItemBehavior.PROGRESSIVE + else: + return ProgressiveItemBehavior.SPLIT + elif progression_name == "Progressive Colorful": + if world.options.progressive_colorful: + return ProgressiveItemBehavior.PROGRESSIVE + else: + return ProgressiveItemBehavior.SPLIT + + return ProgressiveItemBehavior.PROGRESSIVE + + class LingoPlayerLogic: """ Defines logic after a player's options have been applied @@ -83,10 +105,13 @@ def set_door_item(self, room: str, door: str, item: str): def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"): if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]: - if (room_name == "Orange Tower" and not world.options.progressive_orange_tower)\ - or (room_name == "The Colorful" and not world.options.progressive_colorful): + progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name + progression_handling = should_split_progression(progression_name, world) + + if progression_handling == ProgressiveItemBehavior.SPLIT: self.set_door_item(room_name, door_data.name, door_data.item_name) - else: + self.real_items.append(door_data.item_name) + elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE: progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name self.set_door_item(room_name, door_data.name, progressive_item_name) self.real_items.append(progressive_item_name) @@ -196,9 +221,8 @@ def __init__(self, world: "LingoWorld"): ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], - ["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], - ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"], - ["Outside The Agreeable", "Tenacious Entrance"] + ["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Entrance"], ["Art Gallery", "Exit"], + ["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"] ] pilgrimage_reqs = AccessRequirements() for door in fake_pilgrimage: diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index 8edc7ce6cce..081d6743a5f 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -96,7 +96,7 @@ def test_item(self): self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name(["Second Room - Exit Door", "Crossroads - Tower Entrance", @@ -105,7 +105,7 @@ def test_item(self): self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) progressive_gallery_room = self.get_items_by_name("Progressive Art Gallery") @@ -115,7 +115,7 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect(progressive_gallery_room[1]) @@ -123,7 +123,7 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect(progressive_gallery_room[2]) @@ -131,7 +131,7 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect(progressive_gallery_room[3]) @@ -139,15 +139,15 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertTrue(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) - self.collect(progressive_gallery_room[4]) + self.collect_by_name("Orange Tower Fifth Floor - Quadruple Intersection") self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertTrue(self.can_reach_location("Art Gallery - ORDER")) self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) @@ -162,7 +162,7 @@ def test_item(self): self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Yellow") @@ -170,7 +170,7 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Brown") @@ -178,7 +178,7 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Blue") @@ -186,7 +186,7 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name(["Orange", "Gray"]) @@ -194,5 +194,5 @@ def test_item(self): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertTrue(self.can_reach_location("Art Gallery - ORDER")) self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb index 3ac49dc220c..96ed9fcd66b 100644 --- a/worlds/lingo/utils/validate_config.rb +++ b/worlds/lingo/utils/validate_config.rb @@ -8,7 +8,8 @@ require 'yaml' configpath = ARGV[0] -mappath = ARGV[1] +idspath = ARGV[1] +mappath = ARGV[2] panels = Set["Countdown Panels/Panel_1234567890_wanderlust"] doors = Set["Naps Room Doors/Door_hider_new1", "Tower Room Area Doors/Door_wanderer_entrance"] @@ -46,6 +47,8 @@ non_counting = 0 +ids = YAML.load_file(idspath) + config = YAML.load_file(configpath) config.each do |room_name, room| configured_rooms.add(room_name) @@ -162,6 +165,10 @@ unless bad_subdirectives.empty? then puts "#{room_name} - #{panel_name} :::: Panel has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end + + unless ids.include?("panels") and ids["panels"].include?(room_name) and ids["panels"][room_name].include?(panel_name) + puts "#{room_name} - #{panel_name} :::: Panel is missing a location ID" + end end (room["doors"] || {}).each do |door_name, door| @@ -229,6 +236,18 @@ unless bad_subdirectives.empty? then puts "#{room_name} - #{door_name} :::: Door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end + + unless door["skip_item"] or door["event"] + unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("item") + puts "#{room_name} - #{door_name} :::: Door is missing an item ID" + end + end + + unless door["skip_location"] or door["event"] + unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("location") + puts "#{room_name} - #{door_name} :::: Door is missing a location ID" + end + end end (room["paintings"] || []).each do |painting| @@ -281,6 +300,10 @@ mentioned_doors.add("#{room_name} - #{door}") end end + + unless ids.include?("progression") and ids["progression"].include?(progression_name) + puts "#{room_name} - #{progression_name} :::: Progression is missing an item ID" + end end end @@ -303,6 +326,10 @@ if num == 1 then puts "Door group \"#{group}\" only has one door in it" end + + unless ids.include?("door_groups") and ids["door_groups"].include?(group) + puts "#{group} :::: Door group is missing an item ID" + end end slashed_rooms = configured_rooms.select do |room| diff --git a/worlds/messenger/test/__init__.py b/worlds/messenger/test/__init__.py index 7ab1e11781d..f3fcd4ae2d6 100644 --- a/worlds/messenger/test/__init__.py +++ b/worlds/messenger/test/__init__.py @@ -1,6 +1,7 @@ from test.TestBase import WorldTestBase +from .. import MessengerWorld class MessengerTestBase(WorldTestBase): game = "The Messenger" - player: int = 1 + world: MessengerWorld diff --git a/worlds/messenger/test/test_locations.py b/worlds/messenger/test/test_locations.py index 0c330be4bd3..627d58c2906 100644 --- a/worlds/messenger/test/test_locations.py +++ b/worlds/messenger/test/test_locations.py @@ -12,5 +12,5 @@ def run_default_tests(self) -> bool: return False def test_locations_exist(self) -> None: - for location in self.multiworld.worlds[1].location_name_to_id: + for location in self.world.location_name_to_id: self.assertIsInstance(self.multiworld.get_location(location, self.player), MessengerLocation) diff --git a/worlds/messenger/test/test_shop.py b/worlds/messenger/test/test_shop.py index afb1b32b88e..ee7e82d6cdb 100644 --- a/worlds/messenger/test/test_shop.py +++ b/worlds/messenger/test/test_shop.py @@ -17,7 +17,7 @@ def test_shop_rules(self) -> None: self.assertFalse(self.can_reach_location(loc)) def test_shop_prices(self) -> None: - prices: Dict[str, int] = self.multiworld.worlds[self.player].shop_prices + prices: Dict[str, int] = self.world.shop_prices for loc, price in prices.items(): with self.subTest("prices", loc=loc): self.assertLessEqual(price, self.multiworld.get_location(f"The Shop - {loc}", self.player).cost) @@ -51,7 +51,7 @@ class ShopCostMinTest(ShopCostTest): } def test_shop_rules(self) -> None: - if self.multiworld.worlds[self.player].total_shards: + if self.world.total_shards: super().test_shop_rules() else: for loc in SHOP_ITEMS: @@ -85,7 +85,7 @@ def test_costs(self) -> None: with self.subTest("has cost", loc=loc): self.assertFalse(self.can_reach_location(loc)) - prices = self.multiworld.worlds[self.player].shop_prices + prices = self.world.shop_prices for loc, price in prices.items(): with self.subTest("prices", loc=loc): if loc == "Karuta Plates": @@ -98,7 +98,7 @@ def test_costs(self) -> None: self.assertTrue(loc.replace("The Shop - ", "") in SHOP_ITEMS) self.assertEqual(len(prices), len(SHOP_ITEMS)) - figures = self.multiworld.worlds[self.player].figurine_prices + figures = self.world.figurine_prices for loc, price in figures.items(): with self.subTest("figure prices", loc=loc): if loc == "Barmath'azel Figurine": diff --git a/worlds/messenger/test/test_shop_chest.py b/worlds/messenger/test/test_shop_chest.py index a34fa0fb96c..f2030c63de9 100644 --- a/worlds/messenger/test/test_shop_chest.py +++ b/worlds/messenger/test/test_shop_chest.py @@ -41,8 +41,8 @@ class HalfSealsRequired(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 45 power seals in the item pool and half that required""" self.assertEqual(self.multiworld.total_seals[self.player], 45) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 45) - self.assertEqual(self.multiworld.worlds[self.player].required_seals, 22) + self.assertEqual(self.world.total_seals, 45) + self.assertEqual(self.world.required_seals, 22) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing] @@ -60,8 +60,8 @@ class ThirtyThirtySeals(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 30 power seals in the pool and 33 percent of that required.""" self.assertEqual(self.multiworld.total_seals[self.player], 30) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 30) - self.assertEqual(self.multiworld.worlds[self.player].required_seals, 10) + self.assertEqual(self.world.total_seals, 30) + self.assertEqual(self.world.required_seals, 10) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing] @@ -78,7 +78,7 @@ class MaxSealsNoShards(MessengerTestBase): def test_seals_amount(self) -> None: """Should set total seals to 70 since shards aren't shuffled.""" self.assertEqual(self.multiworld.total_seals[self.player], 85) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 70) + self.assertEqual(self.world.total_seals, 70) class MaxSealsWithShards(MessengerTestBase): @@ -91,8 +91,8 @@ class MaxSealsWithShards(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 85 seals in the pool with all required and be a valid seed.""" self.assertEqual(self.multiworld.total_seals[self.player], 85) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 85) - self.assertEqual(self.multiworld.worlds[self.player].required_seals, 85) + self.assertEqual(self.world.total_seals, 85) + self.assertEqual(self.world.required_seals, 85) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing] diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 5d50e0db96d..7a7596c096f 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -279,6 +279,7 @@ def create_items(self) -> None: def refresh_tm_choices() -> None: fill_item_candidates_by_category["TM"] = all_tm_choices.copy() self.random.shuffle(fill_item_candidates_by_category["TM"]) + refresh_tm_choices() # Create items for item in default_itempool: diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index ee7cca45361..ad203e1e82f 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -377,13 +377,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # nmg: ice grapple through the big gold door, can do it both ways regions["Eastern Vault Fortress"].connect( connecting_region=regions["Eastern Vault Fortress Gold Door"], - name="Fortress Gold Door", + name="Fortress to Gold Door", rule=lambda state: state.has_all({"Activate Eastern Vault West Fuses", "Activate Eastern Vault East Fuse"}, player) or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) regions["Eastern Vault Fortress Gold Door"].connect( connecting_region=regions["Eastern Vault Fortress"], - name="Fortress Gold Door", + name="Gold Door to Fortress", rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks)) regions["Fortress Grave Path"].connect(