Merge pull request #141 from espeon65536/oot

Ocarina of Time updates
This commit is contained in:
Fabian Dill 2021-11-25 17:57:31 +00:00 committed by GitHub
commit 81397936ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 9647 additions and 9061 deletions

View File

@ -494,7 +494,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
handle_option(ret, game_weights, option_key, option)
if "items" in plando_options:
ret.plando_items = roll_item_plando(world_type, game_weights)
if ret.game == "Minecraft":
if ret.game == "Minecraft" or ret.game == "Ocarina of Time":
# bad hardcoded behavior to make this work for now
ret.plando_connections = []
if "connections" in plando_options:
@ -504,7 +504,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
ret.plando_connections.append(PlandoConnection(
get_choice("entrance", placement),
get_choice("exit", placement),
get_choice("direction", placement, "both")
get_choice("direction", placement)
))
elif ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights, plando_options)

237
OoTAdjuster.py Normal file
View File

@ -0,0 +1,237 @@
import tkinter as tk
import argparse
import logging
import random
import os
from itertools import chain
from BaseClasses import MultiWorld
from Options import Choice, Range, Toggle
from worlds.oot import OOTWorld
from worlds.oot.Cosmetics import patch_cosmetics
from worlds.oot.Options import cosmetic_options, sfx_options
from worlds.oot.Rom import Rom, compress_rom_file
from worlds.oot.N64Patch import apply_patch_file
from Utils import local_path
logger = logging.getLogger('OoTAdjuster')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--rom', default='',
help='Path to an OoT randomized ROM to adjust.')
parser.add_argument('--vanilla_rom', default='',
help='Path to a vanilla OoT ROM for patching.')
for name, option in chain(cosmetic_options.items(), sfx_options.items()):
parser.add_argument('--'+name, default=None,
help=option.__doc__)
parser.add_argument('--is_glitched', default=False, action='store_true',
help='Setting this to true will enable protection on kokiri tunic colors for weirdshot.')
parser.add_argument('--deathlink',
help='Enable DeathLink system', action='store_true')
args = parser.parse_args()
if not os.path.isfile(args.rom):
adjustGUI()
else:
adjust(args)
def adjustGUI():
from tkinter import Tk, LEFT, BOTTOM, TOP, E, W, \
StringVar, IntVar, Checkbutton, Frame, Label, X, Entry, Button, \
OptionMenu, filedialog, messagebox, ttk
from argparse import Namespace
from Main import __version__ as MWVersion
window = tk.Tk()
window.wm_title(f"Archipelago {MWVersion} OoT Adjuster")
set_icon(window)
opts = Namespace()
# Select ROM
romDialogFrame = Frame(window)
romLabel = Label(romDialogFrame, text='Rom/patch to adjust')
vanillaLabel = Label(romDialogFrame, text='OoT Base Rom')
opts.rom = StringVar()
opts.vanilla_rom = StringVar(value="The Legend of Zelda - Ocarina of Time.z64")
romEntry = Entry(romDialogFrame, textvariable=opts.rom)
vanillaEntry = Entry(romDialogFrame, textvariable=opts.vanilla_rom)
def RomSelect():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".z64", ".n64", ".apz5")), ("All Files", "*")])
opts.rom.set(rom)
def VanillaSelect():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".z64", ".n64")), ("All Files", "*")])
opts.vanilla_rom.set(rom)
romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect)
vanillaSelectButton = Button(romDialogFrame, text='Select Rom', command=VanillaSelect)
romDialogFrame.pack(side=TOP, expand=True, fill=X)
romLabel.pack(side=LEFT)
romEntry.pack(side=LEFT, expand=True, fill=X)
romSelectButton.pack(side=LEFT)
vanillaLabel.pack(side=LEFT)
vanillaEntry.pack(side=LEFT, expand=True, fill=X)
vanillaSelectButton.pack(side=LEFT)
# Cosmetic options
romSettingsFrame = Frame(window)
def dropdown_option(type, option_name, row, column):
if type == 'cosmetic':
option = cosmetic_options[option_name]
elif type == 'sfx':
option = sfx_options[option_name]
optionFrame = Frame(romSettingsFrame)
optionFrame.grid(row=row, column=column, sticky=E)
optionLabel = Label(optionFrame, text=option.displayname)
optionLabel.pack(side=LEFT)
setattr(opts, option_name, StringVar())
getattr(opts, option_name).set(option.name_lookup[option.default])
optionMenu = OptionMenu(optionFrame, getattr(opts, option_name), *option.name_lookup.values())
optionMenu.pack(side=LEFT)
dropdown_option('cosmetic', 'default_targeting', 0, 0)
dropdown_option('cosmetic', 'display_dpad', 0, 1)
dropdown_option('cosmetic', 'correct_model_colors', 0, 2)
dropdown_option('cosmetic', 'background_music', 1, 0)
dropdown_option('cosmetic', 'fanfares', 1, 1)
dropdown_option('cosmetic', 'ocarina_fanfares', 1, 2)
dropdown_option('cosmetic', 'kokiri_color', 2, 0)
dropdown_option('cosmetic', 'goron_color', 2, 1)
dropdown_option('cosmetic', 'zora_color', 2, 2)
dropdown_option('cosmetic', 'silver_gauntlets_color', 3, 0)
dropdown_option('cosmetic', 'golden_gauntlets_color', 3, 1)
dropdown_option('cosmetic', 'mirror_shield_frame_color', 3, 2)
dropdown_option('cosmetic', 'navi_color_default_inner', 4, 0)
dropdown_option('cosmetic', 'navi_color_default_outer', 4, 1)
dropdown_option('cosmetic', 'navi_color_enemy_inner', 5, 0)
dropdown_option('cosmetic', 'navi_color_enemy_outer', 5, 1)
dropdown_option('cosmetic', 'navi_color_npc_inner', 6, 0)
dropdown_option('cosmetic', 'navi_color_npc_outer', 6, 1)
dropdown_option('cosmetic', 'navi_color_prop_inner', 7, 0)
dropdown_option('cosmetic', 'navi_color_prop_outer', 7, 1)
# sword_trail_duration, 8, 2
dropdown_option('cosmetic', 'sword_trail_color_inner', 8, 0)
dropdown_option('cosmetic', 'sword_trail_color_outer', 8, 1)
dropdown_option('cosmetic', 'bombchu_trail_color_inner', 9, 0)
dropdown_option('cosmetic', 'bombchu_trail_color_outer', 9, 1)
dropdown_option('cosmetic', 'boomerang_trail_color_inner', 10, 0)
dropdown_option('cosmetic', 'boomerang_trail_color_outer', 10, 1)
dropdown_option('cosmetic', 'heart_color', 11, 0)
dropdown_option('cosmetic', 'magic_color', 12, 0)
dropdown_option('cosmetic', 'a_button_color', 11, 1)
dropdown_option('cosmetic', 'b_button_color', 11, 2)
dropdown_option('cosmetic', 'c_button_color', 12, 1)
dropdown_option('cosmetic', 'start_button_color', 12, 2)
dropdown_option('sfx', 'sfx_navi_overworld', 14, 0)
dropdown_option('sfx', 'sfx_navi_enemy', 14, 1)
dropdown_option('sfx', 'sfx_low_hp', 14, 2)
dropdown_option('sfx', 'sfx_menu_cursor', 15, 0)
dropdown_option('sfx', 'sfx_menu_select', 15, 1)
dropdown_option('sfx', 'sfx_nightfall', 15, 2)
dropdown_option('sfx', 'sfx_horse_neigh', 16, 0)
dropdown_option('sfx', 'sfx_hover_boots', 16, 1)
dropdown_option('sfx', 'sfx_ocarina', 16, 2)
# Special cases
# Sword trail duration is a range
option = cosmetic_options['sword_trail_duration']
optionFrame = Frame(romSettingsFrame)
optionFrame.grid(row=8, column=2, sticky=E)
optionLabel = Label(optionFrame, text=option.displayname)
optionLabel.pack(side=LEFT)
setattr(opts, 'sword_trail_duration', StringVar())
getattr(opts, 'sword_trail_duration').set(option.default)
optionMenu = OptionMenu(optionFrame, getattr(opts, 'sword_trail_duration'), *range(4, 21))
optionMenu.pack(side=LEFT)
# Glitched is a checkbox
opts.is_glitched = IntVar(value=0)
glitched_checkbox = Checkbutton(romSettingsFrame, text="Glitched Logic?", variable=opts.is_glitched)
glitched_checkbox.grid(row=17, column=0, sticky=W)
# Deathlink is a checkbox
opts.deathlink = IntVar(value=0)
deathlink_checkbox = Checkbutton(romSettingsFrame, text="DeathLink (Team Deaths)", variable=opts.deathlink)
deathlink_checkbox.grid(row=17, column=1, sticky=W)
romSettingsFrame.pack(side=TOP)
def adjustRom():
try:
guiargs = Namespace()
options = vars(opts)
for o in options:
result = options[o].get()
if result == 'true':
result = True
if result == 'false':
result = False
setattr(guiargs, o, result)
guiargs.sword_trail_duration = int(guiargs.sword_trail_duration)
path = adjust(guiargs)
except Exception as e:
logging.exception(e)
messagebox.showerror(title="Error while adjusting Rom", message=str(e))
else:
messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}")
# Adjust button
bottomFrame = Frame(window)
adjustButton = Button(bottomFrame, text='Adjust Rom', command=adjustRom)
adjustButton.pack(side=BOTTOM, padx=(5, 5))
bottomFrame.pack(side=BOTTOM, pady=(5, 5))
window.mainloop()
def set_icon(window):
logo = tk.PhotoImage(file=local_path('data', 'icon.png'))
window.tk.call('wm', 'iconphoto', window._w, logo)
def adjust(args):
# Create a fake world and OOTWorld to use as a base
world = MultiWorld(1)
world.slot_seeds = {1: random}
ootworld = OOTWorld(world, 1)
# Set options in the fake OOTWorld
for name, option in chain(cosmetic_options.items(), sfx_options.items()):
result = getattr(args, name, None)
if result is None:
if issubclass(option, Choice):
result = option.name_lookup[option.default]
elif issubclass(option, Range) or issubclass(option, Toggle):
result = option.default
else:
raise Exception("Unsupported option type")
setattr(ootworld, name, result)
ootworld.logic_rules = 'glitched' if args.is_glitched else 'glitchless'
ootworld.death_link = args.deathlink
if os.path.splitext(args.rom)[-1] in ['.z64', '.n64']:
# Load up the ROM
rom = Rom(file=args.rom, force_use=True)
elif os.path.splitext(args.rom)[-1] == '.apz5':
# Load vanilla ROM
rom = Rom(file=args.vanilla_rom, force_use=True)
# Patch file
apply_patch_file(rom, args.rom)
else:
raise Exception("Invalid file extension; requires .n64, .z64, .apz5")
# Call patch_cosmetics
patch_cosmetics(ootworld, rom)
rom.write_byte(rom.sym('DEATH_LINK'), args.deathlink)
# Output new file
path_pieces = os.path.splitext(args.rom)
decomp_path = path_pieces[0] + '-adjusted-decomp.n64'
comp_path = path_pieces[0] + '-adjusted.n64'
rom.write_to_file(decomp_path)
compress_rom_file(decomp_path, comp_path)
os.remove(decomp_path)
return comp_path
if __name__ == '__main__':
main()

