From 281fe01c250015967bc07534320f714d11f3ca95 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Sun, 4 Feb 2024 18:38:00 -0500 Subject: [PATCH] Core: Purge the evil (`world: MultiWorld`) (#2749) * Purge the evil * Some files didn't save * Fix a couple of missed string references * multi_world -> multiworld --- BaseClasses.py | 10 +- Fill.py | 186 ++++++++-------- Main.py | 286 ++++++++++++------------ OoTAdjuster.py | 8 +- Utils.py | 4 +- test/general/test_fill.py | 350 +++++++++++++++--------------- test/general/test_reachability.py | 18 +- 7 files changed, 431 insertions(+), 431 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 38598d42d999..39f822668c45 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -823,8 +823,8 @@ def __repr__(self): return self.__str__() def __str__(self): - world = self.parent_region.multiworld if self.parent_region else None - return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' + multiworld = self.parent_region.multiworld if self.parent_region else None + return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})' class Region: @@ -1040,8 +1040,8 @@ def __repr__(self): return self.__str__() def __str__(self): - world = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None - return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})' + multiworld = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None + return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})' def __hash__(self): return hash((self.name, self.player)) @@ -1175,7 +1175,7 @@ def set_entrance(self, entrance: str, exit_: str, direction: str, player: int) - {"player": player, "entrance": entrance, "exit": exit_, "direction": direction} def create_playthrough(self, create_paths: bool = True) -> None: - """Destructive to the world while it is run, damage gets repaired afterwards.""" + """Destructive to the multiworld while it is run, damage gets repaired afterwards.""" from itertools import chain # get locations containing progress items multiworld = self.multiworld diff --git a/Fill.py b/Fill.py index 525d27d3388e..97ce4cbdb57c 100644 --- a/Fill.py +++ b/Fill.py @@ -27,12 +27,12 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item] return new_state -def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location], +def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locations: typing.List[Location], item_pool: typing.List[Item], single_player_placement: bool = False, lock: bool = False, swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None, allow_partial: bool = False, allow_excluded: bool = False, name: str = "Unknown") -> None: """ - :param world: Multiworld to be filled. + :param multiworld: Multiworld to be filled. :param base_state: State assumed before fill. :param locations: Locations to be filled with item_pool :param item_pool: Items to fill into the locations @@ -68,7 +68,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: maximum_exploration_state = sweep_from_pool( base_state, item_pool + unplaced_items) - has_beaten_game = world.has_beaten_game(maximum_exploration_state) + has_beaten_game = multiworld.has_beaten_game(maximum_exploration_state) while items_to_place: # if we have run out of locations to fill,break out of this loop @@ -80,8 +80,8 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: spot_to_fill: typing.Optional[Location] = None # if minimal accessibility, only check whether location is reachable if game not beatable - if world.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal: - perform_access_check = not world.has_beaten_game(maximum_exploration_state, + if multiworld.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal: + perform_access_check = not multiworld.has_beaten_game(maximum_exploration_state, item_to_place.player) \ if single_player_placement else not has_beaten_game else: @@ -122,11 +122,11 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: # Verify placing this item won't reduce available locations, which would be a useless swap. prev_state = swap_state.copy() prev_loc_count = len( - world.get_reachable_locations(prev_state)) + multiworld.get_reachable_locations(prev_state)) swap_state.collect(item_to_place, True) new_loc_count = len( - world.get_reachable_locations(swap_state)) + multiworld.get_reachable_locations(swap_state)) if new_loc_count >= prev_loc_count: # Add this item to the existing placement, and @@ -156,7 +156,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: else: unplaced_items.append(item_to_place) continue - world.push_item(spot_to_fill, item_to_place, False) + multiworld.push_item(spot_to_fill, item_to_place, False) spot_to_fill.locked = lock placements.append(spot_to_fill) spot_to_fill.event = item_to_place.advancement @@ -173,7 +173,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: # validate all placements and remove invalid ones state = sweep_from_pool(base_state, []) for placement in placements: - if world.accessibility[placement.item.player] != "minimal" and not placement.can_reach(state): + if multiworld.accessibility[placement.item.player] != "minimal" and not placement.can_reach(state): placement.item.location = None unplaced_items.append(placement.item) placement.item = None @@ -188,7 +188,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: if excluded_locations: for location in excluded_locations: location.progress_type = location.progress_type.DEFAULT - fill_restrictive(world, base_state, excluded_locations, unplaced_items, single_player_placement, lock, + fill_restrictive(multiworld, base_state, excluded_locations, unplaced_items, single_player_placement, lock, swap, on_place, allow_partial, False) for location in excluded_locations: if not location.item: @@ -196,7 +196,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: 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(): + if multiworld.can_beat_game(): logging.warning( f'Not all items placed. Game beatable anyway. (Could not place {unplaced_items})') else: @@ -206,7 +206,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: item_pool.extend(unplaced_items) -def remaining_fill(world: MultiWorld, +def remaining_fill(multiworld: MultiWorld, locations: typing.List[Location], itempool: typing.List[Item]) -> None: unplaced_items: typing.List[Item] = [] @@ -261,7 +261,7 @@ def remaining_fill(world: MultiWorld, unplaced_items.append(item_to_place) continue - world.push_item(spot_to_fill, item_to_place, False) + multiworld.push_item(spot_to_fill, item_to_place, False) placements.append(spot_to_fill) placed += 1 if not placed % 1000: @@ -278,19 +278,19 @@ def remaining_fill(world: MultiWorld, itempool.extend(unplaced_items) -def fast_fill(world: MultiWorld, +def fast_fill(multiworld: MultiWorld, item_pool: typing.List[Item], fill_locations: typing.List[Location]) -> typing.Tuple[typing.List[Item], typing.List[Location]]: placing = min(len(item_pool), len(fill_locations)) for item, location in zip(item_pool, fill_locations): - world.push_item(location, item, False) + multiworld.push_item(location, item, False) return item_pool[placing:], fill_locations[placing:] -def accessibility_corrections(world: MultiWorld, state: CollectionState, locations, pool=[]): +def accessibility_corrections(multiworld: MultiWorld, state: CollectionState, locations, pool=[]): maximum_exploration_state = sweep_from_pool(state, pool) - minimal_players = {player for player in world.player_ids if world.worlds[player].options.accessibility == "minimal"} - unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and + minimal_players = {player for player in multiworld.player_ids if multiworld.worlds[player].options.accessibility == "minimal"} + unreachable_locations = [location for location in multiworld.get_locations() if location.player in minimal_players and not location.can_reach(maximum_exploration_state)] for location in unreachable_locations: if (location.item is not None and location.item.advancement and location.address is not None and not @@ -304,36 +304,36 @@ def accessibility_corrections(world: MultiWorld, state: CollectionState, locatio locations.append(location) if pool and locations: locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY) - fill_restrictive(world, state, locations, pool, name="Accessibility Corrections") + fill_restrictive(multiworld, state, locations, pool, name="Accessibility Corrections") -def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations): +def inaccessible_location_rules(multiworld: MultiWorld, state: CollectionState, locations): maximum_exploration_state = sweep_from_pool(state) unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)] if unreachable_locations: def forbid_important_item_rule(item: Item): - return not ((item.classification & 0b0011) and world.worlds[item.player].options.accessibility != 'minimal') + return not ((item.classification & 0b0011) and multiworld.worlds[item.player].options.accessibility != 'minimal') for location in unreachable_locations: add_item_rule(location, forbid_important_item_rule) -def distribute_early_items(world: MultiWorld, +def distribute_early_items(multiworld: 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], typing.List[int]] = {} - for player in world.player_ids: - items = itertools.chain(world.early_items[player], world.local_early_items[player]) + for player in multiworld.player_ids: + items = itertools.chain(multiworld.early_items[player], multiworld.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] = [multiworld.early_items[player].get(item, 0), + multiworld.local_early_items[player].get(item, 0)] if early_items_count: early_locations: typing.List[Location] = [] early_priority_locations: typing.List[Location] = [] loc_indexes_to_remove: typing.Set[int] = set() - base_state = world.state.copy() - base_state.sweep_for_events(locations=(loc for loc in world.get_filled_locations() if loc.address is None)) + base_state = multiworld.state.copy() + base_state.sweep_for_events(locations=(loc for loc in multiworld.get_filled_locations() if loc.address is None)) for i, loc in enumerate(fill_locations): if loc.can_reach(base_state): if loc.progress_type == LocationProgressType.PRIORITY: @@ -345,8 +345,8 @@ def distribute_early_items(world: MultiWorld, early_prog_items: typing.List[Item] = [] early_rest_items: typing.List[Item] = [] - early_local_prog_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in world.player_ids} - early_local_rest_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in world.player_ids} + early_local_prog_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in multiworld.player_ids} + early_local_rest_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in multiworld.player_ids} item_indexes_to_remove: typing.Set[int] = set() for i, item in enumerate(itempool): if (item.name, item.player) in early_items_count: @@ -370,28 +370,28 @@ def distribute_early_items(world: MultiWorld, 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: + for player in multiworld.player_ids: player_local = early_local_rest_items[player] - fill_restrictive(world, base_state, + fill_restrictive(multiworld, base_state, [loc for loc in early_locations if loc.player == player], player_local, lock=True, allow_partial=True, name=f"Local Early Items P{player}") 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, allow_partial=True, + fill_restrictive(multiworld, base_state, early_locations, early_rest_items, lock=True, allow_partial=True, name="Early Items") early_locations += early_priority_locations - for player in world.player_ids: + for player in multiworld.player_ids: player_local = early_local_prog_items[player] - fill_restrictive(world, base_state, + fill_restrictive(multiworld, base_state, [loc for loc in early_locations if loc.player == player], player_local, lock=True, allow_partial=True, name=f"Local Early Progression P{player}") 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, allow_partial=True, + fill_restrictive(multiworld, base_state, early_locations, early_prog_items, lock=True, allow_partial=True, name="Early Progression") unplaced_early_items = early_rest_items + early_prog_items if unplaced_early_items: @@ -400,18 +400,18 @@ def distribute_early_items(world: MultiWorld, itempool += unplaced_early_items fill_locations.extend(early_locations) - world.random.shuffle(fill_locations) + multiworld.random.shuffle(fill_locations) return fill_locations, itempool -def distribute_items_restrictive(world: MultiWorld) -> None: - fill_locations = sorted(world.get_unfilled_locations()) - world.random.shuffle(fill_locations) +def distribute_items_restrictive(multiworld: MultiWorld) -> None: + fill_locations = sorted(multiworld.get_unfilled_locations()) + multiworld.random.shuffle(fill_locations) # get items to distribute - itempool = sorted(world.itempool) - world.random.shuffle(itempool) + itempool = sorted(multiworld.itempool) + multiworld.random.shuffle(itempool) - fill_locations, itempool = distribute_early_items(world, fill_locations, itempool) + fill_locations, itempool = distribute_early_items(multiworld, fill_locations, itempool) progitempool: typing.List[Item] = [] usefulitempool: typing.List[Item] = [] @@ -425,7 +425,7 @@ def distribute_items_restrictive(world: MultiWorld) -> None: else: filleritempool.append(item) - call_all(world, "fill_hook", progitempool, usefulitempool, filleritempool, fill_locations) + call_all(multiworld, "fill_hook", progitempool, usefulitempool, filleritempool, fill_locations) locations: typing.Dict[LocationProgressType, typing.List[Location]] = { loc_type: [] for loc_type in LocationProgressType} @@ -446,34 +446,34 @@ def mark_for_locking(location: Location): if prioritylocations: # "priority fill" - fill_restrictive(world, world.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking, + fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking, name="Priority") - accessibility_corrections(world, world.state, prioritylocations, progitempool) + accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool) defaultlocations = prioritylocations + defaultlocations if progitempool: # "advancement/progression fill" - fill_restrictive(world, world.state, defaultlocations, progitempool, name="Progression") + fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, name="Progression") if progitempool: raise FillError( f'Not enough locations for progress items. There are {len(progitempool)} more items than locations') - accessibility_corrections(world, world.state, defaultlocations) + accessibility_corrections(multiworld, multiworld.state, defaultlocations) for location in lock_later: if location.item: location.locked = True del mark_for_locking, lock_later - inaccessible_location_rules(world, world.state, defaultlocations) + inaccessible_location_rules(multiworld, multiworld.state, defaultlocations) - remaining_fill(world, excludedlocations, filleritempool) + remaining_fill(multiworld, excludedlocations, filleritempool) if excludedlocations: raise FillError( f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items") restitempool = filleritempool + usefulitempool - remaining_fill(world, defaultlocations, restitempool) + remaining_fill(multiworld, defaultlocations, restitempool) unplaced = restitempool unfilled = defaultlocations @@ -481,40 +481,40 @@ def mark_for_locking(location: Location): if unplaced or unfilled: logging.warning( f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}') - items_counter = Counter(location.item.player for location in world.get_locations() if location.item) - locations_counter = Counter(location.player for location in world.get_locations()) + items_counter = Counter(location.item.player for location in multiworld.get_locations() if location.item) + locations_counter = Counter(location.player for location in multiworld.get_locations()) items_counter.update(item.player for item in unplaced) locations_counter.update(location.player for location in unfilled) print_data = {"items": items_counter, "locations": locations_counter} logging.info(f'Per-Player counts: {print_data})') -def flood_items(world: MultiWorld) -> None: +def flood_items(multiworld: MultiWorld) -> None: # get items to distribute - world.random.shuffle(world.itempool) - itempool = world.itempool + multiworld.random.shuffle(multiworld.itempool) + itempool = multiworld.itempool progress_done = False # sweep once to pick up preplaced items - world.state.sweep_for_events() + multiworld.state.sweep_for_events() - # fill world from top of itempool while we can + # fill multiworld from top of itempool while we can while not progress_done: - location_list = world.get_unfilled_locations() - world.random.shuffle(location_list) + location_list = multiworld.get_unfilled_locations() + multiworld.random.shuffle(location_list) spot_to_fill = None for location in location_list: - if location.can_fill(world.state, itempool[0]): + if location.can_fill(multiworld.state, itempool[0]): spot_to_fill = location break if spot_to_fill: item = itempool.pop(0) - world.push_item(spot_to_fill, item, True) + multiworld.push_item(spot_to_fill, item, True) continue # ran out of spots, check if we need to step in and correct things - if len(world.get_reachable_locations()) == len(world.get_locations()): + if len(multiworld.get_reachable_locations()) == len(multiworld.get_locations()): progress_done = True continue @@ -524,7 +524,7 @@ def flood_items(world: MultiWorld) -> None: for item in itempool: if item.advancement: candidate_item_to_place = item - if world.unlocks_new_location(item): + if multiworld.unlocks_new_location(item): item_to_place = item break @@ -537,15 +537,15 @@ def flood_items(world: MultiWorld) -> None: raise FillError('No more progress items left to place.') # find item to replace with progress item - location_list = world.get_reachable_locations() - world.random.shuffle(location_list) + location_list = multiworld.get_reachable_locations() + multiworld.random.shuffle(location_list) for location in location_list: if location.item is not None and not location.item.advancement: # safe to replace replace_item = location.item replace_item.location = None itempool.append(replace_item) - world.push_item(location, item_to_place, True) + multiworld.push_item(location, item_to_place, True) itempool.remove(item_to_place) break @@ -755,7 +755,7 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked: location_1.event, location_2.event = location_2.event, location_1.event -def distribute_planned(world: MultiWorld) -> None: +def distribute_planned(multiworld: MultiWorld) -> None: def warn(warning: str, force: typing.Union[bool, str]) -> None: if force in [True, 'fail', 'failure', 'none', False, 'warn', 'warning']: logging.warning(f'{warning}') @@ -768,24 +768,24 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: else: warn(warning, force) - swept_state = world.state.copy() + swept_state = multiworld.state.copy() swept_state.sweep_for_events() - reachable = frozenset(world.get_reachable_locations(swept_state)) + reachable = frozenset(multiworld.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(): + for loc in multiworld.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) - world_name_lookup = world.world_name_lookup + world_name_lookup = multiworld.world_name_lookup block_value = typing.Union[typing.List[str], typing.Dict[str, typing.Any], str] plando_blocks: typing.List[typing.Dict[str, typing.Any]] = [] - player_ids = set(world.player_ids) + player_ids = set(multiworld.player_ids) for player in player_ids: - for block in world.plando_items[player]: + for block in multiworld.plando_items[player]: block['player'] = player if 'force' not in block: block['force'] = 'silent' @@ -799,12 +799,12 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: else: target_world = block['world'] - if target_world is False or world.players == 1: # target own world + if target_world is False or multiworld.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} + worlds = set(multiworld.player_ids) - {player} elif target_world is None: # target all worlds - worlds = set(world.player_ids) + worlds = set(multiworld.player_ids) elif type(target_world) == list: # list of target worlds worlds = set() for listed_world in target_world: @@ -814,9 +814,9 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: 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): + if target_world not in range(1, multiworld.players + 1): failed( - f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})", + f"Cannot place item in world {target_world} as it is not in range of (1, {multiworld.players})", block['force']) continue worlds = {target_world} @@ -844,7 +844,7 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: item_list: typing.List[str] = [] for key, value in items.items(): if value is True: - value = world.itempool.count(world.worlds[player].create_item(key)) + value = multiworld.itempool.count(multiworld.worlds[player].create_item(key)) item_list += [key] * value items = item_list if isinstance(items, str): @@ -894,17 +894,17 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: count = block['count'] failed(f"Plando count {count} greater than locations specified", block['force']) block['count'] = len(block['locations']) - block['count']['target'] = world.random.randint(block['count']['min'], block['count']['max']) + block['count']['target'] = multiworld.random.randint(block['count']['min'], block['count']['max']) if block['count']['target'] > 0: plando_blocks.append(block) # shuffle, but then sort blocks by number of locations minus number of items, # so less-flexible blocks get priority - world.random.shuffle(plando_blocks) + multiworld.random.shuffle(plando_blocks) plando_blocks.sort(key=lambda block: (len(block['locations']) - block['count']['target'] if len(block['locations']) > 0 - else len(world.get_unfilled_locations(player)) - block['count']['target'])) + else len(multiworld.get_unfilled_locations(player)) - block['count']['target'])) for placement in plando_blocks: player = placement['player'] @@ -915,19 +915,19 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: maxcount = placement['count']['target'] from_pool = placement['from_pool'] - candidates = list(world.get_unfilled_locations_for_players(locations, sorted(worlds))) - world.random.shuffle(candidates) - world.random.shuffle(items) + candidates = list(multiworld.get_unfilled_locations_for_players(locations, sorted(worlds))) + multiworld.random.shuffle(candidates) + multiworld.random.shuffle(items) count = 0 err: typing.List[str] = [] successful_pairs: typing.List[typing.Tuple[Item, Location]] = [] for item_name in items: - item = world.worlds[player].create_item(item_name) + item = multiworld.worlds[player].create_item(item_name) for location in reversed(candidates): if (location.address is None) == (item.code is None): # either both None or both not None if not location.item: if location.item_rule(item): - if location.can_fill(world.state, item, False): + if location.can_fill(multiworld.state, item, False): successful_pairs.append((item, location)) candidates.remove(location) count = count + 1 @@ -945,21 +945,21 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None: if count < placement['count']['min']: m = placement['count']['min'] failed( - f"Plando block failed to place {m - count} of {m} item(s) for {world.player_name[player]}, error(s): {' '.join(err)}", + f"Plando block failed to place {m - count} of {m} item(s) for {multiworld.player_name[player]}, error(s): {' '.join(err)}", placement['force']) for (item, location) in successful_pairs: - world.push_item(location, item, collect=False) + multiworld.push_item(location, item, collect=False) location.event = True # flag location to be checked during fill location.locked = True logging.debug(f"Plando placed {item} at {location}") if from_pool: try: - world.itempool.remove(item) + multiworld.itempool.remove(item) except ValueError: warn( - f"Could not remove {item} from pool for {world.player_name[player]} as it's already missing from it.", + f"Could not remove {item} from pool for {multiworld.player_name[player]} as it's already missing from it.", placement['force']) except Exception as e: raise Exception( - f"Error running plando for player {player} ({world.player_name[player]})") from e + f"Error running plando for player {player} ({multiworld.player_name[player]})") from e diff --git a/Main.py b/Main.py index e49d8e781df9..f1d2f63692d6 100644 --- a/Main.py +++ b/Main.py @@ -30,49 +30,49 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No output_path.cached_path = args.outputpath start = time.perf_counter() - # initialize the world - world = MultiWorld(args.multi) + # initialize the multiworld + multiworld = MultiWorld(args.multi) logger = logging.getLogger() - world.set_seed(seed, args.race, str(args.outputname) if args.outputname else None) - world.plando_options = args.plando_options - - world.shuffle = args.shuffle.copy() - world.logic = args.logic.copy() - world.mode = args.mode.copy() - world.difficulty = args.difficulty.copy() - world.item_functionality = args.item_functionality.copy() - world.timer = args.timer.copy() - world.goal = args.goal.copy() - world.boss_shuffle = args.shufflebosses.copy() - world.enemy_health = args.enemy_health.copy() - world.enemy_damage = args.enemy_damage.copy() - world.beemizer_total_chance = args.beemizer_total_chance.copy() - world.beemizer_trap_chance = args.beemizer_trap_chance.copy() - world.countdown_start_time = args.countdown_start_time.copy() - world.red_clock_time = args.red_clock_time.copy() - world.blue_clock_time = args.blue_clock_time.copy() - world.green_clock_time = args.green_clock_time.copy() - world.dungeon_counters = args.dungeon_counters.copy() - world.triforce_pieces_available = args.triforce_pieces_available.copy() - world.triforce_pieces_required = args.triforce_pieces_required.copy() - world.shop_shuffle = args.shop_shuffle.copy() - world.shuffle_prizes = args.shuffle_prizes.copy() - world.sprite_pool = args.sprite_pool.copy() - world.dark_room_logic = args.dark_room_logic.copy() - world.plando_items = args.plando_items.copy() - world.plando_texts = args.plando_texts.copy() - world.plando_connections = args.plando_connections.copy() - world.required_medallions = args.required_medallions.copy() - world.game = args.game.copy() - world.player_name = args.name.copy() - world.sprite = args.sprite.copy() - world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. - - world.set_options(args) - world.set_item_links() - world.state = CollectionState(world) - logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) + multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None) + multiworld.plando_options = args.plando_options + + multiworld.shuffle = args.shuffle.copy() + multiworld.logic = args.logic.copy() + multiworld.mode = args.mode.copy() + multiworld.difficulty = args.difficulty.copy() + multiworld.item_functionality = args.item_functionality.copy() + multiworld.timer = args.timer.copy() + multiworld.goal = args.goal.copy() + multiworld.boss_shuffle = args.shufflebosses.copy() + multiworld.enemy_health = args.enemy_health.copy() + multiworld.enemy_damage = args.enemy_damage.copy() + multiworld.beemizer_total_chance = args.beemizer_total_chance.copy() + multiworld.beemizer_trap_chance = args.beemizer_trap_chance.copy() + multiworld.countdown_start_time = args.countdown_start_time.copy() + multiworld.red_clock_time = args.red_clock_time.copy() + multiworld.blue_clock_time = args.blue_clock_time.copy() + multiworld.green_clock_time = args.green_clock_time.copy() + multiworld.dungeon_counters = args.dungeon_counters.copy() + multiworld.triforce_pieces_available = args.triforce_pieces_available.copy() + multiworld.triforce_pieces_required = args.triforce_pieces_required.copy() + multiworld.shop_shuffle = args.shop_shuffle.copy() + multiworld.shuffle_prizes = args.shuffle_prizes.copy() + multiworld.sprite_pool = args.sprite_pool.copy() + multiworld.dark_room_logic = args.dark_room_logic.copy() + multiworld.plando_items = args.plando_items.copy() + multiworld.plando_texts = args.plando_texts.copy() + multiworld.plando_connections = args.plando_connections.copy() + multiworld.required_medallions = args.required_medallions.copy() + multiworld.game = args.game.copy() + multiworld.player_name = args.name.copy() + multiworld.sprite = args.sprite.copy() + multiworld.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. + + multiworld.set_options(args) + multiworld.set_item_links() + multiworld.state = CollectionState(multiworld) + logger.info('Archipelago Version %s - Seed: %s\n', __version__, multiworld.seed) logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:") longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types) @@ -103,93 +103,93 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No # This assertion method should not be necessary to run if we are not outputting any multidata. if not args.skip_output: - AutoWorld.call_stage(world, "assert_generate") + AutoWorld.call_stage(multiworld, "assert_generate") - AutoWorld.call_all(world, "generate_early") + AutoWorld.call_all(multiworld, "generate_early") logger.info('') - for player in world.player_ids: - for item_name, count in world.worlds[player].options.start_inventory.value.items(): + for player in multiworld.player_ids: + for item_name, count in multiworld.worlds[player].options.start_inventory.value.items(): for _ in range(count): - world.push_precollected(world.create_item(item_name, player)) + multiworld.push_precollected(multiworld.create_item(item_name, player)) - for item_name, count in getattr(world.worlds[player].options, + for item_name, count in getattr(multiworld.worlds[player].options, "start_inventory_from_pool", StartInventoryPool({})).value.items(): for _ in range(count): - world.push_precollected(world.create_item(item_name, player)) + multiworld.push_precollected(multiworld.create_item(item_name, player)) # remove from_pool items also from early items handling, as starting is plenty early. - early = world.early_items[player].get(item_name, 0) + early = multiworld.early_items[player].get(item_name, 0) if early: - world.early_items[player][item_name] = max(0, early-count) + multiworld.early_items[player][item_name] = max(0, early-count) remaining_count = count-early if remaining_count > 0: - local_early = world.early_local_items[player].get(item_name, 0) + local_early = multiworld.early_local_items[player].get(item_name, 0) if local_early: - world.early_items[player][item_name] = max(0, local_early - remaining_count) + multiworld.early_items[player][item_name] = max(0, local_early - remaining_count) del local_early del early - logger.info('Creating World.') - AutoWorld.call_all(world, "create_regions") + logger.info('Creating MultiWorld.') + AutoWorld.call_all(multiworld, "create_regions") logger.info('Creating Items.') - AutoWorld.call_all(world, "create_items") + AutoWorld.call_all(multiworld, "create_items") logger.info('Calculating Access Rules.') - for player in world.player_ids: + for player in multiworld.player_ids: # items can't be both local and non-local, prefer local - world.worlds[player].options.non_local_items.value -= world.worlds[player].options.local_items.value - world.worlds[player].options.non_local_items.value -= set(world.local_early_items[player]) + multiworld.worlds[player].options.non_local_items.value -= multiworld.worlds[player].options.local_items.value + multiworld.worlds[player].options.non_local_items.value -= set(multiworld.local_early_items[player]) - AutoWorld.call_all(world, "set_rules") + AutoWorld.call_all(multiworld, "set_rules") - for player in world.player_ids: - exclusion_rules(world, player, world.worlds[player].options.exclude_locations.value) - world.worlds[player].options.priority_locations.value -= world.worlds[player].options.exclude_locations.value - for location_name in world.worlds[player].options.priority_locations.value: + for player in multiworld.player_ids: + exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value) + multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value + for location_name in multiworld.worlds[player].options.priority_locations.value: try: - location = world.get_location(location_name, player) + location = multiworld.get_location(location_name, player) except KeyError as e: # failed to find the given location. Check if it's a legitimate location - if location_name not in world.worlds[player].location_name_to_id: + if location_name not in multiworld.worlds[player].location_name_to_id: raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e else: location.progress_type = LocationProgressType.PRIORITY # Set local and non-local item rules. - if world.players > 1: - locality_rules(world) + if multiworld.players > 1: + locality_rules(multiworld) else: - world.worlds[1].options.non_local_items.value = set() - world.worlds[1].options.local_items.value = set() + multiworld.worlds[1].options.non_local_items.value = set() + multiworld.worlds[1].options.local_items.value = set() - AutoWorld.call_all(world, "generate_basic") + AutoWorld.call_all(multiworld, "generate_basic") # remove starting inventory from pool items. # Because some worlds don't actually create items during create_items this has to be as late as possible. - if any(getattr(world.worlds[player].options, "start_inventory_from_pool", None) for player in world.player_ids): + if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids): new_items: List[Item] = [] depletion_pool: Dict[int, Dict[str, int]] = { - player: getattr(world.worlds[player].options, + player: getattr(multiworld.worlds[player].options, "start_inventory_from_pool", StartInventoryPool({})).value.copy() - for player in world.player_ids + for player in multiworld.player_ids } for player, items in depletion_pool.items(): - player_world: AutoWorld.World = world.worlds[player] + player_world: AutoWorld.World = multiworld.worlds[player] for count in items.values(): for _ in range(count): new_items.append(player_world.create_filler()) target: int = sum(sum(items.values()) for items in depletion_pool.values()) - for i, item in enumerate(world.itempool): + for i, item in enumerate(multiworld.itempool): if depletion_pool[item.player].get(item.name, 0): target -= 1 depletion_pool[item.player][item.name] -= 1 # quick abort if we have found all items if not target: - new_items.extend(world.itempool[i+1:]) + new_items.extend(multiworld.itempool[i+1:]) break else: new_items.append(item) @@ -199,19 +199,19 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No for player, remaining_items in depletion_pool.items(): remaining_items = {name: count for name, count in remaining_items.items() if count} if remaining_items: - raise Exception(f"{world.get_player_name(player)}" + raise Exception(f"{multiworld.get_player_name(player)}" f" is trying to remove items from their pool that don't exist: {remaining_items}") - assert len(world.itempool) == len(new_items), "Item Pool amounts should not change." - world.itempool[:] = new_items + assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change." + multiworld.itempool[:] = new_items # temporary home for item links, should be moved out of Main - for group_id, group in world.groups.items(): + for group_id, group in multiworld.groups.items(): def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] ]: classifications: Dict[str, int] = collections.defaultdict(int) counters = {player: {name: 0 for name in shared_pool} for player in players} - for item in world.itempool: + for item in multiworld.itempool: if item.player in counters and item.name in shared_pool: counters[item.player][item.name] += 1 classifications[item.name] |= item.classification @@ -246,13 +246,13 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ new_item.classification |= classifications[item_name] new_itempool.append(new_item) - region = Region("Menu", group_id, world, "ItemLink") - world.regions.append(region) + region = Region("Menu", group_id, multiworld, "ItemLink") + multiworld.regions.append(region) locations = region.locations - for item in world.itempool: + for item in multiworld.itempool: count = common_item_count.get(item.player, {}).get(item.name, 0) if count: - loc = Location(group_id, f"Item Link: {item.name} -> {world.player_name[item.player]} {count}", + loc = Location(group_id, f"Item Link: {item.name} -> {multiworld.player_name[item.player]} {count}", None, region) loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ state.has(item_name, group_id_, count_) @@ -263,10 +263,10 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ else: new_itempool.append(item) - itemcount = len(world.itempool) - world.itempool = new_itempool + itemcount = len(multiworld.itempool) + multiworld.itempool = new_itempool - while itemcount > len(world.itempool): + while itemcount > len(multiworld.itempool): items_to_add = [] for player in group["players"]: if group["link_replacement"]: @@ -274,64 +274,64 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[ else: item_player = player if group["replacement_items"][player]: - items_to_add.append(AutoWorld.call_single(world, "create_item", item_player, + items_to_add.append(AutoWorld.call_single(multiworld, "create_item", item_player, group["replacement_items"][player])) else: - items_to_add.append(AutoWorld.call_single(world, "create_filler", item_player)) - world.random.shuffle(items_to_add) - world.itempool.extend(items_to_add[:itemcount - len(world.itempool)]) + items_to_add.append(AutoWorld.call_single(multiworld, "create_filler", item_player)) + multiworld.random.shuffle(items_to_add) + multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)]) - if any(world.item_links.values()): - world._all_state = None + if any(multiworld.item_links.values()): + multiworld._all_state = None logger.info("Running Item Plando.") - distribute_planned(world) + distribute_planned(multiworld) logger.info('Running Pre Main Fill.') - AutoWorld.call_all(world, "pre_fill") + AutoWorld.call_all(multiworld, "pre_fill") - logger.info(f'Filling the world with {len(world.itempool)} items.') + logger.info(f'Filling the multiworld with {len(multiworld.itempool)} items.') - if world.algorithm == 'flood': - flood_items(world) # different algo, biased towards early game progress items - elif world.algorithm == 'balanced': - distribute_items_restrictive(world) + if multiworld.algorithm == 'flood': + flood_items(multiworld) # different algo, biased towards early game progress items + elif multiworld.algorithm == 'balanced': + distribute_items_restrictive(multiworld) - AutoWorld.call_all(world, 'post_fill') + AutoWorld.call_all(multiworld, 'post_fill') - if world.players > 1 and not args.skip_prog_balancing: - balance_multiworld_progression(world) + if multiworld.players > 1 and not args.skip_prog_balancing: + balance_multiworld_progression(multiworld) else: logger.info("Progression balancing skipped.") # we're about to output using multithreading, so we're removing the global random state to prevent accidental use - world.random.passthrough = False + multiworld.random.passthrough = False if args.skip_output: logger.info('Done. Skipped output/spoiler generation. Total Time: %s', time.perf_counter() - start) - return world + return multiworld logger.info(f'Beginning output...') - outfilebase = 'AP_' + world.seed_name + outfilebase = 'AP_' + multiworld.seed_name output = tempfile.TemporaryDirectory() with output as temp_dir: - output_players = [player for player in world.player_ids if AutoWorld.World.generate_output.__code__ - is not world.worlds[player].generate_output.__code__] + output_players = [player for player in multiworld.player_ids if AutoWorld.World.generate_output.__code__ + is not multiworld.worlds[player].generate_output.__code__] with concurrent.futures.ThreadPoolExecutor(len(output_players) + 2) as pool: - check_accessibility_task = pool.submit(world.fulfills_accessibility) + check_accessibility_task = pool.submit(multiworld.fulfills_accessibility) - output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)] + output_file_futures = [pool.submit(AutoWorld.call_stage, multiworld, "generate_output", temp_dir)] for player in output_players: # skip starting a thread for methods that say "pass". output_file_futures.append( - pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir)) + pool.submit(AutoWorld.call_single, multiworld, "generate_output", player, temp_dir)) # collect ER hint info er_hint_data: Dict[int, Dict[int, str]] = {} - AutoWorld.call_all(world, 'extend_hint_information', er_hint_data) + AutoWorld.call_all(multiworld, 'extend_hint_information', er_hint_data) def write_multidata(): import NetUtils @@ -340,38 +340,38 @@ def write_multidata(): games = {} minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions} slot_info = {} - names = [[name for player, name in sorted(world.player_name.items())]] - for slot in world.player_ids: - player_world: AutoWorld.World = world.worlds[slot] + names = [[name for player, name in sorted(multiworld.player_name.items())]] + for slot in multiworld.player_ids: + player_world: AutoWorld.World = multiworld.worlds[slot] minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version) client_versions[slot] = player_world.required_client_version - games[slot] = world.game[slot] - slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], world.game[slot], - world.player_types[slot]) - for slot, group in world.groups.items(): - games[slot] = world.game[slot] - slot_info[slot] = NetUtils.NetworkSlot(group["name"], world.game[slot], world.player_types[slot], + games[slot] = multiworld.game[slot] + slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], multiworld.game[slot], + multiworld.player_types[slot]) + for slot, group in multiworld.groups.items(): + games[slot] = multiworld.game[slot] + slot_info[slot] = NetUtils.NetworkSlot(group["name"], multiworld.game[slot], multiworld.player_types[slot], group_members=sorted(group["players"])) precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int] - for player, world_precollected in world.precollected_items.items()} - precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))} + for player, world_precollected in multiworld.precollected_items.items()} + precollected_hints = {player: set() for player in range(1, multiworld.players + 1 + len(multiworld.groups))} - for slot in world.player_ids: - slot_data[slot] = world.worlds[slot].fill_slot_data() + for slot in multiworld.player_ids: + slot_data[slot] = multiworld.worlds[slot].fill_slot_data() def precollect_hint(location): entrance = er_hint_data.get(location.player, {}).get(location.address, "") hint = NetUtils.Hint(location.item.player, location.player, location.address, location.item.code, False, entrance, location.item.flags) precollected_hints[location.player].add(hint) - if location.item.player not in world.groups: + if location.item.player not in multiworld.groups: precollected_hints[location.item.player].add(hint) else: - for player in world.groups[location.item.player]["players"]: + for player in multiworld.groups[location.item.player]["players"]: precollected_hints[player].add(hint) - locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids} - for location in world.get_filled_locations(): + locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in multiworld.player_ids} + for location in multiworld.get_filled_locations(): if type(location.address) == int: assert location.item.code is not None, "item code None should be event, " \ "location.address should then also be None. Location: " \ @@ -381,18 +381,18 @@ def precollect_hint(location): f"{locations_data[location.player][location.address]}") locations_data[location.player][location.address] = \ location.item.code, location.item.player, location.item.flags - if location.name in world.worlds[location.player].options.start_location_hints: + if location.name in multiworld.worlds[location.player].options.start_location_hints: precollect_hint(location) - elif location.item.name in world.worlds[location.item.player].options.start_hints: + elif location.item.name in multiworld.worlds[location.item.player].options.start_hints: precollect_hint(location) - elif any([location.item.name in world.worlds[player].options.start_hints - for player in world.groups.get(location.item.player, {}).get("players", [])]): + elif any([location.item.name in multiworld.worlds[player].options.start_hints + for player in multiworld.groups.get(location.item.player, {}).get("players", [])]): precollect_hint(location) # embedded data package data_package = { game_world.game: worlds.network_data_package["games"][game_world.game] - for game_world in world.worlds.values() + for game_world in multiworld.worlds.values() } checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {} @@ -400,7 +400,7 @@ def precollect_hint(location): multidata = { "slot_data": slot_data, "slot_info": slot_info, - "connect_names": {name: (0, player) for player, name in world.player_name.items()}, + "connect_names": {name: (0, player) for player, name in multiworld.player_name.items()}, "locations": locations_data, "checks_in_area": checks_in_area, "server_options": baked_server_options, @@ -410,10 +410,10 @@ def precollect_hint(location): "version": tuple(version_tuple), "tags": ["AP"], "minimum_versions": minimum_versions, - "seed_name": world.seed_name, + "seed_name": multiworld.seed_name, "datapackage": data_package, } - AutoWorld.call_all(world, "modify_multidata", multidata) + AutoWorld.call_all(multiworld, "modify_multidata", multidata) multidata = zlib.compress(pickle.dumps(multidata), 9) @@ -423,7 +423,7 @@ def precollect_hint(location): output_file_futures.append(pool.submit(write_multidata)) if not check_accessibility_task.result(): - if not world.can_beat_game(): + if not multiworld.can_beat_game(): raise Exception("Game appears as unbeatable. Aborting.") else: logger.warning("Location Accessibility requirements not fulfilled.") @@ -436,12 +436,12 @@ def precollect_hint(location): if args.spoiler > 1: logger.info('Calculating playthrough.') - world.spoiler.create_playthrough(create_paths=args.spoiler > 2) + multiworld.spoiler.create_playthrough(create_paths=args.spoiler > 2) if args.spoiler: - world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase)) + multiworld.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase)) - zipfilename = output_path(f"AP_{world.seed_name}.zip") + zipfilename = output_path(f"AP_{multiworld.seed_name}.zip") logger.info(f"Creating final archive at {zipfilename}") with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: @@ -449,4 +449,4 @@ def precollect_hint(location): zf.write(file.path, arcname=file.name) logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start) - return world + return multiworld diff --git a/OoTAdjuster.py b/OoTAdjuster.py index 38ebe62e2ae1..9519b191e704 100644 --- a/OoTAdjuster.py +++ b/OoTAdjuster.py @@ -195,10 +195,10 @@ def set_icon(window): window.tk.call('wm', 'iconphoto', window._w, logo) def adjust(args): - # Create a fake world and OOTWorld to use as a base - world = MultiWorld(1) - world.per_slot_randoms = {1: random} - ootworld = OOTWorld(world, 1) + # Create a fake multiworld and OOTWorld to use as a base + multiworld = MultiWorld(1) + multiworld.per_slot_randoms = {1: random} + ootworld = OOTWorld(multiworld, 1) # Set options in the fake OOTWorld for name, option in chain(cosmetic_options.items(), sfx_options.items()): result = getattr(args, name, None) diff --git a/Utils.py b/Utils.py index f6e4a9ab6052..8b91226bed9f 100644 --- a/Utils.py +++ b/Utils.py @@ -871,8 +871,8 @@ def visualize_regions(root_region: Region, file_name: str, *, Example usage in Main code: from Utils import visualize_regions - for player in world.player_ids: - visualize_regions(world.get_region("Menu", player), f"{world.get_out_file_name_base(player)}.puml") + for player in multiworld.player_ids: + visualize_regions(multiworld.get_region("Menu", player), f"{multiworld.get_out_file_name_base(player)}.puml") """ assert root_region.multiworld, "The multiworld attribute of root_region has to be filled" from BaseClasses import Entrance, Item, Location, LocationProgressType, MultiWorld, Region diff --git a/test/general/test_fill.py b/test/general/test_fill.py index e454b3e61d7a..489417771d2a 100644 --- a/test/general/test_fill.py +++ b/test/general/test_fill.py @@ -11,30 +11,30 @@ from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule -def generate_multi_world(players: int = 1) -> MultiWorld: - multi_world = MultiWorld(players) - multi_world.player_name = {} - multi_world.state = CollectionState(multi_world) +def generate_multiworld(players: int = 1) -> MultiWorld: + multiworld = MultiWorld(players) + multiworld.player_name = {} + multiworld.state = CollectionState(multiworld) for i in range(players): player_id = i+1 - world = World(multi_world, player_id) - multi_world.game[player_id] = f"Game {player_id}" - multi_world.worlds[player_id] = world - multi_world.player_name[player_id] = "Test Player " + str(player_id) - region = Region("Menu", player_id, multi_world, "Menu Region Hint") - multi_world.regions.append(region) + world = World(multiworld, player_id) + multiworld.game[player_id] = f"Game {player_id}" + multiworld.worlds[player_id] = world + multiworld.player_name[player_id] = "Test Player " + str(player_id) + region = Region("Menu", player_id, multiworld, "Menu Region Hint") + multiworld.regions.append(region) for option_key, option in Options.PerGameCommonOptions.type_hints.items(): - if hasattr(multi_world, option_key): - getattr(multi_world, option_key).setdefault(player_id, option.from_any(getattr(option, "default"))) + if hasattr(multiworld, option_key): + getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default"))) else: - setattr(multi_world, option_key, {player_id: option.from_any(getattr(option, "default"))}) + setattr(multiworld, option_key, {player_id: option.from_any(getattr(option, "default"))}) # TODO - remove this loop once all worlds use options dataclasses - world.options = world.options_dataclass(**{option_key: getattr(multi_world, option_key)[player_id] + world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id] for option_key in world.options_dataclass.type_hints}) - multi_world.set_seed(0) + multiworld.set_seed(0) - return multi_world + return multiworld class PlayerDefinition(object): @@ -46,8 +46,8 @@ class PlayerDefinition(object): basic_items: List[Item] regions: List[Region] - def __init__(self, world: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []): - self.multiworld = world + def __init__(self, multiworld: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []): + self.multiworld = multiworld self.id = id self.menu = menu self.locations = locations @@ -72,7 +72,7 @@ def generate_region(self, parent: Region, size: int, access_rule: CollectionRule return region -def fill_region(world: MultiWorld, region: Region, items: List[Item]) -> List[Item]: +def fill_region(multiworld: MultiWorld, region: Region, items: List[Item]) -> List[Item]: items = items.copy() while len(items) > 0: location = region.locations.pop(0) @@ -80,7 +80,7 @@ def fill_region(world: MultiWorld, region: Region, items: List[Item]) -> List[It if location.item: return items item = items.pop(0) - world.push_item(location, item, False) + multiworld.push_item(location, item, False) location.event = item.advancement return items @@ -94,15 +94,15 @@ def region_contains(region: Region, item: Item) -> bool: return False -def generate_player_data(multi_world: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition: - menu = multi_world.get_region("Menu", player_id) +def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition: + menu = multiworld.get_region("Menu", player_id) locations = generate_locations(location_count, player_id, None, menu) prog_items = generate_items(prog_item_count, player_id, True) - multi_world.itempool += prog_items + multiworld.itempool += prog_items basic_items = generate_items(basic_item_count, player_id, False) - multi_world.itempool += basic_items + multiworld.itempool += basic_items - return PlayerDefinition(multi_world, player_id, menu, locations, prog_items, basic_items) + return PlayerDefinition(multiworld, player_id, menu, locations, prog_items, basic_items) def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]: @@ -134,15 +134,15 @@ def names(objs: list) -> Iterable[str]: class TestFillRestrictive(unittest.TestCase): def test_basic_fill(self): """Tests `fill_restrictive` fills and removes the locations and items from their respective lists""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) item0 = player1.prog_items[0] item1 = player1.prog_items[1] loc0 = player1.locations[0] loc1 = player1.locations[1] - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items) self.assertEqual(loc0.item, item1) @@ -152,16 +152,16 @@ def test_basic_fill(self): def test_ordered_fill(self): """Tests `fill_restrictive` fulfills set rules""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) items = player1.prog_items locations = player1.locations - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( items[0].name, player1.id) and state.has(items[1].name, player1.id) set_rule(locations[1], lambda state: state.has( items[0].name, player1.id)) - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, player1.locations.copy(), player1.prog_items.copy()) self.assertEqual(locations[0].item, items[0]) @@ -169,8 +169,8 @@ def test_ordered_fill(self): def test_partial_fill(self): """Tests that `fill_restrictive` returns unfilled locations""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 3, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 3, 2) item0 = player1.prog_items[0] item1 = player1.prog_items[1] @@ -178,14 +178,14 @@ def test_partial_fill(self): loc1 = player1.locations[1] loc2 = player1.locations[2] - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( item0.name, player1.id) and state.has(item1.name, player1.id) set_rule(loc1, lambda state: state.has( item0.name, player1.id)) # forces a swap set_rule(loc2, lambda state: state.has( item0.name, player1.id)) - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items) self.assertEqual(loc0.item, item0) @@ -195,19 +195,19 @@ def test_partial_fill(self): def test_minimal_fill(self): """Test that fill for minimal player can have unreachable items""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) items = player1.prog_items locations = player1.locations - multi_world.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal) - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal) + multiworld.completion_condition[player1.id] = lambda state: state.has( items[1].name, player1.id) set_rule(locations[1], lambda state: state.has( items[0].name, player1.id)) - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, player1.locations.copy(), player1.prog_items.copy()) self.assertEqual(locations[0].item, items[1]) @@ -220,15 +220,15 @@ def test_minimal_mixed_fill(self): the non-minimal player get all items. """ - multi_world = generate_multi_world(2) - player1 = generate_player_data(multi_world, 1, 3, 3) - player2 = generate_player_data(multi_world, 2, 3, 3) + multiworld = generate_multiworld(2) + player1 = generate_player_data(multiworld, 1, 3, 3) + player2 = generate_player_data(multiworld, 2, 3, 3) - multi_world.accessibility[player1.id].value = multi_world.accessibility[player1.id].option_minimal - multi_world.accessibility[player2.id].value = multi_world.accessibility[player2.id].option_locations + multiworld.accessibility[player1.id].value = multiworld.accessibility[player1.id].option_minimal + multiworld.accessibility[player2.id].value = multiworld.accessibility[player2.id].option_locations - multi_world.completion_condition[player1.id] = lambda state: True - multi_world.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id) + multiworld.completion_condition[player1.id] = lambda state: True + multiworld.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id) set_rule(player1.locations[1], lambda state: state.has(player1.prog_items[0].name, player1.id)) set_rule(player1.locations[2], lambda state: state.has(player1.prog_items[1].name, player1.id)) @@ -241,28 +241,28 @@ def test_minimal_mixed_fill(self): # fill remaining locations with remaining items location_pool = player1.locations[1:] + player2.locations item_pool = player1.prog_items[:-1] + player2.prog_items - fill_restrictive(multi_world, multi_world.state, location_pool, item_pool) - multi_world.state.sweep_for_events() # collect everything + fill_restrictive(multiworld, multiworld.state, location_pool, item_pool) + multiworld.state.sweep_for_events() # collect everything # all of player2's locations and items should be accessible (not all of player1's) for item in player2.prog_items: - self.assertTrue(multi_world.state.has(item.name, player2.id), + self.assertTrue(multiworld.state.has(item.name, player2.id), f'{item} is unreachable in {item.location}') def test_reversed_fill(self): """Test a different set of rules can be satisfied""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) item0 = player1.prog_items[0] item1 = player1.prog_items[1] loc0 = player1.locations[0] loc1 = player1.locations[1] - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( item0.name, player1.id) and state.has(item1.name, player1.id) set_rule(loc1, lambda state: state.has(item1.name, player1.id)) - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items) self.assertEqual(loc0.item, item1) @@ -270,13 +270,13 @@ def test_reversed_fill(self): def test_multi_step_fill(self): """Test that fill is able to satisfy multiple spheres""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 4, 4) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 4, 4) items = player1.prog_items locations = player1.locations - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( items[2].name, player1.id) and state.has(items[3].name, player1.id) set_rule(locations[1], lambda state: state.has( items[0].name, player1.id)) @@ -285,7 +285,7 @@ def test_multi_step_fill(self): set_rule(locations[3], lambda state: state.has( items[1].name, player1.id)) - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, player1.locations.copy(), player1.prog_items.copy()) self.assertEqual(locations[0].item, items[1]) @@ -295,25 +295,25 @@ def test_multi_step_fill(self): def test_impossible_fill(self): """Test that fill raises an error when it can't place any items""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) items = player1.prog_items locations = player1.locations - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( items[0].name, player1.id) and state.has(items[1].name, player1.id) set_rule(locations[1], lambda state: state.has( items[1].name, player1.id)) set_rule(locations[0], lambda state: state.has( items[0].name, player1.id)) - self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state, + self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state, player1.locations.copy(), player1.prog_items.copy()) def test_circular_fill(self): """Test that fill raises an error when it can't place all items""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 3, 3) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 3, 3) item0 = player1.prog_items[0] item1 = player1.prog_items[1] @@ -322,46 +322,46 @@ def test_circular_fill(self): loc1 = player1.locations[1] loc2 = player1.locations[2] - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( item0.name, player1.id) and state.has(item1.name, player1.id) and state.has(item2.name, player1.id) set_rule(loc1, lambda state: state.has(item0.name, player1.id)) set_rule(loc2, lambda state: state.has(item1.name, player1.id)) set_rule(loc0, lambda state: state.has(item2.name, player1.id)) - self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state, + self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state, player1.locations.copy(), player1.prog_items.copy()) def test_competing_fill(self): """Test that fill raises an error when it can't place items in a way to satisfy the conditions""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) item0 = player1.prog_items[0] item1 = player1.prog_items[1] loc1 = player1.locations[1] - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( item0.name, player1.id) and state.has(item0.name, player1.id) and state.has(item1.name, player1.id) set_rule(loc1, lambda state: state.has(item0.name, player1.id) and state.has(item1.name, player1.id)) - self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state, + self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state, player1.locations.copy(), player1.prog_items.copy()) def test_multiplayer_fill(self): """Test that items can be placed across worlds""" - multi_world = generate_multi_world(2) - player1 = generate_player_data(multi_world, 1, 2, 2) - player2 = generate_player_data(multi_world, 2, 2, 2) + multiworld = generate_multiworld(2) + player1 = generate_player_data(multiworld, 1, 2, 2) + player2 = generate_player_data(multiworld, 2, 2, 2) - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( player1.prog_items[0].name, player1.id) and state.has( player1.prog_items[1].name, player1.id) - multi_world.completion_condition[player2.id] = lambda state: state.has( + multiworld.completion_condition[player2.id] = lambda state: state.has( player2.prog_items[0].name, player2.id) and state.has( player2.prog_items[1].name, player2.id) - fill_restrictive(multi_world, multi_world.state, player1.locations + + fill_restrictive(multiworld, multiworld.state, player1.locations + player2.locations, player1.prog_items + player2.prog_items) self.assertEqual(player1.locations[0].item, player1.prog_items[1]) @@ -371,21 +371,21 @@ def test_multiplayer_fill(self): def test_multiplayer_rules_fill(self): """Test that fill across worlds satisfies the rules""" - multi_world = generate_multi_world(2) - player1 = generate_player_data(multi_world, 1, 2, 2) - player2 = generate_player_data(multi_world, 2, 2, 2) + multiworld = generate_multiworld(2) + player1 = generate_player_data(multiworld, 1, 2, 2) + player2 = generate_player_data(multiworld, 2, 2, 2) - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( player1.prog_items[0].name, player1.id) and state.has( player1.prog_items[1].name, player1.id) - multi_world.completion_condition[player2.id] = lambda state: state.has( + multiworld.completion_condition[player2.id] = lambda state: state.has( player2.prog_items[0].name, player2.id) and state.has( player2.prog_items[1].name, player2.id) set_rule(player2.locations[1], lambda state: state.has( player2.prog_items[0].name, player2.id)) - fill_restrictive(multi_world, multi_world.state, player1.locations + + fill_restrictive(multiworld, multiworld.state, player1.locations + player2.locations, player1.prog_items + player2.prog_items) self.assertEqual(player1.locations[0].item, player2.prog_items[0]) @@ -395,10 +395,10 @@ def test_multiplayer_rules_fill(self): def test_restrictive_progress(self): """Test that various spheres with different requirements can be filled""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, prog_item_count=25) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, prog_item_count=25) items = player1.prog_items.copy() - multi_world.completion_condition[player1.id] = lambda state: state.has_all( + multiworld.completion_condition[player1.id] = lambda state: state.has_all( names(player1.prog_items), player1.id) player1.generate_region(player1.menu, 5) @@ -411,16 +411,16 @@ def test_restrictive_progress(self): player1.generate_region(player1.menu, 5, lambda state: state.has_all( names(items[17:22]), player1.id)) - locations = multi_world.get_unfilled_locations() + locations = multiworld.get_unfilled_locations() - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, locations, player1.prog_items) def test_swap_to_earlier_location_with_item_rule(self): """Test that item swap happens and works as intended""" # test for PR#1109 - multi_world = generate_multi_world(1) - player1 = generate_player_data(multi_world, 1, 4, 4) + multiworld = generate_multiworld(1) + player1 = generate_player_data(multiworld, 1, 4, 4) locations = player1.locations[:] # copy required items = player1.prog_items[:] # copy required # for the test to work, item and location order is relevant: Sphere 1 last, allowed_item not last @@ -437,15 +437,15 @@ def test_swap_to_earlier_location_with_item_rule(self): self.assertTrue(sphere1_loc.can_fill(None, allowed_item, False), "Test is flawed") self.assertFalse(sphere1_loc.can_fill(None, items[2], False), "Test is flawed") # fill has to place items[1] in locations[0] which will result in a swap because of placement order - fill_restrictive(multi_world, multi_world.state, player1.locations, player1.prog_items) + fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items) # assert swap happened self.assertTrue(sphere1_loc.item, "Did not swap required item into Sphere 1") self.assertEqual(sphere1_loc.item, allowed_item, "Wrong item in Sphere 1") def test_swap_to_earlier_location_with_item_rule2(self): """Test that swap works before all items are placed""" - multi_world = generate_multi_world(1) - player1 = generate_player_data(multi_world, 1, 5, 5) + multiworld = generate_multiworld(1) + player1 = generate_player_data(multiworld, 1, 5, 5) locations = player1.locations[:] # copy required items = player1.prog_items[:] # copy required # Two items provide access to sphere 2. @@ -477,7 +477,7 @@ def test_swap_to_earlier_location_with_item_rule2(self): # Now fill should place one_to_two1 in sphere1_loc1 or sphere1_loc2 via swap, # which it will attempt before two_to_three and three_to_four are placed, testing the behavior. - fill_restrictive(multi_world, multi_world.state, player1.locations, player1.prog_items) + fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items) # assert swap happened self.assertTrue(sphere1_loc1.item and sphere1_loc2.item, "Did not swap required item into Sphere 1") self.assertTrue(sphere1_loc1.item.name == one_to_two1 or @@ -486,29 +486,29 @@ def test_swap_to_earlier_location_with_item_rule2(self): def test_double_sweep(self): """Test that sweep doesn't duplicate Event items when sweeping""" # test for PR1114 - multi_world = generate_multi_world(1) - player1 = generate_player_data(multi_world, 1, 1, 1) + multiworld = generate_multiworld(1) + player1 = generate_player_data(multiworld, 1, 1, 1) location = player1.locations[0] location.address = None location.event = True item = player1.prog_items[0] item.code = None location.place_locked_item(item) - multi_world.state.sweep_for_events() - multi_world.state.sweep_for_events() - self.assertTrue(multi_world.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed") - self.assertEqual(multi_world.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times") + multiworld.state.sweep_for_events() + multiworld.state.sweep_for_events() + self.assertTrue(multiworld.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed") + self.assertEqual(multiworld.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times") def test_correct_item_instance_removed_from_pool(self): """Test that a placed item gets removed from the submitted pool""" - multi_world = generate_multi_world() - player1 = generate_player_data(multi_world, 1, 2, 2) + multiworld = generate_multiworld() + player1 = generate_player_data(multiworld, 1, 2, 2) player1.prog_items[0].name = "Different_item_instance_but_same_item_name" player1.prog_items[1].name = "Different_item_instance_but_same_item_name" loc0 = player1.locations[0] - fill_restrictive(multi_world, multi_world.state, + fill_restrictive(multiworld, multiworld.state, [loc0], player1.prog_items) self.assertEqual(1, len(player1.prog_items)) @@ -518,14 +518,14 @@ def test_correct_item_instance_removed_from_pool(self): class TestDistributeItemsRestrictive(unittest.TestCase): def test_basic_distribute(self): """Test that distribute_items_restrictive is deterministic""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations prog_items = player1.prog_items basic_items = player1.basic_items - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertEqual(locations[0].item, basic_items[1]) self.assertFalse(locations[0].event) @@ -538,52 +538,52 @@ def test_basic_distribute(self): def test_excluded_distribute(self): """Test that distribute_items_restrictive doesn't put advancement items on excluded locations""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations locations[1].progress_type = LocationProgressType.EXCLUDED locations[2].progress_type = LocationProgressType.EXCLUDED - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertFalse(locations[1].item.advancement) self.assertFalse(locations[2].item.advancement) def test_non_excluded_item_distribute(self): """Test that useful items aren't placed on excluded locations""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations basic_items = player1.basic_items locations[1].progress_type = LocationProgressType.EXCLUDED basic_items[1].classification = ItemClassification.useful - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertEqual(locations[1].item, basic_items[0]) def test_too_many_excluded_distribute(self): """Test that fill fails if it can't place all progression items due to too many excluded locations""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations locations[0].progress_type = LocationProgressType.EXCLUDED locations[1].progress_type = LocationProgressType.EXCLUDED locations[2].progress_type = LocationProgressType.EXCLUDED - self.assertRaises(FillError, distribute_items_restrictive, multi_world) + self.assertRaises(FillError, distribute_items_restrictive, multiworld) def test_non_excluded_item_must_distribute(self): """Test that fill fails if it can't place useful items due to too many excluded locations""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations basic_items = player1.basic_items @@ -592,47 +592,47 @@ def test_non_excluded_item_must_distribute(self): basic_items[0].classification = ItemClassification.useful basic_items[1].classification = ItemClassification.useful - self.assertRaises(FillError, distribute_items_restrictive, multi_world) + self.assertRaises(FillError, distribute_items_restrictive, multiworld) def test_priority_distribute(self): """Test that priority locations receive advancement items""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations locations[0].progress_type = LocationProgressType.PRIORITY locations[3].progress_type = LocationProgressType.PRIORITY - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertTrue(locations[0].item.advancement) self.assertTrue(locations[3].item.advancement) def test_excess_priority_distribute(self): """Test that if there's more priority locations than advancement items, they can still fill""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) locations = player1.locations locations[0].progress_type = LocationProgressType.PRIORITY locations[1].progress_type = LocationProgressType.PRIORITY locations[2].progress_type = LocationProgressType.PRIORITY - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertFalse(locations[3].item.advancement) def test_multiple_world_priority_distribute(self): """Test that priority fill can be satisfied for multiple worlds""" - multi_world = generate_multi_world(3) + multiworld = generate_multiworld(3) player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) player2 = generate_player_data( - multi_world, 2, 4, prog_item_count=1, basic_item_count=3) + multiworld, 2, 4, prog_item_count=1, basic_item_count=3) player3 = generate_player_data( - multi_world, 3, 6, prog_item_count=4, basic_item_count=2) + multiworld, 3, 6, prog_item_count=4, basic_item_count=2) player1.locations[2].progress_type = LocationProgressType.PRIORITY player1.locations[3].progress_type = LocationProgressType.PRIORITY @@ -644,7 +644,7 @@ def test_multiple_world_priority_distribute(self): player3.locations[2].progress_type = LocationProgressType.PRIORITY player3.locations[3].progress_type = LocationProgressType.PRIORITY - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertTrue(player1.locations[2].item.advancement) self.assertTrue(player1.locations[3].item.advancement) @@ -656,9 +656,9 @@ def test_multiple_world_priority_distribute(self): def test_can_remove_locations_in_fill_hook(self): """Test that distribute_items_restrictive calls the fill hook and allows for item and location removal""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, 4, prog_item_count=2, basic_item_count=2) + multiworld, 1, 4, prog_item_count=2, basic_item_count=2) removed_item: list[Item] = [] removed_location: list[Location] = [] @@ -667,21 +667,21 @@ def fill_hook(progitempool, usefulitempool, filleritempool, fill_locations): removed_item.append(filleritempool.pop(0)) removed_location.append(fill_locations.pop(0)) - multi_world.worlds[player1.id].fill_hook = fill_hook + multiworld.worlds[player1.id].fill_hook = fill_hook - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertIsNone(removed_item[0].location) self.assertIsNone(removed_location[0].item) def test_seed_robust_to_item_order(self): """Test deterministic fill""" - mw1 = generate_multi_world() + mw1 = generate_multiworld() gen1 = generate_player_data( mw1, 1, 4, prog_item_count=2, basic_item_count=2) distribute_items_restrictive(mw1) - mw2 = generate_multi_world() + mw2 = generate_multiworld() gen2 = generate_player_data( mw2, 1, 4, prog_item_count=2, basic_item_count=2) mw2.itempool.append(mw2.itempool.pop(0)) @@ -694,12 +694,12 @@ def test_seed_robust_to_item_order(self): def test_seed_robust_to_location_order(self): """Test deterministic fill even if locations in a region are reordered""" - mw1 = generate_multi_world() + mw1 = generate_multiworld() gen1 = generate_player_data( mw1, 1, 4, prog_item_count=2, basic_item_count=2) distribute_items_restrictive(mw1) - mw2 = generate_multi_world() + mw2 = generate_multiworld() gen2 = generate_player_data( mw2, 1, 4, prog_item_count=2, basic_item_count=2) reg = mw2.get_region("Menu", gen2.id) @@ -713,45 +713,45 @@ def test_seed_robust_to_location_order(self): def test_can_reserve_advancement_items_for_general_fill(self): """Test that priority locations fill still satisfies item rules""" - multi_world = generate_multi_world() + multiworld = generate_multiworld() player1 = generate_player_data( - multi_world, 1, location_count=5, prog_item_count=5) + multiworld, 1, location_count=5, prog_item_count=5) items = player1.prog_items - multi_world.completion_condition[player1.id] = lambda state: state.has_all( + multiworld.completion_condition[player1.id] = lambda state: state.has_all( names(items), player1.id) location = player1.locations[0] location.progress_type = LocationProgressType.PRIORITY location.item_rule = lambda item: item not in items[:4] - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) self.assertEqual(location.item, items[4]) def test_non_excluded_local_items(self): """Test that local items get placed locally in a multiworld""" - multi_world = generate_multi_world(2) + multiworld = generate_multiworld(2) player1 = generate_player_data( - multi_world, 1, location_count=5, basic_item_count=5) + multiworld, 1, location_count=5, basic_item_count=5) player2 = generate_player_data( - multi_world, 2, location_count=5, basic_item_count=5) + multiworld, 2, location_count=5, basic_item_count=5) - for item in multi_world.get_items(): + for item in multiworld.get_items(): item.classification = ItemClassification.useful - multi_world.local_items[player1.id].value = set(names(player1.basic_items)) - multi_world.local_items[player2.id].value = set(names(player2.basic_items)) - locality_rules(multi_world) + multiworld.local_items[player1.id].value = set(names(player1.basic_items)) + multiworld.local_items[player2.id].value = set(names(player2.basic_items)) + locality_rules(multiworld) - distribute_items_restrictive(multi_world) + distribute_items_restrictive(multiworld) - for item in multi_world.get_items(): + for item in multiworld.get_items(): self.assertEqual(item.player, item.location.player) self.assertFalse(item.location.event, False) def test_early_items(self) -> None: """Test that the early items API successfully places items early""" - mw = generate_multi_world(2) + mw = generate_multiworld(2) player1 = generate_player_data(mw, 1, location_count=5, basic_item_count=5) player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5) mw.early_items[1][player1.basic_items[0].name] = 1 @@ -810,19 +810,19 @@ def assertRegionContains(self, region: Region, item: Item) -> bool: "\n Contains" + str(list(map(lambda location: location.item, region.locations)))) def setUp(self) -> None: - multi_world = generate_multi_world(2) - self.multi_world = multi_world + multiworld = generate_multiworld(2) + self.multiworld = multiworld player1 = generate_player_data( - multi_world, 1, prog_item_count=2, basic_item_count=40) + multiworld, 1, prog_item_count=2, basic_item_count=40) self.player1 = player1 player2 = generate_player_data( - multi_world, 2, prog_item_count=2, basic_item_count=40) + multiworld, 2, prog_item_count=2, basic_item_count=40) self.player2 = player2 - multi_world.completion_condition[player1.id] = lambda state: state.has( + multiworld.completion_condition[player1.id] = lambda state: state.has( player1.prog_items[0].name, player1.id) and state.has( player1.prog_items[1].name, player1.id) - multi_world.completion_condition[player2.id] = lambda state: state.has( + multiworld.completion_condition[player2.id] = lambda state: state.has( player2.prog_items[0].name, player2.id) and state.has( player2.prog_items[1].name, player2.id) @@ -830,42 +830,42 @@ def setUp(self) -> None: # Sphere 1 region = player1.generate_region(player1.menu, 20) - items = fill_region(multi_world, region, [ + items = fill_region(multiworld, region, [ player1.prog_items[0]] + items) # Sphere 2 region = player1.generate_region( player1.regions[1], 20, lambda state: state.has(player1.prog_items[0].name, player1.id)) items = fill_region( - multi_world, region, [player1.prog_items[1], player2.prog_items[0]] + items) + multiworld, region, [player1.prog_items[1], player2.prog_items[0]] + items) # Sphere 3 region = player2.generate_region( player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id)) - fill_region(multi_world, region, [player2.prog_items[1]] + items) + fill_region(multiworld, region, [player2.prog_items[1]] + items) def test_balances_progression(self) -> None: """Tests that progression balancing moves progression items earlier""" - self.multi_world.progression_balancing[self.player1.id].value = 50 - self.multi_world.progression_balancing[self.player2.id].value = 50 + self.multiworld.progression_balancing[self.player1.id].value = 50 + self.multiworld.progression_balancing[self.player2.id].value = 50 self.assertRegionContains( self.player1.regions[2], self.player2.prog_items[0]) - balance_multiworld_progression(self.multi_world) + balance_multiworld_progression(self.multiworld) self.assertRegionContains( self.player1.regions[1], self.player2.prog_items[0]) def test_balances_progression_light(self) -> None: """Test that progression balancing still moves items earlier on minimum value""" - self.multi_world.progression_balancing[self.player1.id].value = 1 - self.multi_world.progression_balancing[self.player2.id].value = 1 + self.multiworld.progression_balancing[self.player1.id].value = 1 + self.multiworld.progression_balancing[self.player2.id].value = 1 self.assertRegionContains( self.player1.regions[2], self.player2.prog_items[0]) - balance_multiworld_progression(self.multi_world) + balance_multiworld_progression(self.multiworld) # TODO: arrange for a result that's different from the default self.assertRegionContains( @@ -873,13 +873,13 @@ def test_balances_progression_light(self) -> None: def test_balances_progression_heavy(self) -> None: """Test that progression balancing moves items earlier on maximum value""" - self.multi_world.progression_balancing[self.player1.id].value = 99 - self.multi_world.progression_balancing[self.player2.id].value = 99 + self.multiworld.progression_balancing[self.player1.id].value = 99 + self.multiworld.progression_balancing[self.player2.id].value = 99 self.assertRegionContains( self.player1.regions[2], self.player2.prog_items[0]) - balance_multiworld_progression(self.multi_world) + balance_multiworld_progression(self.multiworld) # TODO: arrange for a result that's different from the default self.assertRegionContains( @@ -887,25 +887,25 @@ def test_balances_progression_heavy(self) -> None: def test_skips_balancing_progression(self) -> None: """Test that progression balancing is skipped when players have it disabled""" - self.multi_world.progression_balancing[self.player1.id].value = 0 - self.multi_world.progression_balancing[self.player2.id].value = 0 + self.multiworld.progression_balancing[self.player1.id].value = 0 + self.multiworld.progression_balancing[self.player2.id].value = 0 self.assertRegionContains( self.player1.regions[2], self.player2.prog_items[0]) - balance_multiworld_progression(self.multi_world) + balance_multiworld_progression(self.multiworld) self.assertRegionContains( self.player1.regions[2], self.player2.prog_items[0]) def test_ignores_priority_locations(self) -> None: """Test that progression items on priority locations don't get moved by balancing""" - self.multi_world.progression_balancing[self.player1.id].value = 50 - self.multi_world.progression_balancing[self.player2.id].value = 50 + self.multiworld.progression_balancing[self.player1.id].value = 50 + self.multiworld.progression_balancing[self.player2.id].value = 50 self.player2.prog_items[0].location.progress_type = LocationProgressType.PRIORITY - balance_multiworld_progression(self.multi_world) + balance_multiworld_progression(self.multiworld) self.assertRegionContains( self.player1.regions[2], self.player2.prog_items[0]) diff --git a/test/general/test_reachability.py b/test/general/test_reachability.py index cfd83c940463..e57c398b7beb 100644 --- a/test/general/test_reachability.py +++ b/test/general/test_reachability.py @@ -36,15 +36,15 @@ def test_default_all_state_can_reach_everything(self): for game_name, world_type in AutoWorldRegister.world_types.items(): unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set()) with self.subTest("Game", game=game_name): - world = setup_solo_multiworld(world_type) - excluded = world.worlds[1].options.exclude_locations.value - state = world.get_all_state(False) - for location in world.get_locations(): + multiworld = setup_solo_multiworld(world_type) + excluded = multiworld.worlds[1].options.exclude_locations.value + state = multiworld.get_all_state(False) + for location in multiworld.get_locations(): if location.name not in excluded: with self.subTest("Location should be reached", location=location): self.assertTrue(location.can_reach(state), f"{location.name} unreachable") - for region in world.get_regions(): + for region in multiworld.get_regions(): if region.name in unreachable_regions: with self.subTest("Region should be unreachable", region=region): self.assertFalse(region.can_reach(state)) @@ -53,15 +53,15 @@ def test_default_all_state_can_reach_everything(self): self.assertTrue(region.can_reach(state)) with self.subTest("Completion Condition"): - self.assertTrue(world.can_beat_game(state)) + self.assertTrue(multiworld.can_beat_game(state)) def test_default_empty_state_can_reach_something(self): """Ensure empty state can reach at least one location with the defined options""" for game_name, world_type in AutoWorldRegister.world_types.items(): with self.subTest("Game", game=game_name): - world = setup_solo_multiworld(world_type) - state = CollectionState(world) - all_locations = world.get_locations() + multiworld = setup_solo_multiworld(world_type) + state = CollectionState(multiworld) + all_locations = multiworld.get_locations() if all_locations: locations = set() for location in all_locations: