From af9b61ad7dd04ba00e724c576fd1646071faa823 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 11 Mar 2026 10:08:54 -0600 Subject: [PATCH 1/4] fix: beemizer + pottery + multiworld generation issue --- Fill.py | 9 +++++++-- Main.py | 2 +- PastReleaseNotes.md | 3 +++ RELEASENOTES.md | 5 ++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Fill.py b/Fill.py index 349ff83e..935dad37 100644 --- a/Fill.py +++ b/Fill.py @@ -608,8 +608,13 @@ def fast_fill_pot_for_multiworld(world, item_pool, fill_locations): flex = 256 - world.data_tables[player].pot_secret_table.multiworld_count fill_count = len(pot_fill_locations[player]) - flex if fill_count > 0: - fill_spots = random.sample(pot_fill_locations[player], fill_count) - fill_items = random.sample(pot_item_pool[player], fill_count) + first_count = min(fill_count, len(pot_item_pool[player])) + fill_items = random.sample(pot_item_pool[player], first_count) + remaining = fill_count - first_count + if remaining > 0: + other_items = [i for i in item_pool if i.player == player and i not in pot_item_pool[player]] + fill_items += random.sample(other_items, min(remaining, len(other_items))) + fill_spots = random.sample(pot_fill_locations[player], len(fill_items)) for x in fill_items: item_pool.remove(x) for x in fill_spots: diff --git a/Main.py b/Main.py index 3e3a3952..d6925fbd 100644 --- a/Main.py +++ b/Main.py @@ -38,7 +38,7 @@ from source.enemizer.DamageTables import DamageTable from source.enemizer.Enemizer import randomize_enemies from source.rom.DataTables import init_data_tables -version_number = '1.5.5' +version_number = '1.5.6' version_branch = '-u' __version__ = f'{version_number}{version_branch}' diff --git a/PastReleaseNotes.md b/PastReleaseNotes.md index e2915b60..d7984bec 100644 --- a/PastReleaseNotes.md +++ b/PastReleaseNotes.md @@ -10,6 +10,9 @@ # Patch Notes Changelog archive +* 1.5.5 + * Logic: Fixed an issue where PoD Bridge lead to Arena Main instead of Arena Landing Area. (Potentially unnecessarily requiring bombs or Somaria to progress) + * HUD: Key counters are correct even when door shuffle is off * 1.5.4 * Documentation: New AI-assisted documentation [Site](https://aerinon.github.io/ALttPDoorRandomizer) * Generation Error: Fixed Issue with Shop Code and Take Any Caves (thanks Codemann for assistance) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 86d2c0f2..0a098615 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,7 +1,6 @@ # Patch Notes -* 1.5.5 - * Logic: Fixed an issue where PoD Bridge lead to Arena Main instead of Arena Landing Area. (Potentially unnecessarily requiring bombs or Somaria to progress) - * HUD: Key counters are correct even when door shuffle is off +* 1.5.6 + * Multiworld: Fixed a generation crash when beemizer is active and pottery is enabled. Pot locations are now properly filled with same-player items when the MW limit is hit even when beemizer has replaced native pot items. From b2b775e243f41244eef11f8b8a54f3a10549d42f Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 11 Mar 2026 15:22:11 -0600 Subject: [PATCH 2/4] feat: item_pool_adjust added to customizer --- ItemList.py | 47 ++++++++++++++++++++++++-------- RELEASENOTES.md | 1 + docs/Customizer.md | 24 ++++++++++++++-- docs/customizer_example.yaml | 5 ++++ source/classes/CustomSettings.py | 5 ++++ 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/ItemList.py b/ItemList.py index 32da56cc..ecff7555 100644 --- a/ItemList.py +++ b/ItemList.py @@ -287,6 +287,42 @@ def generate_itempool(world, player): for _ in range(0, amt): pool.append('Rupees (20)') + if world.shopsanity[player] and not skip_pool_adjustments: + for shop in world.shops[player]: + if shop.region.name in shop_to_location_table: + for index, slot in enumerate(shop.inventory): + if slot: + item = slot['item'] + if shop.region.name == 'Capacity Upgrade' and world.difficulty[player] != 'normal': + pool.append('Rupees (20)') + else: + pool.append(item) + + if (world.customizer and world.customizer.get_item_pool_adjust() + and player in world.customizer.get_item_pool_adjust()): + diff = difficulties[world.difficulty[player]] + for item_name, delta in world.customizer.get_item_pool_adjust()[player].items(): + if not isinstance(delta, int): + continue + if delta > 0: + if item_name == 'Bottle (Random)': + for _ in range(delta): + pool.append(random.choice(diff.bottles)) + else: + pool.extend([item_name] * delta) + elif delta < 0: + remove_count = abs(delta) + if item_name == 'Bottle (Random)': + bottle_names = set(diff.bottles) + for _ in range(remove_count): + bottle = next((x for x in pool if x in bottle_names), None) + if bottle: + pool.remove(bottle) + else: + for _ in range(remove_count): + if item_name in pool: + pool.remove(item_name) + if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: # In HMG force swamp smalls in pots to allow getting out of swamp palace placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' @@ -329,17 +365,6 @@ def generate_itempool(world, player): world.get_location(location, player).event = True world.get_location(location, player).locked = True - if world.shopsanity[player] and not skip_pool_adjustments: - for shop in world.shops[player]: - if shop.region.name in shop_to_location_table: - for index, slot in enumerate(shop.inventory): - if slot: - item = slot['item'] - if shop.region.name == 'Capacity Upgrade' and world.difficulty[player] != 'normal': - pool.append('Rupees (20)') - else: - pool.append(item) - items = ItemFactory(pool, player) if world.shopsanity[player]: for potion in ['Green Potion', 'Blue Potion', 'Red Potion']: diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0a098615..af6ac06b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,5 +2,6 @@ * 1.5.6 * Multiworld: Fixed a generation crash when beemizer is active and pottery is enabled. Pot locations are now properly filled with same-player items when the MW limit is hit even when beemizer has replaced native pot items. + * Customizer: Added `item_pool_adjust` section to apply additive/subtractive deltas to the base item pool rather than replacing it entirely. diff --git a/docs/Customizer.md b/docs/Customizer.md index 2c76da96..b8b7af28 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -64,10 +64,30 @@ Then each player can have the entire item pool defined. The name of item should `Bottle (Random)` is supported to randomize bottle contents according to those allowed by difficulty. Pendants and crystals are supported here. -##### Caveat - +**Note**: `item_pool` is a **full replacement** of the base item pool. Every item the player will receive must be listed. If you only want to tweak the default pool, use `item_pool_adjust` instead. + +##### Caveat + Dungeon items amount can be increased (but not decreased as the minimum of each dungeon item is either pre-determined or calculated by door rando) if the type of dungeon item is not shuffled then it is attempted to be placed in the dungeon. Extra item beyond dungeon capacity not be confined to the dungeon. +### item_pool_adjust + +This must be defined by player. Each player number should be listed with the appropriate adjustments. + +Unlike `item_pool`, this section **adjusts** the base item pool generated by the randomizer settings rather than replacing it. Use positive values to add items and negative values to remove items. + +```yaml +item_pool_adjust: + 1: + Bottle (Random): 2 # add 2 extra random bottles + Rupees (300): 1 # add 1 extra rupee pack + Boss Heart Container: -3 # remove 3 heart containers from the base pool +``` + +`Bottle (Random)` follows the same bottle randomization rules as in `item_pool`. When removing bottles with `Bottle (Random): -N`, any bottle (regardless of contents) will be removed. + +Removals that exceed the number of that item currently in the pool are silently ignored. Item pool adjustments are applied after beemizer but before the standard-mode Link's Uncle weapon selection, so weapons added here are eligible for that placement. + ### placements This must be defined by player. Each player number should be listed with the appropriate placement list. diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index 82f3fb7b..17c6f90c 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -60,6 +60,11 @@ item_pool: Green Potion: 1 Blue Potion: 1 Red Potion: 1 +item_pool_adjust: + 1: + Bottle (Random): 2 # add 2 extra random bottles on top of the base pool + Magic Upgrade (1/2): 1 # add 1 extra half-magic + Boss Heart Container: -3 # remove 3 heart containers from the base pool placements: 1: Palace of Darkness - Big Chest: Hammer diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index a9ef34d9..80b25d44 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -202,6 +202,11 @@ class CustomSettings(object): return self.file_source['item_pool'] return None + def get_item_pool_adjust(self): + if 'item_pool_adjust' in self.file_source: + return self.file_source['item_pool_adjust'] + return None + def get_placements(self): if 'placements' in self.file_source: return self.file_source['placements'] From a4d7d632355ee453b7251a3bd85b065cf3617a9a Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 11 Mar 2026 15:29:09 -0600 Subject: [PATCH 3/4] fix: disable pikit drops due to vanilla steal failure bug --- RELEASENOTES.md | 1 + source/dungeon/EnemyList.py | 2 +- source/enemizer/SpriteSheets.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index af6ac06b..2aa01541 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,5 +3,6 @@ * 1.5.6 * Multiworld: Fixed a generation crash when beemizer is active and pottery is enabled. Pot locations are now properly filled with same-player items when the MW limit is hit even when beemizer has replaced native pot items. * Customizer: Added `item_pool_adjust` section to apply additive/subtractive deltas to the base item pool rather than replacing it entirely. + * Enemy Drops: Pikit are no longer eligible for dropped items due to a vanilla bug where a failed steal can overwrite the assigned drop. diff --git a/source/dungeon/EnemyList.py b/source/dungeon/EnemyList.py index 2ce45a26..896c17b1 100644 --- a/source/dungeon/EnemyList.py +++ b/source/dungeon/EnemyList.py @@ -449,7 +449,7 @@ def init_enemy_stats(): EnemySprite.Stalfos: EnemyStats(EnemySprite.Stalfos, False, True, 6, health=4, dmg=1), EnemySprite.GreenZirro: EnemyStats(EnemySprite.GreenZirro, False, False, 1, health=4, dmg=5, dmask=0x80), EnemySprite.BlueZirro: EnemyStats(EnemySprite.BlueZirro, False, False, 7, health=8, dmg=3, dmask=0x80), - EnemySprite.Pikit: EnemyStats(EnemySprite.Pikit, False, True, 2, health=12, dmg=5), + EnemySprite.Pikit: EnemyStats(EnemySprite.Pikit, False, False, 2, health=12, dmg=5), EnemySprite.OldMan: EnemyStats(EnemySprite.OldMan, True, dmg=0), EnemySprite.PipeDown: EnemyStats(EnemySprite.PipeDown, True, dmg=0), diff --git a/source/enemizer/SpriteSheets.py b/source/enemizer/SpriteSheets.py index 7de739f3..e2abaa7d 100644 --- a/source/enemizer/SpriteSheets.py +++ b/source/enemizer/SpriteSheets.py @@ -329,7 +329,7 @@ def init_sprite_requirements(): SpriteRequirement(EnemySprite.Stalfos).sub_group(0, 0x1f), SpriteRequirement(EnemySprite.GreenZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms), SpriteRequirement(EnemySprite.BlueZirro).no_drop().sub_group(3, 0x1b).exclude(NoFlyingRooms), - SpriteRequirement(EnemySprite.Pikit).sub_group(3, 0x1b), + SpriteRequirement(EnemySprite.Pikit).no_drop().sub_group(3, 0x1b), SpriteRequirement(EnemySprite.CrystalMaiden).affix(), SpriteRequirement(EnemySprite.OldMan).affix().sub_group(2, 0x1c), SpriteRequirement(EnemySprite.PipeDown).affix(), From 4da73127a224ec86e762c1f838d8d9c3f9735845 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 12 Mar 2026 12:59:58 -0600 Subject: [PATCH 4/4] fix: reworked spawn refills for standard + enemy drop --- RELEASENOTES.md | 7 ++++--- Rom.py | 30 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2aa01541..a43e0725 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,8 +1,9 @@ # Patch Notes * 1.5.6 - * Multiworld: Fixed a generation crash when beemizer is active and pottery is enabled. Pot locations are now properly filled with same-player items when the MW limit is hit even when beemizer has replaced native pot items. - * Customizer: Added `item_pool_adjust` section to apply additive/subtractive deltas to the base item pool rather than replacing it entirely. * Enemy Drops: Pikit are no longer eligible for dropped items due to a vanilla bug where a failed steal can overwrite the assigned drop. - + * Standard Mode: Reworked spawn refills to be more generous for enemy drop modes. + * Customizer: Added `item_pool_adjust` section to apply additive/subtractive deltas to the base item pool rather than replacing it entirely. + * Multiworld: Fixed a generation crash when beemizer is active and pottery is enabled. Pot locations are now properly filled with same-player items when the MW limit is hit even when beemizer has replaced native pot items. + diff --git a/Rom.py b/Rom.py index 6d52e6c8..4a88b1f5 100644 --- a/Rom.py +++ b/Rom.py @@ -1348,38 +1348,38 @@ def patch_rom(world, rom, player, team, is_mystery=False): rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) bow_max, bomb_max, magic_max = 0, 0, 0 - bow_small, magic_small = 0, 0 + bow_small, bomb_small, magic_small = 10, 3, 0x20 if world.mode[player] == 'standard': if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']: rom.write_byte(0x18004E, 1) # Escape Fill (arrows) write_int16(rom, 0x180183, 300) # Escape fill rupee bow rom.write_bytes(0x180185, [0, 0, 70]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows) - bow_max, bow_small = 70, 10 + rom.write_bytes(0x180188, [0, 0, 70]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0, 0, 70]) # Mantle respawn refills (magic, bombs, arrows) + bow_max = 70 elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']: rom.write_byte(0x18004E, 2) # Escape Fill (bombs) rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0, 3, 0]) # Mantle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180188, [0, 50, 0]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0, 50, 0]) # Mantle respawn refills (magic, bombs, arrows) bomb_max = 50 elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']: rom.write_byte(0x18004E, 4) # Escape Fill (magic) rom.write_bytes(0x180185, [0x80, 0, 0]) # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [0x20, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [0x20, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) - magic_max, magic_small = 0x80, 0x20 + rom.write_bytes(0x180188, [0x80, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [0x80, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) + magic_max = 0x80 if world.doorShuffle[player] not in ['vanilla', 'basic']: # Uncle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) + rom.write_bytes(0x180185, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x180188, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) + rom.write_bytes(0x180188, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)]) # Mantle respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) + rom.write_bytes(0x18018B, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)]) elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest - rom.write_bytes(0x180185, [max(0x00, magic_max), max(3, bomb_max), max(0, bow_max)]) - rom.write_bytes(0x180188, [magic_small, 3, bow_small]) # Zelda respawn refills (magic, bombs, arrows) - rom.write_bytes(0x18018B, [magic_small, 3, bow_small]) # Mantle respawn refills (magic, bombs, arrows) + rom.write_bytes(0x180185, [max(magic_small, magic_max), max(bomb_small, bomb_max), max(bow_small, bow_max)]) + rom.write_bytes(0x180188, [magic_small, max(bomb_small, bomb_max), bow_small]) # Zelda respawn refills (magic, bombs, arrows) + rom.write_bytes(0x18018B, [magic_small, max(bomb_small, bomb_max), bow_small]) # Mantle respawn refills (magic, bombs, arrows) # patch swamp: Need to enable permanent drain of water as dam or swamp were moved rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)