View File

@ -61,6 +61,7 @@ Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Se
Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
Name: "client/factorio"; Description: "Factorio"; Types: full playing
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
Name: "client/oot"; Description: "Ocarina of Time Adjuster"; Types: full playing
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
[Dirs]
@ -82,6 +83,7 @@ Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: igno
Source: "{#sourcepath}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp
Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft
Source: "{#sourcepath}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
;minecraft temp files

View File

@ -61,6 +61,7 @@ Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Se
Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
Name: "client/factorio"; Description: "Factorio"; Types: full playing
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
Name: "client/oot"; Description: "Ocarina of Time Adjuster"; Types: full playing
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
[Dirs]
@ -82,6 +83,7 @@ Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: igno
Source: "{#sourcepath}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp
Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft
Source: "{#sourcepath}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
;minecraft temp files

View File

@ -80,6 +80,8 @@ scripts = {
"FactorioClient.py": ("ArchipelagoFactorioClient", True, icon),
# Minecraft
"MinecraftClient.py": ("ArchipelagoMinecraftClient", False, mcicon),
# Ocarina of Time
"OoTAdjuster.py": ("ArchipelagoOoTAdjuster", True, icon),
}
exes = []

View File

@ -1,6 +1,6 @@
class Dungeon(object):
def __init__(self, world, name, hint, boss_key, small_keys, dungeon_items):
def __init__(self, world, name, hint, font_color, boss_key, small_keys, dungeon_items):
def to_array(obj):
if obj == None:
return []
@ -12,6 +12,7 @@ class Dungeon(object):
self.world = world
self.name = name
self.hint_text = hint
self.font_color = font_color
self.regions = []
self.boss_key = to_array(boss_key)
self.small_keys = to_array(small_keys)
@ -28,7 +29,7 @@ class Dungeon(object):
new_small_keys = [item.copy(new_world) for item in self.small_keys]
new_dungeon_items = [item.copy(new_world) for item in self.dungeon_items]
new_dungeon = Dungeon(new_world, self.name, self.hint, new_boss_key, new_small_keys, new_dungeon_items)
new_dungeon = Dungeon(new_world, self.name, self.hint_text, self.font_color, new_boss_key, new_small_keys, new_dungeon_items)
return new_dungeon

View File

