diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index 6b7da69593..04d107f1ff 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -239,7 +239,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld): multiworld.worlds[item.player].collect(all_state_base, item) pre_fill_items = [] for player in in_dungeon_player_ids: - pre_fill_items += multiworld.worlds[player].get_pre_fill_items() + pre_fill_items += [item for item in multiworld.worlds[player].get_pre_fill_items() if not item.crystal] for item in in_dungeon_items: try: pre_fill_items.remove(item) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 53059c64bc..7b64e6e5d3 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -2,7 +2,7 @@ from collections import namedtuple import logging from BaseClasses import ItemClassification -from Fill import FillError +from Options import OptionError from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType @@ -410,15 +410,16 @@ def generate_itempool(world): pool_count = len(items) new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)] if world.options.shuffle_capacity_upgrades or world.options.bombless_start: - progressive = world.options.progressive - progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on' + progressive = world.options.progressive.want_progressives(world.random) if world.options.shuffle_capacity_upgrades == "on_combined": new_items.append("Bomb Upgrade (50)") elif world.options.shuffle_capacity_upgrades == "on": new_items += ["Bomb Upgrade (+5)"] * 6 new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") - if world.options.shuffle_capacity_upgrades != "on_combined" and world.options.bombless_start: - new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") + if world.options.bombless_start: + new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") + elif world.options.bombless_start: + new_items.append("Bomb Upgrade (+10)") if world.options.shuffle_capacity_upgrades and not world.options.retro_bow: if world.options.shuffle_capacity_upgrades == "on_combined": @@ -466,6 +467,9 @@ def generate_itempool(world): items_were_cut = items_were_cut or cut_item(items, *reduce_item) elif len(reduce_item) == 4: items_were_cut = items_were_cut or condense_items(items, *reduce_item) + if reduce_item[0] == "Piece of Heart" and world.logical_heart_pieces: + world.logical_heart_pieces -= reduce_item[2] + world.logical_heart_containers += reduce_item[3] elif len(reduce_item) == 1: # Bottles bottles = [item for item in items if item.name in item_name_groups["Bottles"]] if len(bottles) > 4: @@ -476,7 +480,7 @@ def generate_itempool(world): if items_were_cut: break else: - raise Exception(f"Failed to limit item pool size for player {player}") + raise OptionError(f"Failed to limit item pool size for player {player}") if len(items) < pool_count: items += removed_filler[len(items) - pool_count:] diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 6a5792d21a..2e0b81a4bd 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1197,8 +1197,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade 0x58, 0x01, 0x36 if local_world.options.retro_bow else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) - 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 - 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 + 0x3E, local_world.logical_heart_containers, 0x47, 0xff, # boss heart -> green 20 + 0x17, local_world.logical_heart_pieces, 0x47, 0xff, # piece of heart -> green 20 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel ]) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index a5b14e0c2d..18e2965d8c 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -20,7 +20,7 @@ from .StateHelpers import (can_extend_magic, can_kill_most_things, has_fire_source, has_hearts, has_melee_weapon, has_misery_mire_medallion, has_sword, has_turtle_rock_medallion, has_triforce_pieces, can_use_bombs, can_bomb_or_bonk, - can_activate_crystal_switch) + can_activate_crystal_switch, can_kill_standard_start) from .UnderworldGlitchRules import underworld_glitches_rules @@ -1093,22 +1093,23 @@ def standard_rules(world, player): if world.worlds[player].options.small_key_shuffle != small_key_shuffle.option_universal: set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1) - and can_kill_most_things(state, player, 2)) + and can_kill_standard_start(state, player, 2)) set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1) - and can_kill_most_things(state, player, 1)) - + and can_kill_standard_start(state, player, 1)) + set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), + lambda state: can_kill_standard_start(state, player, 1)) set_rule(world.get_location('Hyrule Castle - Big Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2)) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) and state.has('Big Key (Hyrule Castle)', player) and (world.worlds[player].options.enemy_health in ("easy", "default") - or can_kill_most_things(state, player, 1))) + or can_kill_standard_start(state, player, 1))) set_rule(world.get_location('Sewers - Key Rat Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) - and can_kill_most_things(state, player, 1)) + and can_kill_standard_start(state, player, 1)) else: set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has('Big Key (Hyrule Castle)', player)) diff --git a/worlds/alttp/StateHelpers.py b/worlds/alttp/StateHelpers.py index 98409c8a8d..2c1b5e2ab8 100644 --- a/worlds/alttp/StateHelpers.py +++ b/worlds/alttp/StateHelpers.py @@ -59,10 +59,11 @@ def has_hearts(state: CollectionState, player: int, count: int) -> int: def heart_count(state: CollectionState, player: int) -> int: # Warning: This only considers items that are marked as advancement items - diff = state.multiworld.worlds[player].difficulty_requirements - return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \ + max_heart_pieces = state.multiworld.worlds[player].logical_heart_pieces + max_heart_containers = state.multiworld.worlds[player].logical_heart_containers + return min(state.count('Boss Heart Container', player), max_heart_containers) \ + state.count('Sanctuary Heart Container', player) \ - + min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \ + + min(state.count('Piece of Heart', player), max_heart_pieces) // 4 \ + 3 # starting hearts @@ -139,6 +140,16 @@ def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) and can_use_bombs(state, player, enemies * 4))) +def can_kill_standard_start(state: CollectionState, player: int, enemies: int = 5) -> bool: + # Enemizer does not randomize standard start enemies + return (has_melee_weapon(state, player) + or state.has('Cane of Somaria', player) + or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player))) + or state.has_any(["Bow", "Progressive Bow"], player) + or state.has('Fire Rod', player) + or can_use_bombs(state, player, enemies)) # Escape assist is set + + def can_get_good_bee(state: CollectionState, player: int) -> bool: cave = state.multiworld.get_region('Good Bee Cave', player) return ( diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 4ee5b9d266..2b99162837 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -305,6 +305,8 @@ class ALTTPWorld(World): self.required_medallions = ["Ether", "Quake"] self.escape_assist = [] self.shops = [] + self.logical_heart_containers = 10 + self.logical_heart_pieces = 24 super(ALTTPWorld, self).__init__(*args, **kwargs) @classmethod @@ -384,6 +386,8 @@ class ALTTPWorld(World): self.options.local_items.value |= self.dungeon_local_item_names self.difficulty_requirements = difficulties[self.options.item_pool.current_key] + self.logical_heart_pieces = self.difficulty_requirements.heart_piece_limit + self.logical_heart_containers = self.difficulty_requirements.boss_heart_container_limit # enforce pre-defined local items. if self.options.goal in ["local_triforce_hunt", "local_ganon_triforce_hunt"]: