mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-25 06:13:20 -07:00
Violet code review updates part 2.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user