diff --git a/BaseClasses.py b/BaseClasses.py index 29264f34..71573258 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -692,17 +692,25 @@ class CollectionState(): def update_reachable_regions(self, player: int): self.stale[player] = False + world: AutoWorld.World = self.multiworld.worlds[player] reachable_regions = self.reachable_regions[player] - blocked_connections = self.blocked_connections[player] queue = deque(self.blocked_connections[player]) - start = self.multiworld.get_region("Menu", player) + start: Region = world.get_region(world.origin_region_name) # init on first call - this can't be done on construction since the regions don't exist yet if start not in reachable_regions: reachable_regions.add(start) - blocked_connections.update(start.exits) + self.blocked_connections[player].update(start.exits) queue.extend(start.exits) + if world.explicit_indirect_conditions: + self._update_reachable_regions_explicit_indirect_conditions(player, queue) + else: + self._update_reachable_regions_auto_indirect_conditions(player, queue) + + def _update_reachable_regions_explicit_indirect_conditions(self, player: int, queue: deque): + reachable_regions = self.reachable_regions[player] + blocked_connections = self.blocked_connections[player] # run BFS on all connections, and keep track of those blocked by missing items while queue: connection = queue.popleft() @@ -722,6 +730,29 @@ class CollectionState(): if new_entrance in blocked_connections and new_entrance not in queue: queue.append(new_entrance) + def _update_reachable_regions_auto_indirect_conditions(self, player: int, queue: deque): + reachable_regions = self.reachable_regions[player] + blocked_connections = self.blocked_connections[player] + new_connection: bool = True + # run BFS on all connections, and keep track of those blocked by missing items + while new_connection: + new_connection = False + while queue: + connection = queue.popleft() + new_region = connection.connected_region + if new_region in reachable_regions: + blocked_connections.remove(connection) + elif connection.can_reach(self): + assert new_region, f"tried to search through an Entrance \"{connection}\" with no Region" + reachable_regions.add(new_region) + blocked_connections.remove(connection) + blocked_connections.update(new_region.exits) + queue.extend(new_region.exits) + self.path[new_region] = (new_region.name, self.path.get(connection, None)) + new_connection = True + # sweep for indirect connections, mostly Entrance.can_reach(unrelated_Region) + queue.extend(blocked_connections) + def copy(self) -> CollectionState: ret = CollectionState(self.multiworld) ret.prog_items = {player: counter.copy() for player, counter in self.prog_items.items()} diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index af067e5c..19ec9a14 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -292,6 +292,14 @@ class World(metaclass=AutoWorldRegister): web: ClassVar[WebWorld] = WebWorld() """see WebWorld for options""" + origin_region_name: str = "Menu" + """Name of the Region from which accessibility is tested.""" + + explicit_indirect_conditions: bool = True + """If True, the world implementation is supposed to use MultiWorld.register_indirect_condition() correctly. + If False, everything is rechecked at every step, which is slower computationally, + but may be desirable in complex/dynamic worlds.""" + multiworld: "MultiWorld" """autoset on creation. The MultiWorld object for the currently generating multiworld.""" player: int diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 1ea2f6e4..753c5672 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -101,6 +101,7 @@ class Factorio(World): tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]] tech_mix: int = 0 skip_silo: bool = False + origin_region_name = "Nauvis" science_locations: typing.List[FactorioScienceLocation] settings: typing.ClassVar[FactorioSettings] @@ -125,9 +126,6 @@ class Factorio(World): def create_regions(self): player = self.player random = self.multiworld.random - menu = Region("Menu", player, self.multiworld) - crash = Entrance(player, "Crash Land", menu) - menu.exits.append(crash) nauvis = Region("Nauvis", player, self.multiworld) location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \ @@ -184,8 +182,7 @@ class Factorio(World): event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player) location.place_locked_item(event) - crash.connect(nauvis) - self.multiworld.regions += [menu, nauvis] + self.multiworld.regions.append(nauvis) def create_items(self) -> None: player = self.player diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index 58d8fa54..c3cf40a7 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -45,7 +45,7 @@ class SubnauticaWorld(World): options_dataclass = options.SubnauticaOptions options: options.SubnauticaOptions required_client_version = (0, 5, 0) - + origin_region_name = "Planet 4546B" creatures_to_scan: List[str] def generate_early(self) -> None: @@ -66,13 +66,9 @@ class SubnauticaWorld(World): creature_pool, self.options.creature_scans.value) def create_regions(self): - # Create Regions - menu_region = Region("Menu", self.player, self.multiworld) + # Create Region planet_region = Region("Planet 4546B", self.player, self.multiworld) - # Link regions together - menu_region.connect(planet_region, "Lifepod 5") - # Create regular locations location_names = itertools.chain((location["name"] for location in locations.location_table.values()), (creature + creatures.suffix for creature in self.creatures_to_scan)) @@ -93,11 +89,8 @@ class SubnauticaWorld(World): # make the goal event the victory "item" location.item.name = "Victory" - # Register regions to multiworld - self.multiworld.regions += [ - menu_region, - planet_region - ] + # Register region to multiworld + self.multiworld.regions.append(planet_region) # refer to rules.py set_rules = set_rules