diff --git a/Fill.py b/Fill.py index f32d0ced..935e327f 100644 --- a/Fill.py +++ b/Fill.py @@ -683,6 +683,17 @@ def distribute_planned(world: MultiWorld) -> None: else: warn(warning, force) + swept_state = world.state.copy() + swept_state.sweep_for_events() + reachable = frozenset(world.get_reachable_locations(swept_state)) + early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list) + non_early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list) + for loc in world.get_unfilled_locations(): + if loc in reachable: + early_locations[loc.player].append(loc.name) + else: # not reachable with swept state + non_early_locations[loc.player].append(loc.name) + # TODO: remove. Preferably by implementing key drop from worlds.alttp.Regions import key_drop_data world_name_lookup = world.world_name_lookup @@ -698,7 +709,39 @@ def distribute_planned(world: MultiWorld) -> None: if 'from_pool' not in block: block['from_pool'] = True if 'world' not in block: - block['world'] = False + target_world = False + else: + target_world = block['world'] + + if target_world is False or world.players == 1: # target own world + worlds: typing.Set[int] = {player} + elif target_world is True: # target any worlds besides own + worlds = set(world.player_ids) - {player} + elif target_world is None: # target all worlds + worlds = set(world.player_ids) + elif type(target_world) == list: # list of target worlds + worlds = set() + for listed_world in target_world: + if listed_world not in world_name_lookup: + failed(f"Cannot place item to {target_world}'s world as that world does not exist.", + block['force']) + continue + worlds.add(world_name_lookup[listed_world]) + elif type(target_world) == int: # target world by slot number + if target_world not in range(1, world.players + 1): + failed( + f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})", + block['force']) + continue + worlds = {target_world} + else: # target world by slot name + if target_world not in world_name_lookup: + failed(f"Cannot place item to {target_world}'s world as that world does not exist.", + block['force']) + continue + worlds = {world_name_lookup[target_world]} + block['world'] = worlds + items: block_value = [] if "items" in block: items = block["items"] @@ -735,6 +778,17 @@ def distribute_planned(world: MultiWorld) -> None: for key, value in locations.items(): location_list += [key] * value locations = location_list + + if "early_locations" in locations: + locations.remove("early_locations") + for player in worlds: + locations += early_locations[player] + if "non_early_locations" in locations: + locations.remove("non_early_locations") + for player in worlds: + locations += non_early_locations[player] + + block['locations'] = locations if not block['count']: @@ -770,38 +824,11 @@ def distribute_planned(world: MultiWorld) -> None: for placement in plando_blocks: player = placement['player'] try: - target_world = placement['world'] + worlds = placement['world'] locations = placement['locations'] items = placement['items'] maxcount = placement['count']['target'] from_pool = placement['from_pool'] - if target_world is False or world.players == 1: # target own world - worlds: typing.Set[int] = {player} - elif target_world is True: # target any worlds besides own - worlds = set(world.player_ids) - {player} - elif target_world is None: # target all worlds - worlds = set(world.player_ids) - elif type(target_world) == list: # list of target worlds - worlds = set() - for listed_world in target_world: - if listed_world not in world_name_lookup: - failed(f"Cannot place item to {target_world}'s world as that world does not exist.", - placement['force']) - continue - worlds.add(world_name_lookup[listed_world]) - elif type(target_world) == int: # target world by slot number - if target_world not in range(1, world.players + 1): - failed( - f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})", - placement['force']) - continue - worlds = {target_world} - else: # target world by slot name - if target_world not in world_name_lookup: - failed(f"Cannot place item to {target_world}'s world as that world does not exist.", - placement['force']) - continue - worlds = {world_name_lookup[target_world]} candidates = list(location for location in world.get_unfilled_locations_for_players(locations, worlds)) diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index c9f70fcb..2d40f451 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -56,6 +56,9 @@ list of specific locations both in their own game or in another player's game. * Multi Placement uses a plando block to place multiple items in multiple locations until either list is exhausted. * `items` defines the items to use, each with a number for the amount. Using `true` instead of a number uses however many of that item are in your item pool. * `locations` is a list of possible locations those items can be placed in. + * Some special location group names can be specified: + * `early_locations` will add all sphere 1 locations (locations logically reachable only with your starting inventory) + * `non_early_locations` will add all locations beyond sphere 1 (locations that require finding at least one item before they become logically reachable) * Using the multi placement method, placements are picked randomly. * `count` can be used to set the maximum number of items placed from the block. The default is 1 if using `item` and False if using `items`