Core: Fill fix local logic conflict (#1271)

This commit is contained in:
Fabian Dill 2022-11-28 07:03:09 +01:00 committed by GitHub
parent cde2a6e754
commit 1288f15e45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 34 deletions

48
Fill.py
View File

@ -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], def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location],
itempool: typing.List[Item], single_player_placement: bool = False, lock: bool = False, 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] = [] unplaced_items: typing.List[Item] = []
placements: typing.List[Location] = [] placements: typing.List[Location] = []
@ -132,7 +133,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
if on_place: if on_place:
on_place(spot_to_fill) 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 # There are leftover unplaceable items and locations that won't accept them
if world.can_beat_game(): if world.can_beat_game():
logging.warning( logging.warning(
@ -252,11 +253,12 @@ def distribute_early_items(world: MultiWorld,
fill_locations: typing.List[Location], fill_locations: typing.List[Location],
itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]: itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]:
""" returns new fill_locations and itempool """ """ 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: for player in world.player_ids:
items = itertools.chain(world.early_items[player], world.local_early_items[player]) items = itertools.chain(world.early_items[player], world.local_early_items[player])
for item in items: 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: if early_items_count:
early_locations: typing.List[Location] = [] early_locations: typing.List[Location] = []
early_priority_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): for i, item in enumerate(itempool):
if (item.name, item.player) in early_items_count: if (item.name, item.player) in early_items_count:
if item.advancement: 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_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: else:
early_prog_items.append(item) early_prog_items.append(item)
early_items_count[(item.name, item.player)][0] -= 1 early_items_count[item.name, item.player][0] -= 1
else: 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_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: else:
early_rest_items.append(item) 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) item_indexes_to_remove.add(i)
if early_items_count[(item.name, item.player)] == [0, 0]: if early_items_count[item.name, item.player] == [0, 0]:
del early_items_count[(item.name, item.player)] del early_items_count[item.name, item.player]
if len(early_items_count) == 0: if len(early_items_count) == 0:
break break
itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove] itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove]
for player in world.player_ids: for player in world.player_ids:
player_local = early_local_rest_items[player]
fill_restrictive(world, base_state, fill_restrictive(world, base_state,
[loc for loc in early_locations if loc.player == player], [loc for loc in early_locations if loc.player == player],
early_local_rest_items[player], lock=True) 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] 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 early_locations += early_priority_locations
for player in world.player_ids: for player in world.player_ids:
player_local = early_local_prog_items[player]
fill_restrictive(world, base_state, fill_restrictive(world, base_state,
[loc for loc in early_locations if loc.player == player], [loc for loc in early_locations if loc.player == player],
early_local_prog_items[player], lock=True) 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] 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 unplaced_early_items = early_rest_items + early_prog_items
if unplaced_early_items: if unplaced_early_items:
logging.warning("Ran out of early locations for early items. Failed to place " 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 itempool += unplaced_early_items
fill_locations.extend(early_locations) fill_locations.extend(early_locations)

19
Main.py
View File

@ -116,19 +116,6 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
for _ in range(count): for _ in range(count):
world.push_precollected(world.create_item(item_name, player)) 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.') logger.info('Creating World.')
AutoWorld.call_all(world, "create_regions") 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") AutoWorld.call_all(world, "create_items")
logger.info('Calculating Access Rules.') 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: if world.players > 1:
locality_rules(world) locality_rules(world)
else: else:

View File

@ -195,6 +195,14 @@ class ALTTPWorld(World):
world.difficulty_requirements[player] = difficulties[world.difficulty[player]] 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): def create_regions(self):
player = self.player player = self.player
world = self.multiworld world = self.multiworld

View File

@ -1,9 +1,13 @@
import typing import typing
from Options import Choice, Range, DeathLink from Options import Choice, Range, DeathLink, DefaultOnToggle
from .Creatures import all_creatures, Definitions 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): class ItemPool(Choice):
"""Valuable item pool leaves all filler items in their vanilla locations and """Valuable item pool leaves all filler items in their vanilla locations and
creates random duplicates of important items into freed spots.""" creates random duplicates of important items into freed spots."""
@ -75,6 +79,7 @@ class SubnauticaDeathLink(DeathLink):
options = { options = {
"early_seaglide": EarlySeaglide,
"item_pool": ItemPool, "item_pool": ItemPool,
"goal": Goal, "goal": Goal,
"creature_scans": CreatureScans, "creature_scans": CreatureScans,

View File

@ -47,7 +47,8 @@ class SubnauticaWorld(World):
creatures_to_scan: List[str] creatures_to_scan: List[str]
def generate_early(self) -> None: 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] scan_option: Options.AggressiveScanLogic = self.multiworld.creature_scan_logic[self.player]
creature_pool = scan_option.get_pool() creature_pool = scan_option.get_pool()