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