Violet code review updates.

This commit is contained in:
massimilianodelliubaldini
2025-05-06 23:16:23 -04:00
parent a91e8c1821
commit 920b4b5a30
20 changed files with 96 additions and 72 deletions

View File

@@ -10,13 +10,6 @@ from .locs import (OrbLocations as Orbs,
class JakAndDaxterLocation(Location):
game: str = jak1_name
# In AP 0.5.0, the base Location.can_reach function had its two boolean conditions swapped for a faster
# short-circuit for better performance. However, Jak seeds actually generate faster using the older method,
# which has been re-implemented below.
def can_reach(self, state: CollectionState) -> bool:
assert self.parent_region, "Can't reach location without region"
return self.parent_region.can_reach(state) and self.access_rule(state)
# Different tables for location groups.
# Each Item ID == its corresponding Location ID. While we're here, do all the ID conversions needed.

View File

@@ -55,10 +55,11 @@ def create_regions(world: JakAndDaxterWorld):
for bundle_index in range(bundle_count):
# Unlike Per-Level Orbsanity, Global Orbsanity Locations always have a level_index of 16.
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(16,
bundle_index,
access_rule=lambda state, bundle=bundle_index:
can_reach_orbs_global(state, player, world, bundle))
access_rule=lambda state, orb_amount=amount:
can_reach_orbs_global(state, player, world, orb_amount))
multiworld.regions.append(orbs)
menu.connect(orbs)

View File

