OoT bug fixes (#955)

* OoT: fix shop patching crash due to Item changes

* OoT: more informative failure in triforce piece replacement

* OoT: in triforce hunt, remove ganon BK from pool and lock the door

* OoT: no longer store trap information on the item
This commit is contained in:
espeon65536 2022-08-30 11:54:40 -07:00 committed by GitHub
parent 2a7babce68
commit a753905ee4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 22 deletions

View File

@ -1388,6 +1388,10 @@ def get_pool_core(world):
remove_junk_pool = list(remove_junk_pool) + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)', 'Ice Trap']
junk_candidates = [item for item in pool if item in remove_junk_pool]
if len(pending_junk_pool) > len(junk_candidates):
excess = len(pending_junk_pool) - len(junk_candidates)
if world.triforce_hunt:
raise RuntimeError(f"Items in the pool for player {world.player} exceed locations. Add {excess} location(s) or remove {excess} triforce piece(s).")
while pending_junk_pool:
pending_item = pending_junk_pool.pop()
if not junk_candidates:

View File

@ -49,7 +49,6 @@ class OOTItem(Item):
self.type = type
self.index = index
self.special = special or {}
self.looks_like_item = None
self.price = special.get('price', None) if special else None
self.internal = False

View File

@ -158,12 +158,12 @@ class TriforceGoal(Range):
"""Number of Triforce pieces required to complete the game."""
display_name = "Required Triforce Pieces"
range_start = 1
range_end = 100
range_end = 80
default = 20
class ExtraTriforces(Range):
"""Percentage of additional Triforce pieces in the pool, separate from the item pool setting."""
"""Percentage of additional Triforce pieces in the pool. With high numbers, you may need to randomize additional locations to have enough items."""
display_name = "Percentage of Extra Triforce Pieces"
range_start = 0
range_end = 100

View File

@ -1844,7 +1844,7 @@ def write_rom_item(rom, item_id, item):
def get_override_table(world):
return list(filter(lambda val: val != None, map(partial(get_override_entry, world.player), world.world.get_filled_locations(world.player))))
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.world.get_filled_locations(world.player))))
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
@ -1852,10 +1852,10 @@ def get_override_table_bytes(override_table):
return b''.join(sorted(itertools.starmap(override_struct.pack, override_table)))
def get_override_entry(player_id, location):
def get_override_entry(ootworld, location):
scene = location.scene
default = location.default
player_id = 0 if player_id == location.item.player else min(location.item.player, 255)
player_id = 0 if ootworld.player == location.item.player else min(location.item.player, 255)
if location.item.game != 'Ocarina of Time':
# This is an AP sendable. It's guaranteed to not be None.
if location.item.advancement:
@ -1869,7 +1869,7 @@ def get_override_entry(player_id, location):
if location.item.trap:
item_id = 0x7C # Ice Trap ID, to get "X is a fool" message
looks_like_item_id = location.item.looks_like_item.index
looks_like_item_id = ootworld.trap_appearances[location.address].index
else:
looks_like_item_id = 0
@ -2091,7 +2091,8 @@ def get_locked_doors(rom, world):
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
# If boss door, set the door's unlock flag
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A):
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (
world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A and not world.triforce_hunt):
if actor_id == 0x002E and actor_type == 0x05:
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
@ -2109,23 +2110,20 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
rom.write_int16(location.address1, location.item.index)
else:
if location.item.trap:
item_display = location.item.looks_like_item
elif location.item.game != "Ocarina of Time":
item_display = location.item
if location.item.advancement:
item_display.index = 0xCB
else:
item_display.index = 0xCC
item_display.special = {}
item_display = world.trap_appearances[location.address]
else:
item_display = location.item
# bottles in shops should look like empty bottles
# so that that are different than normal shop refils
if 'shop_object' in item_display.special:
rom_item = read_rom_item(rom, item_display.special['shop_object'])
if location.item.trap or location.item.game == "Ocarina of Time":
if 'shop_object' in item_display.special:
rom_item = read_rom_item(rom, item_display.special['shop_object'])
else:
rom_item = read_rom_item(rom, item_display.index)
else:
rom_item = read_rom_item(rom, item_display.index)
display_index = 0xCB if location.item.advancement else 0xCC
rom_item = read_rom_item(rom, display_index)
shop_objs.add(rom_item['object_id'])
shop_id = world.current_shop_id

View File

@ -178,6 +178,10 @@ class OOTWorld(World):
if self.skip_child_zelda:
self.shuffle_weird_egg = False
# Ganon boss key should not be in itempool in triforce hunt
if self.triforce_hunt:
self.shuffle_ganon_bosskey = 'remove'
# Determine skipped trials in GT
# This needs to be done before the logic rules in GT are parsed
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
@ -803,9 +807,10 @@ class OOTWorld(World):
with i_o_limiter:
# Make traps appear as other random items
ice_traps = [loc.item for loc in self.get_locations() if loc.item.trap]
for trap in ice_traps:
trap.looks_like_item = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
self.trap_appearances = {}
for loc_id in trap_location_ids:
self.trap_appearances[loc_id] = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
# Seed hint RNG, used for ganon text lines also
self.hint_rng = self.world.slot_seeds[self.player]