@ -7,6 +7,8 @@ from .Utils import data_path
dungeon_table = [
{
'name': 'Deku Tree',
'hint': 'the Deku Tree',
'font_color': 'Green',
'boss_key': 0,
'small_key': 0,
'small_key_mq': 0,
@ -15,6 +17,7 @@ dungeon_table = [
{
'name': 'Dodongos Cavern',
'hint': 'Dodongo\'s Cavern',
'font_color': 'Red',
'boss_key': 0,
'small_key': 0,
'small_key_mq': 0,
@ -23,6 +26,7 @@ dungeon_table = [
{
'name': 'Jabu Jabus Belly',
'hint': 'Jabu Jabu\'s Belly',
'font_color': 'Blue',
'boss_key': 0,
'small_key': 0,
'small_key_mq': 0,
@ -30,6 +34,8 @@ dungeon_table = [
},
{
'name': 'Forest Temple',
'hint': 'the Forest Temple',
'font_color': 'Green',
'boss_key': 1,
'small_key': 5,
'small_key_mq': 6,
@ -37,6 +43,8 @@ dungeon_table = [
},
{
'name': 'Bottom of the Well',
'hint': 'the Bottom of the Well',
'font_color': 'Pink',
'boss_key': 0,
'small_key': 3,
'small_key_mq': 2,
@ -44,6 +52,8 @@ dungeon_table = [
},
{
'name': 'Fire Temple',
'hint': 'the Fire Temple',
'font_color': 'Red',
'boss_key': 1,
'small_key': 8,
'small_key_mq': 5,
@ -51,6 +61,8 @@ dungeon_table = [
},
{
'name': 'Ice Cavern',
'hint': 'the Ice Cavern',
'font_color': 'Blue',
'boss_key': 0,
'small_key': 0,
'small_key_mq': 0,
@ -58,6 +70,8 @@ dungeon_table = [
},
{
'name': 'Water Temple',
'hint': 'the Water Temple',
'font_color': 'Blue',
'boss_key': 1,
'small_key': 6,
'small_key_mq': 2,
@ -65,6 +79,8 @@ dungeon_table = [
},
{
'name': 'Shadow Temple',
'hint': 'the Shadow Temple',
'font_color': 'Pink',
'boss_key': 1,
'small_key': 5,
'small_key_mq': 6,
@ -72,6 +88,8 @@ dungeon_table = [
},
{
'name': 'Gerudo Training Grounds',
'hint': 'the Gerudo Training Grounds',
'font_color': 'Yellow',
'boss_key': 0,
'small_key': 9,
'small_key_mq': 3,
@ -79,6 +97,8 @@ dungeon_table = [
},
{
'name': 'Spirit Temple',
'hint': 'the Spirit Temple',
'font_color': 'Yellow',
'boss_key': 1,
'small_key': 5,
'small_key_mq': 7,
@ -100,6 +120,7 @@ def create_dungeons(ootworld):
for dungeon_info in dungeon_table:
name = dungeon_info['name']
hint = dungeon_info['hint'] if 'hint' in dungeon_info else name
font_color = dungeon_info['font_color'] if 'font_color' in dungeon_info else 'White'
if ootworld.logic_rules == 'glitchless':
if not ootworld.dungeon_mq[name]:
@ -125,5 +146,5 @@ def create_dungeons(ootworld):
for item in dungeon_items:
item.priority = True
ootworld.dungeons.append(Dungeon(ootworld, name, hint, boss_keys, small_keys, dungeon_items))
ootworld.dungeons.append(Dungeon(ootworld, name, hint, font_color, boss_keys, small_keys, dungeon_items))

View File

@ -1,7 +1,7 @@
from itertools import chain
import logging
from worlds.generic.Rules import set_rule
from worlds.generic.Rules import set_rule, add_rule
from .Hints import get_hint_area, HintAreaNotFound
from .Regions import TimeOfDay
@ -29,12 +29,13 @@ def assume_entrance_pool(entrance_pool, ootworld):
assumed_pool = []
for entrance in entrance_pool:
assumed_forward = entrance.assume_reachable()
if entrance.reverse != None:
if entrance.reverse != None and not ootworld.decouple_entrances:
assumed_return = entrance.reverse.assume_reachable()
if (entrance.type in ('Dungeon', 'Grotto', 'Grave') and entrance.reverse.name != 'Spirit Temple Lobby -> Desert Colossus From Spirit Lobby') or \
(entrance.type == 'Interior' and ootworld.shuffle_special_interior_entrances):
# In most cases, Dungeon, Grotto/Grave and Simple Interior exits shouldn't be assumed able to give access to their parent region
set_rule(assumed_return, lambda state, **kwargs: False)
if not (ootworld.mix_entrance_pools != 'off' and (ootworld.shuffle_overworld_entrances or ootworld.shuffle_special_interior_entrances)):
if (entrance.type in ('Dungeon', 'Grotto', 'Grave') and entrance.reverse.name != 'Spirit Temple Lobby -> Desert Colossus From Spirit Lobby') or \
(entrance.type == 'Interior' and ootworld.shuffle_special_interior_entrances):
# In most cases, Dungeon, Grotto/Grave and Simple Interior exits shouldn't be assumed able to give access to their parent region
set_rule(assumed_return, lambda state, **kwargs: False)
assumed_forward.bind_two_way(assumed_return)
assumed_pool.append(assumed_forward)
return assumed_pool
@ -308,6 +309,8 @@ entrance_shuffle_table = [
('Overworld', ('ZD Behind King Zora -> Zoras Fountain', { 'index': 0x0225 }),
('Zoras Fountain -> ZD Behind King Zora', { 'index': 0x01A1 })),
('Overworld', ('GV Lower Stream -> Lake Hylia', { 'index': 0x0219 })),
('OwlDrop', ('LH Owl Flight -> Hyrule Field', { 'index': 0x027E, 'addresses': [0xAC9F26] })),
('OwlDrop', ('DMT Owl Flight -> Kak Impas Rooftop', { 'index': 0x0554, 'addresses': [0xAC9EF2] })),
@ -376,15 +379,24 @@ def shuffle_random_entrances(ootworld):
entrance_pools['Dungeon'] = ootworld.get_shufflable_entrances(type='Dungeon', only_primary=True)
if ootworld.open_forest == 'closed':
entrance_pools['Dungeon'].remove(world.get_entrance('KF Outside Deku Tree -> Deku Tree Lobby', player))
if ootworld.decouple_entrances:
entrance_pools['DungeonReverse'] = [entrance.reverse for entrance in entrance_pools['Dungeon']]
if ootworld.shuffle_interior_entrances != 'off':
entrance_pools['Interior'] = ootworld.get_shufflable_entrances(type='Interior', only_primary=True)
if ootworld.shuffle_special_interior_entrances:
entrance_pools['Interior'] += ootworld.get_shufflable_entrances(type='SpecialInterior', only_primary=True)
if ootworld.decouple_entrances:
entrance_pools['InteriorReverse'] = [entrance.reverse for entrance in entrance_pools['Interior']]
if ootworld.shuffle_grotto_entrances:
entrance_pools['GrottoGrave'] = ootworld.get_shufflable_entrances(type='Grotto', only_primary=True)
entrance_pools['GrottoGrave'] += ootworld.get_shufflable_entrances(type='Grave', only_primary=True)
if ootworld.decouple_entrances:
entrance_pools['GrottoGraveReverse'] = [entrance.reverse for entrance in entrance_pools['GrottoGrave']]
if ootworld.shuffle_overworld_entrances:
entrance_pools['Overworld'] = ootworld.get_shufflable_entrances(type='Overworld')
exclude_overworld_reverse = ootworld.mix_entrance_pools == 'all' and not ootworld.decouple_entrances
entrance_pools['Overworld'] = ootworld.get_shufflable_entrances(type='Overworld', only_primary=exclude_overworld_reverse)
if not ootworld.decouple_entrances:
entrance_pools['Overworld'].remove(world.get_entrance('GV Lower Stream -> Lake Hylia', player))
# Mark shuffled entrances
for entrance in chain(chain.from_iterable(one_way_entrance_pools.values()), chain.from_iterable(entrance_pools.values())):
@ -392,6 +404,16 @@ def shuffle_random_entrances(ootworld):
if entrance.reverse:
entrance.reverse.shuffled = True
# Combine all entrance pools if mixing
if ootworld.mix_entrance_pools == 'all':
entrance_pools = {'Mixed': list(chain.from_iterable(entrance_pools.values()))}
elif ootworld.mix_entrance_pools == 'indoor':
if ootworld.shuffle_overworld_entrances:
ow_pool = entrance_pools['Overworld']
entrance_pools = {'Mixed': list(filter(lambda entrance: entrance.type != 'Overworld', chain.from_iterable(entrance_pools.values())))}
if ootworld.shuffle_overworld_entrances:
entrance_pools['Overworld'] = ow_pool
# Build target entrance pools
one_way_target_entrance_pools = {}
for pool_type, entrance_pool in one_way_entrance_pools.items():
@ -403,7 +425,9 @@ def shuffle_random_entrances(ootworld):
elif pool_type in {'Spawn', 'WarpSong'}:
valid_target_types = ('Spawn', 'WarpSong', 'OwlDrop', 'Overworld', 'Interior', 'SpecialInterior', 'Extra')
one_way_target_entrance_pools[pool_type] = build_one_way_targets(ootworld, valid_target_types)
# Ensure that the last entrance doesn't assume the rest of the targets are reachable?
# Ensure that the last entrance doesn't assume the rest of the targets are reachable
for target in one_way_target_entrance_pools[pool_type]:
add_rule(target, (lambda entrances=entrance_pool: (lambda state: any(entrance.connected_region == None for entrance in entrances)))())
# Disconnect one-way entrances for priority placement
for entrance in chain.from_iterable(one_way_entrance_pools.values()):
entrance.disconnect()
@ -419,7 +443,52 @@ def shuffle_random_entrances(ootworld):
if item_tuple[1] == player:
none_state.prog_items[item_tuple] = 0
# Plando entrances?
# Plando entrances
if world.plando_connections[player]:
rollbacks = []
all_targets = {**one_way_target_entrance_pools, **target_entrance_pools}
for conn in world.plando_connections[player]:
try:
entrance = ootworld.get_entrance(conn.entrance)
exit = ootworld.get_entrance(conn.exit)
if entrance is None:
raise EntranceShuffleError(f"Could not find entrance to plando: {conn.entrance}")
if exit is None:
raise EntranceShuffleError(f"Could not find entrance to plando: {conn.exit}")
target_region = exit.name.split(' -> ')[1]
target_parent = exit.parent_region.name
pool_type = entrance.type
matched_targets_to_region = list(filter(lambda target: target.connected_region and target.connected_region.name == target_region,
all_targets[pool_type]))
target = next(filter(lambda target: target.replaces.parent_region.name == target_parent, matched_targets_to_region))
replace_entrance(ootworld, entrance, target, rollbacks, locations_to_ensure_reachable, all_state, none_state)
if conn.direction == 'both' and entrance.reverse and ootworld.decouple_entrances:
replace_entrance(ootworld, entrance.reverse, target.reverse, rollbacks, locations_to_ensure_reachable, all_state, none_state)
except EntranceShuffleError as e:
raise RuntimeError(f"Failed to plando OoT entrances. Reason: {e}")
except StopIteration:
raise RuntimeError(f"Could not find entrance to plando: {conn.entrance} => {conn.exit}")
finally:
for (entrance, target) in rollbacks:
confirm_replacement(entrance, target)
# Check placed one way entrances and trim.
# The placed entrances are already pointing at their new regions.
placed_entrances = [entrance for entrance in chain.from_iterable(one_way_entrance_pools.values())
if entrance.replaces is not None]
replaced_entrances = [entrance.replaces for entrance in placed_entrances]
# Remove replaced entrances so we don't place two in one target.
for remaining_target in chain.from_iterable(one_way_target_entrance_pools.values()):
if remaining_target.replaces and remaining_target.replaces in replaced_entrances:
delete_target_entrance(remaining_target)
# Remove priority targets if any placed entrances point at their region(s).
for key, (regions, _) in priority_entrance_table.items():
if key in one_way_priorities:
for entrance in placed_entrances:
if entrance.connected_region and entrance.connected_region.name in regions:
del one_way_priorities[key]
break
# Place priority entrances
shuffle_one_way_priority_entrances(ootworld, one_way_priorities, one_way_entrance_pools, one_way_target_entrance_pools, locations_to_ensure_reachable, all_state, none_state, retry_count=2)
@ -619,24 +688,25 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
time_travel_state.collect(ootworld.create_item('Time Travel'), event=True)
time_travel_state._oot_update_age_reachable_regions(player)
# For various reasons, we don't want the player to end up through certain entrances as the wrong age
# Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age
# This means we need to hard check that none of the relevant entrances are ever reachable as that age
# This is mostly relevant when shuffling special interiors (such as windmill or kak potion shop)
# Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
CHILD_FORBIDDEN = ['OGC Great Fairy Fountain -> Castle Grounds', 'GV Carpenter Tent -> GV Fortress Side']
ADULT_FORBIDDEN = ['HC Great Fairy Fountain -> Castle Grounds', 'HC Storms Grotto -> Castle Grounds']
for entrance in ootworld.get_shufflable_entrances():
if entrance.shuffled and entrance.replaces:
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential child access')
if entrance.replaces.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.replaces.reverse]):
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
else:
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
raise EntranceShuffleError(f'{entrance.name} potentially accessible as child')
if entrance.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.reverse]):
raise EntranceShuffleError(f'{entrance.name} potentially accessible as adult')
if not ootworld.decouple_entrances:
for entrance in ootworld.get_shufflable_entrances():
if entrance.shuffled and entrance.replaces:
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential child access')
if entrance.replaces.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.replaces.reverse]):
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
else:
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
raise EntranceShuffleError(f'{entrance.name} potentially accessible as child')
if entrance.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.reverse]):
raise EntranceShuffleError(f'{entrance.name} potentially accessible as adult')
# Check if all locations are reachable if not beatable-only or game is not yet complete
if locations_to_ensure_reachable:
@ -645,7 +715,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
if not all_state.can_reach(loc, 'Location', player):
raise EntranceShuffleError(f'{loc} is unreachable')
if ootworld.shuffle_interior_entrances and (entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']):
if ootworld.shuffle_interior_entrances and (ootworld.misc_hints or ootworld.hints != 'none') and \
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']):
# Ensure Kak Potion Shop entrances are in the same hint area so there is no ambiguity as to which entrance is used for hints
potion_front_entrance = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
potion_back_entrance = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
@ -733,14 +804,14 @@ def get_entrance_replacing(region, entrance_name, player):
def change_connections(entrance, target):
entrance.connect(target.disconnect())
entrance.replaces = target.replaces
if entrance.reverse:
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
target.replaces.reverse.connect(entrance.reverse.assumed.disconnect())
target.replaces.reverse.replaces = entrance.reverse
def restore_connections(entrance, target):
target.connect(entrance.disconnect())
entrance.replaces = None
if entrance.reverse:
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
entrance.reverse.assumed.connect(target.replaces.reverse.disconnect())
target.replaces.reverse.replaces = None
@ -757,7 +828,7 @@ def check_entrances_compatibility(entrance, target, rollbacks):
def confirm_replacement(entrance, target):
delete_target_entrance(target)
logging.getLogger('').debug(f'Connected {entrance} to {entrance.connected_region}')
if entrance.reverse:
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
replaced_reverse = target.replaces.reverse
delete_target_entrance(entrance.reverse.assumed)
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')

View File

@ -11,7 +11,7 @@ import json
from enum import Enum
from .HintList import getHint, getHintGroup, Hint, hintExclusions
from .Messages import update_message_by_id
from .Messages import COLOR_MAP, update_message_by_id
from .TextBox import line_wrap
from .Utils import data_path, read_json
@ -266,17 +266,6 @@ def getSimpleHintNoPrefix(item):
def colorText(gossip_text):
colorMap = {
'White': '\x40',
'Red': '\x41',
'Green': '\x42',
'Blue': '\x43',
'Light Blue': '\x44',
'Pink': '\x45',
'Yellow': '\x46',
'Black': '\x47',
}
text = gossip_text.text
colors = list(gossip_text.colors) if gossip_text.colors is not None else []
color = 'White'
@ -292,7 +281,7 @@ def colorText(gossip_text):
splitText[1] = splitText[1][len(prefix):]
break
splitText[1] = '\x05' + colorMap[color] + splitText[1] + '\x05\x40'
splitText[1] = '\x05' + COLOR_MAP[color] + splitText[1] + '\x05\x40'
text = ''.join(splitText)
return text
@ -649,9 +638,9 @@ def buildWorldGossipHints(world, checkedLocations=None):
if checkedLocations is None:
checkedLocations = {player: set() for player in world.world.player_ids}
# If Ganondorf can be reached without Light Arrows, add to checkedLocations to prevent extra hinting
# If Ganondorf hints Light Arrows and is reachable without them, add to checkedLocations to prevent extra hinting
# Can only be forced with vanilla bridge or trials
if world.bridge != 'vanilla' and world.trials == 0:
if world.bridge != 'vanilla' and world.trials == 0 and world.misc_hints:
try:
light_arrow_location = world.world.find_item("Light Arrows", world.player)
checkedLocations[light_arrow_location.player].add(light_arrow_location.name)

View File

@ -1329,9 +1329,10 @@ def get_pool_core(world):
# We can resolve this by starting with some extra keys
if world.dungeon_mq['Spirit Temple']:
# Yes somehow you need 3 keys. This dungeon is bonkers
world.world.push_precollected(world.create_item('Small Key (Spirit Temple)'))
world.world.push_precollected(world.create_item('Small Key (Spirit Temple)'))
world.world.push_precollected(world.create_item('Small Key (Spirit Temple)'))
items = [world.create_item('Small Key (Spirit Temple)') for i in range(3)]
for item in items:
world.world.push_precollected(item)
world.remove_from_start_inventory.append(item.name)
#if not world.dungeon_mq['Fire Temple']:
# world.state.collect(ItemFactory('Small Key (Fire Temple)'))
if world.shuffle_bosskeys == 'vanilla':

View File

@ -1,5 +1,6 @@
# text details: https://wiki.cloudmodding.com/oot/Text_Format
import logging
import random
from .TextBox import line_wrap
@ -316,6 +317,17 @@ KEYSANITY_MESSAGES = {
0x00A9: "\x13\x77\x08You found a \x05\x41Small Key\x05\x40\x01for the \x05\x45Shadow Temple\x05\x40!\x09",
}
COLOR_MAP = {
'White': '\x40',
'Red': '\x41',
'Green': '\x42',
'Blue': '\x43',
'Light Blue': '\x44',
'Pink': '\x45',
'Yellow': '\x46',
'Black': '\x47',
}
MISC_MESSAGES = {
0x507B: (bytearray(
b"\x08I tell you, I saw him!\x04" \
@ -995,3 +1007,30 @@ def shuffle_messages(messages, except_hints=True, always_allow_skip=True):
]))
return permutation
# Update warp song text boxes for ER
def update_warp_song_text(messages, ootworld):
msg_list = {
0x088D: 'Minuet of Forest Warp -> Sacred Forest Meadow',
0x088E: 'Bolero of Fire Warp -> DMC Central Local',
0x088F: 'Serenade of Water Warp -> Lake Hylia',
0x0890: 'Requiem of Spirit Warp -> Desert Colossus',
0x0891: 'Nocturne of Shadow Warp -> Graveyard Warp Pad Region',
0x0892: 'Prelude of Light Warp -> Temple of Time',
}
for id, entr in msg_list.items():
destination = ootworld.world.get_entrance(entr, ootworld.player).connected_region
if destination.pretty_name:
destination_name = destination.pretty_name
elif destination.hint_text:
destination_name = destination.hint_text
elif destination.dungeon:
destination_name = destination.dungeon.hint
else:
destination_name = destination.name
color = COLOR_MAP[destination.font_color or 'White']
new_msg = f"\x08\x05{color}Warp to {destination_name}?\x05\40\x09\x01\x01\x1b\x05{color}OK\x01No\x05\40"
update_message_by_id(messages, id, new_msg)

View File

@ -96,6 +96,7 @@ class StartingAge(Choice):
class InteriorEntrances(Choice):
"""Shuffles interior entrances. "Simple" shuffles houses and Great Fairies; "All" includes Windmill, Link's House, Temple of Time, and Kak potion shop."""
displayname = "Shuffle Interior Entrances"
option_off = 0
option_simple = 1
option_all = 2
@ -105,26 +106,46 @@ class InteriorEntrances(Choice):
class GrottoEntrances(Toggle):
"""Shuffles grotto and grave entrances."""
displayname = "Shuffle Grotto/Grave Entrances"
class DungeonEntrances(Toggle):
"""Shuffles dungeon entrances, excluding Ganon's Castle. Opens Deku, Fire and BotW to both ages."""
displayname = "Shuffle Dungeon Entrances"
class OverworldEntrances(Toggle):
"""Shuffles overworld loading zones."""
displayname = "Shuffle Overworld Entrances"
class OwlDrops(Toggle):
"""Randomizes owl drops from Lake Hylia or Death Mountain Trail as child."""
displayname = "Randomize Owl Drops"
class WarpSongs(Toggle):
"""Randomizes warp song destinations."""
displayname = "Randomize Warp Songs"
class SpawnPositions(Toggle):
"""Randomizes the starting position on loading a save. Consistent between savewarps."""
displayname = "Randomize Spawn Positions"
class MixEntrancePools(Choice):
"""Shuffles entrances into a mixed pool instead of separate ones. "indoor" keeps overworld entrances separate; "all" mixes them in."""
displayname = "Mix Entrance Pools"
option_off = 0
option_indoor = 1
option_all = 2
alias_false = 0
class DecoupleEntrances(Toggle):
"""Decouple entrances when shuffling them. Also adds the one-way entrance from Gerudo Valley to Lake Hylia if overworld is shuffled."""
displayname = "Decouple Entrances"
class TriforceHunt(Toggle):
@ -170,6 +191,8 @@ world_options: typing.Dict[str, type(Option)] = {
"owl_drops": OwlDrops,
"warp_songs": WarpSongs,
"spawn_positions": SpawnPositions,
"mix_entrance_pools": MixEntrancePools,
"decouple_entrances": DecoupleEntrances,
"triforce_hunt": TriforceHunt,
"triforce_goal": TriforceGoal,
"extra_triforce_percentage": ExtraTriforces,
@ -540,6 +563,11 @@ class Hints(Choice):
alias_false = 0
class MiscHints(DefaultOnToggle):
"""Controls whether the Temple of Time altar gives dungeon prize info and whether Ganondorf hints the Light Arrows."""
displayname = "Misc Hints"
class HintDistribution(Choice):
"""Choose the hint distribution to use. Affects the frequency of strong hints, which items are always hinted, etc."""
displayname = "Hint Distribution"
@ -607,6 +635,7 @@ class RupeeStart(Toggle):
misc_options: typing.Dict[str, type(Option)] = {
"correct_chest_sizes": CSMC,
"hints": Hints,
"misc_hints": MiscHints,
"hint_dist": HintDistribution,
"text_shuffle": TextShuffle,
"damage_multiplier": DamageMultiplier,

View File

@ -9,7 +9,7 @@ from .LocationList import business_scrubs
from .Hints import writeGossipStoneHints, buildAltarHints, \
buildGanonText, getSimpleHintNoPrefix
from .Utils import data_path
from .Messages import read_messages, update_message_by_id, read_shop_items, \
from .Messages import read_messages, update_message_by_id, read_shop_items, update_warp_song_text, \
write_shop_items, remove_unused_messages, make_player_message, \
add_item_messages, repack_messages, shuffle_messages, \
get_message_by_id
@ -1007,6 +1007,12 @@ def patch_rom(world, rom):
# Archipelago forces this item to be local so it can always be given to the player. Usually it's a song so it's no problem.
item = world.get_location('Song from Impa').item
save_context.give_raw_item(item.name)
if item.name == 'Slingshot':
save_context.give_raw_item("Deku Seeds (30)")
elif item.name == 'Bow':
save_context.give_raw_item("Arrows (30)")
elif item.name == 'Bomb Bag':
save_context.give_raw_item("Bombs (20)")
save_context.write_bits(0x0ED7, 0x04) # "Obtained Malon's Item"
save_context.write_bits(0x0ED7, 0x08) # "Woke Talon in castle"
save_context.write_bits(0x0ED7, 0x10) # "Talon has fled castle"
@ -1634,7 +1640,7 @@ def patch_rom(world, rom):
rom.write_int16(chest_address + 2, 0x0190) # X pos
rom.write_int16(chest_address + 6, 0xFABC) # Z pos
else:
if location.item.advancement:
if not location.item.advancement:
rom.write_int16(chest_address + 2, 0x0190) # X pos
rom.write_int16(chest_address + 6, 0xFABC) # Z pos
@ -1650,7 +1656,7 @@ def patch_rom(world, rom):
rom.write_int16(chest_address_0 + 6, 0x0172) # Z pos
rom.write_int16(chest_address_2 + 6, 0x0172) # Z pos
else:
if location.item.advancement:
if not location.item.advancement:
rom.write_int16(chest_address_0 + 6, 0x0172) # Z pos
rom.write_int16(chest_address_2 + 6, 0x0172) # Z pos
@ -1741,6 +1747,10 @@ def patch_rom(world, rom):
elif world.text_shuffle == 'complete':
permutation = shuffle_messages(messages, except_hints=False)
# If Warp Song ER is on, update text boxes
if world.warp_songs:
update_warp_song_text(messages, world)
repack_messages(rom, messages, permutation)
# output a text dump, for testing...

View File

@ -38,6 +38,8 @@ class OOTRegion(Region):
self.provides_time = TimeOfDay.NONE
self.scene = None
self.dungeon = None
self.pretty_name = None
self.font_color = None
def get_scene(self):
if self.scene:

View File

@ -17,7 +17,7 @@ double_cache_prevention = threading.Lock()
class Rom(BigStream):
original = None
def __init__(self, file=None):
def __init__(self, file=None, force_use=False):
super().__init__([])
self.changed_address = {}
@ -34,22 +34,25 @@ class Rom(BigStream):
self.symbols = {name: int(addr, 16) for name, addr in symbols.items()}
# If decompressed file already exists, read from it
if os.path.exists(decomp_file):
file = decomp_file
if not force_use:
if os.path.exists(decomp_file):
file = decomp_file
if file == '':
# if not specified, try to read from the previously decompressed rom
file = decomp_file
try:
if file == '':
# if not specified, try to read from the previously decompressed rom
file = decomp_file
try:
self.read_rom(file)
except FileNotFoundError:
# could not find the decompressed rom either
raise FileNotFoundError('Must specify path to base ROM')
else:
self.read_rom(file)
except FileNotFoundError:
# could not find the decompressed rom either
raise FileNotFoundError('Must specify path to base ROM')
else:
self.read_rom(file)
# decompress rom, or check if it's already decompressed
self.decompress_rom_file(file, decomp_file)
self.decompress_rom_file(file, decomp_file, force_use)
# Add file to maximum size
self.buffer.extend(bytearray([0x00] * (0x4000000 - len(self.buffer))))
@ -69,7 +72,7 @@ class Rom(BigStream):
new_rom.force_patch = copy.copy(self.force_patch)
return new_rom
def decompress_rom_file(self, file, decomp_file):
def decompress_rom_file(self, file, decomp_file, skip_crc_check):
validCRC = [
[0xEC, 0x70, 0x11, 0xB7, 0x76, 0x16, 0xD7, 0x2B], # Compressed
[0x70, 0xEC, 0xB7, 0x11, 0x16, 0x76, 0x2B, 0xD7], # Byteswap compressed
@ -79,7 +82,7 @@ class Rom(BigStream):
# Validate ROM file
file_name = os.path.splitext(file)
romCRC = list(self.buffer[0x10:0x18])
if romCRC not in validCRC:
if romCRC not in validCRC and not skip_crc_check:
# Bad CRC validation
raise RuntimeError('ROM file %s is not a valid OoT 1.0 US ROM.' % file)
elif len(self.buffer) < 0x2000000 or len(self.buffer) > (0x4000000) or file_name[1].lower() not in ['.z64',

View File

@ -4,7 +4,7 @@ import subprocess
import Utils
from functools import lru_cache
__version__ = Utils.__version__ + ' f.LUM'
__version__ = '6.1.0 f.LUM'
def data_path(*args):

View File

@ -191,7 +191,6 @@ class OOTWorld(World):
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
# Hint stuff
self.misc_hints = True # this is just always on
self.clearer_hints = True # this is being enforced since non-oot items do not have non-clear hint text
self.gossip_hints = {}
self.required_locations = []
@ -276,6 +275,10 @@ class OOTWorld(World):
for region in region_json:
new_region = OOTRegion(region['region_name'], RegionType.Generic, None, self.player)
new_region.world = self.world
if 'pretty_name' in region:
new_region.pretty_name = region['pretty_name']
if 'font_color' in region:
new_region.font_color = region['font_color']
if 'scene' in region:
new_region.scene = region['scene']
if 'hint' in region:
@ -513,20 +516,6 @@ class OOTWorld(World):
else:
break
# Write entrances to spoiler log
all_entrances = self.get_shuffled_entrances()
all_entrances.sort(key=lambda x: x.name)
all_entrances.sort(key=lambda x: x.type)
for loadzone in all_entrances:
if loadzone.primary:
entrance = loadzone
else:
entrance = loadzone.reverse
if entrance.reverse is not None:
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'both', self.player)
else:
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
set_rules(self)
set_entrances_based_rules(self)
@ -790,6 +779,24 @@ class OOTWorld(World):
create_patch_file(rom, output_path(output_directory, outfile_name + '.apz5'))
rom.restore()
# Write entrances to spoiler log
all_entrances = self.get_shuffled_entrances()
all_entrances.sort(key=lambda x: x.name)
all_entrances.sort(key=lambda x: x.type)
if not self.decouple_entrances:
for loadzone in all_entrances:
if loadzone.primary:
entrance = loadzone
else:
entrance = loadzone.reverse
if entrance.reverse is not None:
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'both', self.player)
else:
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
else:
for entrance in all_entrances:
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
@classmethod
def stage_generate_output(cls, world: MultiWorld, output_directory: str):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"AUDIO_THREAD_INFO": "03482FC0",
"AUDIO_THREAD_INFO_MEM_SIZE": "03482FDC",
"AUDIO_THREAD_INFO_MEM_START": "03482FD8",
"AUDIO_THREAD_MEM_START": "0348EF50",
"AUDIO_THREAD_MEM_START": "0348EF80",
"BOMBCHUS_IN_LOGIC": "03480CBC",
"CFG_A_BUTTON_COLOR": "03480854",
"CFG_A_NOTE_COLOR": "03480872",
@ -38,7 +38,7 @@
"CFG_TEXT_CURSOR_COLOR": "03480866",
"CHAIN_HBA_REWARDS": "03483950",
"CHEST_SIZE_MATCH_CONTENTS": "034826F0",
"COMPLETE_MASK_QUEST": "0348B1D1",
"COMPLETE_MASK_QUEST": "0348B201",
"COOP_CONTEXT": "03480020",
"COOP_VERSION": "03480020",
"COSMETIC_CONTEXT": "03480844",
@ -47,13 +47,13 @@
"DEATH_LINK": "0348002A",
"DEBUG_OFFSET": "034828A0",
"DISABLE_TIMERS": "03480CDC",
"DPAD_TEXTURE": "0348D750",
"DPAD_TEXTURE": "0348D780",
"DUNGEONS_SHUFFLED": "03480CDE",
"EXTENDED_OBJECT_TABLE": "03480C9C",
"EXTERN_DAMAGE_MULTIPLYER": "03482CB1",
"FAST_BUNNY_HOOD_ENABLED": "03480CE0",
"FAST_CHESTS": "03480CD6",
"FONT_TEXTURE": "0348C288",
"FONT_TEXTURE": "0348C2B8",
"FREE_SCARECROW_ENABLED": "03480CCC",
"GET_CHEST_OVERRIDE_COLOR_WRAPPER": "03482720",
"GET_CHEST_OVERRIDE_SIZE_WRAPPER": "034826F4",
@ -69,17 +69,17 @@
"LACS_CONDITION_COUNT": "03480CD2",
"MALON_GAVE_ICETRAP": "0348368C",
"MALON_TEXT_ID": "03480CDB",
"MAX_RUPEES": "0348B1D3",
"MAX_RUPEES": "0348B203",
"MOVED_ADULT_KING_ZORA": "03482FFC",
"NO_ESCAPE_SEQUENCE": "0348B19C",
"NO_ESCAPE_SEQUENCE": "0348B1CC",
"NO_FOG_STATE": "03480CDD",
"OCARINAS_SHUFFLED": "03480CD5",
"OPEN_KAKARIKO": "0348B1D2",
"OPEN_KAKARIKO": "0348B202",
"OUTGOING_ITEM": "03480030",
"OUTGOING_KEY": "0348002C",
"OUTGOING_PLAYER": "03480032",
"OVERWORLD_SHUFFLED": "03480CDF",
"PAYLOAD_END": "0348EF50",
"PAYLOAD_END": "0348EF80",
"PAYLOAD_START": "03480000",
"PLAYED_WARP_SONG": "03481210",
"PLAYER_ID": "03480024",
@ -97,88 +97,88 @@
"SPEED_MULTIPLIER": "03482760",
"START_TWINROVA_FIGHT": "0348307C",
"TIME_TRAVEL_SAVED_EQUIPS": "03481A64",
"TRIFORCE_ICON_TEXTURE": "0348DF50",
"TRIFORCE_ICON_TEXTURE": "0348DF80",
"TWINROVA_ACTION_TIMER": "03483080",
"WINDMILL_SONG_ID": "03480CD9",
"WINDMILL_TEXT_ID": "03480CDA",
"a_button": "0348B160",
"a_note_b": "0348B14C",
"a_note_font_glow_base": "0348B134",
"a_note_font_glow_max": "0348B130",
"a_note_g": "0348B150",
"a_note_glow_base": "0348B13C",
"a_note_glow_max": "0348B138",
"a_note_r": "0348B154",
"active_item_action_id": "0348B1B4",
"active_item_fast_chest": "0348B1A4",
"active_item_graphic_id": "0348B1A8",
"active_item_object_id": "0348B1AC",
"active_item_row": "0348B1B8",
"active_item_text_id": "0348B1B0",
"active_override": "0348B1C0",
"active_override_is_outgoing": "0348B1BC",
"b_button": "0348B15C",
"beating_dd": "0348B168",
"beating_no_dd": "0348B170",
"c_button": "0348B158",
"c_note_b": "0348B140",
"c_note_font_glow_base": "0348B124",
"c_note_font_glow_max": "0348B120",
"c_note_g": "0348B144",
"c_note_glow_base": "0348B12C",
"c_note_glow_max": "0348B128",
"c_note_r": "0348B148",
"cfg_dungeon_info_enable": "0348B0EC",
"cfg_dungeon_info_mq_enable": "0348B190",
"cfg_dungeon_info_mq_need_map": "0348B18C",
"cfg_dungeon_info_reward_enable": "0348B0E8",
"cfg_dungeon_info_reward_need_altar": "0348B184",
"cfg_dungeon_info_reward_need_compass": "0348B188",
"cfg_dungeon_is_mq": "0348B1F0",
"cfg_dungeon_rewards": "03489EE4",
"cfg_file_select_hash": "0348B198",
"cfg_item_overrides": "0348B244",
"defaultDDHeart": "0348B174",
"defaultHeart": "0348B17C",
"dpad_sprite": "0348A058",
"dummy_actor": "0348B1C8",
"dungeon_count": "0348B0F0",
"dungeons": "03489F08",
"empty_dlist": "0348B108",
"extern_ctxt": "03489FA4",
"font_sprite": "0348A068",
"freecam_modes": "03489C60",
"hash_sprites": "0348B0FC",
"hash_symbols": "03489FB8",
"heap_next": "0348B1EC",
"heart_sprite": "03489FF8",
"icon_sprites": "03489E24",
"item_digit_sprite": "0348A018",
"item_overrides_count": "0348B1CC",
"item_table": "0348A0E0",
"items_sprite": "0348A088",
"key_rupee_clock_sprite": "0348A028",
"last_fog_distance": "0348B0F4",
"linkhead_skull_sprite": "0348A008",
"medal_colors": "03489EF4",
"medals_sprite": "0348A098",
"normal_dd": "0348B164",
"normal_no_dd": "0348B16C",
"object_slots": "0348C244",
"pending_freezes": "0348B1D0",
"pending_item_queue": "0348B22C",
"quest_items_sprite": "0348A078",
"rupee_colors": "03489E30",
"satisified_pending_frames": "0348B1A0",
"scene_fog_distance": "0348B0F8",
"setup_db": "0348A0B8",
"song_note_sprite": "0348A038",
"stones_sprite": "0348A0A8",
"text_cursor_border_base": "0348B114",
"text_cursor_border_max": "0348B110",
"text_cursor_inner_base": "0348B11C",
"text_cursor_inner_max": "0348B118",
"triforce_hunt_enabled": "0348B1E0",
"triforce_pieces_requied": "0348B182",
"triforce_sprite": "0348A048"
"a_button": "0348B190",
"a_note_b": "0348B17C",
"a_note_font_glow_base": "0348B164",
"a_note_font_glow_max": "0348B160",
"a_note_g": "0348B180",
"a_note_glow_base": "0348B16C",
"a_note_glow_max": "0348B168",
"a_note_r": "0348B184",
"active_item_action_id": "0348B1E4",
"active_item_fast_chest": "0348B1D4",
"active_item_graphic_id": "0348B1D8",
"active_item_object_id": "0348B1DC",
"active_item_row": "0348B1E8",
"active_item_text_id": "0348B1E0",
"active_override": "0348B1F0",
"active_override_is_outgoing": "0348B1EC",
"b_button": "0348B18C",
"beating_dd": "0348B198",
"beating_no_dd": "0348B1A0",
"c_button": "0348B188",
"c_note_b": "0348B170",
"c_note_font_glow_base": "0348B154",
"c_note_font_glow_max": "0348B150",
"c_note_g": "0348B174",
"c_note_glow_base": "0348B15C",
"c_note_glow_max": "0348B158",
"c_note_r": "0348B178",
"cfg_dungeon_info_enable": "0348B11C",
"cfg_dungeon_info_mq_enable": "0348B1C0",
"cfg_dungeon_info_mq_need_map": "0348B1BC",
"cfg_dungeon_info_reward_enable": "0348B118",
"cfg_dungeon_info_reward_need_altar": "0348B1B4",
"cfg_dungeon_info_reward_need_compass": "0348B1B8",
"cfg_dungeon_is_mq": "0348B220",
"cfg_dungeon_rewards": "03489F14",
"cfg_file_select_hash": "0348B1C8",
"cfg_item_overrides": "0348B274",
"defaultDDHeart": "0348B1A4",
"defaultHeart": "0348B1AC",
"dpad_sprite": "0348A088",
"dummy_actor": "0348B1F8",
"dungeon_count": "0348B120",
"dungeons": "03489F38",
"empty_dlist": "0348B138",
"extern_ctxt": "03489FD4",
"font_sprite": "0348A098",
"freecam_modes": "03489C90",
"hash_sprites": "0348B12C",
"hash_symbols": "03489FE8",
"heap_next": "0348B21C",
"heart_sprite": "0348A028",
"icon_sprites": "03489E54",
"item_digit_sprite": "0348A048",
"item_overrides_count": "0348B1FC",
"item_table": "0348A110",
"items_sprite": "0348A0B8",
"key_rupee_clock_sprite": "0348A058",
"last_fog_distance": "0348B124",
"linkhead_skull_sprite": "0348A038",
"medal_colors": "03489F24",
"medals_sprite": "0348A0C8",
"normal_dd": "0348B194",
"normal_no_dd": "0348B19C",
"object_slots": "0348C274",
"pending_freezes": "0348B200",
"pending_item_queue": "0348B25C",
"quest_items_sprite": "0348A0A8",
"rupee_colors": "03489E60",
"satisified_pending_frames": "0348B1D0",
"scene_fog_distance": "0348B128",
"setup_db": "0348A0E8",
"song_note_sprite": "0348A068",
"stones_sprite": "0348A0D8",
"text_cursor_border_base": "0348B144",
"text_cursor_border_max": "0348B140",
"text_cursor_inner_base": "0348B14C",
"text_cursor_inner_max": "0348B148",
"triforce_hunt_enabled": "0348B210",
"triforce_pieces_requied": "0348B1B2",
"triforce_sprite": "0348A078"
}