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
This commit is contained in:
PoryGone 2024-02-04 18:38:00 -05:00 committed by GitHub
parent 6c19bc42bb
commit 281fe01c25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 429 additions and 429 deletions

View File

@ -823,8 +823,8 @@ class Entrance:
return self.__str__() return self.__str__()
def __str__(self): def __str__(self):
world = self.parent_region.multiworld if self.parent_region else None multiworld = 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})' return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})'
class Region: class Region:
@ -1040,8 +1040,8 @@ class Location:
return self.__str__() return self.__str__()
def __str__(self): def __str__(self):
world = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None multiworld = 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})' return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})'
def __hash__(self): def __hash__(self):
return hash((self.name, self.player)) return hash((self.name, self.player))
@ -1175,7 +1175,7 @@ class Spoiler:
{"player": player, "entrance": entrance, "exit": exit_, "direction": direction} {"player": player, "entrance": entrance, "exit": exit_, "direction": direction}
def create_playthrough(self, create_paths: bool = True) -> None: 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 from itertools import chain
# get locations containing progress items # get locations containing progress items
multiworld = self.multiworld multiworld = self.multiworld

186
Fill.py
View File

@ -27,12 +27,12 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item]
return new_state 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, 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, swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None,
allow_partial: bool = False, allow_excluded: bool = False, name: str = "Unknown") -> 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 base_state: State assumed before fill.
:param locations: Locations to be filled with item_pool :param locations: Locations to be filled with item_pool
:param item_pool: Items to fill into the locations :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( maximum_exploration_state = sweep_from_pool(
base_state, item_pool + unplaced_items) 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: while items_to_place:
# if we have run out of locations to fill,break out of this loop # 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 spot_to_fill: typing.Optional[Location] = None
# if minimal accessibility, only check whether location is reachable if game not beatable # 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: if multiworld.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal:
perform_access_check = not world.has_beaten_game(maximum_exploration_state, perform_access_check = not multiworld.has_beaten_game(maximum_exploration_state,
item_to_place.player) \ item_to_place.player) \
if single_player_placement else not has_beaten_game if single_player_placement else not has_beaten_game
else: 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. # Verify placing this item won't reduce available locations, which would be a useless swap.
prev_state = swap_state.copy() prev_state = swap_state.copy()
prev_loc_count = len( prev_loc_count = len(
world.get_reachable_locations(prev_state)) multiworld.get_reachable_locations(prev_state))
swap_state.collect(item_to_place, True) swap_state.collect(item_to_place, True)
new_loc_count = len( new_loc_count = len(
world.get_reachable_locations(swap_state)) multiworld.get_reachable_locations(swap_state))
if new_loc_count >= prev_loc_count: if new_loc_count >= prev_loc_count:
# Add this item to the existing placement, and # Add this item to the existing placement, and
@ -156,7 +156,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
else: else:
unplaced_items.append(item_to_place) unplaced_items.append(item_to_place)
continue 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 spot_to_fill.locked = lock
placements.append(spot_to_fill) placements.append(spot_to_fill)
spot_to_fill.event = item_to_place.advancement 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 # validate all placements and remove invalid ones
state = sweep_from_pool(base_state, []) state = sweep_from_pool(base_state, [])
for placement in placements: 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 placement.item.location = None
unplaced_items.append(placement.item) unplaced_items.append(placement.item)
placement.item = None placement.item = None
@ -188,7 +188,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
if excluded_locations: if excluded_locations:
for location in excluded_locations: for location in excluded_locations:
location.progress_type = location.progress_type.DEFAULT 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) swap, on_place, allow_partial, False)
for location in excluded_locations: for location in excluded_locations:
if not location.item: 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: if not allow_partial and len(unplaced_items) > 0 and len(locations) > 0:
# There are leftover unplaceable items and locations that won't accept them # There are leftover unplaceable items and locations that won't accept them
if world.can_beat_game(): if multiworld.can_beat_game():
logging.warning( logging.warning(
f'Not all items placed. Game beatable anyway. (Could not place {unplaced_items})') f'Not all items placed. Game beatable anyway. (Could not place {unplaced_items})')
else: else:
@ -206,7 +206,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
item_pool.extend(unplaced_items) item_pool.extend(unplaced_items)
def remaining_fill(world: MultiWorld, def remaining_fill(multiworld: MultiWorld,
locations: typing.List[Location], locations: typing.List[Location],
itempool: typing.List[Item]) -> None: itempool: typing.List[Item]) -> None:
unplaced_items: typing.List[Item] = [] unplaced_items: typing.List[Item] = []
@ -261,7 +261,7 @@ def remaining_fill(world: MultiWorld,
unplaced_items.append(item_to_place) unplaced_items.append(item_to_place)
continue 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) placements.append(spot_to_fill)
placed += 1 placed += 1
if not placed % 1000: if not placed % 1000:
@ -278,19 +278,19 @@ def remaining_fill(world: MultiWorld,
itempool.extend(unplaced_items) itempool.extend(unplaced_items)
def fast_fill(world: MultiWorld, def fast_fill(multiworld: MultiWorld,
item_pool: typing.List[Item], item_pool: typing.List[Item],
fill_locations: typing.List[Location]) -> typing.Tuple[typing.List[Item], typing.List[Location]]: fill_locations: typing.List[Location]) -> typing.Tuple[typing.List[Item], typing.List[Location]]:
placing = min(len(item_pool), len(fill_locations)) placing = min(len(item_pool), len(fill_locations))
for item, location in zip(item_pool, 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:] 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) maximum_exploration_state = sweep_from_pool(state, pool)
minimal_players = {player for player in world.player_ids if world.worlds[player].options.accessibility == "minimal"} minimal_players = {player for player in multiworld.player_ids if multiworld.worlds[player].options.accessibility == "minimal"}
unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and unreachable_locations = [location for location in multiworld.get_locations() if location.player in minimal_players and
not location.can_reach(maximum_exploration_state)] not location.can_reach(maximum_exploration_state)]
for location in unreachable_locations: for location in unreachable_locations:
if (location.item is not None and location.item.advancement and location.address is not None and not 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) locations.append(location)
if pool and locations: if pool and locations:
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY) 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) maximum_exploration_state = sweep_from_pool(state)
unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)] unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)]
if unreachable_locations: if unreachable_locations:
def forbid_important_item_rule(item: Item): 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: for location in unreachable_locations:
add_item_rule(location, forbid_important_item_rule) 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], fill_locations: typing.List[Location],
itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]: itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]:
""" returns new fill_locations and itempool """ """ returns new fill_locations and itempool """
early_items_count: typing.Dict[typing.Tuple[str, int], typing.List[int]] = {} early_items_count: typing.Dict[typing.Tuple[str, int], typing.List[int]] = {}
for player in world.player_ids: for player in multiworld.player_ids:
items = itertools.chain(world.early_items[player], world.local_early_items[player]) items = itertools.chain(multiworld.early_items[player], multiworld.local_early_items[player])
for item in items: for item in items:
early_items_count[item, player] = [world.early_items[player].get(item, 0), early_items_count[item, player] = [multiworld.early_items[player].get(item, 0),
world.local_early_items[player].get(item, 0)] multiworld.local_early_items[player].get(item, 0)]
if early_items_count: if early_items_count:
early_locations: typing.List[Location] = [] early_locations: typing.List[Location] = []
early_priority_locations: typing.List[Location] = [] early_priority_locations: typing.List[Location] = []
loc_indexes_to_remove: typing.Set[int] = set() loc_indexes_to_remove: typing.Set[int] = set()
base_state = world.state.copy() base_state = multiworld.state.copy()
base_state.sweep_for_events(locations=(loc for loc in world.get_filled_locations() if loc.address is None)) 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): for i, loc in enumerate(fill_locations):
if loc.can_reach(base_state): if loc.can_reach(base_state):
if loc.progress_type == LocationProgressType.PRIORITY: if loc.progress_type == LocationProgressType.PRIORITY:
@ -345,8 +345,8 @@ def distribute_early_items(world: MultiWorld,
early_prog_items: typing.List[Item] = [] early_prog_items: typing.List[Item] = []
early_rest_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_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 world.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() item_indexes_to_remove: typing.Set[int] = set()
for i, item in enumerate(itempool): for i, item in enumerate(itempool):
if (item.name, item.player) in early_items_count: if (item.name, item.player) in early_items_count:
@ -370,28 +370,28 @@ def distribute_early_items(world: MultiWorld,
if len(early_items_count) == 0: if len(early_items_count) == 0:
break break
itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove] itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove]
for player in world.player_ids: for player in multiworld.player_ids:
player_local = early_local_rest_items[player] 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], [loc for loc in early_locations if loc.player == player],
player_local, lock=True, allow_partial=True, name=f"Local Early Items P{player}") player_local, lock=True, allow_partial=True, name=f"Local Early Items P{player}")
if player_local: if player_local:
logging.warning(f"Could not fulfill rules of early items: {player_local}") logging.warning(f"Could not fulfill rules of early items: {player_local}")
early_rest_items.extend(early_local_rest_items[player]) early_rest_items.extend(early_local_rest_items[player])
early_locations = [loc for loc in early_locations if not loc.item] early_locations = [loc for loc in early_locations if not loc.item]
fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True, fill_restrictive(multiworld, base_state, early_locations, early_rest_items, lock=True, allow_partial=True,
name="Early Items") name="Early Items")
early_locations += early_priority_locations early_locations += early_priority_locations
for player in world.player_ids: for player in multiworld.player_ids:
player_local = early_local_prog_items[player] 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], [loc for loc in early_locations if loc.player == player],
player_local, lock=True, allow_partial=True, name=f"Local Early Progression P{player}") player_local, lock=True, allow_partial=True, name=f"Local Early Progression P{player}")
if player_local: if player_local:
logging.warning(f"Could not fulfill rules of early items: {player_local}") logging.warning(f"Could not fulfill rules of early items: {player_local}")
early_prog_items.extend(player_local) early_prog_items.extend(player_local)
early_locations = [loc for loc in early_locations if not loc.item] early_locations = [loc for loc in early_locations if not loc.item]
fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True, fill_restrictive(multiworld, base_state, early_locations, early_prog_items, lock=True, allow_partial=True,
name="Early Progression") name="Early Progression")
unplaced_early_items = early_rest_items + early_prog_items unplaced_early_items = early_rest_items + early_prog_items
if unplaced_early_items: if unplaced_early_items:
@ -400,18 +400,18 @@ def distribute_early_items(world: MultiWorld,
itempool += unplaced_early_items itempool += unplaced_early_items
fill_locations.extend(early_locations) fill_locations.extend(early_locations)
world.random.shuffle(fill_locations) multiworld.random.shuffle(fill_locations)
return fill_locations, itempool return fill_locations, itempool
def distribute_items_restrictive(world: MultiWorld) -> None: def distribute_items_restrictive(multiworld: MultiWorld) -> None:
fill_locations = sorted(world.get_unfilled_locations()) fill_locations = sorted(multiworld.get_unfilled_locations())
world.random.shuffle(fill_locations) multiworld.random.shuffle(fill_locations)
# get items to distribute # get items to distribute
itempool = sorted(world.itempool) itempool = sorted(multiworld.itempool)
world.random.shuffle(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] = [] progitempool: typing.List[Item] = []
usefulitempool: typing.List[Item] = [] usefulitempool: typing.List[Item] = []
@ -425,7 +425,7 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
else: else:
filleritempool.append(item) 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]] = { locations: typing.Dict[LocationProgressType, typing.List[Location]] = {
loc_type: [] for loc_type in LocationProgressType} loc_type: [] for loc_type in LocationProgressType}
@ -446,34 +446,34 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
if prioritylocations: if prioritylocations:
# "priority fill" # "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") name="Priority")
accessibility_corrections(world, world.state, prioritylocations, progitempool) accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool)
defaultlocations = prioritylocations + defaultlocations defaultlocations = prioritylocations + defaultlocations
if progitempool: if progitempool:
# "advancement/progression fill" # "advancement/progression fill"
fill_restrictive(world, world.state, defaultlocations, progitempool, name="Progression") fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, name="Progression")
if progitempool: if progitempool:
raise FillError( raise FillError(
f'Not enough locations for progress items. There are {len(progitempool)} more items than locations') 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: for location in lock_later:
if location.item: if location.item:
location.locked = True location.locked = True
del mark_for_locking, lock_later 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: if excludedlocations:
raise FillError( raise FillError(
f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items") f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items")
restitempool = filleritempool + usefulitempool restitempool = filleritempool + usefulitempool
remaining_fill(world, defaultlocations, restitempool) remaining_fill(multiworld, defaultlocations, restitempool)
unplaced = restitempool unplaced = restitempool
unfilled = defaultlocations unfilled = defaultlocations
@ -481,40 +481,40 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
if unplaced or unfilled: if unplaced or unfilled:
logging.warning( logging.warning(
f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}') 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) items_counter = Counter(location.item.player for location in multiworld.get_locations() if location.item)
locations_counter = Counter(location.player for location in world.get_locations()) locations_counter = Counter(location.player for location in multiworld.get_locations())
items_counter.update(item.player for item in unplaced) items_counter.update(item.player for item in unplaced)
locations_counter.update(location.player for location in unfilled) locations_counter.update(location.player for location in unfilled)
print_data = {"items": items_counter, "locations": locations_counter} print_data = {"items": items_counter, "locations": locations_counter}
logging.info(f'Per-Player counts: {print_data})') logging.info(f'Per-Player counts: {print_data})')
def flood_items(world: MultiWorld) -> None: def flood_items(multiworld: MultiWorld) -> None:
# get items to distribute # get items to distribute
world.random.shuffle(world.itempool) multiworld.random.shuffle(multiworld.itempool)
itempool = world.itempool itempool = multiworld.itempool
progress_done = False progress_done = False
# sweep once to pick up preplaced items # 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: while not progress_done:
location_list = world.get_unfilled_locations() location_list = multiworld.get_unfilled_locations()
world.random.shuffle(location_list) multiworld.random.shuffle(location_list)
spot_to_fill = None spot_to_fill = None
for location in location_list: 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 spot_to_fill = location
break break
if spot_to_fill: if spot_to_fill:
item = itempool.pop(0) item = itempool.pop(0)
world.push_item(spot_to_fill, item, True) multiworld.push_item(spot_to_fill, item, True)
continue continue
# ran out of spots, check if we need to step in and correct things # 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 progress_done = True
continue continue
@ -524,7 +524,7 @@ def flood_items(world: MultiWorld) -> None:
for item in itempool: for item in itempool:
if item.advancement: if item.advancement:
candidate_item_to_place = item candidate_item_to_place = item
if world.unlocks_new_location(item): if multiworld.unlocks_new_location(item):
item_to_place = item item_to_place = item
break break
@ -537,15 +537,15 @@ def flood_items(world: MultiWorld) -> None:
raise FillError('No more progress items left to place.') raise FillError('No more progress items left to place.')
# find item to replace with progress item # find item to replace with progress item
location_list = world.get_reachable_locations() location_list = multiworld.get_reachable_locations()
world.random.shuffle(location_list) multiworld.random.shuffle(location_list)
for location in location_list: for location in location_list:
if location.item is not None and not location.item.advancement: if location.item is not None and not location.item.advancement:
# safe to replace # safe to replace
replace_item = location.item replace_item = location.item
replace_item.location = None replace_item.location = None
itempool.append(replace_item) 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) itempool.remove(item_to_place)
break 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 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: def warn(warning: str, force: typing.Union[bool, str]) -> None:
if force in [True, 'fail', 'failure', 'none', False, 'warn', 'warning']: if force in [True, 'fail', 'failure', 'none', False, 'warn', 'warning']:
logging.warning(f'{warning}') logging.warning(f'{warning}')
@ -768,24 +768,24 @@ def distribute_planned(world: MultiWorld) -> None:
else: else:
warn(warning, force) warn(warning, force)
swept_state = world.state.copy() swept_state = multiworld.state.copy()
swept_state.sweep_for_events() 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) early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list)
non_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: if loc in reachable:
early_locations[loc.player].append(loc.name) early_locations[loc.player].append(loc.name)
else: # not reachable with swept state else: # not reachable with swept state
non_early_locations[loc.player].append(loc.name) 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] block_value = typing.Union[typing.List[str], typing.Dict[str, typing.Any], str]
plando_blocks: typing.List[typing.Dict[str, typing.Any]] = [] 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 player in player_ids:
for block in world.plando_items[player]: for block in multiworld.plando_items[player]:
block['player'] = player block['player'] = player
if 'force' not in block: if 'force' not in block:
block['force'] = 'silent' block['force'] = 'silent'
@ -799,12 +799,12 @@ def distribute_planned(world: MultiWorld) -> None:
else: else:
target_world = block['world'] 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} worlds: typing.Set[int] = {player}
elif target_world is True: # target any worlds besides own 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 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 elif type(target_world) == list: # list of target worlds
worlds = set() worlds = set()
for listed_world in target_world: for listed_world in target_world:
@ -814,9 +814,9 @@ def distribute_planned(world: MultiWorld) -> None:
continue continue
worlds.add(world_name_lookup[listed_world]) worlds.add(world_name_lookup[listed_world])
elif type(target_world) == int: # target world by slot number 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( 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']) block['force'])
continue continue
worlds = {target_world} worlds = {target_world}
@ -844,7 +844,7 @@ def distribute_planned(world: MultiWorld) -> None:
item_list: typing.List[str] = [] item_list: typing.List[str] = []
for key, value in items.items(): for key, value in items.items():
if value is True: 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 item_list += [key] * value
items = item_list items = item_list
if isinstance(items, str): if isinstance(items, str):
@ -894,17 +894,17 @@ def distribute_planned(world: MultiWorld) -> None:
count = block['count'] count = block['count']
failed(f"Plando count {count} greater than locations specified", block['force']) failed(f"Plando count {count} greater than locations specified", block['force'])
block['count'] = len(block['locations']) 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: if block['count']['target'] > 0:
plando_blocks.append(block) plando_blocks.append(block)
# shuffle, but then sort blocks by number of locations minus number of items, # shuffle, but then sort blocks by number of locations minus number of items,
# so less-flexible blocks get priority # 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'] plando_blocks.sort(key=lambda block: (len(block['locations']) - block['count']['target']
if len(block['locations']) > 0 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: for placement in plando_blocks:
player = placement['player'] player = placement['player']
@ -915,19 +915,19 @@ def distribute_planned(world: MultiWorld) -> None:
maxcount = placement['count']['target'] maxcount = placement['count']['target']
from_pool = placement['from_pool'] from_pool = placement['from_pool']
candidates = list(world.get_unfilled_locations_for_players(locations, sorted(worlds))) candidates = list(multiworld.get_unfilled_locations_for_players(locations, sorted(worlds)))
world.random.shuffle(candidates) multiworld.random.shuffle(candidates)
world.random.shuffle(items) multiworld.random.shuffle(items)
count = 0 count = 0
err: typing.List[str] = [] err: typing.List[str] = []
successful_pairs: typing.List[typing.Tuple[Item, Location]] = [] successful_pairs: typing.List[typing.Tuple[Item, Location]] = []
for item_name in items: 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): for location in reversed(candidates):
if (location.address is None) == (item.code is None): # either both None or both not None if (location.address is None) == (item.code is None): # either both None or both not None
if not location.item: if not location.item:
if location.item_rule(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)) successful_pairs.append((item, location))
candidates.remove(location) candidates.remove(location)
count = count + 1 count = count + 1
@ -945,21 +945,21 @@ def distribute_planned(world: MultiWorld) -> None:
if count < placement['count']['min']: if count < placement['count']['min']:
m = placement['count']['min'] m = placement['count']['min']
failed( 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']) placement['force'])
for (item, location) in successful_pairs: 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.event = True # flag location to be checked during fill
location.locked = True location.locked = True
logging.debug(f"Plando placed {item} at {location}") logging.debug(f"Plando placed {item} at {location}")
if from_pool: if from_pool:
try: try:
world.itempool.remove(item) multiworld.itempool.remove(item)
except ValueError: except ValueError:
warn( 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']) placement['force'])
except Exception as e: except Exception as e:
raise Exception( 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

282
Main.py
View File

@ -30,49 +30,49 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
output_path.cached_path = args.outputpath output_path.cached_path = args.outputpath
start = time.perf_counter() start = time.perf_counter()
# initialize the world # initialize the multiworld
world = MultiWorld(args.multi) multiworld = MultiWorld(args.multi)
logger = logging.getLogger() logger = logging.getLogger()
world.set_seed(seed, args.race, str(args.outputname) if args.outputname else None) multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
world.plando_options = args.plando_options multiworld.plando_options = args.plando_options
world.shuffle = args.shuffle.copy() multiworld.shuffle = args.shuffle.copy()
world.logic = args.logic.copy() multiworld.logic = args.logic.copy()
world.mode = args.mode.copy() multiworld.mode = args.mode.copy()
world.difficulty = args.difficulty.copy() multiworld.difficulty = args.difficulty.copy()
world.item_functionality = args.item_functionality.copy() multiworld.item_functionality = args.item_functionality.copy()
world.timer = args.timer.copy() multiworld.timer = args.timer.copy()
world.goal = args.goal.copy() multiworld.goal = args.goal.copy()
world.boss_shuffle = args.shufflebosses.copy() multiworld.boss_shuffle = args.shufflebosses.copy()
world.enemy_health = args.enemy_health.copy() multiworld.enemy_health = args.enemy_health.copy()
world.enemy_damage = args.enemy_damage.copy() multiworld.enemy_damage = args.enemy_damage.copy()
world.beemizer_total_chance = args.beemizer_total_chance.copy() multiworld.beemizer_total_chance = args.beemizer_total_chance.copy()
world.beemizer_trap_chance = args.beemizer_trap_chance.copy() multiworld.beemizer_trap_chance = args.beemizer_trap_chance.copy()
world.countdown_start_time = args.countdown_start_time.copy() multiworld.countdown_start_time = args.countdown_start_time.copy()
world.red_clock_time = args.red_clock_time.copy() multiworld.red_clock_time = args.red_clock_time.copy()
world.blue_clock_time = args.blue_clock_time.copy() multiworld.blue_clock_time = args.blue_clock_time.copy()
world.green_clock_time = args.green_clock_time.copy() multiworld.green_clock_time = args.green_clock_time.copy()
world.dungeon_counters = args.dungeon_counters.copy() multiworld.dungeon_counters = args.dungeon_counters.copy()
world.triforce_pieces_available = args.triforce_pieces_available.copy() multiworld.triforce_pieces_available = args.triforce_pieces_available.copy()
world.triforce_pieces_required = args.triforce_pieces_required.copy() multiworld.triforce_pieces_required = args.triforce_pieces_required.copy()
world.shop_shuffle = args.shop_shuffle.copy() multiworld.shop_shuffle = args.shop_shuffle.copy()
world.shuffle_prizes = args.shuffle_prizes.copy() multiworld.shuffle_prizes = args.shuffle_prizes.copy()
world.sprite_pool = args.sprite_pool.copy() multiworld.sprite_pool = args.sprite_pool.copy()
world.dark_room_logic = args.dark_room_logic.copy() multiworld.dark_room_logic = args.dark_room_logic.copy()
world.plando_items = args.plando_items.copy() multiworld.plando_items = args.plando_items.copy()
world.plando_texts = args.plando_texts.copy() multiworld.plando_texts = args.plando_texts.copy()
world.plando_connections = args.plando_connections.copy() multiworld.plando_connections = args.plando_connections.copy()
world.required_medallions = args.required_medallions.copy() multiworld.required_medallions = args.required_medallions.copy()
world.game = args.game.copy() multiworld.game = args.game.copy()
world.player_name = args.name.copy() multiworld.player_name = args.name.copy()
world.sprite = args.sprite.copy() multiworld.sprite = args.sprite.copy()
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option. multiworld.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
world.set_options(args) multiworld.set_options(args)
world.set_item_links() multiworld.set_item_links()
world.state = CollectionState(world) multiworld.state = CollectionState(multiworld)
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) logger.info('Archipelago Version %s - Seed: %s\n', __version__, multiworld.seed)
logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:") logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:")
longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.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. # This assertion method should not be necessary to run if we are not outputting any multidata.
if not args.skip_output: 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('') logger.info('')
for player in world.player_ids: for player in multiworld.player_ids:
for item_name, count in world.worlds[player].options.start_inventory.value.items(): for item_name, count in multiworld.worlds[player].options.start_inventory.value.items():
for _ in range(count): 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", "start_inventory_from_pool",
StartInventoryPool({})).value.items(): StartInventoryPool({})).value.items():
for _ in range(count): 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. # 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: 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 remaining_count = count-early
if remaining_count > 0: 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: 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 local_early
del early del early
logger.info('Creating World.') logger.info('Creating MultiWorld.')
AutoWorld.call_all(world, "create_regions") AutoWorld.call_all(multiworld, "create_regions")
logger.info('Creating Items.') logger.info('Creating Items.')
AutoWorld.call_all(world, "create_items") AutoWorld.call_all(multiworld, "create_items")
logger.info('Calculating Access Rules.') 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 # 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 multiworld.worlds[player].options.non_local_items.value -= multiworld.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 -= set(multiworld.local_early_items[player])
AutoWorld.call_all(world, "set_rules") AutoWorld.call_all(multiworld, "set_rules")
for player in world.player_ids: for player in multiworld.player_ids:
exclusion_rules(world, player, world.worlds[player].options.exclude_locations.value) exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value)
world.worlds[player].options.priority_locations.value -= world.worlds[player].options.exclude_locations.value multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value
for location_name in world.worlds[player].options.priority_locations.value: for location_name in multiworld.worlds[player].options.priority_locations.value:
try: 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 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 raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e
else: else:
location.progress_type = LocationProgressType.PRIORITY location.progress_type = LocationProgressType.PRIORITY
# Set local and non-local item rules. # Set local and non-local item rules.
if world.players > 1: if multiworld.players > 1:
locality_rules(world) locality_rules(multiworld)
else: else:
world.worlds[1].options.non_local_items.value = set() multiworld.worlds[1].options.non_local_items.value = set()
world.worlds[1].options.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. # 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. # 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] = [] new_items: List[Item] = []
depletion_pool: Dict[int, Dict[str, int]] = { depletion_pool: Dict[int, Dict[str, int]] = {
player: getattr(world.worlds[player].options, player: getattr(multiworld.worlds[player].options,
"start_inventory_from_pool", "start_inventory_from_pool",
StartInventoryPool({})).value.copy() StartInventoryPool({})).value.copy()
for player in world.player_ids for player in multiworld.player_ids
} }
for player, items in depletion_pool.items(): 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 count in items.values():
for _ in range(count): for _ in range(count):
new_items.append(player_world.create_filler()) new_items.append(player_world.create_filler())
target: int = sum(sum(items.values()) for items in depletion_pool.values()) 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): if depletion_pool[item.player].get(item.name, 0):
target -= 1 target -= 1
depletion_pool[item.player][item.name] -= 1 depletion_pool[item.player][item.name] -= 1
# quick abort if we have found all items # quick abort if we have found all items
if not target: if not target:
new_items.extend(world.itempool[i+1:]) new_items.extend(multiworld.itempool[i+1:])
break break
else: else:
new_items.append(item) 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(): for player, remaining_items in depletion_pool.items():
remaining_items = {name: count for name, count in remaining_items.items() if count} remaining_items = {name: count for name, count in remaining_items.items() if count}
if remaining_items: 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}") 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." assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change."
world.itempool[:] = new_items multiworld.itempool[:] = new_items
# temporary home for item links, should be moved out of Main # 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[ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]] Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]]
]: ]:
classifications: Dict[str, int] = collections.defaultdict(int) classifications: Dict[str, int] = collections.defaultdict(int)
counters = {player: {name: 0 for name in shared_pool} for player in players} 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: if item.player in counters and item.name in shared_pool:
counters[item.player][item.name] += 1 counters[item.player][item.name] += 1
classifications[item.name] |= item.classification classifications[item.name] |= item.classification
@ -246,13 +246,13 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
new_item.classification |= classifications[item_name] new_item.classification |= classifications[item_name]
new_itempool.append(new_item) new_itempool.append(new_item)
region = Region("Menu", group_id, world, "ItemLink") region = Region("Menu", group_id, multiworld, "ItemLink")
world.regions.append(region) multiworld.regions.append(region)
locations = region.locations locations = region.locations
for item in world.itempool: for item in multiworld.itempool:
count = common_item_count.get(item.player, {}).get(item.name, 0) count = common_item_count.get(item.player, {}).get(item.name, 0)
if count: 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) None, region)
loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \ loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \
state.has(item_name, group_id_, count_) state.has(item_name, group_id_, count_)
@ -263,10 +263,10 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
else: else:
new_itempool.append(item) new_itempool.append(item)
itemcount = len(world.itempool) itemcount = len(multiworld.itempool)
world.itempool = new_itempool multiworld.itempool = new_itempool
while itemcount > len(world.itempool): while itemcount > len(multiworld.itempool):
items_to_add = [] items_to_add = []
for player in group["players"]: for player in group["players"]:
if group["link_replacement"]: if group["link_replacement"]:
@ -274,64 +274,64 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
else: else:
item_player = player item_player = player
if group["replacement_items"][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])) group["replacement_items"][player]))
else: else:
items_to_add.append(AutoWorld.call_single(world, "create_filler", item_player)) items_to_add.append(AutoWorld.call_single(multiworld, "create_filler", item_player))
world.random.shuffle(items_to_add) multiworld.random.shuffle(items_to_add)
world.itempool.extend(items_to_add[:itemcount - len(world.itempool)]) multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)])
if any(world.item_links.values()): if any(multiworld.item_links.values()):
world._all_state = None multiworld._all_state = None
logger.info("Running Item Plando.") logger.info("Running Item Plando.")
distribute_planned(world) distribute_planned(multiworld)
logger.info('Running Pre Main Fill.') 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': if multiworld.algorithm == 'flood':
flood_items(world) # different algo, biased towards early game progress items flood_items(multiworld) # different algo, biased towards early game progress items
elif world.algorithm == 'balanced': elif multiworld.algorithm == 'balanced':
distribute_items_restrictive(world) 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: if multiworld.players > 1 and not args.skip_prog_balancing:
balance_multiworld_progression(world) balance_multiworld_progression(multiworld)
else: else:
logger.info("Progression balancing skipped.") logger.info("Progression balancing skipped.")
# we're about to output using multithreading, so we're removing the global random state to prevent accidental use # 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: if args.skip_output:
logger.info('Done. Skipped output/spoiler generation. Total Time: %s', time.perf_counter() - start) logger.info('Done. Skipped output/spoiler generation. Total Time: %s', time.perf_counter() - start)
return world return multiworld
logger.info(f'Beginning output...') logger.info(f'Beginning output...')
outfilebase = 'AP_' + world.seed_name outfilebase = 'AP_' + multiworld.seed_name
output = tempfile.TemporaryDirectory() output = tempfile.TemporaryDirectory()
with output as temp_dir: with output as temp_dir:
output_players = [player for player in world.player_ids if AutoWorld.World.generate_output.__code__ output_players = [player for player in multiworld.player_ids if AutoWorld.World.generate_output.__code__
is not world.worlds[player].generate_output.__code__] is not multiworld.worlds[player].generate_output.__code__]
with concurrent.futures.ThreadPoolExecutor(len(output_players) + 2) as pool: 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: for player in output_players:
# skip starting a thread for methods that say "pass". # skip starting a thread for methods that say "pass".
output_file_futures.append( 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 # collect ER hint info
er_hint_data: Dict[int, Dict[int, str]] = {} 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(): def write_multidata():
import NetUtils import NetUtils
@ -340,38 +340,38 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
games = {} games = {}
minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions} minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions}
slot_info = {} slot_info = {}
names = [[name for player, name in sorted(world.player_name.items())]] names = [[name for player, name in sorted(multiworld.player_name.items())]]
for slot in world.player_ids: for slot in multiworld.player_ids:
player_world: AutoWorld.World = world.worlds[slot] player_world: AutoWorld.World = multiworld.worlds[slot]
minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version) minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version)
client_versions[slot] = player_world.required_client_version client_versions[slot] = player_world.required_client_version
games[slot] = world.game[slot] games[slot] = multiworld.game[slot]
slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], world.game[slot], slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], multiworld.game[slot],
world.player_types[slot]) multiworld.player_types[slot])
for slot, group in world.groups.items(): for slot, group in multiworld.groups.items():
games[slot] = world.game[slot] games[slot] = multiworld.game[slot]
slot_info[slot] = NetUtils.NetworkSlot(group["name"], world.game[slot], world.player_types[slot], slot_info[slot] = NetUtils.NetworkSlot(group["name"], multiworld.game[slot], multiworld.player_types[slot],
group_members=sorted(group["players"])) group_members=sorted(group["players"]))
precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int] precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int]
for player, world_precollected in world.precollected_items.items()} for player, world_precollected in multiworld.precollected_items.items()}
precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))} precollected_hints = {player: set() for player in range(1, multiworld.players + 1 + len(multiworld.groups))}
for slot in world.player_ids: for slot in multiworld.player_ids:
slot_data[slot] = world.worlds[slot].fill_slot_data() slot_data[slot] = multiworld.worlds[slot].fill_slot_data()
def precollect_hint(location): def precollect_hint(location):
entrance = er_hint_data.get(location.player, {}).get(location.address, "") entrance = er_hint_data.get(location.player, {}).get(location.address, "")
hint = NetUtils.Hint(location.item.player, location.player, location.address, hint = NetUtils.Hint(location.item.player, location.player, location.address,
location.item.code, False, entrance, location.item.flags) location.item.code, False, entrance, location.item.flags)
precollected_hints[location.player].add(hint) 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) precollected_hints[location.item.player].add(hint)
else: else:
for player in world.groups[location.item.player]["players"]: for player in multiworld.groups[location.item.player]["players"]:
precollected_hints[player].add(hint) precollected_hints[player].add(hint)
locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids} locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in multiworld.player_ids}
for location in world.get_filled_locations(): for location in multiworld.get_filled_locations():
if type(location.address) == int: if type(location.address) == int:
assert location.item.code is not None, "item code None should be event, " \ assert location.item.code is not None, "item code None should be event, " \
"location.address should then also be None. Location: " \ "location.address should then also be None. Location: " \
@ -381,18 +381,18 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
f"{locations_data[location.player][location.address]}") f"{locations_data[location.player][location.address]}")
locations_data[location.player][location.address] = \ locations_data[location.player][location.address] = \
location.item.code, location.item.player, location.item.flags 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) 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) precollect_hint(location)
elif any([location.item.name in world.worlds[player].options.start_hints elif any([location.item.name in multiworld.worlds[player].options.start_hints
for player in world.groups.get(location.item.player, {}).get("players", [])]): for player in multiworld.groups.get(location.item.player, {}).get("players", [])]):
precollect_hint(location) precollect_hint(location)
# embedded data package # embedded data package
data_package = { data_package = {
game_world.game: worlds.network_data_package["games"][game_world.game] 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]]]] = {} checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {}
@ -400,7 +400,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
multidata = { multidata = {
"slot_data": slot_data, "slot_data": slot_data,
"slot_info": slot_info, "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, "locations": locations_data,
"checks_in_area": checks_in_area, "checks_in_area": checks_in_area,
"server_options": baked_server_options, "server_options": baked_server_options,
@ -410,10 +410,10 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
"version": tuple(version_tuple), "version": tuple(version_tuple),
"tags": ["AP"], "tags": ["AP"],
"minimum_versions": minimum_versions, "minimum_versions": minimum_versions,
"seed_name": world.seed_name, "seed_name": multiworld.seed_name,
"datapackage": data_package, "datapackage": data_package,
} }
AutoWorld.call_all(world, "modify_multidata", multidata) AutoWorld.call_all(multiworld, "modify_multidata", multidata)
multidata = zlib.compress(pickle.dumps(multidata), 9) multidata = zlib.compress(pickle.dumps(multidata), 9)
@ -423,7 +423,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
output_file_futures.append(pool.submit(write_multidata)) output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result(): 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.") raise Exception("Game appears as unbeatable. Aborting.")
else: else:
logger.warning("Location Accessibility requirements not fulfilled.") logger.warning("Location Accessibility requirements not fulfilled.")
@ -436,12 +436,12 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
if args.spoiler > 1: if args.spoiler > 1:
logger.info('Calculating playthrough.') 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: 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}") logger.info(f"Creating final archive at {zipfilename}")
with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED, with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED,
compresslevel=9) as zf: compresslevel=9) as zf:
@ -449,4 +449,4 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
zf.write(file.path, arcname=file.name) zf.write(file.path, arcname=file.name)
logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start) logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start)
return world return multiworld

