DS3: Convert post_fill to stage_post_fill for better performance (#4122)

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Mysteryem 2024-12-26 13:50:18 +00:00 committed by GitHub
parent 62942704bd
commit 33ae68c756
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 105 additions and 85 deletions

View File

@ -1366,7 +1366,8 @@ class DarkSouls3World(World):
text = "\n" + text + "\n" text = "\n" + text + "\n"
spoiler_handle.write(text) spoiler_handle.write(text)
def post_fill(self): @classmethod
def stage_post_fill(cls, multiworld: MultiWorld):
"""If item smoothing is enabled, rearrange items so they scale up smoothly through the run. """If item smoothing is enabled, rearrange items so they scale up smoothly through the run.
This determines the approximate order a given silo of items (say, soul items) show up in the This determines the approximate order a given silo of items (say, soul items) show up in the
@ -1375,106 +1376,125 @@ class DarkSouls3World(World):
items, later spheres get higher-level ones. Within a sphere, items in DS3 are distributed in items, later spheres get higher-level ones. Within a sphere, items in DS3 are distributed in
region order, and then the best items in a sphere go into the multiworld. region order, and then the best items in a sphere go into the multiworld.
""" """
ds3_worlds = [world for world in cast(List[DarkSouls3World], multiworld.get_game_worlds(cls.game)) if
world.options.smooth_upgrade_items
or world.options.smooth_soul_items
or world.options.smooth_upgraded_weapons]
if not ds3_worlds:
# No worlds need item smoothing.
return
locations_by_sphere = [ spheres_per_player: Dict[int, List[List[Location]]] = {world.player: [] for world in ds3_worlds}
sorted(loc for loc in sphere if loc.item.player == self.player and not loc.locked) for sphere in multiworld.get_spheres():
for sphere in self.multiworld.get_spheres() locations_per_item_player: Dict[int, List[Location]] = {player: [] for player in spheres_per_player.keys()}
] for location in sphere:
if location.locked:
continue
item_player = location.item.player
if item_player in locations_per_item_player:
locations_per_item_player[item_player].append(location)
for player, locations in locations_per_item_player.items():
# Sort for deterministic results.
locations.sort()
spheres_per_player[player].append(locations)
# All items in the base game in approximately the order they appear for ds3_world in ds3_worlds:
all_item_order: List[DS3ItemData] = [ locations_by_sphere = spheres_per_player[ds3_world.player]
item_dictionary[location.default_item_name]
for region in region_order
# Shuffle locations within each region.
for location in self._shuffle(location_tables[region])
if self._is_location_available(location)
]
# All DarkSouls3Items for this world that have been assigned anywhere, grouped by name # All items in the base game in approximately the order they appear
full_items_by_name: Dict[str, List[DarkSouls3Item]] = defaultdict(list) all_item_order: List[DS3ItemData] = [
for location in self.multiworld.get_filled_locations(): item_dictionary[location.default_item_name]
if location.item.player == self.player and ( for region in region_order
location.player != self.player or self._is_location_available(location) # Shuffle locations within each region.
): for location in ds3_world._shuffle(location_tables[region])
full_items_by_name[location.item.name].append(location.item) if ds3_world._is_location_available(location)
]
def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None: # All DarkSouls3Items for this world that have been assigned anywhere, grouped by name
"""Rearrange all items in item_order to match that order. full_items_by_name: Dict[str, List[DarkSouls3Item]] = defaultdict(list)
for location in multiworld.get_filled_locations():
if location.item.player == ds3_world.player and (
location.player != ds3_world.player or ds3_world._is_location_available(location)
):
full_items_by_name[location.item.name].append(location.item)
Note: this requires that item_order exactly matches the number of placed items from this def smooth_items(item_order: List[Union[DS3ItemData, DarkSouls3Item]]) -> None:
world matching the given names. """Rearrange all items in item_order to match that order.
"""
# Convert items to full DarkSouls3Items. Note: this requires that item_order exactly matches the number of placed items from this
converted_item_order: List[DarkSouls3Item] = [ world matching the given names.
item for item in ( """
(
# full_items_by_name won't contain DLC items if the DLC is disabled. # Convert items to full DarkSouls3Items.
(full_items_by_name[item.name] or [None]).pop(0) converted_item_order: List[DarkSouls3Item] = [
if isinstance(item, DS3ItemData) else item item for item in (
(
# full_items_by_name won't contain DLC items if the DLC is disabled.
(full_items_by_name[item.name] or [None]).pop(0)
if isinstance(item, DS3ItemData) else item
)
for item in item_order
) )
for item in item_order # Never re-order event items, because they weren't randomized in the first place.
) if item and item.code is not None
# Never re-order event items, because they weren't randomized in the first place. ]
if item and item.code is not None
]
names = {item.name for item in converted_item_order} names = {item.name for item in converted_item_order}
all_matching_locations = [ all_matching_locations = [
loc loc
for sphere in locations_by_sphere for sphere in locations_by_sphere
for loc in sphere for loc in sphere
if loc.item.name in names if loc.item.name in names
] ]
# It's expected that there may be more total items than there are matching locations if # It's expected that there may be more total items than there are matching locations if
# the player has chosen a more limited accessibility option, since the matching # the player has chosen a more limited accessibility option, since the matching
# locations *only* include items in the spheres of accessibility. # locations *only* include items in the spheres of accessibility.
if len(converted_item_order) < len(all_matching_locations): if len(converted_item_order) < len(all_matching_locations):
raise Exception( raise Exception(
f"DS3 bug: there are {len(all_matching_locations)} locations that can " + f"DS3 bug: there are {len(all_matching_locations)} locations that can " +
f"contain smoothed items, but only {len(converted_item_order)} items to smooth." f"contain smoothed items, but only {len(converted_item_order)} items to smooth."
) )
for sphere in locations_by_sphere: for sphere in locations_by_sphere:
locations = [loc for loc in sphere if loc.item.name in names] locations = [loc for loc in sphere if loc.item.name in names]
# Check the game, not the player, because we know how to sort within regions for DS3 # Check the game, not the player, because we know how to sort within regions for DS3
offworld = self._shuffle([loc for loc in locations if loc.game != "Dark Souls III"]) offworld = ds3_world._shuffle([loc for loc in locations if loc.game != "Dark Souls III"])
onworld = sorted((loc for loc in locations if loc.game == "Dark Souls III"), onworld = sorted((loc for loc in locations if loc.game == "Dark Souls III"),
key=lambda loc: loc.data.region_value) key=lambda loc: loc.data.region_value)
# Give offworld regions the last (best) items within a given sphere # Give offworld regions the last (best) items within a given sphere
for location in onworld + offworld: for location in onworld + offworld:
new_item = self._pop_item(location, converted_item_order) new_item = ds3_world._pop_item(location, converted_item_order)
location.item = new_item location.item = new_item
new_item.location = location new_item.location = location
if self.options.smooth_upgrade_items: if ds3_world.options.smooth_upgrade_items:
base_names = { base_names = {
"Titanite Shard", "Large Titanite Shard", "Titanite Chunk", "Titanite Slab", "Titanite Shard", "Large Titanite Shard", "Titanite Chunk", "Titanite Slab",
"Titanite Scale", "Twinkling Titanite", "Farron Coal", "Sage's Coal", "Giant's Coal", "Titanite Scale", "Twinkling Titanite", "Farron Coal", "Sage's Coal", "Giant's Coal",
"Profaned Coal" "Profaned Coal"
} }
smooth_items([item for item in all_item_order if item.base_name in base_names]) smooth_items([item for item in all_item_order if item.base_name in base_names])
if self.options.smooth_soul_items: if ds3_world.options.smooth_soul_items:
smooth_items([ smooth_items([
item for item in all_item_order item for item in all_item_order
if item.souls and item.classification != ItemClassification.progression if item.souls and item.classification != ItemClassification.progression
]) ])
if self.options.smooth_upgraded_weapons: if ds3_world.options.smooth_upgraded_weapons:
upgraded_weapons = [ upgraded_weapons = [
location.item location.item
for location in self.multiworld.get_filled_locations() for location in multiworld.get_filled_locations()
if location.item.player == self.player if location.item.player == ds3_world.player
and location.item.level and location.item.level > 0 and location.item.level and location.item.level > 0
and location.item.classification != ItemClassification.progression and location.item.classification != ItemClassification.progression
] ]
upgraded_weapons.sort(key=lambda item: item.level) upgraded_weapons.sort(key=lambda item: item.level)
smooth_items(upgraded_weapons) smooth_items(upgraded_weapons)
def _shuffle(self, seq: Sequence) -> List: def _shuffle(self, seq: Sequence) -> List:
"""Returns a shuffled copy of a sequence.""" """Returns a shuffled copy of a sequence."""