@@ -29,27 +29,26 @@ def set_orb_trade_rule(world: JakAndDaxterWorld):
def recalculate_reachable_orbs(state: CollectionState, player: int, world: JakAndDaxterWorld) -> None:
if not state.prog_items[player]["Reachable Orbs Fresh"]:
# Recalculate every level, every time the cache is stale, because you don't know
# when a specific bundle of orbs in one level may unlock access to another.
accessible_total_orbs = 0
for level in level_table:
accessible_level_orbs = count_reachable_orbs_level(state, world, level)
accessible_total_orbs += accessible_level_orbs
state.prog_items[player][f"{level} Reachable Orbs".lstrip()] = accessible_level_orbs
# Recalculate every level, every time the cache is stale, because you don't know
# when a specific bundle of orbs in one level may unlock access to another.
for level in level_table:
state.prog_items[player][f"{level} Reachable Orbs".strip()] = (
count_reachable_orbs_level(state, world, level))
# Also recalculate the global count, still used even when Orbsanity is Off.
state.prog_items[player]["Reachable Orbs"] = count_reachable_orbs_global(state, world)
state.prog_items[player]["Reachable Orbs Fresh"] = True
# Also recalculate the global count, still used even when Orbsanity is Off.
state.prog_items[player]["Reachable Orbs"] = accessible_total_orbs
state.prog_items[player]["Reachable Orbs Fresh"] = True
def count_reachable_orbs_global(state: CollectionState,
world: JakAndDaxterWorld) -> int:
accessible_orbs = 0
for level_regions in world.level_to_regions.values():
for level_regions in world.level_to_orb_regions.values():
for region in level_regions:
# Rely on short-circuiting to skip region.can_reach whenever possible.
if region.orb_count > 0 and region.can_reach(state):
if region.can_reach(state):
accessible_orbs += region.orb_count
return accessible_orbs
@@ -59,9 +58,8 @@ def count_reachable_orbs_level(state: CollectionState,
level_name: str = "") -> int:
accessible_orbs = 0
for region in world.level_to_regions[level_name]:
# Rely on short-circuiting to skip region.can_reach whenever possible.
if region.orb_count > 0 and region.can_reach(state):
for region in world.level_to_orb_regions[level_name]:
if region.can_reach(state):
accessible_orbs += region.orb_count
return accessible_orbs
@@ -69,20 +67,24 @@ def count_reachable_orbs_level(state: CollectionState,
def can_reach_orbs_global(state: CollectionState,
player: int,
world: JakAndDaxterWorld,
bundle: int) -> bool:
orb_amount: int) -> bool:
recalculate_reachable_orbs(state, player, world)
return state.has("Reachable Orbs", player, world.orb_bundle_size * (bundle + 1))
if not state.prog_items[player]["Reachable Orbs Fresh"]:
recalculate_reachable_orbs(state, player, world)
return state.has("Reachable Orbs", player, orb_amount)
def can_reach_orbs_level(state: CollectionState,
player: int,
world: JakAndDaxterWorld,
level_name: str,
bundle: int) -> bool:
orb_amount: int) -> bool:
recalculate_reachable_orbs(state, player, world)
return state.has(f"{level_name} Reachable Orbs", player, world.orb_bundle_size * (bundle + 1))
if not state.prog_items[player]["Reachable Orbs Fresh"]:
recalculate_reachable_orbs(state, player, world)
return state.has(f"{level_name} Reachable Orbs", player, orb_amount)
def can_trade_vanilla(state: CollectionState,
@@ -92,7 +94,9 @@ def can_trade_vanilla(state: CollectionState,
required_previous_trade: typing.Optional[int] = None) -> bool:
# With Orbsanity Off, Reachable Orbs are in fact Tradeable Orbs.
recalculate_reachable_orbs(state, player, world)
if not state.prog_items[player]["Reachable Orbs Fresh"]:
recalculate_reachable_orbs(state, player, world)
if required_previous_trade:
name_of_previous_trade = location_table[Cells.to_ap_id(required_previous_trade)]
return (state.has("Reachable Orbs", player, required_orbs)
@@ -107,7 +111,9 @@ def can_trade_orbsanity(state: CollectionState,
required_previous_trade: typing.Optional[int] = None) -> bool:
# Yes, even Orbsanity trades may unlock access to new Reachable Orbs.
recalculate_reachable_orbs(state, player, world)
if not state.prog_items[player]["Reachable Orbs Fresh"]:
recalculate_reachable_orbs(state, player, world)
if required_previous_trade:
name_of_previous_trade = location_table[Cells.to_ap_id(required_previous_trade)]
return (state.has("Tradeable Orbs", player, required_orbs)

View File

@@ -226,14 +226,16 @@ class JakAndDaxterWorld(World):
power_cell_thresholds: list[int]
trap_weights: tuple[list[str], list[int]]
# Store a dictionary of levels to regions for faster access.
level_to_regions: dict[str, list[JakAndDaxterRegion]]
# Store these dictionaries for speed improvements.
level_to_regions: dict[str, list[JakAndDaxterRegion]] # Contains all levels and regions.
level_to_orb_regions: dict[str, list[JakAndDaxterRegion]] # Contains only regions which contain orbs.
# Handles various options validation, rules enforcement, and caching of important information.
def generate_early(self) -> None:
# Initialize the level-region dictionary.
self.level_to_regions = defaultdict(list)
self.level_to_orb_regions = defaultdict(list)
# Cache the power cell threshold values for quicker reference.
self.power_cell_thresholds = [
@@ -315,16 +317,22 @@ class JakAndDaxterWorld(World):
create_regions(self)
# Don't forget to add the created regions to the multiworld!
for level_regions in self.level_to_regions.values():
self.multiworld.regions.extend(level_regions)
for level in self.level_to_regions:
self.multiworld.regions.extend(self.level_to_regions[level])
# As a lazy measure, let's also fill level_to_orb_regions here.
# This should help speed up orbsanity calculations.
self.level_to_orb_regions[level] = [reg for reg in self.level_to_regions[level] if reg.orb_count > 0]
# from Utils import visualize_regions
# visualize_regions(self.multiworld.get_region("Menu", self.player), "jakanddaxter.puml")
# Helper function to reuse some nasty if/else trees. This outputs a list of pairs of item count and classification.
# For instance, not all 101 power cells need to be marked progression if you only need 72 to beat the game. So we
# will have 72 Progression Power Cells, and 29 Filler Power Cells.
def item_type_helper(self, item: int) -> list[tuple[int, ItemClass]]:
"""
Helper function to reuse some nasty if/else trees. This outputs a list of pairs of item count and classification.
For instance, not all 101 power cells need to be marked progression if you only need 72 to beat the game. So we
will have 72 Progression Power Cells, and 29 Filler Power Cells.
"""
counts_and_classes: list[tuple[int, ItemClass]] = []
# Make N Power Cells. We only want AP's Progression Fill routine to handle the amount of cells we need

View File

@@ -160,10 +160,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 200 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(8,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -25,10 +25,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(5,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -90,10 +90,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
bundle_count = 150 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(3,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -35,10 +35,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(0,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -115,10 +115,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
bundle_count = 200 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(15,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -25,10 +25,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(14,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -142,10 +142,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 200 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(7,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -118,10 +118,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 150 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(4,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -53,10 +53,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(10,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -25,10 +25,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 200 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(9,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -61,10 +61,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(6,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -70,10 +70,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(1,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -95,10 +95,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 150 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(2,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -190,10 +190,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 200 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(12,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -114,10 +114,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 200 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(13,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)

View File

@@ -39,10 +39,11 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bundle_count = 50 // world.orb_bundle_size
for bundle_index in range(bundle_count):
amount = world.orb_bundle_size * (bundle_index + 1)
orbs.add_orb_locations(11,
bundle_index,
access_rule=lambda state, level=level_name, bundle=bundle_index:
can_reach_orbs_level(state, player, world, level, bundle))
access_rule=lambda state, level=level_name, orb_amount=amount:
can_reach_orbs_level(state, player, world, level, orb_amount))
multiworld.regions.append(orbs)
main_area.connect(orbs)