From a4ba8f4a4bd8ad8ff6c90ecba072ecdc22af36dc Mon Sep 17 00:00:00 2001 From: Mattis Turin-Zelenko Date: Sat, 2 May 2026 01:45:15 +0200 Subject: [PATCH 1/3] Jak1: Add Eco Randomizer Logic for SV, SB, FJ (orb count in temple currently wrong), MI, MP --- worlds/jakanddaxter/__init__.py | 15 +++++-- worlds/jakanddaxter/items.py | 12 ++++-- worlds/jakanddaxter/options.py | 36 ++++++++++++++++ .../regs/forbidden_jungle_regions.py | 42 +++++++++++++------ .../jakanddaxter/regs/geyser_rock_regions.py | 36 +++++++++++++--- .../jakanddaxter/regs/misty_island_regions.py | 16 ++++--- .../regs/mountain_pass_regions.py | 10 +++-- .../regs/sandover_village_regions.py | 25 ++++++----- .../regs/sentinel_beach_regions.py | 23 ++++++---- 9 files changed, 165 insertions(+), 50 deletions(-) diff --git a/worlds/jakanddaxter/__init__.py b/worlds/jakanddaxter/__init__.py index 61a41a49b498..376f209ee1e7 100644 --- a/worlds/jakanddaxter/__init__.py +++ b/worlds/jakanddaxter/__init__.py @@ -24,7 +24,7 @@ special_item_table, move_item_table, orb_item_table, - trap_item_table) + trap_item_table, eco_item_table) from .levels import level_table, level_table_with_global from .locations import (JakAndDaxterLocation, location_table, @@ -116,6 +116,10 @@ class JakAndDaxterWebWorld(WebWorld): options.CitizenOrbTradeAmount, options.OracleOrbTradeAmount, ]), + OptionGroup("Eco Randomizer", [ + options.EnableEcoRandomizer, + options.GeyserRockDoorSkip, + ], True), OptionGroup("Tricks & Glitches - Easy", [ options.AttackWithRollJump, # Use Roll Jump instead of regular attacks to hit certain targets. options.AttacklessLurkerCannons, # Shoot the lurkers with their own cannon. @@ -192,6 +196,7 @@ class JakAndDaxterWorld(World): "Scout Flies": set(scout_item_table.values()), "Specials": set(special_item_table.values()), "Moves": set(move_item_table.values()), + "Eco": set(eco_item_table.values()), "Precursor Orbs": set(orb_item_table.values()), "Traps": set(trap_item_table.values()), } @@ -447,10 +452,10 @@ def create_items(self) -> None: for item_name in self.item_name_to_id: item_id = self.item_name_to_id[item_name] - # Handle Move Randomizer option. + # Handle Move/Eco Randomizer option. # If it is OFF, put all moves in your starting inventory instead of the item pool, # then fill the item pool with a corresponding amount of filler items. - if item_name in self.item_name_groups["Moves"] and not self.options.enable_move_randomizer: + if (item_name in self.item_name_groups["Moves"] and not self.options.enable_move_randomizer) or (item_name in self.item_name_groups["Eco"] and not self.options.enable_eco_randomizer): self.multiworld.push_precollected(self.create_item(item_name)) self.multiworld.itempool.append(self.create_filler()) items_made += 1 @@ -561,6 +566,10 @@ def fill_slot_data(self) -> dict[str, Any]: "trap_weights", "jak_completion_condition", "require_punch_for_klaww", + # Eco Randomizer + "enable_eco_randomizer", + "geyser_rock_door_skip", + "klaww_fight_skip", # Tricks & Glitches "boosted_and_extended_uppercuts", "punch_uppercut_scout_flies", diff --git a/worlds/jakanddaxter/items.py b/worlds/jakanddaxter/items.py index e15f28ae325e..79393dd32b1b 100644 --- a/worlds/jakanddaxter/items.py +++ b/worlds/jakanddaxter/items.py @@ -119,9 +119,14 @@ def __init__(self, name: str, 23348: "Punch", 23349: "Punch Uppercut", 23350: "Kick", - # 24038: "Orb Cache at End of Blast Furnace", # Hold onto these ID's for future use. - # 24039: "Orb Cache at End of Launch Pad Room", - # 24040: "Orb Cache at Start of Launch Pad Room", +} + +# Same as above, but contains the eco items for eco randomizer. These are treated like moves in GOAL, +# but defined separately to correctly pre-collect them depending on the player options. +eco_item_table = { + 24038: "Blue Eco", + 24039: "Red Eco", + 24040: "Yellow Eco", } # These are trap items. Their Item ID is to be subtracted from the base game ID. They do not have corresponding @@ -150,6 +155,7 @@ def __init__(self, name: str, **{scouts.to_ap_id(k): name for k, name in scout_item_table.items()}, **{specials.to_ap_id(k): name for k, name in special_item_table.items()}, **{caches.to_ap_id(k): name for k, name in move_item_table.items()}, + **{caches.to_ap_id(k): name for k, name in eco_item_table.items()}, **{orbs.to_ap_id(k): name for k, name in orb_item_table.items()}, **{jak1_max - k: name for k, name in trap_item_table.items()}, jak1_max: "Green Eco Pill" # Filler item. diff --git a/worlds/jakanddaxter/options.py b/worlds/jakanddaxter/options.py index 7e612c607268..5a9cd91deca1 100644 --- a/worlds/jakanddaxter/options.py +++ b/worlds/jakanddaxter/options.py @@ -327,6 +327,37 @@ class TrapWeights(OptionCounter): def weights_pair(self) -> tuple[list[str], list[int]]: return list(self.value.keys()), list(self.value.values()) +# region Eco Randomizer +class EnableEcoRandomizer(Toggle): + """Include eco collection as items in the randomizer. Until you find other eco colors, Jak can only collect green + eco. + + This adds 3 items to the pool.""" + display_name = "Enable Eco Randomizer" + + +class GeyserRockDoorSkip(Toggle): + """ + Create an alternative path through the precursor door in geyser rock. + + Enabling this setting may require Jak to reach the area behind the door with no *Blue Eco* unlocked. + + If "Enable Move Randomizer" is ON, Jak may be required to do the trick with *Jump Kick* and either *Crouch Jump* + or *Jump Dive* + *Double Jump*. + """ + display_name = "Geyser Rock Door Skip" + + +class KlawwFightSkip(Toggle): + """ + Create an alternative path to Mountain Pass without having to fight Klaww. + + Enabling this setting may require Jak to reach Mountain Pass by skipping the Klaww fight, without *Blue Eco* or + *Yellow Eco* unlocked. + """ +# endregion + +# region Advanced Movement class BoostedAndExtendedUppercuts(Toggle): """ @@ -663,6 +694,8 @@ class SentinelBeachBlueEcoSwitchSkip(Toggle): """ display_name = "Sentinel Beach Blue Eco Switch Skip" +# endregion + class CompletionCondition(Choice): """Set the goal for completing the game.""" display_name = "Completion Condition" @@ -693,6 +726,9 @@ class JakAndDaxterOptions(PerGameCommonOptions): filler_orb_bundles_replaced_with_traps: FillerOrbBundlesReplacedWithTraps trap_effect_duration: TrapEffectDuration trap_weights: TrapWeights + enable_eco_randomizer: EnableEcoRandomizer + geyser_rock_door_skip: GeyserRockDoorSkip + klaww_fight_skip: KlawwFightSkip boosted_and_extended_uppercuts: BoostedAndExtendedUppercuts punch_uppercut_scout_flies: PunchUppercutScoutFlies geyser_rock_cliff_climb: GeyserRockCliffClimb diff --git a/worlds/jakanddaxter/regs/forbidden_jungle_regions.py b/worlds/jakanddaxter/regs/forbidden_jungle_regions.py index c20cd2ecc4ef..0da743ee04d8 100644 --- a/worlds/jakanddaxter/regs/forbidden_jungle_regions.py +++ b/worlds/jakanddaxter/regs/forbidden_jungle_regions.py @@ -12,38 +12,46 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa player = world.player - main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 25) + main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 27) # You can get this scout fly by running from the blue eco vent across the temple bridge, # falling onto the river, collecting the 3 blue clusters, using the jump pad, and running straight to the box. - main_area.add_fly_locations([393223]) + main_area.add_fly_locations([393223], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) lurker_machine = JakAndDaxterRegion("Lurker Machine", player, multiworld, level_name, 5) lurker_machine.add_cell_locations([3], access_rule=lambda state: world.can_fight_or_roll_jump(state, player)) # This cell and this scout fly can both be gotten with the blue eco clusters near the jump pad. - lurker_machine.add_cell_locations([9]) - lurker_machine.add_fly_locations([131079]) + lurker_machine.add_cell_locations([9], access_rule=lambda state: state.has("Blue Eco", player)) + lurker_machine.add_fly_locations([131079], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) - river = JakAndDaxterRegion("River", player, multiworld, level_name, 42) + river = JakAndDaxterRegion("River", player, multiworld, level_name, 22) # All of these can be gotten with blue eco, hitting the dark eco boxes, or by running. river.add_cell_locations([5, 8]) - river.add_fly_locations([7, 196615]) + # Can be gotten by running into the dark eco boxes. + river.add_fly_locations([7]) + river.add_fly_locations([196615], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) river.add_special_locations([5]) - river.add_cache_locations([10369]) + # 20 orbs + river_cache = JakAndDaxterRegion("River Cache", player, multiworld, level_name, 20) + river_cache.add_cache_locations([10369]) # 12 orbs around temple exit, excluding those directly above (which are in temple_plant_boss_defeated). temple_exit = JakAndDaxterRegion("Temple Exit", player, multiworld, level_name, 12) if options.forbidden_jungle_attackless_spiral_stumps_scout_fly: # This fly can be reached using the blue eco vent inside the temple (easiest with Fall Damage Animation Cancel). - temple_exit.add_fly_locations([262151]) + temple_exit.add_fly_locations([262151], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) else: # This fly is too far from accessible blue eco sources. temple_exit.add_fly_locations([262151], access_rule=lambda state: world.can_free_scout_flies(state, player)) - temple_exterior = JakAndDaxterRegion("Temple Exterior", player, multiworld, level_name, 10) + temple_exterior = JakAndDaxterRegion("Temple Exterior", player, multiworld, level_name, 8) # All of these can be gotten with blue eco and running. temple_exterior.add_cell_locations([4]) @@ -66,16 +74,22 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa lurker_machine.connect(main_area) # Jump down. lurker_machine.connect(river) # Jump down. - lurker_machine.connect(temple_exterior) # Jump down (ledge). river.connect(main_area) # Jump up (ledges near fisherman). river.connect(lurker_machine) # Jump pad (aim toward machine). river.connect(temple_exit) # Run and jump (trampolines). - river.connect(temple_exterior) # Jump pad (aim toward temple door). + river.connect(temple_exterior, # Jump pad (aim toward temple door). + rule=lambda state: + state.has("Blue Eco", player)) + river.connect(river_cache, # Use Blue eco to get the orbs in the cache. + rule=lambda state: + state.has("Blue Eco", player)) temple_exit.connect(main_area) # Run and jump (bridges). temple_exit.connect(river) # Jump down. - temple_exit.connect(temple_exterior) # Run and jump (bridges, dodge spikes). + temple_exit.connect(temple_exterior, # Run and jump (bridges, dodge spikes). + rule=lambda state: + state.has("Blue Eco", player)) if options.forbidden_jungle_elevator_skip: # With a deload, it's possible to skip the Elevator, but Jak has to hit the loading zone while falling down. @@ -88,9 +102,10 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa # It is possible to reach the boss by jumping through a collision hole above the door, using boosteds. # After defeating the boss, it's possible to go back with blue eco and grab everything (including jump pads). + # TODO - Jump pads are not reachable if Blue Eco has not been unlocked temple_int_pre_blue.connect(temple_int_post_blue, rule=lambda state: world.can_do_boosted_extended(state, player) - or state.has("Blue Eco Switch", player)) + or state.has_all(("Blue Eco Switch", "Blue Eco"), player)) # Requires defeating the plant boss (combat). temple_int_post_blue.connect(temple_plant_boss_defeated, rule=lambda state: can_fight(state, player)) @@ -99,6 +114,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa world.level_to_regions[level_name].append(main_area) world.level_to_regions[level_name].append(lurker_machine) world.level_to_regions[level_name].append(river) + world.level_to_regions[level_name].append(river_cache) world.level_to_regions[level_name].append(temple_exit) world.level_to_regions[level_name].append(temple_exterior) world.level_to_regions[level_name].append(temple_int_pre_blue) diff --git a/worlds/jakanddaxter/regs/geyser_rock_regions.py b/worlds/jakanddaxter/regs/geyser_rock_regions.py index 3aa20950eabe..0312c3cf6ec4 100644 --- a/worlds/jakanddaxter/regs/geyser_rock_regions.py +++ b/worlds/jakanddaxter/regs/geyser_rock_regions.py @@ -12,18 +12,42 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe options = world.options player = world.player - main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 48) - main_area.add_cell_locations([92, 93]) - main_area.add_fly_locations(scouts.locGR_scoutTable.keys()) # All Flies here are accessible with blue eco. + main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 30) + main_area.add_cell_locations([92]) + main_area.add_fly_locations(scouts.locGR_scoutTable.keys(), access_rule=lambda state: + world.can_free_scout_flies(state, player) + or state.has("Blue Eco", player)) # All Flies here are accessible with blue eco. + + behind_door = JakAndDaxterRegion("Behind Precursor Door", player, multiworld, level_name, 17) + behind_door.add_cell_locations([93]) + + # 1 orb on the way is too high to be collected with a single jump without blue eco. + behind_door_high = JakAndDaxterRegion("Behind Precursor Door - High Orbs", player, multiworld, level_name, 1) + + behind_door.connect(behind_door_high, rule=lambda state: + state.has_any(("Blue Eco", "Double Jump", "Jump Kick"), player) + or state.has_all(("Crouch", "Crouch Jump"), player) + or state.has_all(("Crouch", "Crouch Uppercut"), player)) # The last 2 orbs are barely gettable with the blue eco vent, but it's pushing accessibility. So I moved them here. cliff = JakAndDaxterRegion("Cliff", player, multiworld, level_name, 2) cliff.add_cell_locations([94]) + # The door opens only if Jak has Blue Eco or can execute door skip. + if options.geyser_rock_door_skip: + # Door skip always requires Jump Kick + either Crouch Jump or Jump Dive + Double Jump. + main_area.connect(behind_door, rule=lambda state: + state.has("Blue Eco", player) + or (state.has("Jump Kick", player) + and (state.has("Crouch Jump", player) + or state.has_all(("Jump Dive", "Double Jump"), player)))) + else: + main_area.connect(behind_door, rule=lambda state: state.has("Blue Eco", player)) + if options.geyser_rock_cliff_climb: - main_area.connect(cliff) + behind_door.connect(cliff) else: - main_area.connect(cliff, rule=lambda state: + behind_door.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)) @@ -31,6 +55,8 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe cliff.connect(main_area) # Jump down or ride blue eco elevator. world.level_to_regions[level_name].append(main_area) + world.level_to_regions[level_name].append(behind_door) + world.level_to_regions[level_name].append(behind_door_high) world.level_to_regions[level_name].append(cliff) # If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always diff --git a/worlds/jakanddaxter/regs/misty_island_regions.py b/worlds/jakanddaxter/regs/misty_island_regions.py index b4f23d8a45ed..75c1cf52cab4 100644 --- a/worlds/jakanddaxter/regs/misty_island_regions.py +++ b/worlds/jakanddaxter/regs/misty_island_regions.py @@ -17,7 +17,8 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe muse_course.add_cell_locations([23]) if options.misty_island_attackless_scout_flies: # Grabbing blue eco orbs and running back can reach this scout fly - muse_course.add_fly_locations([327708]) + muse_course.add_fly_locations([327708], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) else: muse_course.add_fly_locations([327708], access_rule=lambda state: world.can_free_scout_flies(state, player)) @@ -58,13 +59,16 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe upper_approach = JakAndDaxterRegion("Upper Arena Approach", player, multiworld, level_name, 6) if options.misty_island_attackless_scout_flies: # These can be reached with blue eco. - upper_approach.add_fly_locations([65564, 262172]) + upper_approach.add_fly_locations([65564, 262172], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) else: upper_approach.add_fly_locations([65564, 262172], access_rule=lambda state: world.can_free_scout_flies(state, player)) lower_approach = JakAndDaxterRegion("Lower Arena Approach", player, multiworld, level_name, 7) - lower_approach.add_cell_locations([30]) + # Use the blue eco to reach this power cell, or do an extended boosted + lower_approach.add_cell_locations([30], access_rule=lambda state: + state.has("Blue Eco", player) or world.can_do_boosted_extended(state, player)) arena = JakAndDaxterRegion("Arena", player, multiworld, level_name, 5) if options.misty_island_arena_fight_skip: @@ -73,7 +77,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe else: arena.add_cell_locations([25], access_rule=lambda state: can_fight(state, player)) - main_area.connect(muse_course) # TODO - What do you need to chase the muse the whole way around? + main_area.connect(muse_course) # You can catch the muse using only Single Jump. main_area.connect(zoomer) # Run and jump down. main_area.connect(ship) # Run and jump. main_area.connect(lower_approach) # Run and jump. @@ -100,10 +104,10 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe if options.misty_island_single_jump_far_side_orb_cache: # 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)) + far_side.connect(far_side_cache, rule=lambda state: can_fight(state, player) and state.has("Blue Eco", player)) else: # It's possible to reach the orb cache without any attacks. - far_side.connect(far_side_cache) + far_side.connect(far_side_cache, rule=lambda state: state.has("Blue Eco", player)) far_side_cliff.connect(far_side) # Run and jump down. diff --git a/worlds/jakanddaxter/regs/mountain_pass_regions.py b/worlds/jakanddaxter/regs/mountain_pass_regions.py index bd26be8dd2bd..00f9a09db41a 100644 --- a/worlds/jakanddaxter/regs/mountain_pass_regions.py +++ b/worlds/jakanddaxter/regs/mountain_pass_regions.py @@ -15,7 +15,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa # This is basically just Klaww. main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 0) - main_area.add_cell_locations([86]) + main_area.add_cell_locations([86], access_rule=lambda state: state.has_all(("Blue Eco", "Yellow Eco"), player)) # Some folks prefer firing Yellow Eco from the hip, so optionally put this rule before Klaww. Klaww is the only # location in main_area, so he's at index 0. @@ -33,14 +33,18 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa # Of course, in order to make it to the race region, you must defeat Klaww. He's not optional. # So we need to set up this inter-region rule as well (or make it free if the setting is off). - if world.options.require_punch_for_klaww: + + if options.klaww_fight_skip: + # Actually, Klaww is optional with this setting and can be skipped using lava walking or idle deload. + main_area.connect(race) + elif world.options.require_punch_for_klaww: main_area.connect(race, rule=lambda state: state.has("Punch", player)) else: main_area.connect(race) # You actually can go backwards from the race back to Klaww's area. race.connect(main_area) - race.connect(shortcut, rule=lambda state: state.has("Yellow Eco Switch", player)) + race.connect(shortcut, rule=lambda state: state.has_all(("Yellow Eco", "Yellow Eco Switch"), player)) shortcut.connect(race) diff --git a/worlds/jakanddaxter/regs/sandover_village_regions.py b/worlds/jakanddaxter/regs/sandover_village_regions.py index 73b768232c29..485869beb4bd 100644 --- a/worlds/jakanddaxter/regs/sandover_village_regions.py +++ b/worlds/jakanddaxter/regs/sandover_village_regions.py @@ -19,17 +19,20 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe main_area.add_cell_locations([12], access_rule=lambda state: world.can_trade(state, world.total_trade_orbs, None)) # These 4 scout fly boxes can be broken by running with all the blue eco from Sentinel Beach. - main_area.add_fly_locations([262219, 327755, 131147, 65611]) + main_area.add_fly_locations([262219, 327755, 131147, 65611], access_rule=lambda state: + world.can_free_scout_flies(state, player) or (state.has("Blue Eco", player))) if options.sandover_village_cliff_orb_cache_climb: # It is possible to jump up to the blue Eco next to the Orb Cache, and take it to the Farmer's scout fly. - main_area.add_fly_locations([196683]) + main_area.add_fly_locations([196683], access_rule=lambda state: + world.can_free_scout_flies(state, player) or (state.has("Blue Eco", player))) else: # 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 world.can_free_scout_flies(state, player)) + world.can_free_scout_flies(state, player) + or state.has("Blue Eco", player) + and (state.has("Double Jump", player) + or state.has_all(("Crouch", "Crouch Jump"), player))) orb_cache_cliff = JakAndDaxterRegion("Orb Cache Cliff", player, multiworld, level_name, 15) orb_cache_cliff.add_cache_locations([10344]) @@ -46,13 +49,15 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe world.can_free_scout_flies(state, player)) if options.sandover_village_cliff_orb_cache_climb: - # It is possible to reach this cliff (and the blue Eco next to it) with a single jump - main_area.connect(orb_cache_cliff) + # It is possible to reach this cliff (and the blue Eco next to it) with a single jump. + # However, the orbs (in the cache) can only be collected if Jak can use Blue Eco. + main_area.connect(orb_cache_cliff, rule=lambda state: state.has("Blue Eco", player)) else: 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)) + state.has("Blue Eco", player) + and (state.has("Double Jump", 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) diff --git a/worlds/jakanddaxter/regs/sentinel_beach_regions.py b/worlds/jakanddaxter/regs/sentinel_beach_regions.py index b93aa08b7502..ab32b326c148 100644 --- a/worlds/jakanddaxter/regs/sentinel_beach_regions.py +++ b/worlds/jakanddaxter/regs/sentinel_beach_regions.py @@ -23,13 +23,13 @@ def can_climb_cannon_tower(_: CollectionState, __: int) -> bool: return False def can_reach_cannon(state: CollectionState, p: int) -> bool: - return state.has("Blue Eco Switch", p) or can_climb_cannon_tower(state, p) + return state.has_all(("Blue Eco Switch", "Blue Eco"), p) or can_climb_cannon_tower(state, p) if options.sentinel_beach_blue_eco_switch_skip: # It's possible to grab the Blue Eco Vent with just Punch or Roll Jump by using an optimized route from the # blue eco to the Flut Flut egg. def can_reach_blue_eco_vent(state: CollectionState, p: int) -> bool: - return (state.has("Blue Eco Switch", p) + return (state.has_all(("Blue Eco Switch", "Blue Eco"), p) or (can_climb_cannon_tower(state, p) and world.can_do_boosted_extended(state, p)) or state.has("Punch", p) or state.has_all(("Roll", "Roll Jump"), p)) @@ -39,15 +39,19 @@ def can_reach_blue_eco_vent(state: CollectionState, p: int) -> bool: # use the locked blue eco vent on the beach, or you can climb the cannon tower and boosted uppercut to the spires. def can_reach_blue_eco_vent(state: CollectionState, p: int) -> bool: - return (state.has("Blue Eco Switch", p) + return (state.has_all(("Blue Eco Switch", "Blue Eco"), p) or (can_climb_cannon_tower(state, p) and world.can_do_boosted_extended(state, p))) - main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 128) + main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 108) main_area.add_cell_locations([18, 21, 22]) # These scout fly boxes can be broken by running with freely accessible blue eco. # The 3 clusters by the Flut Flut egg can go surprisingly far. - main_area.add_fly_locations([327700, 20, 65556, 262164]) + main_area.add_fly_locations([327700, 65556, 262164], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) + + # This scout fly box can be broken by the lurker tower. + main_area.add_fly_locations([20]) # This scout fly box can be broken with an open blue eco vent (locked one on beach or open one on rock spires), # or by normal combat tricks. @@ -55,8 +59,9 @@ def can_reach_blue_eco_vent(state: CollectionState, p: int) -> bool: can_reach_blue_eco_vent(state, player) or world.can_free_scout_flies(state, player)) - # No need for the blue eco vent for either of the orb caches. - main_area.add_cache_locations([12634, 12635]) + # No need for the blue eco vent for either of the orb caches (10 orbs per cache). + orb_caches = JakAndDaxterRegion("Orb Caches", player, multiworld, level_name, 20) + orb_caches.add_cache_locations([12634, 12635]) pelican = JakAndDaxterRegion("Pelican", player, multiworld, level_name, 0) if options.sentinel_beach_attackless_pelican: @@ -111,6 +116,9 @@ def can_uppercut_and_jump_logs(state: CollectionState, p: int) -> bool: or state.has_all(("Crouch", "Crouch Jump"), player) or can_uppercut_and_jump_logs(state, player)) + # Jak can only collect the orbs from the cache if he can use Blue Eco. + main_area.connect(orb_caches, rule=lambda state: state.has("Blue Eco", player)) + # You can use an open blue eco vent, or you can use the logs, to reach this place. main_area.connect(blue_ridge, rule=lambda state: can_reach_blue_eco_vent(state, player) @@ -133,6 +141,7 @@ def can_uppercut_and_jump_logs(state: CollectionState, p: int) -> bool: cannon_tower.connect(main_area) world.level_to_regions[level_name].append(main_area) + world.level_to_regions[level_name].append(orb_caches) world.level_to_regions[level_name].append(pelican) world.level_to_regions[level_name].append(flut_flut_egg) world.level_to_regions[level_name].append(eco_harvesters) From 2c1cef92fbc9163ecea887ff44d5496d5c4bc2d5 Mon Sep 17 00:00:00 2001 From: Mattis Turin-Zelenko Date: Sat, 2 May 2026 14:11:49 +0200 Subject: [PATCH 2/3] Jak1: Eco Randomizer logic Eco randomizer logic for all remaining regions (excluding SC robot cave) --- worlds/jakanddaxter/__init__.py | 1 + worlds/jakanddaxter/options.py | 1 + .../jakanddaxter/regs/boggy_swamp_regions.py | 164 ++++++++++++++---- .../regs/forbidden_jungle_regions.py | 16 +- .../regs/gol_and_maias_citadel_regions.py | 21 ++- .../regs/lost_precursor_city_regions.py | 42 +++-- .../jakanddaxter/regs/rock_village_regions.py | 25 +-- .../regs/snowy_mountain_regions.py | 22 ++- .../jakanddaxter/regs/spider_cave_regions.py | 27 ++- .../regs/volcanic_crater_regions.py | 2 +- 10 files changed, 226 insertions(+), 95 deletions(-) diff --git a/worlds/jakanddaxter/__init__.py b/worlds/jakanddaxter/__init__.py index 376f209ee1e7..7e707ff6517c 100644 --- a/worlds/jakanddaxter/__init__.py +++ b/worlds/jakanddaxter/__init__.py @@ -119,6 +119,7 @@ class JakAndDaxterWebWorld(WebWorld): OptionGroup("Eco Randomizer", [ options.EnableEcoRandomizer, options.GeyserRockDoorSkip, + options.KlawwFightSkip, ], True), OptionGroup("Tricks & Glitches - Easy", [ options.AttackWithRollJump, # Use Roll Jump instead of regular attacks to hit certain targets. diff --git a/worlds/jakanddaxter/options.py b/worlds/jakanddaxter/options.py index 5a9cd91deca1..b7f767a7f4fd 100644 --- a/worlds/jakanddaxter/options.py +++ b/worlds/jakanddaxter/options.py @@ -355,6 +355,7 @@ class KlawwFightSkip(Toggle): Enabling this setting may require Jak to reach Mountain Pass by skipping the Klaww fight, without *Blue Eco* or *Yellow Eco* unlocked. """ + display_name = "Klaww Fight Skip" # endregion # region Advanced Movement diff --git a/worlds/jakanddaxter/regs/boggy_swamp_regions.py b/worlds/jakanddaxter/regs/boggy_swamp_regions.py index c43dae1d18b7..e10ee222b070 100644 --- a/worlds/jakanddaxter/regs/boggy_swamp_regions.py +++ b/worlds/jakanddaxter/regs/boggy_swamp_regions.py @@ -24,32 +24,40 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: 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. - main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 23) - main_area.add_fly_locations([43]) + main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 5) + # The scout fly box can be broken with either blue or yellow eco. + main_area.add_fly_locations([43], access_rule=lambda state: + world.can_free_scout_flies(state, player) + or state.has_any(("Blue Eco", "Yellow Eco"), player)) - # Includes 4 orbs collectable with the blue eco vent. - first_bats = JakAndDaxterRegion("First Bats Area", player, multiworld, level_name, 4) + # These boxes can only be broken with yellow eco (or Flut Flut). + main_area_boxes = JakAndDaxterRegion("Main Area Boxes", player, multiworld, level_name, 18) + + first_bats = JakAndDaxterRegion("First Bats Area", player, multiworld, level_name, 0) + # 4 orbs collectable with the blue eco vent. + first_bats_orbs = JakAndDaxterRegion("First Bats Area (Orbs)", player, multiworld, level_name, 4) first_jump_pad = JakAndDaxterRegion("First Jump Pad", player, multiworld, level_name, 0) - first_jump_pad.add_fly_locations([393259]) + first_jump_pad.add_fly_locations([393259], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) # The tethers in this level are all out of order... a casual playthrough has the following order for the cell ID's: # 42, 39, 40, 41. So that is the order we're calling "first, second, third, fourth". # First tether cell is collectable with yellow eco and goggles. first_tether = JakAndDaxterRegion("First Tether", player, multiworld, level_name, 7) - first_tether.add_cell_locations([42]) + first_tether.add_cell_locations([42], access_rule=lambda state: state.has("Yellow Eco", player)) # This rat colony has 3 orbs on top of it, requires special movement. first_tether_rat_colony = JakAndDaxterRegion("First Tether Rat Colony", player, multiworld, level_name, 3) # If quick enough, combat not required. second_jump_pad = JakAndDaxterRegion("Second Jump Pad", player, multiworld, level_name, 0) - second_jump_pad.add_fly_locations([65579]) + second_jump_pad.add_fly_locations([65579], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) - first_pole_course = JakAndDaxterRegion("First Pole Course", player, multiworld, level_name, 28) + first_pole_course = JakAndDaxterRegion("First Pole Course", player, multiworld, level_name, 20) + first_pole_course_boxes = JakAndDaxterRegion("First Pole Course Boxes", player, multiworld, level_name, 8) # You can break this tether with a yellow eco vent and goggles, # but you can't reach the platform unless you can jump high. @@ -57,71 +65,107 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: if options.boggy_swamp_precise_movement: # Jump Kick is enough to reach the power cell. second_tether.add_cell_locations([39], access_rule=lambda state: - can_jump_higher(state, player) or state.has("Jump Kick", player)) + state.has("Yellow Eco", player) + and (can_jump_higher(state, player) or state.has("Jump Kick", player))) else: - second_tether.add_cell_locations([39], access_rule=lambda state: can_jump_higher(state, player)) + second_tether.add_cell_locations([39], access_rule=lambda state: + state.has("Yellow Eco", player) and can_jump_higher(state, player)) - # Fly and orbs are collectable with nearby blue eco cluster. - second_bats = JakAndDaxterRegion("Second Bats Area", player, multiworld, level_name, 27) + second_bats = JakAndDaxterRegion("Second Bats Area", player, multiworld, level_name, 5) + second_bats_boxes = JakAndDaxterRegion("Second Bats Area Boxes", player, multiworld, level_name, 10) + # Orbs for the boxes in the water with the blue eco. + second_bats_water_boxes = JakAndDaxterRegion("Second Bats Area Water Boxes", player, multiworld, level_name, 12) if options.boggy_swamp_precise_movement: # By taking damage once, this fly is easily reachable with blue eco. It can also be destroyed using yellow eco. - second_bats.add_fly_locations([262187]) + second_bats.add_fly_locations([262187], access_rule=lambda state: + world.can_free_scout_flies(state, player) + or state.has_any(("Yellow Eco", "Blue Eco"), player)) else: - second_bats.add_fly_locations([262187], access_rule=lambda state: can_jump_farther(state, player)) + second_bats.add_fly_locations([262187], access_rule=lambda state: + world.can_free_scout_flies(state, player) + or (can_jump_farther(state, player) + and state.has_any(("Yellow Eco", "Blue Eco"), player))) third_jump_pad = JakAndDaxterRegion("Third Jump Pad (Arena)", player, multiworld, level_name, 0) # We have to choose our access rule depending on our Flut Flut escape option. # We want to make that decision while we are creating regions, NOT INSIDE the access rule itself. + if options.boggy_swamp_flut_flut_escape: + def can_use_flut_flut_to_fight(state, p): + return state.has("Flut Flut", p) + else: + def can_use_flut_flut_to_fight(state, p): + return False + if options.boggy_swamp_attackless_ambush: # It is possible (but annoying/hard) to beat the ambush by shooting yellow eco through the goggles. - third_jump_pad.add_cell_locations([38]) - elif options.boggy_swamp_flut_flut_escape: third_jump_pad.add_cell_locations([38], access_rule=lambda state: - (state.has("Flut Flut", player) or can_fight(state, player))) + state.has("Yellow Eco", player) + or can_fight(state, player) + or can_use_flut_flut_to_fight(state, player)) else: - third_jump_pad.add_cell_locations([38], access_rule=lambda state: can_fight(state, player)) + third_jump_pad.add_cell_locations([38], access_rule=lambda state: + can_fight(state, player) + or can_use_flut_flut_to_fight(state, player)) # The platform for the third tether might look high, but you can get a boost from the yellow eco vent. fourth_jump_pad = JakAndDaxterRegion("Fourth Jump Pad (Third Tether)", player, multiworld, level_name, 9) - fourth_jump_pad.add_cell_locations([40]) + fourth_jump_pad.add_cell_locations([40], access_rule=lambda state: state.has("Yellow Eco", player)) - # Orbs collectable here with yellow eco and goggles. - flut_flut_pad = JakAndDaxterRegion("Flut Flut Pad", player, multiworld, level_name, 36) + flut_flut_pad = JakAndDaxterRegion("Flut Flut Pad", player, multiworld, level_name, 0) + # Orbs collectable here with yellow eco and goggles or Flut Flut. + flut_flut_pad_boxes = JakAndDaxterRegion("Flut Flut Pad Boxes", player, multiworld, level_name, 36) # This region contains the first scout fly + orbs, when going towards the region entrance. early_flut_flut_course = JakAndDaxterRegion("Early Flut Flut Course", player, multiworld, level_name, 6) # This scout fly box can be broken using yellow eco. - early_flut_flut_course.add_fly_locations([327723]) + early_flut_flut_course.add_fly_locations([327723], access_rule=lambda state: + world.can_free_scout_flies(state, player) + or state.has("Yellow Eco", player)) flut_flut_course = JakAndDaxterRegion("Flut Flut Course", player, multiworld, level_name, 17) flut_flut_course.add_cell_locations([37]) # This scout fly box can be broken using yellow eco. - flut_flut_course.add_fly_locations([131115]) + flut_flut_course.add_fly_locations([131115], access_rule=lambda state: + world.can_free_scout_flies(state, player) + or state.has("Yellow Eco", player)) - # Includes some orbs on the way to the cabin, blue+yellow eco to collect. - farthy_snacks = JakAndDaxterRegion("Farthy's Snacks", player, multiworld, level_name, 7) - farthy_snacks.add_cell_locations([36]) + farthy_snacks = JakAndDaxterRegion("Farthy's Snacks", player, multiworld, level_name, 0) + # Orbs on the way to the cabin, blue+yellow eco to collect. + farthy_snacks_water_boxes = JakAndDaxterRegion("Farthy's Snacks Water Boxes", player, multiworld, level_name, 7) + farthy_snacks.add_cell_locations([36], access_rule=lambda state: state.has("Yellow Eco", player)) - # Scout fly in this field can be broken with yellow eco. - box_field = JakAndDaxterRegion("Field of Boxes", player, multiworld, level_name, 10) + # Scout fly in this field can be broken by running into an explosive box next to it, which also grants 3 orbs. + box_field = JakAndDaxterRegion("Field of Boxes", player, multiworld, level_name, 3) box_field.add_fly_locations([196651]) - last_tar_pit = JakAndDaxterRegion("Last Tar Pit", player, multiworld, level_name, 12) + # These boxes can only be destroyed using yellow eco. + box_field_boxes = JakAndDaxterRegion("Field of Boxes Boxes", player, multiworld, level_name, 7) - fourth_tether = JakAndDaxterRegion("Fourth Tether", player, multiworld, level_name, 11) + last_tar_pit = JakAndDaxterRegion("Last Tar Pit", player, multiworld, level_name, 2) + last_tar_pit_boxes = JakAndDaxterRegion("Last Tar Pit Boxes", player, multiworld, level_name, 10) + + fourth_tether = JakAndDaxterRegion("Fourth Tether", player, multiworld, level_name, 8) + # Only the last box cannot be destroyed by running into nearby explosive boxes. + fourth_tether_boxes = JakAndDaxterRegion("Fourth Tether Boxes", player, multiworld, level_name, 3) if options.boggy_swamp_precise_movement: # This power cell can be reached with a single jump. - fourth_tether.add_cell_locations([41]) + fourth_tether.add_cell_locations([41], access_rule=lambda state: state.has("Yellow Eco", player)) main_area.connect(first_bats) + # These orbs can be collected by taking damage. + first_bats.connect(first_bats_orbs) else: - fourth_tether.add_cell_locations([41], access_rule=lambda state: can_jump_higher(state, player)) + fourth_tether.add_cell_locations([41], access_rule=lambda state: + can_jump_higher(state, player) and state.has("Yellow Eco", player)) main_area.connect(first_bats, rule=lambda state: can_jump_farther(state, player)) + first_bats.connect(first_bats_orbs, rule=lambda state: state.has("Blue Eco", player)) + + main_area.connect(main_area_boxes, rule=lambda state: state.has("Yellow Eco", player)) first_bats.connect(main_area) - first_bats.connect(first_jump_pad) + first_bats.connect(first_jump_pad, rule=lambda state: state.has("Blue Eco", player)) first_bats.connect(first_tether) first_jump_pad.connect(first_bats) @@ -135,7 +179,9 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: 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))) - first_tether.connect(second_jump_pad) + + # Requires yellow eco to destroy the rat colony which unlocks the blue eco for the jump pad. + first_tether.connect(second_jump_pad, rule=lambda state: state.has_all(("Blue Eco", "Yellow Eco"), player)) first_tether.connect(first_pole_course) first_tether_rat_colony.connect(first_tether) @@ -144,19 +190,39 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: first_pole_course.connect(first_tether) first_pole_course.connect(second_tether) + # Yellow Eco to break the boxes, Blue Eco to collect it. + if options.boggy_swamp_precise_movement: + # By taking damage, the orbs can be collected without Blue Eco. + first_pole_course.connect(first_pole_course_boxes, rule=lambda state: state.has("Yellow Eco", player)) + else: + first_pole_course.connect(first_pole_course_boxes, rule=lambda state: + state.has_all(("Yellow Eco", "Blue Eco"), player)) second_tether.connect(first_pole_course, rule=lambda state: can_jump_higher(state, player)) second_tether.connect(second_bats) second_bats.connect(second_tether) - second_bats.connect(third_jump_pad) - second_bats.connect(fourth_jump_pad) + if options.boggy_swamp_precise_movement: + # It is possible to use the launch pad without destroying the rock. + second_bats.connect(third_jump_pad, rule=lambda state: state.has("Blue Eco", player)) + else: + second_bats.connect(third_jump_pad, rule=lambda state: state.has_all(("Blue Eco", "Yellow Eco"), player)) + + second_bats.connect(fourth_jump_pad, rule=lambda state: state.has("Blue Eco", player)) second_bats.connect(flut_flut_pad) + second_bats.connect(second_bats_boxes, rule=lambda state: state.has("Yellow Eco", player)) + if options.boggy_swamp_precise_movement: + second_bats.connect(second_bats_water_boxes, rule=lambda state: state.has("Yellow Eco", player)) + else: + second_bats.connect(second_bats_water_boxes, rule=lambda state: + state.has_all(("Yellow Eco", "Blue Eco"), player)) third_jump_pad.connect(second_bats) fourth_jump_pad.connect(second_bats) flut_flut_pad.connect(second_bats) + # The boxes can be collected either by using yellow eco or with Flut Flut. + flut_flut_pad.connect(flut_flut_pad_boxes, rule=lambda state: state.has_any(("Yellow Eco", "Flut Flut"), player)) if options.boggy_swamp_flut_flut_skip: # The course is doable with only Roll Jump, or by using boosteds with Jump Kick. @@ -187,6 +253,9 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: farthy_snacks.connect(flut_flut_pad) if options.boggy_swamp_precise_movement: + # Orbs can be collected by taking damage. + farthy_snacks.connect(farthy_snacks_water_boxes, rule=lambda state: state.has("Yellow Eco", player)) + # These can all be reached with Single Jump only by taking damage, and using invincibility after taking damage # when required. farthy_snacks.connect(box_field) @@ -202,6 +271,9 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: fourth_tether.connect(last_tar_pit) else: + farthy_snacks.connect(farthy_snacks_water_boxes, rule=lambda state: + state.has_all(("Yellow Eco", "Blue Eco"), player)) + farthy_snacks.connect(box_field, rule=lambda state: can_jump_higher(state, player)) box_field.connect(farthy_snacks, rule=lambda state: can_jump_higher(state, player)) @@ -211,26 +283,42 @@ def can_jump_higher(state: CollectionState, p: int) -> bool: last_tar_pit.connect(fourth_tether, rule=lambda state: can_jump_farther(state, player)) fourth_tether.connect(last_tar_pit, rule=lambda state: can_jump_farther(state, player)) + + box_field.connect(box_field_boxes, rule=lambda state: state.has("Yellow Eco", player)) + # Always require blue eco for these orbs as the runback is long, and it's hard enough already to get through + # without taking additional damage. + last_tar_pit.connect(last_tar_pit_boxes, rule=lambda state: state.has_all(("Yellow Eco", "Blue Eco"), player)) + fourth_tether.connect(fourth_tether_boxes, rule=lambda state: state.has("Yellow Eco", player)) fourth_tether.connect(main_area) # Fall down. world.level_to_regions[level_name].append(main_area) + world.level_to_regions[level_name].append(main_area_boxes) world.level_to_regions[level_name].append(first_bats) + world.level_to_regions[level_name].append(first_bats_orbs) world.level_to_regions[level_name].append(first_jump_pad) world.level_to_regions[level_name].append(first_tether) world.level_to_regions[level_name].append(first_tether_rat_colony) world.level_to_regions[level_name].append(second_jump_pad) world.level_to_regions[level_name].append(first_pole_course) + world.level_to_regions[level_name].append(first_pole_course_boxes) world.level_to_regions[level_name].append(second_tether) world.level_to_regions[level_name].append(second_bats) + world.level_to_regions[level_name].append(second_bats_boxes) + world.level_to_regions[level_name].append(second_bats_water_boxes) world.level_to_regions[level_name].append(third_jump_pad) world.level_to_regions[level_name].append(fourth_jump_pad) world.level_to_regions[level_name].append(flut_flut_pad) + world.level_to_regions[level_name].append(flut_flut_pad_boxes) world.level_to_regions[level_name].append(early_flut_flut_course) world.level_to_regions[level_name].append(flut_flut_course) world.level_to_regions[level_name].append(farthy_snacks) + world.level_to_regions[level_name].append(farthy_snacks_water_boxes) world.level_to_regions[level_name].append(box_field) + world.level_to_regions[level_name].append(box_field_boxes) world.level_to_regions[level_name].append(last_tar_pit) + world.level_to_regions[level_name].append(last_tar_pit_boxes) world.level_to_regions[level_name].append(fourth_tether) + world.level_to_regions[level_name].append(fourth_tether_boxes) # If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always # accessible to Main Area. The Locations within are automatically checked when you collect enough orbs. diff --git a/worlds/jakanddaxter/regs/forbidden_jungle_regions.py b/worlds/jakanddaxter/regs/forbidden_jungle_regions.py index 0da743ee04d8..8ef55203d875 100644 --- a/worlds/jakanddaxter/regs/forbidden_jungle_regions.py +++ b/worlds/jakanddaxter/regs/forbidden_jungle_regions.py @@ -62,7 +62,14 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa temple_int_pre_blue.add_cell_locations([2]) temple_int_pre_blue.add_special_locations([2]) - temple_int_post_blue = JakAndDaxterRegion("Temple Interior (Post Blue Eco)", player, multiworld, level_name, 29) + # This region contains everything from the door to the boss. + temple_int_post_blue = JakAndDaxterRegion("Temple Interior (Post Blue Eco)", player, multiworld, level_name, 2) + + # This region contains all reachable orbs when Jak can get behind the door and can collect blue eco. + # This allows Jak to use the Jump Pads (2x 10 orbs) and unlock the door to the blue eco switch (7 orbs). + # The 7 orbs can probably be collected using a boosted as well, but I haven't confirmed this. + temple_int_post_blue_eco_unlocked = JakAndDaxterRegion("Temple Interior (Post Blue Eco, Eco unlocked)", player, + multiworld, level_name, 27) # 5 orbs from Plant Boss + 5 orbs from leaving via jump pad. Only reachable when Jak can fight the plant boss. temple_plant_boss_defeated = JakAndDaxterRegion("Temple (Plant Boss defeated)", player, multiworld, level_name, 10) @@ -101,12 +108,14 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa temple_exterior.connect(temple_int_pre_blue, rule=lambda state: state.has("Jungle Elevator", player)) # It is possible to reach the boss by jumping through a collision hole above the door, using boosteds. - # After defeating the boss, it's possible to go back with blue eco and grab everything (including jump pads). - # TODO - Jump pads are not reachable if Blue Eco has not been unlocked + # After defeating the boss (which is always possible with the boosted moveset), it's possible to go back with + # blue eco and grab everything (including jump pads). temple_int_pre_blue.connect(temple_int_post_blue, rule=lambda state: world.can_do_boosted_extended(state, player) or state.has_all(("Blue Eco Switch", "Blue Eco"), player)) + temple_int_post_blue.connect(temple_int_post_blue_eco_unlocked, rule=lambda state: state.has("Blue Eco", player)) + # Requires defeating the plant boss (combat). temple_int_post_blue.connect(temple_plant_boss_defeated, rule=lambda state: can_fight(state, player)) temple_plant_boss_defeated.connect(temple_exit) @@ -119,6 +128,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa world.level_to_regions[level_name].append(temple_exterior) world.level_to_regions[level_name].append(temple_int_pre_blue) world.level_to_regions[level_name].append(temple_int_post_blue) + world.level_to_regions[level_name].append(temple_int_post_blue_eco_unlocked) world.level_to_regions[level_name].append(temple_plant_boss_defeated) # If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always diff --git a/worlds/jakanddaxter/regs/gol_and_maias_citadel_regions.py b/worlds/jakanddaxter/regs/gol_and_maias_citadel_regions.py index efedf418629a..77e8d5f1e58d 100644 --- a/worlds/jakanddaxter/regs/gol_and_maias_citadel_regions.py +++ b/worlds/jakanddaxter/regs/gol_and_maias_citadel_regions.py @@ -35,7 +35,11 @@ def can_jump_stairs(state: CollectionState, p: int) -> bool: jump_pad_room = JakAndDaxterRegion("Jump Pad Chamber", player, multiworld, level_name, 88) jump_pad_room.add_cell_locations([73], access_rule=lambda state: can_fight(state, player)) jump_pad_room.add_special_locations([73], access_rule=lambda state: can_fight(state, player)) - jump_pad_room.add_fly_locations([131163]) # Blue eco vent is right next to it. + + # The scout fly right next to the entrance is added to the main area since it can be reached without any moves, + # while everything in the jump pad room requires blue eco. + main_area.add_fly_locations([131163], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) jump_pad_room.add_cache_locations([24039, 24040]) # First, blue eco vent, second, blue eco cluster near sage. yellow_sage_scaffolding = JakAndDaxterRegion("Scaffolding Around Yellow Sage", @@ -68,16 +72,20 @@ def can_jump_stairs(state: CollectionState, p: int) -> bool: main_area.connect(robot_scaffolding, rule=lambda state: state.has("Jump Dive", player) or state.has_all(("Roll", "Roll Jump"), player)) - main_area.connect(jump_pad_room) + # While this room can be reached without blue eco, there is nothing that Jak can do. + # The scout fly at the entrance is part of the main_area. + main_area.connect(jump_pad_room, rule=lambda state: state.has("Blue Eco", player)) robot_scaffolding.connect(main_area, rule=lambda state: state.has("Jump Dive", player)) + # Blue eco is required to collect anything in this room. robot_scaffolding.connect(blast_furnace, rule=lambda state: - state.has("Jump Dive", player) - and (state.has_all(("Double Jump", "Jump Kick"), player))) + state.has_all(("Jump Dive", "Double Jump", "Jump Kick", "Blue Eco"), player)) robot_scaffolding.connect(u_turn_room, rule=lambda state: can_jump_farther(state, player)) + # Eco is technically not required to beat the room (but yellow eco to destroy the boxes). + # It is possible to jump down to collect the last 3 orbs, even without blue eco. u_turn_room.connect(bunny_room, rule=lambda state: - state.has("Jump Dive", player) + state.has_all(("Jump Dive", "Red Eco", "Yellow Eco"), player) and can_jump_farther(state, player)) jump_pad_room.connect(yellow_sage_scaffolding, rule=lambda state: can_jump_farther(state, player)) @@ -102,9 +110,10 @@ def can_jump_stairs(state: CollectionState, p: int) -> bool: rotating_tower.connect(main_area) # Take stairs back down. # Final elevator. Need to break boxes at summit to get blue eco for platform. + # Blue eco is required for the jump pad, yellow eco to shoot the boss. rotating_tower.connect(final_boss, rule=lambda state: can_fight(state, player) - and state.has("Freed The Green Sage", player)) + and state.has_all(("Blue Eco", "Yellow Eco", "Freed The Green Sage"), player)) final_boss.connect(rotating_tower) # Take elevator back down. diff --git a/worlds/jakanddaxter/regs/lost_precursor_city_regions.py b/worlds/jakanddaxter/regs/lost_precursor_city_regions.py index 22688541e055..48d34e591222 100644 --- a/worlds/jakanddaxter/regs/lost_precursor_city_regions.py +++ b/worlds/jakanddaxter/regs/lost_precursor_city_regions.py @@ -20,16 +20,13 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe first_room_lower.add_fly_locations([262193], access_rule=lambda state: world.can_free_scout_flies(state, player)) first_room_orb_cache = JakAndDaxterRegion("First Chamber Orb Cache", player, multiworld, level_name, 22) - - # 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)) + first_room_orb_cache.add_cache_locations([14507]) first_hallway = JakAndDaxterRegion("First Hallway", player, multiworld, level_name, 10) first_hallway.add_fly_locations([131121], access_rule=lambda state: world.can_free_scout_flies(state, player)) # This entire room is accessible with floating platforms and single jump. - second_room = JakAndDaxterRegion("Second Chamber", player, multiworld, level_name, 28) + second_room = JakAndDaxterRegion("Second Chamber", player, multiworld, level_name, 18) # These items can only be gotten with jump dive to activate a button. second_room.add_cell_locations([45], access_rule=lambda state: state.has("Jump Dive", player)) @@ -39,8 +36,11 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe second_room.add_fly_locations([196657], access_rule=lambda state: world.can_free_scout_flies(state, player)) # This orb vent and scout fly are right next to each other, can be gotten with blue eco and the floating platforms. - second_room.add_fly_locations([393265]) - second_room.add_cache_locations([14838]) + second_room.add_fly_locations([393265], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) + + second_room_orb_cache = JakAndDaxterRegion("Second Chamber Orb Cache", player, multiworld, level_name, 10) + second_room_orb_cache.add_cache_locations([14838]) # Named after the cell, includes the armored lurker room. center_complex = JakAndDaxterRegion("Center of the Complex", player, multiworld, level_name, 17) @@ -62,10 +62,11 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe # 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) + state.has_all(("Jump Dive", "Blue Eco"), player) and (state.has_any(("Double Jump", "Jump Kick"), player) or state.has_all(("Punch", "Punch Uppercut"), player))) - capsule_room.add_fly_locations([327729]) + capsule_room.add_fly_locations([327729], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) # 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. @@ -80,14 +81,16 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe # 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. + # However, you need Blue Eco to use the jump pads. helix_room = JakAndDaxterRegion("Helix Chamber", player, multiworld, level_name, 30) if options.lost_precursor_city_single_jump_slide_tube_climb: # It is possible (but hard) to escape with only Single Jump unlocked. - helix_room.add_cell_locations([50]) + helix_room.add_cell_locations([50], access_rule=lambda state: state.has("Blue Eco", player)) else: helix_room.add_cell_locations([50], access_rule=lambda state: - state.has("Double Jump", player) - or can_fight(state, player)) + state.has("Blue Eco", player) + and (state.has("Double Jump", player) + or can_fight(state, player))) main_area.connect(first_room_upper) # Run. @@ -97,11 +100,10 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe first_room_lower.connect(first_room_upper) # Run and jump (floating platforms). - # Needs some movement to reach these orbs and orb cache. + # Need jump dive to activate button, then the blue eco can be reached by a Single Jump to unlock cache. first_room_lower.connect(first_room_orb_cache, rule=lambda state: - 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(("Blue Eco", "Jump Dive"), player)) + first_room_orb_cache.connect(first_room_lower) first_hallway.connect(first_room_upper) # Run and jump down. first_hallway.connect(second_room) # Run and jump (floating platforms). @@ -109,6 +111,8 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe second_room.connect(first_hallway) # Run and jump. second_room.connect(center_complex) # Run and jump down. + second_room.connect(second_room_orb_cache, rule=lambda state: state.has("Blue Eco", player)) + center_complex.connect(second_room) # Run and jump (swim). center_complex.connect(color_platforms) # Run and jump (swim). center_complex.connect(quick_platforms) # Run and jump (swim). @@ -135,8 +139,9 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe 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. - or can_fight(state, player)) + state.has("Blue Eco", player) + and (state.has("Double Jump", player) # Capsule is a convenient exit to the level. + or can_fight(state, player))) world.level_to_regions[level_name].append(main_area) world.level_to_regions[level_name].append(first_room_upper) @@ -144,6 +149,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe world.level_to_regions[level_name].append(first_room_orb_cache) world.level_to_regions[level_name].append(first_hallway) world.level_to_regions[level_name].append(second_room) + world.level_to_regions[level_name].append(second_room_orb_cache) world.level_to_regions[level_name].append(center_complex) world.level_to_regions[level_name].append(color_platforms) world.level_to_regions[level_name].append(quick_platforms) diff --git a/worlds/jakanddaxter/regs/rock_village_regions.py b/worlds/jakanddaxter/regs/rock_village_regions.py index 59370e06d29c..4f6d69026894 100644 --- a/worlds/jakanddaxter/regs/rock_village_regions.py +++ b/worlds/jakanddaxter/regs/rock_village_regions.py @@ -21,7 +21,8 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa main_area.add_cell_locations([35], access_rule=lambda state: world.can_trade(state, world.total_trade_orbs, 34)) # These 2 scout fly boxes can be broken by running with nearby blue eco. - main_area.add_fly_locations([196684, 262220]) + main_area.add_fly_locations([196684, 262220], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) main_area.add_fly_locations([76, 131148, 65612, 327756], access_rule=lambda state: world.can_free_scout_flies(state, player)) @@ -29,17 +30,12 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa main_area.add_special_locations([33]) orb_cache = JakAndDaxterRegion("Orb Cache", player, multiworld, level_name, 20) - - if options.rock_village_single_jump_orb_cache: - # It is possible to just reach the orb cache with blue eco without roll jump. - orb_cache.add_cache_locations([10945]) - else: - # 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]) # 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, 2) - pontoon_bridge.add_fly_locations([393292]) + pontoon_bridge.add_fly_locations([393292], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Yellow Eco", player)) # Orbs that are not directly over the pontoons if Warrior's Pontoons is not unlocked. pontoon_bridge_high_orbs = JakAndDaxterRegion("Pontoon Bridge High Orbs", player, multiworld, level_name, 5) @@ -48,23 +44,20 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa if options.rock_village_single_jump_orb_cache: # It is possible to just reach the orb cache with blue eco without roll jump. - main_area.connect(orb_cache) + main_area.connect(orb_cache, rule=lambda state: state.has("Blue Eco", player)) else: - 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(("Blue Eco", "Roll", "Roll Jump"), player)) if options.rock_village_pontoon_skip: # Reachable with Jump/Swim main_area.connect(pontoon_bridge) + pontoon_bridge.connect(main_area) else: main_area.connect(pontoon_bridge, rule=lambda state: state.has("Warrior's Pontoons", player)) + pontoon_bridge.connect(main_area, rule=lambda state: state.has("Warrior's Pontoons", player)) orb_cache.connect(main_area) - if options.rock_village_pontoon_skip: - pontoon_bridge.connect(main_area) - else: - pontoon_bridge.connect(main_area, rule=lambda state: state.has("Warrior's Pontoons", player)) - # Some orbs can only be reached by using the Pontoon Bridge or having Double Jump. pontoon_bridge.connect(pontoon_bridge_high_orbs, rule=lambda state: state.has("Warrior's Pontoons", player) or state.has("Double Jump", player)) diff --git a/worlds/jakanddaxter/regs/snowy_mountain_regions.py b/worlds/jakanddaxter/regs/snowy_mountain_regions.py index a3a81e91f75b..fb18be2e9738 100644 --- a/worlds/jakanddaxter/regs/snowy_mountain_regions.py +++ b/worlds/jakanddaxter/regs/snowy_mountain_regions.py @@ -64,7 +64,8 @@ def can_jump_blockers(state: CollectionState, p: int) -> bool: # Troop in ice_skating_rink: cross main_area and fort_exterior. # Troop in fort_exterior: cross main_area and fort_exterior. glacier_lurkers.add_cell_locations([61], access_rule=lambda state: - can_fight(state, player) or can_free_flut_flut(state, player)) + state.has("Red Eco", player) + and (can_fight(state, player) or can_free_flut_flut(state, player))) # Second, a virtual region for the precursor blockers. Unlike the others, this contains orbs: # the total number of orbs that sit on top of the blockers. Yes, there are only 8. @@ -84,7 +85,7 @@ def can_jump_blockers(state: CollectionState, p: int) -> bool: # The scout fly box *can* be broken without YES, so leave it in this region. frozen_box_cave = JakAndDaxterRegion("Frozen Box Cave", player, multiworld, level_name, 12) frozen_box_cave.add_fly_locations([327745], access_rule=lambda state: - state.has("Yellow Eco Switch", player) + state.has_all(("Yellow Eco Switch", "Yellow Eco"), player) or world.can_free_scout_flies(state, player) or can_free_flut_flut(state, player)) @@ -127,8 +128,12 @@ def can_jump_blockers(state: CollectionState, p: int) -> bool: fort_interior = JakAndDaxterRegion("Fort Interior (Main)", player, multiworld, level_name, 19) # Reaching the top of the watch tower, getting the fly with the blue eco, and falling down to get the caches. - fort_interior_caches = JakAndDaxterRegion("Fort Interior (Caches)", player, multiworld, level_name, 51) - fort_interior_caches.add_fly_locations([196673]) + fort_interior_tower = JakAndDaxterRegion("Fort Interior (Tower)", player, multiworld, level_name, 6) + fort_interior_tower.add_fly_locations([196673], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Blue Eco", player)) + + # The caches can only be opened with blue eco. + fort_interior_caches = JakAndDaxterRegion("Fort Interior (Caches)", player, multiworld, level_name, 45) fort_interior_caches.add_cache_locations([23348, 23349, 23350]) # Need higher jump. @@ -164,8 +169,8 @@ def can_jump_blockers(state: CollectionState, p: int) -> bool: can_cross_medium_gap(state, player) or can_free_flut_flut(state, player)) frozen_box_cave.connect(frozen_box_cave_crates, rule=lambda state: # YES to get these crates. - state.has("Yellow Eco Switch", player) # Flut Flut can break boxes as well. - or can_free_flut_flut(state, player)) + state.has_all(("Yellow Eco Switch", "Yellow Eco"), player) + or can_free_flut_flut(state, player)) # Flut Flut can break boxes as well. frozen_box_cave.connect(ice_skating_rink, rule=lambda state: # Same movement to go forward. can_cross_medium_gap(state, player) or can_free_flut_flut(state, player)) @@ -214,10 +219,11 @@ def can_enter_fort_with_flut_flut(_: CollectionState, __: int) -> bool: if options.snowy_mountain_flut_flut_skip: fort_exterior.connect(fort_gate_button) # Zoom walk down. - fort_interior.connect(fort_interior_caches, rule=lambda state: # Just need a little height. + fort_interior.connect(fort_interior_tower, rule=lambda state: # Just need a little height. state.has("Double Jump", player) or state.has_all(("Crouch", "Crouch Jump"), player) or can_free_flut_flut(state, player)) + fort_interior_tower.connect(fort_interior_caches, rule=lambda state: state.has("Blue Eco", 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) @@ -234,6 +240,7 @@ def can_enter_fort_with_flut_flut(_: CollectionState, __: int) -> bool: bunny_cave_start.connect(bunny_cave_end, rule=lambda state: can_free_flut_flut(state, player) or (can_fight(state, player) + and state.has("Red Eco", player) # Not technically required. and (state.has("Double Jump", player) or state.has_all(("Crouch", "Crouch Jump"), player)))) @@ -259,6 +266,7 @@ def can_enter_fort_with_flut_flut(_: CollectionState, __: int) -> bool: world.level_to_regions[level_name].append(bunny_cave_end) world.level_to_regions[level_name].append(switch_cave) world.level_to_regions[level_name].append(fort_interior) + world.level_to_regions[level_name].append(fort_interior_tower) world.level_to_regions[level_name].append(fort_interior_caches) world.level_to_regions[level_name].append(fort_interior_base) world.level_to_regions[level_name].append(fort_interior_course_end) diff --git a/worlds/jakanddaxter/regs/spider_cave_regions.py b/worlds/jakanddaxter/regs/spider_cave_regions.py index 0a51bfd9dc19..ccf5116d7d69 100644 --- a/worlds/jakanddaxter/regs/spider_cave_regions.py +++ b/worlds/jakanddaxter/regs/spider_cave_regions.py @@ -12,9 +12,17 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe player = world.player # A large amount of this area can be covered by single jump, floating platforms, web trampolines, and goggles. - main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 63) - main_area.add_cell_locations([78, 84]) - main_area.add_fly_locations([327765, 393301, 196693, 131157]) + main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 36) + # Yellow eco is required to shoot the lurkers. + main_area.add_cell_locations([78], access_rule=lambda state: state.has("Yellow Eco", player)) + main_area.add_cell_locations([84]) + main_area.add_fly_locations([327765, 393301, 196693, 131157], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Yellow Eco", player)) + + # 6 orbs in crates towards dark cave. + # 6 orbs in crates towards robot cave. + # 15 orbs by shooting the lurkers. + main_area_yellow_eco_orbs = JakAndDaxterRegion("Main Area (Yellow Eco)", player, multiworld, level_name, 27) # This is a virtual region describing what you need to DO to get the Dark Crystal power cell, # rather than describing where each of the crystals ARE, because you can destroy them in any order, @@ -24,8 +32,10 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe # can_fight = The underwater crystal in dark cave. # Roll Jump = The underwater crystal across a long dark eco pool. # The rest of the crystals can be destroyed with yellow eco in main_area. + # TODO - Figure out if yellow eco is actually required dark_crystals.add_cell_locations([79], access_rule=lambda state: - can_fight(state, player) + state.has("Yellow Eco", player) + and can_fight(state, player) and state.has_all(("Roll", "Roll Jump"), player)) dark_cave = JakAndDaxterRegion("Dark Cave", player, multiworld, level_name, 5) @@ -58,7 +68,10 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe spider_tunnel_crates = JakAndDaxterRegion("Spider Tunnel Orb Crates", player, multiworld, level_name, 12) main_area.connect(dark_crystals) - main_area.connect(robot_cave) + main_area.connect(main_area_yellow_eco_orbs, rule=lambda state: state.has("Yellow Eco", player)) + # TODO - Split up robot cave and figure out which eco is required for which collectables. + # To make it easier for now, robot cave is only in-logic once Jak has access to Yellow and Blue Eco. + main_area.connect(robot_cave, rule=lambda state: state.has_all(("Yellow Eco", "Blue Eco"), player)) main_area.connect(dark_cave, rule=lambda state: can_fight(state, player) and (state.has("Double Jump", player) @@ -95,9 +108,11 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe spider_tunnel.connect(main_area) # Escape with jump pad. # Requires yellow eco switch. - spider_tunnel.connect(spider_tunnel_crates, rule=lambda state: state.has("Yellow Eco Switch", player)) + spider_tunnel.connect(spider_tunnel_crates, rule=lambda state: + state.has_all(("Yellow Eco Switch", "Yellow Eco"), player)) world.level_to_regions[level_name].append(main_area) + world.level_to_regions[level_name].append(main_area_yellow_eco_orbs) world.level_to_regions[level_name].append(dark_crystals) world.level_to_regions[level_name].append(dark_cave) world.level_to_regions[level_name].append(robot_cave) diff --git a/worlds/jakanddaxter/regs/volcanic_crater_regions.py b/worlds/jakanddaxter/regs/volcanic_crater_regions.py index 0bf6cf31c24c..be79905b37d9 100644 --- a/worlds/jakanddaxter/regs/volcanic_crater_regions.py +++ b/worlds/jakanddaxter/regs/volcanic_crater_regions.py @@ -23,7 +23,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe # Hidden Power Cell: you can carry yellow eco from Spider Cave just by running and jumping # and using your Goggles to shoot the box (you do not need Punch to shoot from FP mode). - main_area.add_cell_locations([74]) + main_area.add_cell_locations([74], access_rule=lambda state: state.has("Yellow Eco", player)) # No blue eco sources in this area, all boxes must be broken by hand (yellow eco can't be carried far enough). main_area.add_fly_locations(scouts.locVC_scoutTable.keys(), access_rule=lambda state: From aa8061a0cfbe9625f30fe17524933a3f44c40ead Mon Sep 17 00:00:00 2001 From: Mattis Turin-Zelenko Date: Sun, 10 May 2026 20:20:15 +0200 Subject: [PATCH 3/3] Jak1: Eco Randomizer logic fixes * Require yellow eco for lava tube * Add ecorando enabled option to memory reader --- worlds/jakanddaxter/__init__.py | 1 + worlds/jakanddaxter/agents/memory_reader.py | 3 +++ worlds/jakanddaxter/options.py | 10 ++++++++++ worlds/jakanddaxter/regions.py | 5 +++-- worlds/jakanddaxter/regs/lava_tube_regions.py | 18 +++++++++++++----- .../jakanddaxter/regs/misty_island_regions.py | 1 + .../jakanddaxter/regs/rock_village_regions.py | 10 ++++++++-- 7 files changed, 39 insertions(+), 9 deletions(-) diff --git a/worlds/jakanddaxter/__init__.py b/worlds/jakanddaxter/__init__.py index 7e707ff6517c..bfb5802ce99b 100644 --- a/worlds/jakanddaxter/__init__.py +++ b/worlds/jakanddaxter/__init__.py @@ -120,6 +120,7 @@ class JakAndDaxterWebWorld(WebWorld): options.EnableEcoRandomizer, options.GeyserRockDoorSkip, options.KlawwFightSkip, + options.LavaTubeOrangesSkip, ], True), OptionGroup("Tricks & Glitches - Easy", [ options.AttackWithRollJump, # Use Roll Jump instead of regular attacks to hit certain targets. diff --git a/worlds/jakanddaxter/agents/memory_reader.py b/worlds/jakanddaxter/agents/memory_reader.py index 493fbde39e47..b35d3b6ae0ee 100644 --- a/worlds/jakanddaxter/agents/memory_reader.py +++ b/worlds/jakanddaxter/agents/memory_reader.py @@ -111,6 +111,9 @@ def define(self, size: int, length: int = 1) -> int: # Trap information. trap_duration_offset = offsets.define(sizeof_float) +# Eco randomizer information. +ecorando_enabled_offset = offsets.define(sizeof_uint8) + # The End. end_marker_offset = offsets.define(sizeof_uint8, 4) diff --git a/worlds/jakanddaxter/options.py b/worlds/jakanddaxter/options.py index b7f767a7f4fd..d6106ffc3c56 100644 --- a/worlds/jakanddaxter/options.py +++ b/worlds/jakanddaxter/options.py @@ -356,6 +356,15 @@ class KlawwFightSkip(Toggle): *Yellow Eco* unlocked. """ display_name = "Klaww Fight Skip" + +class LavaTubeOrangesSkip(Toggle): + """ + Create an alternative path through Lava Tube. + + Enabling this setting may require Jak to skip the door in the room with the "oranges" in lava tube, without using + *Yellow Eco*. + """ + display_name = "Lava Tube Oranges Skip" # endregion # region Advanced Movement @@ -730,6 +739,7 @@ class JakAndDaxterOptions(PerGameCommonOptions): enable_eco_randomizer: EnableEcoRandomizer geyser_rock_door_skip: GeyserRockDoorSkip klaww_fight_skip: KlawwFightSkip + lava_tube_oranges_skip: LavaTubeOrangesSkip boosted_and_extended_uppercuts: BoostedAndExtendedUppercuts punch_uppercut_scout_flies: PunchUppercutScoutFlies geyser_rock_cliff_climb: GeyserRockCliffClimb diff --git a/worlds/jakanddaxter/regions.py b/worlds/jakanddaxter/regions.py index da84adca7640..6ad2f196cfa2 100644 --- a/worlds/jakanddaxter/regions.py +++ b/worlds/jakanddaxter/regions.py @@ -80,7 +80,7 @@ def create_regions(world: "JakAndDaxterWorld"): sc = spider_cave.build_regions("Spider Cave", world) sm = snowy_mountain.build_regions("Snowy Mountain", world) - lt = lava_tube.build_regions("Lava Tube", world) + lt, lt_end = lava_tube.build_regions("Lava Tube", world) gmc, fb, fd = gol_and_maias_citadel.build_regions("Gol and Maia's Citadel", world) # Configurable counts of cells for connector levels. @@ -108,7 +108,8 @@ def create_regions(world: "JakAndDaxterWorld"): vc.connect(sc) vc.connect(sm, rule=lambda state: state.has("Snowy Mountain Gondola", player)) vc.connect(lt, rule=lambda state: state.has("Power Cell", player, lt_count)) # Normally 72. - lt.connect(gmc) # gmc->fb connection defined internally by GolAndMaiasCitadelRegions. + # lt -> lt_end connection defined internally by LavaTubeRegions. + lt_end.connect(gmc) # gmc->fb connection defined internally by GolAndMaiasCitadelRegions. # Set the completion condition. if options.jak_completion_condition == CompletionCondition.option_cross_fire_canyon: diff --git a/worlds/jakanddaxter/regs/lava_tube_regions.py b/worlds/jakanddaxter/regs/lava_tube_regions.py index c3a4c5b24d3f..822783284a4a 100644 --- a/worlds/jakanddaxter/regs/lava_tube_regions.py +++ b/worlds/jakanddaxter/regs/lava_tube_regions.py @@ -7,18 +7,26 @@ from ..locs import cell_locations as cells, scout_locations as scouts -def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRegion: +def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDaxterRegion, ...]: multiworld = world.multiworld options = world.options player = world.player main_area = JakAndDaxterRegion("Main Area", player, multiworld, level_name, 50) + main_area.add_fly_locations([90, 65626]) - # Everything is accessible by making contact with the zoomer. - main_area.add_cell_locations(cells.locLT_cellTable.keys()) - main_area.add_fly_locations(scouts.locLT_scoutTable.keys()) + behind_oranges_door = JakAndDaxterRegion("Behind Oranges Door", player, multiworld, level_name, 0) + behind_oranges_door.add_cell_locations(cells.locLT_cellTable.keys()) + behind_oranges_door.add_fly_locations([327770, 262234, 131162, 196698, 393306]) + + if options.lava_tube_oranges_skip: + # It's possible to glitch through/around the door in the room with the "oranges", requiring no yellow eco. + main_area.connect(behind_oranges_door) + else: + main_area.connect(behind_oranges_door, rule=lambda state: state.has("Yellow Eco", player)) world.level_to_regions[level_name].append(main_area) + world.level_to_regions[level_name].append(behind_oranges_door) # If Per-Level Orbsanity is enabled, build the special Orbsanity Region. This is a virtual region always # accessible to Main Area. The Locations within are automatically checked when you collect enough orbs. @@ -35,4 +43,4 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe multiworld.regions.append(orbs) main_area.connect(orbs) - return main_area + return main_area, behind_oranges_door diff --git a/worlds/jakanddaxter/regs/misty_island_regions.py b/worlds/jakanddaxter/regs/misty_island_regions.py index 75c1cf52cab4..8f2728ce576a 100644 --- a/worlds/jakanddaxter/regs/misty_island_regions.py +++ b/worlds/jakanddaxter/regs/misty_island_regions.py @@ -129,6 +129,7 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> JakAndDaxterRe lower_approach.connect(upper_approach, rule=lambda state: state.has_all(("Crouch", "Crouch Jump"), player)) # Requires breaking bone bridges. + # Arena door doesn't require blue eco when opened from the inside, and it's always possible to get inside. lower_approach.connect(arena, rule=lambda state: can_fight(state, player)) arena.connect(lower_approach) # Run. diff --git a/worlds/jakanddaxter/regs/rock_village_regions.py b/worlds/jakanddaxter/regs/rock_village_regions.py index 4f6d69026894..928d242afaf9 100644 --- a/worlds/jakanddaxter/regs/rock_village_regions.py +++ b/worlds/jakanddaxter/regs/rock_village_regions.py @@ -34,8 +34,14 @@ def build_regions(level_name: str, world: "JakAndDaxterWorld") -> tuple[JakAndDa # 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, 2) - pontoon_bridge.add_fly_locations([393292], access_rule=lambda state: - world.can_free_scout_flies(state, player) or state.has("Yellow Eco", player)) + if options.boggy_swamp_precise_movement: + # It is possible to run back with the blue eco to the scout fly as well + pontoon_bridge.add_fly_locations([393292], access_rule=lambda state: + world.can_free_scout_flies(state, player) + or state.has_any(("Yellow Eco", "Blue Eco"), player)) + else: + pontoon_bridge.add_fly_locations([393292], access_rule=lambda state: + world.can_free_scout_flies(state, player) or state.has("Yellow Eco", player)) # Orbs that are not directly over the pontoons if Warrior's Pontoons is not unlocked. pontoon_bridge_high_orbs = JakAndDaxterRegion("Pontoon Bridge High Orbs", player, multiworld, level_name, 5)