From 1288f15e454eda58c1f1e798559d602c2e6591dd Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Mon, 28 Nov 2022 07:03:09 +0100 Subject: [PATCH] Core: Fill fix local logic conflict (#1271) --- Fill.py | 48 +++++++++++++++++++++-------------- Main.py | 19 +++++--------- worlds/alttp/__init__.py | 8 ++++++ worlds/subnautica/Options.py | 7 ++++- worlds/subnautica/__init__.py | 3 ++- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/Fill.py b/Fill.py index 935e327f..ac3ae8fc 100644 --- a/Fill.py +++ b/Fill.py @@ -24,7 +24,8 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location], itempool: typing.List[Item], single_player_placement: bool = False, lock: bool = False, - swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None) -> None: + swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None, + allow_partial: bool = False) -> None: unplaced_items: typing.List[Item] = [] placements: typing.List[Location] = [] @@ -132,7 +133,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: if on_place: on_place(spot_to_fill) - if len(unplaced_items) > 0 and len(locations) > 0: + if not allow_partial and len(unplaced_items) > 0 and len(locations) > 0: # There are leftover unplaceable items and locations that won't accept them if world.can_beat_game(): logging.warning( @@ -252,11 +253,12 @@ def distribute_early_items(world: MultiWorld, fill_locations: typing.List[Location], itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]: """ returns new fill_locations and itempool """ - early_items_count: typing.Dict[typing.Tuple[str, int], int] = {} + early_items_count: typing.Dict[typing.Tuple[str, int], typing.List[int]] = {} for player in world.player_ids: items = itertools.chain(world.early_items[player], world.local_early_items[player]) for item in items: - early_items_count[(item, player)] = [world.early_items[player].get(item, 0), world.local_early_items[player].get(item, 0)] + early_items_count[item, player] = [world.early_items[player].get(item, 0), + world.local_early_items[player].get(item, 0)] if early_items_count: early_locations: typing.List[Location] = [] early_priority_locations: typing.List[Location] = [] @@ -280,42 +282,50 @@ def distribute_early_items(world: MultiWorld, for i, item in enumerate(itempool): if (item.name, item.player) in early_items_count: if item.advancement: - if early_items_count[(item.name, item.player)][1]: + if early_items_count[item.name, item.player][1]: early_local_prog_items[item.player].append(item) - early_items_count[(item.name, item.player)][1] -= 1 + early_items_count[item.name, item.player][1] -= 1 else: early_prog_items.append(item) - early_items_count[(item.name, item.player)][0] -= 1 + early_items_count[item.name, item.player][0] -= 1 else: - if early_items_count[(item.name, item.player)][1]: + if early_items_count[item.name, item.player][1]: early_local_rest_items[item.player].append(item) - early_items_count[(item.name, item.player)][1] -= 1 + early_items_count[item.name, item.player][1] -= 1 else: early_rest_items.append(item) - early_items_count[(item.name, item.player)][0] -= 1 + early_items_count[item.name, item.player][0] -= 1 item_indexes_to_remove.add(i) - if early_items_count[(item.name, item.player)] == [0, 0]: - del early_items_count[(item.name, item.player)] + if early_items_count[item.name, item.player] == [0, 0]: + del early_items_count[item.name, item.player] if len(early_items_count) == 0: break itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove] for player in world.player_ids: + player_local = early_local_rest_items[player] fill_restrictive(world, base_state, - [loc for loc in early_locations if loc.player == player], - early_local_rest_items[player], lock=True) + [loc for loc in early_locations if loc.player == player], + player_local, lock=True, allow_partial=True) + if player_local: + logging.warning(f"Could not fulfill rules of early items: {player_local}") + early_rest_items.extend(early_local_rest_items[player]) early_locations = [loc for loc in early_locations if not loc.item] - fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True) + fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True) early_locations += early_priority_locations for player in world.player_ids: + player_local = early_local_prog_items[player] fill_restrictive(world, base_state, - [loc for loc in early_locations if loc.player == player], - early_local_prog_items[player], lock=True) + [loc for loc in early_locations if loc.player == player], + player_local, lock=True, allow_partial=True) + if player_local: + logging.warning(f"Could not fulfill rules of early items: {player_local}") + early_prog_items.extend(player_local) early_locations = [loc for loc in early_locations if not loc.item] - fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True) + fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True) unplaced_early_items = early_rest_items + early_prog_items if unplaced_early_items: logging.warning("Ran out of early locations for early items. Failed to place " - f"{len(unplaced_early_items)} items early.") + f"{unplaced_early_items} early.") itempool += unplaced_early_items fill_locations.extend(early_locations) diff --git a/Main.py b/Main.py index d4df1b18..e77a2ecf 100644 --- a/Main.py +++ b/Main.py @@ -116,19 +116,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for _ in range(count): world.push_precollected(world.create_item(item_name, player)) - for player in world.player_ids: - if player in world.get_game_players("A Link to the Past"): - # enforce pre-defined local items. - if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: - world.local_items[player].value.add('Triforce Piece') - - # Not possible to place pendants/crystals outside boss prizes yet. - world.non_local_items[player].value -= item_name_groups['Pendants'] - world.non_local_items[player].value -= item_name_groups['Crystals'] - - # items can't be both local and non-local, prefer local - world.non_local_items[player].value -= world.local_items[player].value - logger.info('Creating World.') AutoWorld.call_all(world, "create_regions") @@ -136,6 +123,12 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No AutoWorld.call_all(world, "create_items") logger.info('Calculating Access Rules.') + + for player in world.player_ids: + # items can't be both local and non-local, prefer local + world.non_local_items[player].value -= world.local_items[player].value + world.non_local_items[player].value -= set(world.local_early_items[player]) + if world.players > 1: locality_rules(world) else: diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 4517e269..e2965b43 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -195,6 +195,14 @@ class ALTTPWorld(World): world.difficulty_requirements[player] = difficulties[world.difficulty[player]] + # enforce pre-defined local items. + if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: + world.local_items[player].value.add('Triforce Piece') + + # Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too). + world.non_local_items[player].value -= item_name_groups['Pendants'] + world.non_local_items[player].value -= item_name_groups['Crystals'] + def create_regions(self): player = self.player world = self.multiworld diff --git a/worlds/subnautica/Options.py b/worlds/subnautica/Options.py index f5f05d43..23bc09f2 100644 --- a/worlds/subnautica/Options.py +++ b/worlds/subnautica/Options.py @@ -1,9 +1,13 @@ import typing -from Options import Choice, Range, DeathLink +from Options import Choice, Range, DeathLink, DefaultOnToggle from .Creatures import all_creatures, Definitions +class EarlySeaglide(DefaultOnToggle): + """Make sure 2 of the Seaglide Fragments are available in or near the Safe Shallows (Sphere 1 Locations).""" + + class ItemPool(Choice): """Valuable item pool leaves all filler items in their vanilla locations and creates random duplicates of important items into freed spots.""" @@ -75,6 +79,7 @@ class SubnauticaDeathLink(DeathLink): options = { + "early_seaglide": EarlySeaglide, "item_pool": ItemPool, "goal": Goal, "creature_scans": CreatureScans, diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index 12359d40..7f876192 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -47,7 +47,8 @@ class SubnauticaWorld(World): creatures_to_scan: List[str] def generate_early(self) -> None: - self.multiworld.local_early_items[self.player]["Seaglide Fragment"] = 2 + if self.multiworld.early_seaglide: + self.multiworld.local_early_items[self.player]["Seaglide Fragment"] = 2 scan_option: Options.AggressiveScanLogic = self.multiworld.creature_scan_logic[self.player] creature_pool = scan_option.get_pool()