View File

@ -195,10 +195,10 @@ def set_icon(window):
window.tk.call('wm', 'iconphoto', window._w, logo) window.tk.call('wm', 'iconphoto', window._w, logo)
def adjust(args): def adjust(args):
# Create a fake world and OOTWorld to use as a base # Create a fake multiworld and OOTWorld to use as a base
world = MultiWorld(1) multiworld = MultiWorld(1)
world.per_slot_randoms = {1: random} multiworld.per_slot_randoms = {1: random}
ootworld = OOTWorld(world, 1) ootworld = OOTWorld(multiworld, 1)
# Set options in the fake OOTWorld # Set options in the fake OOTWorld
for name, option in chain(cosmetic_options.items(), sfx_options.items()): for name, option in chain(cosmetic_options.items(), sfx_options.items()):
result = getattr(args, name, None) result = getattr(args, name, None)

View File

@ -871,8 +871,8 @@ def visualize_regions(root_region: Region, file_name: str, *,
Example usage in Main code: Example usage in Main code:
from Utils import visualize_regions from Utils import visualize_regions
for player in world.player_ids: for player in multiworld.player_ids:
visualize_regions(world.get_region("Menu", player), f"{world.get_out_file_name_base(player)}.puml") 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" assert root_region.multiworld, "The multiworld attribute of root_region has to be filled"
from BaseClasses import Entrance, Item, Location, LocationProgressType, MultiWorld, Region from BaseClasses import Entrance, Item, Location, LocationProgressType, MultiWorld, Region

View File

@ -11,30 +11,30 @@ from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, Item
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
def generate_multi_world(players: int = 1) -> MultiWorld: def generate_multiworld(players: int = 1) -> MultiWorld:
multi_world = MultiWorld(players) multiworld = MultiWorld(players)
multi_world.player_name = {} multiworld.player_name = {}
multi_world.state = CollectionState(multi_world) multiworld.state = CollectionState(multiworld)
for i in range(players): for i in range(players):
player_id = i+1 player_id = i+1
world = World(multi_world, player_id) world = World(multiworld, player_id)
multi_world.game[player_id] = f"Game {player_id}" multiworld.game[player_id] = f"Game {player_id}"
multi_world.worlds[player_id] = world multiworld.worlds[player_id] = world
multi_world.player_name[player_id] = "Test Player " + str(player_id) multiworld.player_name[player_id] = "Test Player " + str(player_id)
region = Region("Menu", player_id, multi_world, "Menu Region Hint") region = Region("Menu", player_id, multiworld, "Menu Region Hint")
multi_world.regions.append(region) multiworld.regions.append(region)
for option_key, option in Options.PerGameCommonOptions.type_hints.items(): for option_key, option in Options.PerGameCommonOptions.type_hints.items():
if hasattr(multi_world, option_key): if hasattr(multiworld, option_key):
getattr(multi_world, option_key).setdefault(player_id, option.from_any(getattr(option, "default"))) getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
else: 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 # 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}) 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): class PlayerDefinition(object):
@ -46,8 +46,8 @@ class PlayerDefinition(object):
basic_items: List[Item] basic_items: List[Item]
regions: List[Region] regions: List[Region]
def __init__(self, world: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []): def __init__(self, multiworld: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
self.multiworld = world self.multiworld = multiworld
self.id = id self.id = id
self.menu = menu self.menu = menu
self.locations = locations self.locations = locations
@ -72,7 +72,7 @@ class PlayerDefinition(object):
return region 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() items = items.copy()
while len(items) > 0: while len(items) > 0:
location = region.locations.pop(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: if location.item:
return items return items
item = items.pop(0) item = items.pop(0)
world.push_item(location, item, False) multiworld.push_item(location, item, False)
location.event = item.advancement location.event = item.advancement
return items return items
@ -94,15 +94,15 @@ def region_contains(region: Region, item: Item) -> bool:
return False 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: 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 = multi_world.get_region("Menu", player_id) menu = multiworld.get_region("Menu", player_id)
locations = generate_locations(location_count, player_id, None, menu) locations = generate_locations(location_count, player_id, None, menu)
prog_items = generate_items(prog_item_count, player_id, True) 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) 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]: 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): class TestFillRestrictive(unittest.TestCase):
def test_basic_fill(self): def test_basic_fill(self):
"""Tests `fill_restrictive` fills and removes the locations and items from their respective lists""" """Tests `fill_restrictive` fills and removes the locations and items from their respective lists"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0] item0 = player1.prog_items[0]
item1 = player1.prog_items[1] item1 = player1.prog_items[1]
loc0 = player1.locations[0] loc0 = player1.locations[0]
loc1 = player1.locations[1] loc1 = player1.locations[1]
fill_restrictive(multi_world, multi_world.state, fill_restrictive(multiworld, multiworld.state,
player1.locations, player1.prog_items) player1.locations, player1.prog_items)
self.assertEqual(loc0.item, item1) self.assertEqual(loc0.item, item1)
@ -152,16 +152,16 @@ class TestFillRestrictive(unittest.TestCase):
def test_ordered_fill(self): def test_ordered_fill(self):
"""Tests `fill_restrictive` fulfills set rules""" """Tests `fill_restrictive` fulfills set rules"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items items = player1.prog_items
locations = player1.locations 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) items[0].name, player1.id) and state.has(items[1].name, player1.id)
set_rule(locations[1], lambda state: state.has( set_rule(locations[1], lambda state: state.has(
items[0].name, player1.id)) items[0].name, player1.id))
fill_restrictive(multi_world, multi_world.state, fill_restrictive(multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy()) player1.locations.copy(), player1.prog_items.copy())
self.assertEqual(locations[0].item, items[0]) self.assertEqual(locations[0].item, items[0])
@ -169,8 +169,8 @@ class TestFillRestrictive(unittest.TestCase):
def test_partial_fill(self): def test_partial_fill(self):
"""Tests that `fill_restrictive` returns unfilled locations""" """Tests that `fill_restrictive` returns unfilled locations"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 3, 2) player1 = generate_player_data(multiworld, 1, 3, 2)
item0 = player1.prog_items[0] item0 = player1.prog_items[0]
item1 = player1.prog_items[1] item1 = player1.prog_items[1]
@ -178,14 +178,14 @@ class TestFillRestrictive(unittest.TestCase):
loc1 = player1.locations[1] loc1 = player1.locations[1]
loc2 = player1.locations[2] 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) item0.name, player1.id) and state.has(item1.name, player1.id)
set_rule(loc1, lambda state: state.has( set_rule(loc1, lambda state: state.has(
item0.name, player1.id)) item0.name, player1.id))
# forces a swap # forces a swap
set_rule(loc2, lambda state: state.has( set_rule(loc2, lambda state: state.has(
item0.name, player1.id)) item0.name, player1.id))
fill_restrictive(multi_world, multi_world.state, fill_restrictive(multiworld, multiworld.state,
player1.locations, player1.prog_items) player1.locations, player1.prog_items)
self.assertEqual(loc0.item, item0) self.assertEqual(loc0.item, item0)
@ -195,19 +195,19 @@ class TestFillRestrictive(unittest.TestCase):
def test_minimal_fill(self): def test_minimal_fill(self):
"""Test that fill for minimal player can have unreachable items""" """Test that fill for minimal player can have unreachable items"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items items = player1.prog_items
locations = player1.locations locations = player1.locations
multi_world.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal) multiworld.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal)
multi_world.completion_condition[player1.id] = lambda state: state.has( multiworld.completion_condition[player1.id] = lambda state: state.has(
items[1].name, player1.id) items[1].name, player1.id)
set_rule(locations[1], lambda state: state.has( set_rule(locations[1], lambda state: state.has(
items[0].name, player1.id)) items[0].name, player1.id))
fill_restrictive(multi_world, multi_world.state, fill_restrictive(multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy()) player1.locations.copy(), player1.prog_items.copy())
self.assertEqual(locations[0].item, items[1]) self.assertEqual(locations[0].item, items[1])
@ -220,15 +220,15 @@ class TestFillRestrictive(unittest.TestCase):
the non-minimal player get all items. the non-minimal player get all items.
""" """
multi_world = generate_multi_world(2) multiworld = generate_multiworld(2)
player1 = generate_player_data(multi_world, 1, 3, 3) player1 = generate_player_data(multiworld, 1, 3, 3)
player2 = generate_player_data(multi_world, 2, 3, 3) player2 = generate_player_data(multiworld, 2, 3, 3)
multi_world.accessibility[player1.id].value = multi_world.accessibility[player1.id].option_minimal multiworld.accessibility[player1.id].value = multiworld.accessibility[player1.id].option_minimal
multi_world.accessibility[player2.id].value = multi_world.accessibility[player2.id].option_locations multiworld.accessibility[player2.id].value = multiworld.accessibility[player2.id].option_locations
multi_world.completion_condition[player1.id] = lambda state: True multiworld.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[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[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)) set_rule(player1.locations[2], lambda state: state.has(player1.prog_items[1].name, player1.id))
@ -241,28 +241,28 @@ class TestFillRestrictive(unittest.TestCase):
# fill remaining locations with remaining items # fill remaining locations with remaining items
location_pool = player1.locations[1:] + player2.locations location_pool = player1.locations[1:] + player2.locations
item_pool = player1.prog_items[:-1] + player2.prog_items item_pool = player1.prog_items[:-1] + player2.prog_items
fill_restrictive(multi_world, multi_world.state, location_pool, item_pool) fill_restrictive(multiworld, multiworld.state, location_pool, item_pool)
multi_world.state.sweep_for_events() # collect everything multiworld.state.sweep_for_events() # collect everything
# all of player2's locations and items should be accessible (not all of player1's) # all of player2's locations and items should be accessible (not all of player1's)
for item in player2.prog_items: 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}') f'{item} is unreachable in {item.location}')
def test_reversed_fill(self): def test_reversed_fill(self):
"""Test a different set of rules can be satisfied""" """Test a different set of rules can be satisfied"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0] item0 = player1.prog_items[0]
item1 = player1.prog_items[1] item1 = player1.prog_items[1]
loc0 = player1.locations[0] loc0 = player1.locations[0]
loc1 = player1.locations[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(item1.name, player1.id) item0.name, player1.id) and state.has(item1.name, player1.id)
set_rule(loc1, lambda state: 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) player1.locations, player1.prog_items)
self.assertEqual(loc0.item, item1) self.assertEqual(loc0.item, item1)
@ -270,13 +270,13 @@ class TestFillRestrictive(unittest.TestCase):
def test_multi_step_fill(self): def test_multi_step_fill(self):
"""Test that fill is able to satisfy multiple spheres""" """Test that fill is able to satisfy multiple spheres"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 4, 4) player1 = generate_player_data(multiworld, 1, 4, 4)
items = player1.prog_items items = player1.prog_items
locations = player1.locations 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) items[2].name, player1.id) and state.has(items[3].name, player1.id)
set_rule(locations[1], lambda state: state.has( set_rule(locations[1], lambda state: state.has(
items[0].name, player1.id)) items[0].name, player1.id))
@ -285,7 +285,7 @@ class TestFillRestrictive(unittest.TestCase):
set_rule(locations[3], lambda state: state.has( set_rule(locations[3], lambda state: state.has(
items[1].name, player1.id)) items[1].name, player1.id))
fill_restrictive(multi_world, multi_world.state, fill_restrictive(multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy()) player1.locations.copy(), player1.prog_items.copy())
self.assertEqual(locations[0].item, items[1]) self.assertEqual(locations[0].item, items[1])
@ -295,25 +295,25 @@ class TestFillRestrictive(unittest.TestCase):
def test_impossible_fill(self): def test_impossible_fill(self):
"""Test that fill raises an error when it can't place any items""" """Test that fill raises an error when it can't place any items"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items items = player1.prog_items
locations = player1.locations 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) items[0].name, player1.id) and state.has(items[1].name, player1.id)
set_rule(locations[1], lambda state: state.has( set_rule(locations[1], lambda state: state.has(
items[1].name, player1.id)) items[1].name, player1.id))
set_rule(locations[0], lambda state: state.has( set_rule(locations[0], lambda state: state.has(
items[0].name, player1.id)) 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()) player1.locations.copy(), player1.prog_items.copy())
def test_circular_fill(self): def test_circular_fill(self):
"""Test that fill raises an error when it can't place all items""" """Test that fill raises an error when it can't place all items"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 3, 3) player1 = generate_player_data(multiworld, 1, 3, 3)
item0 = player1.prog_items[0] item0 = player1.prog_items[0]
item1 = player1.prog_items[1] item1 = player1.prog_items[1]
@ -322,46 +322,46 @@ class TestFillRestrictive(unittest.TestCase):
loc1 = player1.locations[1] loc1 = player1.locations[1]
loc2 = player1.locations[2] 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) 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(loc1, lambda state: state.has(item0.name, player1.id))
set_rule(loc2, lambda state: state.has(item1.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)) 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()) player1.locations.copy(), player1.prog_items.copy())
def test_competing_fill(self): def test_competing_fill(self):
"""Test that fill raises an error when it can't place items in a way to satisfy the conditions""" """Test that fill raises an error when it can't place items in a way to satisfy the conditions"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0] item0 = player1.prog_items[0]
item1 = player1.prog_items[1] item1 = player1.prog_items[1]
loc1 = player1.locations[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) 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) set_rule(loc1, lambda state: state.has(item0.name, player1.id)
and state.has(item1.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()) player1.locations.copy(), player1.prog_items.copy())
def test_multiplayer_fill(self): def test_multiplayer_fill(self):
"""Test that items can be placed across worlds""" """Test that items can be placed across worlds"""
multi_world = generate_multi_world(2) multiworld = generate_multiworld(2)
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
player2 = generate_player_data(multi_world, 2, 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[0].name, player1.id) and state.has(
player1.prog_items[1].name, player1.id) 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[0].name, player2.id) and state.has(
player2.prog_items[1].name, player2.id) 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) player2.locations, player1.prog_items + player2.prog_items)
self.assertEqual(player1.locations[0].item, player1.prog_items[1]) self.assertEqual(player1.locations[0].item, player1.prog_items[1])
@ -371,21 +371,21 @@ class TestFillRestrictive(unittest.TestCase):
def test_multiplayer_rules_fill(self): def test_multiplayer_rules_fill(self):
"""Test that fill across worlds satisfies the rules""" """Test that fill across worlds satisfies the rules"""
multi_world = generate_multi_world(2) multiworld = generate_multiworld(2)
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
player2 = generate_player_data(multi_world, 2, 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[0].name, player1.id) and state.has(
player1.prog_items[1].name, player1.id) 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[0].name, player2.id) and state.has(
player2.prog_items[1].name, player2.id) player2.prog_items[1].name, player2.id)
set_rule(player2.locations[1], lambda state: state.has( set_rule(player2.locations[1], lambda state: state.has(
player2.prog_items[0].name, player2.id)) 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) player2.locations, player1.prog_items + player2.prog_items)
self.assertEqual(player1.locations[0].item, player2.prog_items[0]) self.assertEqual(player1.locations[0].item, player2.prog_items[0])
@ -395,10 +395,10 @@ class TestFillRestrictive(unittest.TestCase):
def test_restrictive_progress(self): def test_restrictive_progress(self):
"""Test that various spheres with different requirements can be filled""" """Test that various spheres with different requirements can be filled"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, prog_item_count=25) player1 = generate_player_data(multiworld, 1, prog_item_count=25)
items = player1.prog_items.copy() 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) names(player1.prog_items), player1.id)
player1.generate_region(player1.menu, 5) player1.generate_region(player1.menu, 5)
@ -411,16 +411,16 @@ class TestFillRestrictive(unittest.TestCase):
player1.generate_region(player1.menu, 5, lambda state: state.has_all( player1.generate_region(player1.menu, 5, lambda state: state.has_all(
names(items[17:22]), player1.id)) 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) locations, player1.prog_items)
def test_swap_to_earlier_location_with_item_rule(self): def test_swap_to_earlier_location_with_item_rule(self):
"""Test that item swap happens and works as intended""" """Test that item swap happens and works as intended"""
# test for PR#1109 # test for PR#1109
multi_world = generate_multi_world(1) multiworld = generate_multiworld(1)
player1 = generate_player_data(multi_world, 1, 4, 4) player1 = generate_player_data(multiworld, 1, 4, 4)
locations = player1.locations[:] # copy required locations = player1.locations[:] # copy required
items = player1.prog_items[:] # 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 # for the test to work, item and location order is relevant: Sphere 1 last, allowed_item not last
@ -437,15 +437,15 @@ class TestFillRestrictive(unittest.TestCase):
self.assertTrue(sphere1_loc.can_fill(None, allowed_item, False), "Test is flawed") 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") 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 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 # assert swap happened
self.assertTrue(sphere1_loc.item, "Did not swap required item into Sphere 1") 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") self.assertEqual(sphere1_loc.item, allowed_item, "Wrong item in Sphere 1")
def test_swap_to_earlier_location_with_item_rule2(self): def test_swap_to_earlier_location_with_item_rule2(self):
"""Test that swap works before all items are placed""" """Test that swap works before all items are placed"""
multi_world = generate_multi_world(1) multiworld = generate_multiworld(1)
player1 = generate_player_data(multi_world, 1, 5, 5) player1 = generate_player_data(multiworld, 1, 5, 5)
locations = player1.locations[:] # copy required locations = player1.locations[:] # copy required
items = player1.prog_items[:] # copy required items = player1.prog_items[:] # copy required
# Two items provide access to sphere 2. # Two items provide access to sphere 2.
@ -477,7 +477,7 @@ class TestFillRestrictive(unittest.TestCase):
# Now fill should place one_to_two1 in sphere1_loc1 or sphere1_loc2 via swap, # 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. # 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 # 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 and sphere1_loc2.item, "Did not swap required item into Sphere 1")
self.assertTrue(sphere1_loc1.item.name == one_to_two1 or self.assertTrue(sphere1_loc1.item.name == one_to_two1 or
@ -486,29 +486,29 @@ class TestFillRestrictive(unittest.TestCase):
def test_double_sweep(self): def test_double_sweep(self):
"""Test that sweep doesn't duplicate Event items when sweeping""" """Test that sweep doesn't duplicate Event items when sweeping"""
# test for PR1114 # test for PR1114
multi_world = generate_multi_world(1) multiworld = generate_multiworld(1)
player1 = generate_player_data(multi_world, 1, 1, 1) player1 = generate_player_data(multiworld, 1, 1, 1)
location = player1.locations[0] location = player1.locations[0]
location.address = None location.address = None
location.event = True location.event = True
item = player1.prog_items[0] item = player1.prog_items[0]
item.code = None item.code = None
location.place_locked_item(item) location.place_locked_item(item)
multi_world.state.sweep_for_events() multiworld.state.sweep_for_events()
multi_world.state.sweep_for_events() multiworld.state.sweep_for_events()
self.assertTrue(multi_world.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed") self.assertTrue(multiworld.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") self.assertEqual(multiworld.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times")
def test_correct_item_instance_removed_from_pool(self): def test_correct_item_instance_removed_from_pool(self):
"""Test that a placed item gets removed from the submitted pool""" """Test that a placed item gets removed from the submitted pool"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data(multi_world, 1, 2, 2) player1 = generate_player_data(multiworld, 1, 2, 2)
player1.prog_items[0].name = "Different_item_instance_but_same_item_name" player1.prog_items[0].name = "Different_item_instance_but_same_item_name"
player1.prog_items[1].name = "Different_item_instance_but_same_item_name" player1.prog_items[1].name = "Different_item_instance_but_same_item_name"
loc0 = player1.locations[0] loc0 = player1.locations[0]
fill_restrictive(multi_world, multi_world.state, fill_restrictive(multiworld, multiworld.state,
[loc0], player1.prog_items) [loc0], player1.prog_items)
self.assertEqual(1, len(player1.prog_items)) self.assertEqual(1, len(player1.prog_items))
@ -518,14 +518,14 @@ class TestFillRestrictive(unittest.TestCase):
class TestDistributeItemsRestrictive(unittest.TestCase): class TestDistributeItemsRestrictive(unittest.TestCase):
def test_basic_distribute(self): def test_basic_distribute(self):
"""Test that distribute_items_restrictive is deterministic""" """Test that distribute_items_restrictive is deterministic"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data( 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 = player1.locations
prog_items = player1.prog_items prog_items = player1.prog_items
basic_items = player1.basic_items basic_items = player1.basic_items
distribute_items_restrictive(multi_world) distribute_items_restrictive(multiworld)
self.assertEqual(locations[0].item, basic_items[1]) self.assertEqual(locations[0].item, basic_items[1])
self.assertFalse(locations[0].event) self.assertFalse(locations[0].event)
@ -538,52 +538,52 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_excluded_distribute(self): def test_excluded_distribute(self):
"""Test that distribute_items_restrictive doesn't put advancement items on excluded locations""" """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( 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 = player1.locations
locations[1].progress_type = LocationProgressType.EXCLUDED locations[1].progress_type = LocationProgressType.EXCLUDED
locations[2].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[1].item.advancement)
self.assertFalse(locations[2].item.advancement) self.assertFalse(locations[2].item.advancement)
def test_non_excluded_item_distribute(self): def test_non_excluded_item_distribute(self):
"""Test that useful items aren't placed on excluded locations""" """Test that useful items aren't placed on excluded locations"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data( 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 = player1.locations
basic_items = player1.basic_items basic_items = player1.basic_items
locations[1].progress_type = LocationProgressType.EXCLUDED locations[1].progress_type = LocationProgressType.EXCLUDED
basic_items[1].classification = ItemClassification.useful basic_items[1].classification = ItemClassification.useful
distribute_items_restrictive(multi_world) distribute_items_restrictive(multiworld)
self.assertEqual(locations[1].item, basic_items[0]) self.assertEqual(locations[1].item, basic_items[0])
def test_too_many_excluded_distribute(self): 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""" """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( 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 = player1.locations
locations[0].progress_type = LocationProgressType.EXCLUDED locations[0].progress_type = LocationProgressType.EXCLUDED
locations[1].progress_type = LocationProgressType.EXCLUDED locations[1].progress_type = LocationProgressType.EXCLUDED
locations[2].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): 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""" """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( 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 = player1.locations
basic_items = player1.basic_items basic_items = player1.basic_items
@ -592,47 +592,47 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
basic_items[0].classification = ItemClassification.useful basic_items[0].classification = ItemClassification.useful
basic_items[1].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): def test_priority_distribute(self):
"""Test that priority locations receive advancement items""" """Test that priority locations receive advancement items"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data( 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 = player1.locations
locations[0].progress_type = LocationProgressType.PRIORITY locations[0].progress_type = LocationProgressType.PRIORITY
locations[3].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[0].item.advancement)
self.assertTrue(locations[3].item.advancement) self.assertTrue(locations[3].item.advancement)
def test_excess_priority_distribute(self): def test_excess_priority_distribute(self):
"""Test that if there's more priority locations than advancement items, they can still fill""" """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( 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 = player1.locations
locations[0].progress_type = LocationProgressType.PRIORITY locations[0].progress_type = LocationProgressType.PRIORITY
locations[1].progress_type = LocationProgressType.PRIORITY locations[1].progress_type = LocationProgressType.PRIORITY
locations[2].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) self.assertFalse(locations[3].item.advancement)
def test_multiple_world_priority_distribute(self): def test_multiple_world_priority_distribute(self):
"""Test that priority fill can be satisfied for multiple worlds""" """Test that priority fill can be satisfied for multiple worlds"""
multi_world = generate_multi_world(3) multiworld = generate_multiworld(3)
player1 = generate_player_data( 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( 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( 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[2].progress_type = LocationProgressType.PRIORITY
player1.locations[3].progress_type = LocationProgressType.PRIORITY player1.locations[3].progress_type = LocationProgressType.PRIORITY
@ -644,7 +644,7 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
player3.locations[2].progress_type = LocationProgressType.PRIORITY player3.locations[2].progress_type = LocationProgressType.PRIORITY
player3.locations[3].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[2].item.advancement)
self.assertTrue(player1.locations[3].item.advancement) self.assertTrue(player1.locations[3].item.advancement)
@ -656,9 +656,9 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_can_remove_locations_in_fill_hook(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""" """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( 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_item: list[Item] = []
removed_location: list[Location] = [] removed_location: list[Location] = []
@ -667,21 +667,21 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
removed_item.append(filleritempool.pop(0)) removed_item.append(filleritempool.pop(0))
removed_location.append(fill_locations.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_item[0].location)
self.assertIsNone(removed_location[0].item) self.assertIsNone(removed_location[0].item)
def test_seed_robust_to_item_order(self): def test_seed_robust_to_item_order(self):
"""Test deterministic fill""" """Test deterministic fill"""
mw1 = generate_multi_world() mw1 = generate_multiworld()
gen1 = generate_player_data( gen1 = generate_player_data(
mw1, 1, 4, prog_item_count=2, basic_item_count=2) mw1, 1, 4, prog_item_count=2, basic_item_count=2)
distribute_items_restrictive(mw1) distribute_items_restrictive(mw1)
mw2 = generate_multi_world() mw2 = generate_multiworld()
gen2 = generate_player_data( gen2 = generate_player_data(
mw2, 1, 4, prog_item_count=2, basic_item_count=2) mw2, 1, 4, prog_item_count=2, basic_item_count=2)
mw2.itempool.append(mw2.itempool.pop(0)) mw2.itempool.append(mw2.itempool.pop(0))
@ -694,12 +694,12 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_seed_robust_to_location_order(self): def test_seed_robust_to_location_order(self):
"""Test deterministic fill even if locations in a region are reordered""" """Test deterministic fill even if locations in a region are reordered"""
mw1 = generate_multi_world() mw1 = generate_multiworld()
gen1 = generate_player_data( gen1 = generate_player_data(
mw1, 1, 4, prog_item_count=2, basic_item_count=2) mw1, 1, 4, prog_item_count=2, basic_item_count=2)
distribute_items_restrictive(mw1) distribute_items_restrictive(mw1)
mw2 = generate_multi_world() mw2 = generate_multiworld()
gen2 = generate_player_data( gen2 = generate_player_data(
mw2, 1, 4, prog_item_count=2, basic_item_count=2) mw2, 1, 4, prog_item_count=2, basic_item_count=2)
reg = mw2.get_region("Menu", gen2.id) reg = mw2.get_region("Menu", gen2.id)
@ -713,45 +713,45 @@ class TestDistributeItemsRestrictive(unittest.TestCase):
def test_can_reserve_advancement_items_for_general_fill(self): def test_can_reserve_advancement_items_for_general_fill(self):
"""Test that priority locations fill still satisfies item rules""" """Test that priority locations fill still satisfies item rules"""
multi_world = generate_multi_world() multiworld = generate_multiworld()
player1 = generate_player_data( 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 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) names(items), player1.id)
location = player1.locations[0] location = player1.locations[0]
location.progress_type = LocationProgressType.PRIORITY location.progress_type = LocationProgressType.PRIORITY
location.item_rule = lambda item: item not in items[:4] 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]) self.assertEqual(location.item, items[4])
def test_non_excluded_local_items(self): def test_non_excluded_local_items(self):
"""Test that local items get placed locally in a multiworld""" """Test that local items get placed locally in a multiworld"""
multi_world = generate_multi_world(2) multiworld = generate_multiworld(2)
player1 = generate_player_data( 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( 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 item.classification = ItemClassification.useful
multi_world.local_items[player1.id].value = set(names(player1.basic_items)) multiworld.local_items[player1.id].value = set(names(player1.basic_items))
multi_world.local_items[player2.id].value = set(names(player2.basic_items)) multiworld.local_items[player2.id].value = set(names(player2.basic_items))
locality_rules(multi_world) 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.assertEqual(item.player, item.location.player)
self.assertFalse(item.location.event, False) self.assertFalse(item.location.event, False)
def test_early_items(self) -> None: def test_early_items(self) -> None:
"""Test that the early items API successfully places items early""" """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) 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) player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5)
mw.early_items[1][player1.basic_items[0].name] = 1 mw.early_items[1][player1.basic_items[0].name] = 1
@ -810,19 +810,19 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
"\n Contains" + str(list(map(lambda location: location.item, region.locations)))) "\n Contains" + str(list(map(lambda location: location.item, region.locations))))
def setUp(self) -> None: def setUp(self) -> None:
multi_world = generate_multi_world(2) multiworld = generate_multiworld(2)
self.multi_world = multi_world self.multiworld = multiworld
player1 = generate_player_data( 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 self.player1 = player1
player2 = generate_player_data( 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 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[0].name, player1.id) and state.has(
player1.prog_items[1].name, player1.id) 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[0].name, player2.id) and state.has(
player2.prog_items[1].name, player2.id) player2.prog_items[1].name, player2.id)
@ -830,42 +830,42 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
# Sphere 1 # Sphere 1
region = player1.generate_region(player1.menu, 20) region = player1.generate_region(player1.menu, 20)
items = fill_region(multi_world, region, [ items = fill_region(multiworld, region, [
player1.prog_items[0]] + items) player1.prog_items[0]] + items)
# Sphere 2 # Sphere 2
region = player1.generate_region( region = player1.generate_region(
player1.regions[1], 20, lambda state: state.has(player1.prog_items[0].name, player1.id)) player1.regions[1], 20, lambda state: state.has(player1.prog_items[0].name, player1.id))
items = fill_region( 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 # Sphere 3
region = player2.generate_region( region = player2.generate_region(
player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id)) 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: def test_balances_progression(self) -> None:
"""Tests that progression balancing moves progression items earlier""" """Tests that progression balancing moves progression items earlier"""
self.multi_world.progression_balancing[self.player1.id].value = 50 self.multiworld.progression_balancing[self.player1.id].value = 50
self.multi_world.progression_balancing[self.player2.id].value = 50 self.multiworld.progression_balancing[self.player2.id].value = 50
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0]) self.player1.regions[2], self.player2.prog_items[0])
balance_multiworld_progression(self.multi_world) balance_multiworld_progression(self.multiworld)
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[1], self.player2.prog_items[0]) self.player1.regions[1], self.player2.prog_items[0])
def test_balances_progression_light(self) -> None: def test_balances_progression_light(self) -> None:
"""Test that progression balancing still moves items earlier on minimum value""" """Test that progression balancing still moves items earlier on minimum value"""
self.multi_world.progression_balancing[self.player1.id].value = 1 self.multiworld.progression_balancing[self.player1.id].value = 1
self.multi_world.progression_balancing[self.player2.id].value = 1 self.multiworld.progression_balancing[self.player2.id].value = 1
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0]) 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 # TODO: arrange for a result that's different from the default
self.assertRegionContains( self.assertRegionContains(
@ -873,13 +873,13 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
def test_balances_progression_heavy(self) -> None: def test_balances_progression_heavy(self) -> None:
"""Test that progression balancing moves items earlier on maximum value""" """Test that progression balancing moves items earlier on maximum value"""
self.multi_world.progression_balancing[self.player1.id].value = 99 self.multiworld.progression_balancing[self.player1.id].value = 99
self.multi_world.progression_balancing[self.player2.id].value = 99 self.multiworld.progression_balancing[self.player2.id].value = 99
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0]) 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 # TODO: arrange for a result that's different from the default
self.assertRegionContains( self.assertRegionContains(
@ -887,25 +887,25 @@ class TestBalanceMultiworldProgression(unittest.TestCase):
def test_skips_balancing_progression(self) -> None: def test_skips_balancing_progression(self) -> None:
"""Test that progression balancing is skipped when players have it disabled""" """Test that progression balancing is skipped when players have it disabled"""
self.multi_world.progression_balancing[self.player1.id].value = 0 self.multiworld.progression_balancing[self.player1.id].value = 0
self.multi_world.progression_balancing[self.player2.id].value = 0 self.multiworld.progression_balancing[self.player2.id].value = 0
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0]) self.player1.regions[2], self.player2.prog_items[0])
balance_multiworld_progression(self.multi_world) balance_multiworld_progression(self.multiworld)
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0]) self.player1.regions[2], self.player2.prog_items[0])
def test_ignores_priority_locations(self) -> None: def test_ignores_priority_locations(self) -> None:
"""Test that progression items on priority locations don't get moved by balancing""" """Test that progression items on priority locations don't get moved by balancing"""
self.multi_world.progression_balancing[self.player1.id].value = 50 self.multiworld.progression_balancing[self.player1.id].value = 50
self.multi_world.progression_balancing[self.player2.id].value = 50 self.multiworld.progression_balancing[self.player2.id].value = 50
self.player2.prog_items[0].location.progress_type = LocationProgressType.PRIORITY self.player2.prog_items[0].location.progress_type = LocationProgressType.PRIORITY
balance_multiworld_progression(self.multi_world) balance_multiworld_progression(self.multiworld)
self.assertRegionContains( self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0]) self.player1.regions[2], self.player2.prog_items[0])

View File

@ -36,15 +36,15 @@ class TestBase(unittest.TestCase):
for game_name, world_type in AutoWorldRegister.world_types.items(): for game_name, world_type in AutoWorldRegister.world_types.items():
unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set()) unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set())
with self.subTest("Game", game=game_name): with self.subTest("Game", game=game_name):
world = setup_solo_multiworld(world_type) multiworld = setup_solo_multiworld(world_type)
excluded = world.worlds[1].options.exclude_locations.value excluded = multiworld.worlds[1].options.exclude_locations.value
state = world.get_all_state(False) state = multiworld.get_all_state(False)
for location in world.get_locations(): for location in multiworld.get_locations():
if location.name not in excluded: if location.name not in excluded:
with self.subTest("Location should be reached", location=location): with self.subTest("Location should be reached", location=location):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable") 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: if region.name in unreachable_regions:
with self.subTest("Region should be unreachable", region=region): with self.subTest("Region should be unreachable", region=region):
self.assertFalse(region.can_reach(state)) self.assertFalse(region.can_reach(state))
@ -53,15 +53,15 @@ class TestBase(unittest.TestCase):
self.assertTrue(region.can_reach(state)) self.assertTrue(region.can_reach(state))
with self.subTest("Completion Condition"): 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): def test_default_empty_state_can_reach_something(self):
"""Ensure empty state can reach at least one location with the defined options""" """Ensure empty state can reach at least one location with the defined options"""
for game_name, world_type in AutoWorldRegister.world_types.items(): for game_name, world_type in AutoWorldRegister.world_types.items():
with self.subTest("Game", game=game_name): with self.subTest("Game", game=game_name):
world = setup_solo_multiworld(world_type) multiworld = setup_solo_multiworld(world_type)
state = CollectionState(world) state = CollectionState(multiworld)
all_locations = world.get_locations() all_locations = multiworld.get_locations()
if all_locations: if all_locations:
locations = set() locations = set()
for location in all_locations: for location in all_locations: