Merged in DR v1.5.6

This commit is contained in:
codemann8
2026-03-14 08:53:07 -05:00
11 changed files with 105 additions and 37 deletions

View File

@@ -722,8 +722,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:

View File

@@ -302,6 +302,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'] \
and world.keyshuffle[player] not in ['none', 'nearby']:
# In HMG force swamp smalls in pots to allow getting out of swamp palace
@@ -346,17 +382,6 @@ def generate_itempool(world, player):
loc.event = True
loc.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']:

View File

@@ -41,7 +41,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}'

View File

@@ -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)

View File

@@ -141,8 +141,10 @@ These are now independent of retro mode and have three options: None, Random, an
# 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
* 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.

30
Rom.py
View File

@@ -1639,38 +1639,38 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
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)

View File

@@ -68,10 +68,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.
### goals
This must be defined by player. Each player number should be listed with the appropriate custom goals. This section has four primary subsections for each of the current supported events:

View File

@@ -67,6 +67,14 @@ item_pool:
Sanctuary Heart Container: 3
Shovel: 3
Single Arrow: 1
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

View File

@@ -245,6 +245,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']

View File

@@ -450,7 +450,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),

View File

@@ -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(),