Violet code review updates part 2.

This commit is contained in:
massimilianodelliubaldini
2025-05-07 21:02:27 -04:00
parent 920b4b5a30
commit 965e9e3a5c
12 changed files with 136 additions and 111 deletions

View File

@@ -1,4 +1,5 @@
from BaseClasses import Item
from enum import IntEnum
from BaseClasses import Item, ItemClassification
from .GameID import jak1_name, jak1_max
from .locs import (OrbLocations as Orbs,
CellLocations as Cells,
@@ -7,8 +8,31 @@ from .locs import (OrbLocations as Orbs,
OrbCacheLocations as Caches)
class OrbAssoc(IntEnum):
"""
Identifies an item's association to unlocking new sources of Precursor Orbs. For example, Double Jump will unlock
new orbs, but Freed the Green Sage will not. Power Cells conditionally unlock new orbs if they get you across
connector levels.
"""
NEVER_UNLOCKS_ORBS = 0
ALWAYS_UNLOCKS_ORBS = 1
IS_POWER_CELL = 2
class JakAndDaxterItem(Item):
game: str = jak1_name
orb_assoc: OrbAssoc
orb_amount: int # Only non-zero for Orb Bundle items.
def __init__(self, name: str,
classification: ItemClassification,
code: int | None,
player: int,
orb_assoc: OrbAssoc = OrbAssoc.NEVER_UNLOCKS_ORBS,
orb_amount: int = 0):
super().__init__(name, classification, code, player)
self.orb_assoc = orb_assoc
self.orb_amount = orb_amount
# Power Cells are generic, fungible, interchangeable items. Every cell is indistinguishable from every other.

View File

@@ -18,6 +18,7 @@ from Options import OptionGroup
from .Options import *
from .GameID import jak1_id, jak1_name, jak1_max
from .Items import (JakAndDaxterItem,
OrbAssoc,
item_table,
cell_item_table,
scout_item_table,
@@ -224,6 +225,7 @@ class JakAndDaxterWorld(World):
total_trap_cells: int = 0
total_filler_cells: int = 0
power_cell_thresholds: list[int]
power_cell_thresholds_minus_one: list[int]
trap_weights: tuple[list[str], list[int]]
# Store these dictionaries for speed improvements.
@@ -252,6 +254,9 @@ class JakAndDaxterWorld(World):
self.options.mountain_pass_cell_count.value = self.power_cell_thresholds[1]
self.options.lava_tube_cell_count.value = self.power_cell_thresholds[2]
# Store this for remove function.
self.power_cell_thresholds_minus_one = [x - 1 for x in self.power_cell_thresholds]
# For the fairness of other players in a multiworld game, enforce some friendly limitations on our options,
# so we don't cause chaos during seed generation. These friendly limits should **guarantee** a successful gen.
# We would have done this earlier, but we needed to sort the power cell thresholds first.
@@ -327,52 +332,55 @@ class JakAndDaxterWorld(World):
# from Utils import visualize_regions
# visualize_regions(self.multiworld.get_region("Menu", self.player), "jakanddaxter.puml")
def item_type_helper(self, item: int) -> list[tuple[int, ItemClass]]:
def item_data_helper(self, item: int) -> list[tuple[int, ItemClass, OrbAssoc, int]]:
"""
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.
Helper function to reuse some nasty if/else trees. This outputs a list of pairs of item count and class.
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]] = []
data: list[tuple[int, ItemClass, OrbAssoc, int]] = []
# Make N Power Cells. We only want AP's Progression Fill routine to handle the amount of cells we need
# to reach the furthest possible region. Even for early completion goals, all areas in the game must be
# reachable or generation will fail. TODO - Option-driven region creation would be an enormous refactor.
if item in range(jak1_id, jak1_id + Scouts.fly_offset):
counts_and_classes.append((self.total_prog_cells, ItemClass.progression_skip_balancing))
counts_and_classes.append((self.total_filler_cells, ItemClass.filler))
data.append((self.total_prog_cells, ItemClass.progression_skip_balancing, OrbAssoc.IS_POWER_CELL, 0))
data.append((self.total_filler_cells, ItemClass.filler, OrbAssoc.IS_POWER_CELL, 0))
# Make 7 Scout Flies per level.
elif item in range(jak1_id + Scouts.fly_offset, jak1_id + Specials.special_offset):
counts_and_classes.append((7, ItemClass.progression_skip_balancing))
data.append((7, ItemClass.progression_skip_balancing, OrbAssoc.NEVER_UNLOCKS_ORBS, 0))
# Make only 1 of each Special Item.
elif item in range(jak1_id + Specials.special_offset, jak1_id + Caches.orb_cache_offset):
counts_and_classes.append((1, ItemClass.progression | ItemClass.useful))
data.append((1, ItemClass.progression | ItemClass.useful, OrbAssoc.ALWAYS_UNLOCKS_ORBS, 0))
# Make only 1 of each Move Item.
elif item in range(jak1_id + Caches.orb_cache_offset, jak1_id + Orbs.orb_offset):
counts_and_classes.append((1, ItemClass.progression | ItemClass.useful))
data.append((1, ItemClass.progression | ItemClass.useful, OrbAssoc.ALWAYS_UNLOCKS_ORBS, 0))
# Make N Precursor Orb bundles. Like Power Cells, only a fraction of these will be marked as Progression
# with the remainder as Filler, but they are still entirely fungible.
# with the remainder as Filler, but they are still entirely fungible. See collect function for why these
# are OrbAssoc.NEVER_UNLOCKS_ORBS.
elif item in range(jak1_id + Orbs.orb_offset, jak1_max - max(trap_item_table)):
counts_and_classes.append((self.total_prog_orb_bundles, ItemClass.progression_skip_balancing))
counts_and_classes.append((self.total_filler_orb_bundles, ItemClass.filler))
data.append((self.total_prog_orb_bundles, ItemClass.progression_skip_balancing,
OrbAssoc.NEVER_UNLOCKS_ORBS, self.orb_bundle_size))
data.append((self.total_filler_orb_bundles, ItemClass.filler,
OrbAssoc.NEVER_UNLOCKS_ORBS, self.orb_bundle_size))
# We will manually create trap items as needed.
elif item in range(jak1_max - max(trap_item_table), jak1_max):
counts_and_classes.append((0, ItemClass.trap))
data.append((0, ItemClass.trap, OrbAssoc.NEVER_UNLOCKS_ORBS, 0))
# We will manually create filler items as needed.
elif item == jak1_max:
counts_and_classes.append((0, ItemClass.filler))
data.append((0, ItemClass.filler, OrbAssoc.NEVER_UNLOCKS_ORBS, 0))
# If we try to make items with ID's higher than we've defined, something has gone wrong.
else:
raise KeyError(f"Tried to fill item pool with unknown ID {item}.")
return counts_and_classes
return data
def create_items(self) -> None:
items_made: int = 0
@@ -401,9 +409,10 @@ class JakAndDaxterWorld(World):
continue
# In almost every other scenario, do this. Not all items with the same name will have the same item class.
counts_and_classes = self.item_type_helper(item_id)
for (count, classification) in counts_and_classes:
self.multiworld.itempool += [JakAndDaxterItem(item_name, classification, item_id, self.player)
data = self.item_data_helper(item_id)
for (count, classification, orb_assoc, orb_amount) in data:
self.multiworld.itempool += [JakAndDaxterItem(item_name, classification, item_id,
self.player, orb_assoc, orb_amount)
for _ in range(count)]
items_made += count
@@ -427,13 +436,15 @@ class JakAndDaxterWorld(World):
def create_item(self, name: str) -> Item:
item_id = self.item_name_to_id[name]
_, classification = self.item_type_helper(item_id)[0] # Use first tuple (will likely be the most important).
return JakAndDaxterItem(name, classification, item_id, self.player)
# Use first tuple (will likely be the most important).
_, classification, orb_assoc, orb_amount = self.item_data_helper(item_id)[0]
return JakAndDaxterItem(name, classification, item_id, self.player, orb_assoc, orb_amount)
def get_filler_item_name(self) -> str:
return "Green Eco Pill"
def collect(self, state: CollectionState, item: Item) -> bool:
def collect(self, state: CollectionState, item: JakAndDaxterItem) -> bool:
change = super().collect(state, item)
if change:
# Orbsanity as an option is no-factor to these conditions. Matching the item name implies Orbsanity is ON,
@@ -442,47 +453,36 @@ class JakAndDaxterWorld(World):
# Orb items do not intrinsically unlock anything that contains more Reachable Orbs, so they do not need to
# set the cache to stale. They just change how many orbs you have to trade with.
if item.name == self.orb_bundle_item_name:
if item.orb_amount > 0:
state.prog_items[self.player]["Tradeable Orbs"] += self.orb_bundle_size # Give a bundle of Trade Orbs
# Scout Flies ALSO do not unlock anything that contains more Reachable Orbs, NOR do they give you more
# tradeable orbs. So let's just pass on them.
elif item.name in self.item_name_groups["Scout Flies"]:
pass
# Power Cells DO unlock new regions that contain more Reachable Orbs - the connector levels and new
# hub levels - BUT they only do that when you have a number of them equal to one of the threshold values.
elif (item.name == "Power Cell"
and state.count("Power Cell", self.player) not in self.power_cell_thresholds):
pass
# However, every other item that changes the CollectionState should set the cache to stale, because they
# likely made it possible to reach more orb locations (level unlocks, region unlocks, etc.).
else:
elif (item.orb_assoc == OrbAssoc.ALWAYS_UNLOCKS_ORBS
or (item.orb_assoc == OrbAssoc.IS_POWER_CELL
and state.count("Power Cell", self.player) in self.power_cell_thresholds)):
state.prog_items[self.player]["Reachable Orbs Fresh"] = False
# However, every other item that does not have an appropriate OrbAssoc that changes the CollectionState
# should NOT set the cache to stale, because they did not make it possible to reach more orb locations
# (level unlocks, region unlocks, etc.).
return change
def remove(self, state: CollectionState, item: Item) -> bool:
def remove(self, state: CollectionState, item: JakAndDaxterItem) -> bool:
change = super().remove(state, item)
if change:
# Do the same thing we did in collect, except subtract trade orbs instead of add.
if item.name == self.orb_bundle_item_name:
if item.orb_amount > 0:
state.prog_items[self.player]["Tradeable Orbs"] -= self.orb_bundle_size # Take a bundle of Trade Orbs
# Ditto Scout Flies.
elif item.name in self.item_name_groups["Scout Flies"]:
pass
# Ditto Power Cells, but check count + 1, because we potentially crossed the threshold in the opposite
# Ditto Power Cells, but check thresholds - 1, because we potentially crossed the threshold in the opposite
# direction. E.g. we've removed the 20th power cell, our count is now 19, so we should stale the cache.
elif (item.name == "Power Cell"
and state.count("Power Cell", self.player) + 1 not in self.power_cell_thresholds):
pass
# Ditto everything else.
else:
elif (item.orb_assoc == OrbAssoc.ALWAYS_UNLOCKS_ORBS
or (item.orb_assoc == OrbAssoc.IS_POWER_CELL
and state.count("Power Cell", self.player) in self.power_cell_thresholds_minus_one)):
state.prog_items[self.player]["Reachable Orbs Fresh"] = False
return change
def fill_slot_data(self) -> dict[str, Any]:

View File

@@ -13,13 +13,14 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# This level is full of short-medium gaps that cannot be crossed by single jump alone.
# These helper functions list out the moves that can cross all these gaps (painting with a broad brush but...)
def can_jump_farther(state: CollectionState, p: int) -> bool:
return state.has_any({"Double Jump", "Jump Kick"}, p) or state.has_all({"Punch", "Punch Uppercut"}, p)
return (state.has_any(("Double Jump", "Jump Kick"), p)
or state.has_all(("Punch", "Punch Uppercut"), p))
def can_jump_higher(state: CollectionState, p: int) -> bool:
return (state.has("Double Jump", p)
or state.has_all({"Crouch", "Crouch Jump"}, p)
or state.has_all({"Crouch", "Crouch Uppercut"}, p)
or state.has_all({"Punch", "Punch Uppercut"}, p))
or state.has_all(("Crouch", "Crouch Jump"), p)
or state.has_all(("Crouch", "Crouch Uppercut"), p)
or state.has_all(("Punch", "Punch Uppercut"), p))
# Orb crates and fly box in this area can be gotten with yellow eco and goggles.
# Start with the first yellow eco cluster near first_bats and work your way backward toward the entrance.
@@ -94,8 +95,8 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
first_tether.connect(first_bats)
first_tether.connect(first_tether_rat_colony, rule=lambda state:
(state.has_all({"Roll", "Roll Jump"}, player)
or state.has_all({"Double Jump", "Jump Kick"}, player)))
(state.has_all(("Roll", "Roll Jump"), player)
or state.has_all(("Double Jump", "Jump Kick"), player)))
first_tether.connect(second_jump_pad)
first_tether.connect(first_pole_course)

View File

@@ -20,8 +20,8 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
main_area.connect(cliff, rule=lambda state:
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)
or state.has_all({"Crouch", "Crouch Uppercut"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player)
or state.has_all(("Crouch", "Crouch Uppercut"), player))
cliff.connect(main_area) # Jump down or ride blue eco elevator.

View File

@@ -14,17 +14,17 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
# This level is full of short-medium gaps that cannot be crossed by single jump alone.
# These helper functions list out the moves that can cross all these gaps (painting with a broad brush but...)
def can_jump_farther(state: CollectionState, p: int) -> bool:
return (state.has_any({"Double Jump", "Jump Kick"}, p)
or state.has_all({"Punch", "Punch Uppercut"}, p))
return (state.has_any(("Double Jump", "Jump Kick"), p)
or state.has_all(("Punch", "Punch Uppercut"), p))
def can_triple_jump(state: CollectionState, p: int) -> bool:
return state.has_all({"Double Jump", "Jump Kick"}, p)
return state.has_all(("Double Jump", "Jump Kick"), p)
def can_jump_stairs(state: CollectionState, p: int) -> bool:
return (state.has("Double Jump", p)
or state.has("Jump Dive", p)
or state.has_all({"Crouch", "Crouch Jump"}, p)
or state.has_all({"Crouch", "Crouch Uppercut"}, p))
or state.has_all(("Crouch", "Crouch Jump"), p)
or state.has_all(("Crouch", "Crouch Uppercut"), p))
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 0)
main_area.add_fly_locations([91], access_rule=lambda state: can_free_scout_flies(state, player))
@@ -60,36 +60,36 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
# Jump Dive required for a lot of buttons, prepare yourself.
main_area.connect(robot_scaffolding, rule=lambda state:
state.has("Jump Dive", player) or state.has_all({"Roll", "Roll Jump"}, player))
state.has("Jump Dive", player) or state.has_all(("Roll", "Roll Jump"), player))
main_area.connect(jump_pad_room)
robot_scaffolding.connect(main_area, rule=lambda state: state.has("Jump Dive", player))
robot_scaffolding.connect(blast_furnace, rule=lambda state:
state.has("Jump Dive", player)
and can_jump_farther(state, player)
and (can_triple_jump(state, player) or state.has_all({"Roll", "Roll Jump"}, player)))
and (can_triple_jump(state, player) or state.has_all(("Roll", "Roll Jump"), player)))
robot_scaffolding.connect(bunny_room, rule=lambda state:
state.has("Jump Dive", player)
and can_jump_farther(state, player)
and (can_triple_jump(state, player) or state.has_all({"Roll", "Roll Jump"}, player)))
and (can_triple_jump(state, player) or state.has_all(("Roll", "Roll Jump"), player)))
jump_pad_room.connect(main_area)
jump_pad_room.connect(robot_scaffolding, rule=lambda state:
state.has("Jump Dive", player)
and (can_triple_jump(state, player) or state.has_all({"Roll", "Roll Jump"}, player)))
and (can_triple_jump(state, player) or state.has_all(("Roll", "Roll Jump"), player)))
blast_furnace.connect(robot_scaffolding) # Blue eco elevator takes you right back.
bunny_room.connect(robot_scaffolding, rule=lambda state:
state.has("Jump Dive", player)
and (can_jump_farther(state, player) or state.has_all({"Roll", "Roll Jump"}, player)))
and (can_jump_farther(state, player) or state.has_all(("Roll", "Roll Jump"), player)))
# Final climb.
robot_scaffolding.connect(rotating_tower, rule=lambda state:
can_jump_stairs(state, player)
and state.has_all({"Freed The Blue Sage",
and state.has_all(("Freed The Blue Sage",
"Freed The Red Sage",
"Freed The Yellow Sage"}, player))
"Freed The Yellow Sage"), player))
rotating_tower.connect(main_area) # Take stairs back down.

View File

@@ -21,7 +21,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# Need jump dive to activate button, double jump to reach blue eco to unlock cache.
first_room_orb_cache.add_cache_locations([14507], access_rule=lambda state:
state.has_all({"Jump Dive", "Double Jump"}, player))
state.has_all(("Jump Dive", "Double Jump"), player))
first_hallway = JakAndDaxterRegion("First Hallway", player, multiworld, level_name, 10)
first_hallway.add_fly_locations([131121], access_rule=lambda state: can_free_scout_flies(state, player))
@@ -60,16 +60,16 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# Use jump dive to activate button inside the capsule. Blue eco vent can ready the chamber and get the scout fly.
capsule_room.add_cell_locations([47], access_rule=lambda state:
state.has("Jump Dive", player)
and (state.has_any({"Double Jump", "Jump Kick"}, player)
or state.has_all({"Punch", "Punch Uppercut"}, player)))
and (state.has_any(("Double Jump", "Jump Kick"), player)
or state.has_all(("Punch", "Punch Uppercut"), player)))
capsule_room.add_fly_locations([327729])
# You can slide to the bottom of the city, but if you spawn down there, you have no momentum from the slide.
# So you need some kind of jump to reach this cell.
second_slide = JakAndDaxterRegion("Second Slide", player, multiworld, level_name, 31)
second_slide.add_cell_locations([46], access_rule=lambda state:
state.has_any({"Double Jump", "Jump Kick"}, player)
or state.has_all({"Punch", "Punch Uppercut"}, player))
state.has_any(("Double Jump", "Jump Kick"), player)
or state.has_all(("Punch", "Punch Uppercut"), player))
# If you can enter the helix room, you can jump or fight your way to the top. But you need some kind of movement
# to enter it in the first place.
@@ -88,9 +88,9 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# Needs some movement to reach these orbs and orb cache.
first_room_lower.connect(first_room_orb_cache, rule=lambda state:
state.has_all({"Jump Dive", "Double Jump"}, player))
state.has_all(("Jump Dive", "Double Jump"), player))
first_room_orb_cache.connect(first_room_lower, rule=lambda state:
state.has_all({"Jump Dive", "Double Jump"}, player))
state.has_all(("Jump Dive", "Double Jump"), player))
first_hallway.connect(first_room_upper) # Run and jump down.
first_hallway.connect(second_room) # Run and jump (floating platforms).
@@ -114,8 +114,8 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
state.has("Jump Dive", player)) # (Assume one-way for sanity.)
second_slide.connect(helix_room, rule=lambda state: # As stated above, you need to jump
state.has_any({"Double Jump", "Jump Kick"}, player) # across the dark eco pool before
or state.has_all({"Punch", "Punch Uppercut"}, player)) # you can climb the helix room.
state.has_any(("Double Jump", "Jump Kick"), player) # across the dark eco pool before
or state.has_all(("Punch", "Punch Uppercut"), player)) # you can climb the helix room.
helix_room.connect(quick_platforms, rule=lambda state: # Escape to get back to here.
state.has("Double Jump", player) # Capsule is a convenient exit to the level.

View File

@@ -61,7 +61,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
muse_course.connect(main_area) # Run and jump down.
# The zoomer pad is low enough that it requires Crouch Jump specifically.
zoomer.connect(main_area, rule=lambda state: state.has_all({"Crouch", "Crouch Jump"}, player))
zoomer.connect(main_area, rule=lambda state: state.has_all(("Crouch", "Crouch Jump"), player))
ship.connect(main_area) # Run and jump down.
ship.connect(far_side) # Run and jump down.
@@ -73,7 +73,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# Only if you can use the seesaw or Crouch Jump from the seesaw's edge.
far_side.connect(far_side_cliff, rule=lambda state:
state.has("Jump Dive", player)
or state.has_all({"Crouch", "Crouch Jump"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player))
# Only if you can break the bone bridges to carry blue eco over the mud pit.
far_side.connect(far_side_cache, rule=lambda state: can_fight(state, player))
@@ -90,7 +90,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
upper_approach.connect(arena) # Jump down.
# One cliff is accessible, but only via Crouch Jump.
lower_approach.connect(upper_approach, rule=lambda state: state.has_all({"Crouch", "Crouch Jump"}, player))
lower_approach.connect(upper_approach, rule=lambda state: state.has_all(("Crouch", "Crouch Jump"), player))
# Requires breaking bone bridges.
lower_approach.connect(arena, rule=lambda state: can_fight(state, player))

View File

@@ -28,7 +28,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
orb_cache = JakAndDaxterRegion("Orb Cache", player, multiworld, level_name, 20)
# You need roll jump to be able to reach this before the blue eco runs out.
orb_cache.add_cache_locations([10945], access_rule=lambda state: state.has_all({"Roll", "Roll Jump"}, player))
orb_cache.add_cache_locations([10945], access_rule=lambda state: state.has_all(("Roll", "Roll Jump"), player))
# Fly here can be gotten with Yellow Eco from Boggy, goggles, and no extra movement options (see fly ID 43).
pontoon_bridge = JakAndDaxterRegion("Pontoon Bridge", player, multiworld, level_name, 7)
@@ -36,7 +36,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
klaww_cliff = JakAndDaxterRegion("Klaww's Cliff", player, multiworld, level_name, 0)
main_area.connect(orb_cache, rule=lambda state: state.has_all({"Roll", "Roll Jump"}, player))
main_area.connect(orb_cache, rule=lambda state: state.has_all(("Roll", "Roll Jump"), player))
main_area.connect(pontoon_bridge, rule=lambda state: state.has("Warrior's Pontoons", player))
orb_cache.connect(main_area)
@@ -44,8 +44,8 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> tuple[JakAndDaxt
pontoon_bridge.connect(main_area, rule=lambda state: state.has("Warrior's Pontoons", player))
pontoon_bridge.connect(klaww_cliff, rule=lambda state:
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)
or state.has_all({"Crouch", "Crouch Uppercut", "Jump Kick"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player)
or state.has_all(("Crouch", "Crouch Uppercut", "Jump Kick"), player))
klaww_cliff.connect(pontoon_bridge) # Just jump back down.

View File

@@ -22,7 +22,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# The farmer's scout fly. You can either get the Orb Cache Cliff blue eco, or break it normally.
main_area.add_fly_locations([196683], access_rule=lambda state:
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)
or state.has_all(("Crouch", "Crouch Jump"), player)
or can_free_scout_flies(state, player))
orb_cache_cliff = JakAndDaxterRegion("Orb Cache Cliff", player, multiworld, level_name, 15)
@@ -41,17 +41,17 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
main_area.connect(orb_cache_cliff, rule=lambda state:
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)
or state.has_all({"Crouch", "Crouch Uppercut", "Jump Kick"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player)
or state.has_all(("Crouch", "Crouch Uppercut", "Jump Kick"), player))
main_area.connect(yakow_cliff, rule=lambda state:
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)
or state.has_all({"Crouch", "Crouch Uppercut", "Jump Kick"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player)
or state.has_all(("Crouch", "Crouch Uppercut", "Jump Kick"), player))
main_area.connect(oracle_platforms, rule=lambda state:
state.has_all({"Roll", "Roll Jump"}, player)
or state.has_all({"Double Jump", "Jump Kick"}, player))
state.has_all(("Roll", "Roll Jump"), player)
or state.has_all(("Double Jump", "Jump Kick"), player))
# All these can go back to main_area immediately.
orb_cache_cliff.connect(main_area)

View File

@@ -31,9 +31,9 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# Only these specific attacks can push the flut flut egg off the cliff.
flut_flut_egg = JakAndDaxterRegion("Flut Flut Egg", player, multiworld, level_name, 0)
flut_flut_egg.add_cell_locations([17], access_rule=lambda state:
state.has_any({"Punch", "Kick", "Jump Kick"}, player))
state.has_any(("Punch", "Kick", "Jump Kick"), player))
flut_flut_egg.add_special_locations([17], access_rule=lambda state:
state.has_any({"Punch", "Kick", "Jump Kick"}, player))
state.has_any(("Punch", "Kick", "Jump Kick"), player))
eco_harvesters = JakAndDaxterRegion("Eco Harvesters", player, multiworld, level_name, 0)
eco_harvesters.add_cell_locations([15], access_rule=lambda state: can_fight(state, player))
@@ -55,14 +55,14 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# We need a helper function for the uppercut logs.
def can_uppercut_and_jump_logs(state: CollectionState, p: int) -> bool:
return (state.has_any({"Double Jump", "Jump Kick"}, p)
and (state.has_all({"Crouch", "Crouch Uppercut"}, p)
or state.has_all({"Punch", "Punch Uppercut"}, p)))
return (state.has_any(("Double Jump", "Jump Kick"), p)
and (state.has_all(("Crouch", "Crouch Uppercut"), p)
or state.has_all(("Punch", "Punch Uppercut"), p)))
# If you have double jump or crouch jump, you don't need the logs to reach this place.
main_area.connect(green_ridge, rule=lambda state:
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)
or state.has_all(("Crouch", "Crouch Jump"), player)
or can_uppercut_and_jump_logs(state, player))
# If you have the blue eco jump pad, you don't need the logs to reach this place.

View File

@@ -13,13 +13,13 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# We need a few helper functions.
def can_cross_long_gap(state: CollectionState, p: int) -> bool:
return (state.has_all({"Roll", "Roll Jump"}, p)
or state.has_all({"Double Jump", "Jump Kick"}, p))
return (state.has_all(("Roll", "Roll Jump"), p)
or state.has_all(("Double Jump", "Jump Kick"), p))
def can_jump_blockers(state: CollectionState, p: int) -> bool:
return (state.has_any({"Double Jump", "Jump Kick"}, p)
or state.has_all({"Crouch", "Crouch Jump"}, p)
or state.has_all({"Punch", "Punch Uppercut"}, p))
return (state.has_any(("Double Jump", "Jump Kick"), p)
or state.has_all(("Crouch", "Crouch Jump"), p)
or state.has_all(("Punch", "Punch Uppercut"), p))
main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 0)
main_area.add_fly_locations([65], access_rule=lambda state: can_free_scout_flies(state, player))
@@ -142,13 +142,13 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
fort_interior.connect(fort_interior_caches, rule=lambda state: # Just need a little height.
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player))
fort_interior.connect(fort_interior_base, rule=lambda state: # Just need a little height.
state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player))
or state.has_all(("Crouch", "Crouch Jump"), player))
fort_interior.connect(fort_interior_course_end, rule=lambda state: # Just need a little distance.
state.has_any({"Double Jump", "Jump Kick"}, player)
or state.has_all({"Punch", "Punch Uppercut"}, player))
state.has_any(("Double Jump", "Jump Kick"), player)
or state.has_all(("Punch", "Punch Uppercut"), player))
flut_flut_course.connect(fort_exterior) # Ride the elevator.
@@ -156,7 +156,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
bunny_cave_start.connect(bunny_cave_end, rule=lambda state:
can_fight(state, player)
and (state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)))
or state.has_all(("Crouch", "Crouch Jump"), player)))
# All jump down.
fort_interior_caches.connect(fort_interior)

View File

@@ -24,7 +24,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# The rest of the crystals can be destroyed with yellow eco in main_area.
dark_crystals.add_cell_locations([79], access_rule=lambda state:
can_fight(state, player)
and state.has_all({"Roll", "Roll Jump"}, player))
and state.has_all(("Roll", "Roll Jump"), player))
dark_cave = JakAndDaxterRegion("Dark Cave", player, multiworld, level_name, 5)
dark_cave.add_cell_locations([80])
@@ -60,7 +60,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
main_area.connect(dark_cave, rule=lambda state:
can_fight(state, player)
and (state.has("Double Jump", player)
or state.has_all({"Crouch", "Crouch Jump"}, player)))
or state.has_all(("Crouch", "Crouch Jump"), player)))
robot_cave.connect(main_area)
robot_cave.connect(pole_course) # Nothing special required.
@@ -73,7 +73,7 @@ def build_regions(level_name: str, world: JakAndDaxterWorld) -> JakAndDaxterRegi
# Elevator, but the orbs need double jump or jump kick.
scaffolding_level_one.connect(scaffolding_level_zero, rule=lambda state:
state.has_any({"Double Jump", "Jump Kick"}, player))
state.has_any(("Double Jump", "Jump Kick"), player))
# Narrow enough that enemies are unavoidable.
scaffolding_level_one.connect(scaffolding_level_two, rule=lambda state: can_fight(state, player))