Core: Fix a playthrough crash when a world uses "placement based logic" (#3915)

* Fix playthrough

* oops

* oops 2

* I don't like this

* that should do it

* Update BaseClasses.py

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* Update BaseClasses.py

---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
This commit is contained in:
NewSoupVi
2025-05-23 23:47:21 +02:00
committed by GitHub
parent 8671e9a391
commit 13ca134d12

View File

@@ -559,7 +559,9 @@ class MultiWorld():
else: else:
return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1))) return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1)))
def can_beat_game(self, starting_state: Optional[CollectionState] = None) -> bool: def can_beat_game(self,
starting_state: Optional[CollectionState] = None,
locations: Optional[Iterable[Location]] = None) -> bool:
if starting_state: if starting_state:
if self.has_beaten_game(starting_state): if self.has_beaten_game(starting_state):
return True return True
@@ -568,7 +570,9 @@ class MultiWorld():
state = CollectionState(self) state = CollectionState(self)
if self.has_beaten_game(state): if self.has_beaten_game(state):
return True return True
prog_locations = {location for location in self.get_locations() if location.item
base_locations = self.get_locations() if locations is None else locations
prog_locations = {location for location in base_locations if location.item
and location.item.advancement and location not in state.locations_checked} and location.item.advancement and location not in state.locations_checked}
while prog_locations: while prog_locations:
@@ -1603,21 +1607,19 @@ class Spoiler:
# in the second phase, we cull each sphere such that the game is still beatable, # in the second phase, we cull each sphere such that the game is still beatable,
# reducing each range of influence to the bare minimum required inside it # reducing each range of influence to the bare minimum required inside it
restore_later: Dict[Location, Item] = {} required_locations = {location for sphere in collection_spheres for location in sphere}
for num, sphere in reversed(tuple(enumerate(collection_spheres))): for num, sphere in reversed(tuple(enumerate(collection_spheres))):
to_delete: Set[Location] = set() to_delete: Set[Location] = set()
for location in sphere: for location in sphere:
# we remove the item at location and check if game is still beatable # we remove the location from required_locations to sweep from, and check if the game is still beatable
logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name,
location.item.player) location.item.player)
old_item = location.item required_locations.remove(location)
location.item = None if multiworld.can_beat_game(state_cache[num], required_locations):
if multiworld.can_beat_game(state_cache[num]):
to_delete.add(location) to_delete.add(location)
restore_later[location] = old_item
else: else:
# still required, got to keep it around # still required, got to keep it around
location.item = old_item required_locations.add(location)
# cull entries in spheres for spoiler walkthrough at end # cull entries in spheres for spoiler walkthrough at end
sphere -= to_delete sphere -= to_delete
@@ -1634,7 +1636,7 @@ class Spoiler:
logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player) logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
precollected_items.remove(item) precollected_items.remove(item)
multiworld.state.remove(item) multiworld.state.remove(item)
if not multiworld.can_beat_game(): if not multiworld.can_beat_game(multiworld.state, required_locations):
# Add the item back into `precollected_items` and collect it into `multiworld.state`. # Add the item back into `precollected_items` and collect it into `multiworld.state`.
multiworld.push_precollected(item) multiworld.push_precollected(item)
else: else:
@@ -1676,9 +1678,6 @@ class Spoiler:
self.create_paths(state, collection_spheres) self.create_paths(state, collection_spheres)
# repair the multiworld again # repair the multiworld again
for location, item in restore_later.items():
location.item = item
for item in removed_precollected: for item in removed_precollected:
multiworld.push_precollected(item) multiworld.push_precollected(item)