From a753905ee4536e4c28ef25dd0b230fe03fa683b1 Mon Sep 17 00:00:00 2001 From: espeon65536 <81029175+espeon65536@users.noreply.github.com> Date: Tue, 30 Aug 2022 11:54:40 -0700 Subject: [PATCH] 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 --- worlds/oot/ItemPool.py | 4 ++++ worlds/oot/Items.py | 1 - worlds/oot/Options.py | 4 ++-- worlds/oot/Patches.py | 30 ++++++++++++++---------------- worlds/oot/__init__.py | 11 ++++++++--- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/worlds/oot/ItemPool.py b/worlds/oot/ItemPool.py index 301c502a..12c9c262 100644 --- a/worlds/oot/ItemPool.py +++ b/worlds/oot/ItemPool.py @@ -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: diff --git a/worlds/oot/Items.py b/worlds/oot/Items.py index 31e6c31f..06164091 100644 --- a/worlds/oot/Items.py +++ b/worlds/oot/Items.py @@ -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 diff --git a/worlds/oot/Options.py b/worlds/oot/Options.py index 50b6c26c..ea9a8160 100644 --- a/worlds/oot/Options.py +++ b/worlds/oot/Options.py @@ -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 diff --git a/worlds/oot/Patches.py b/worlds/oot/Patches.py index 7bf31c4f..322d2d83 100644 --- a/worlds/oot/Patches.py +++ b/worlds/oot/Patches.py @@ -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 diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index b4635ad7..a9b7d5a1 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -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]