commit
81397936ef
|
@ -494,7 +494,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
|
||||||
handle_option(ret, game_weights, option_key, option)
|
handle_option(ret, game_weights, option_key, option)
|
||||||
if "items" in plando_options:
|
if "items" in plando_options:
|
||||||
ret.plando_items = roll_item_plando(world_type, game_weights)
|
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
|
# bad hardcoded behavior to make this work for now
|
||||||
ret.plando_connections = []
|
ret.plando_connections = []
|
||||||
if "connections" in plando_options:
|
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(
|
ret.plando_connections.append(PlandoConnection(
|
||||||
get_choice("entrance", placement),
|
get_choice("entrance", placement),
|
||||||
get_choice("exit", placement),
|
get_choice("exit", placement),
|
||||||
get_choice("direction", placement, "both")
|
get_choice("direction", placement)
|
||||||
))
|
))
|
||||||
elif ret.game == "A Link to the Past":
|
elif ret.game == "A Link to the Past":
|
||||||
roll_alttp_settings(ret, game_weights, plando_options)
|
roll_alttp_settings(ret, game_weights, plando_options)
|
||||||
|
|
|
@ -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()
|
|
@ -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/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
|
||||||
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
||||||
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
|
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
|
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
|
||||||
|
|
||||||
[Dirs]
|
[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}\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}\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}\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
|
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||||
|
|
||||||
;minecraft temp files
|
;minecraft temp files
|
||||||
|
|
|
@ -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/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
|
||||||
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
||||||
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
|
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
|
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
|
||||||
|
|
||||||
[Dirs]
|
[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}\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}\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}\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
|
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||||
|
|
||||||
;minecraft temp files
|
;minecraft temp files
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -80,6 +80,8 @@ scripts = {
|
||||||
"FactorioClient.py": ("ArchipelagoFactorioClient", True, icon),
|
"FactorioClient.py": ("ArchipelagoFactorioClient", True, icon),
|
||||||
# Minecraft
|
# Minecraft
|
||||||
"MinecraftClient.py": ("ArchipelagoMinecraftClient", False, mcicon),
|
"MinecraftClient.py": ("ArchipelagoMinecraftClient", False, mcicon),
|
||||||
|
# Ocarina of Time
|
||||||
|
"OoTAdjuster.py": ("ArchipelagoOoTAdjuster", True, icon),
|
||||||
}
|
}
|
||||||
|
|
||||||
exes = []
|
exes = []
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Dungeon(object):
|
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):
|
def to_array(obj):
|
||||||
if obj == None:
|
if obj == None:
|
||||||
return []
|
return []
|
||||||
|
@ -12,6 +12,7 @@ class Dungeon(object):
|
||||||
self.world = world
|
self.world = world
|
||||||
self.name = name
|
self.name = name
|
||||||
self.hint_text = hint
|
self.hint_text = hint
|
||||||
|
self.font_color = font_color
|
||||||
self.regions = []
|
self.regions = []
|
||||||
self.boss_key = to_array(boss_key)
|
self.boss_key = to_array(boss_key)
|
||||||
self.small_keys = to_array(small_keys)
|
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_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_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
|
return new_dungeon
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ from .Utils import data_path
|
||||||
dungeon_table = [
|
dungeon_table = [
|
||||||
{
|
{
|
||||||
'name': 'Deku Tree',
|
'name': 'Deku Tree',
|
||||||
|
'hint': 'the Deku Tree',
|
||||||
|
'font_color': 'Green',
|
||||||
'boss_key': 0,
|
'boss_key': 0,
|
||||||
'small_key': 0,
|
'small_key': 0,
|
||||||
'small_key_mq': 0,
|
'small_key_mq': 0,
|
||||||
|
@ -15,6 +17,7 @@ dungeon_table = [
|
||||||
{
|
{
|
||||||
'name': 'Dodongos Cavern',
|
'name': 'Dodongos Cavern',
|
||||||
'hint': 'Dodongo\'s Cavern',
|
'hint': 'Dodongo\'s Cavern',
|
||||||
|
'font_color': 'Red',
|
||||||
'boss_key': 0,
|
'boss_key': 0,
|
||||||
'small_key': 0,
|
'small_key': 0,
|
||||||
'small_key_mq': 0,
|
'small_key_mq': 0,
|
||||||
|
@ -23,6 +26,7 @@ dungeon_table = [
|
||||||
{
|
{
|
||||||
'name': 'Jabu Jabus Belly',
|
'name': 'Jabu Jabus Belly',
|
||||||
'hint': 'Jabu Jabu\'s Belly',
|
'hint': 'Jabu Jabu\'s Belly',
|
||||||
|
'font_color': 'Blue',
|
||||||
'boss_key': 0,
|
'boss_key': 0,
|
||||||
'small_key': 0,
|
'small_key': 0,
|
||||||
'small_key_mq': 0,
|
'small_key_mq': 0,
|
||||||
|
@ -30,6 +34,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Forest Temple',
|
'name': 'Forest Temple',
|
||||||
|
'hint': 'the Forest Temple',
|
||||||
|
'font_color': 'Green',
|
||||||
'boss_key': 1,
|
'boss_key': 1,
|
||||||
'small_key': 5,
|
'small_key': 5,
|
||||||
'small_key_mq': 6,
|
'small_key_mq': 6,
|
||||||
|
@ -37,6 +43,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Bottom of the Well',
|
'name': 'Bottom of the Well',
|
||||||
|
'hint': 'the Bottom of the Well',
|
||||||
|
'font_color': 'Pink',
|
||||||
'boss_key': 0,
|
'boss_key': 0,
|
||||||
'small_key': 3,
|
'small_key': 3,
|
||||||
'small_key_mq': 2,
|
'small_key_mq': 2,
|
||||||
|
@ -44,6 +52,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Fire Temple',
|
'name': 'Fire Temple',
|
||||||
|
'hint': 'the Fire Temple',
|
||||||
|
'font_color': 'Red',
|
||||||
'boss_key': 1,
|
'boss_key': 1,
|
||||||
'small_key': 8,
|
'small_key': 8,
|
||||||
'small_key_mq': 5,
|
'small_key_mq': 5,
|
||||||
|
@ -51,6 +61,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Ice Cavern',
|
'name': 'Ice Cavern',
|
||||||
|
'hint': 'the Ice Cavern',
|
||||||
|
'font_color': 'Blue',
|
||||||
'boss_key': 0,
|
'boss_key': 0,
|
||||||
'small_key': 0,
|
'small_key': 0,
|
||||||
'small_key_mq': 0,
|
'small_key_mq': 0,
|
||||||
|
@ -58,6 +70,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Water Temple',
|
'name': 'Water Temple',
|
||||||
|
'hint': 'the Water Temple',
|
||||||
|
'font_color': 'Blue',
|
||||||
'boss_key': 1,
|
'boss_key': 1,
|
||||||
'small_key': 6,
|
'small_key': 6,
|
||||||
'small_key_mq': 2,
|
'small_key_mq': 2,
|
||||||
|
@ -65,6 +79,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Shadow Temple',
|
'name': 'Shadow Temple',
|
||||||
|
'hint': 'the Shadow Temple',
|
||||||
|
'font_color': 'Pink',
|
||||||
'boss_key': 1,
|
'boss_key': 1,
|
||||||
'small_key': 5,
|
'small_key': 5,
|
||||||
'small_key_mq': 6,
|
'small_key_mq': 6,
|
||||||
|
@ -72,6 +88,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Gerudo Training Grounds',
|
'name': 'Gerudo Training Grounds',
|
||||||
|
'hint': 'the Gerudo Training Grounds',
|
||||||
|
'font_color': 'Yellow',
|
||||||
'boss_key': 0,
|
'boss_key': 0,
|
||||||
'small_key': 9,
|
'small_key': 9,
|
||||||
'small_key_mq': 3,
|
'small_key_mq': 3,
|
||||||
|
@ -79,6 +97,8 @@ dungeon_table = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Spirit Temple',
|
'name': 'Spirit Temple',
|
||||||
|
'hint': 'the Spirit Temple',
|
||||||
|
'font_color': 'Yellow',
|
||||||
'boss_key': 1,
|
'boss_key': 1,
|
||||||
'small_key': 5,
|
'small_key': 5,
|
||||||
'small_key_mq': 7,
|
'small_key_mq': 7,
|
||||||
|
@ -100,6 +120,7 @@ def create_dungeons(ootworld):
|
||||||
for dungeon_info in dungeon_table:
|
for dungeon_info in dungeon_table:
|
||||||
name = dungeon_info['name']
|
name = dungeon_info['name']
|
||||||
hint = dungeon_info['hint'] if 'hint' in dungeon_info else 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 ootworld.logic_rules == 'glitchless':
|
||||||
if not ootworld.dungeon_mq[name]:
|
if not ootworld.dungeon_mq[name]:
|
||||||
|
@ -125,5 +146,5 @@ def create_dungeons(ootworld):
|
||||||
for item in dungeon_items:
|
for item in dungeon_items:
|
||||||
item.priority = True
|
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))
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import logging
|
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 .Hints import get_hint_area, HintAreaNotFound
|
||||||
from .Regions import TimeOfDay
|
from .Regions import TimeOfDay
|
||||||
|
@ -29,12 +29,13 @@ def assume_entrance_pool(entrance_pool, ootworld):
|
||||||
assumed_pool = []
|
assumed_pool = []
|
||||||
for entrance in entrance_pool:
|
for entrance in entrance_pool:
|
||||||
assumed_forward = entrance.assume_reachable()
|
assumed_forward = entrance.assume_reachable()
|
||||||
if entrance.reverse != None:
|
if entrance.reverse != None and not ootworld.decouple_entrances:
|
||||||
assumed_return = entrance.reverse.assume_reachable()
|
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 \
|
if not (ootworld.mix_entrance_pools != 'off' and (ootworld.shuffle_overworld_entrances or ootworld.shuffle_special_interior_entrances)):
|
||||||
(entrance.type == 'Interior' and 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 \
|
||||||
# In most cases, Dungeon, Grotto/Grave and Simple Interior exits shouldn't be assumed able to give access to their parent region
|
(entrance.type == 'Interior' and ootworld.shuffle_special_interior_entrances):
|
||||||
set_rule(assumed_return, lambda state, **kwargs: False)
|
# 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_forward.bind_two_way(assumed_return)
|
||||||
assumed_pool.append(assumed_forward)
|
assumed_pool.append(assumed_forward)
|
||||||
return assumed_pool
|
return assumed_pool
|
||||||
|
@ -308,6 +309,8 @@ entrance_shuffle_table = [
|
||||||
('Overworld', ('ZD Behind King Zora -> Zoras Fountain', { 'index': 0x0225 }),
|
('Overworld', ('ZD Behind King Zora -> Zoras Fountain', { 'index': 0x0225 }),
|
||||||
('Zoras Fountain -> ZD Behind King Zora', { 'index': 0x01A1 })),
|
('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', ('LH Owl Flight -> Hyrule Field', { 'index': 0x027E, 'addresses': [0xAC9F26] })),
|
||||||
('OwlDrop', ('DMT Owl Flight -> Kak Impas Rooftop', { 'index': 0x0554, 'addresses': [0xAC9EF2] })),
|
('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)
|
entrance_pools['Dungeon'] = ootworld.get_shufflable_entrances(type='Dungeon', only_primary=True)
|
||||||
if ootworld.open_forest == 'closed':
|
if ootworld.open_forest == 'closed':
|
||||||
entrance_pools['Dungeon'].remove(world.get_entrance('KF Outside Deku Tree -> Deku Tree Lobby', player))
|
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':
|
if ootworld.shuffle_interior_entrances != 'off':
|
||||||
entrance_pools['Interior'] = ootworld.get_shufflable_entrances(type='Interior', only_primary=True)
|
entrance_pools['Interior'] = ootworld.get_shufflable_entrances(type='Interior', only_primary=True)
|
||||||
if ootworld.shuffle_special_interior_entrances:
|
if ootworld.shuffle_special_interior_entrances:
|
||||||
entrance_pools['Interior'] += ootworld.get_shufflable_entrances(type='SpecialInterior', only_primary=True)
|
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:
|
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='Grotto', only_primary=True)
|
||||||
entrance_pools['GrottoGrave'] += ootworld.get_shufflable_entrances(type='Grave', 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:
|
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
|
# Mark shuffled entrances
|
||||||
for entrance in chain(chain.from_iterable(one_way_entrance_pools.values()), chain.from_iterable(entrance_pools.values())):
|
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:
|
if entrance.reverse:
|
||||||
entrance.reverse.shuffled = True
|
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
|
# Build target entrance pools
|
||||||
one_way_target_entrance_pools = {}
|
one_way_target_entrance_pools = {}
|
||||||
for pool_type, entrance_pool in one_way_entrance_pools.items():
|
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'}:
|
elif pool_type in {'Spawn', 'WarpSong'}:
|
||||||
valid_target_types = ('Spawn', 'WarpSong', 'OwlDrop', 'Overworld', 'Interior', 'SpecialInterior', 'Extra')
|
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)
|
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
|
# Disconnect one-way entrances for priority placement
|
||||||
for entrance in chain.from_iterable(one_way_entrance_pools.values()):
|
for entrance in chain.from_iterable(one_way_entrance_pools.values()):
|
||||||
entrance.disconnect()
|
entrance.disconnect()
|
||||||
|
@ -419,7 +443,52 @@ def shuffle_random_entrances(ootworld):
|
||||||
if item_tuple[1] == player:
|
if item_tuple[1] == player:
|
||||||
none_state.prog_items[item_tuple] = 0
|
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
|
# 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)
|
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.collect(ootworld.create_item('Time Travel'), event=True)
|
||||||
time_travel_state._oot_update_age_reachable_regions(player)
|
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 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)
|
# 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
|
# 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']
|
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']
|
ADULT_FORBIDDEN = ['HC Great Fairy Fountain -> Castle Grounds', 'HC Storms Grotto -> Castle Grounds']
|
||||||
|
|
||||||
for entrance in ootworld.get_shufflable_entrances():
|
if not ootworld.decouple_entrances:
|
||||||
if entrance.shuffled and entrance.replaces:
|
for entrance in ootworld.get_shufflable_entrances():
|
||||||
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
|
if entrance.shuffled and entrance.replaces:
|
||||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential child access')
|
if entrance.replaces.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.replaces.reverse]):
|
||||||
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 child access')
|
||||||
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
|
if entrance.replaces.name in ADULT_FORBIDDEN and not entrance_unreachable_as(entrance, 'adult', already_checked=[entrance.replaces.reverse]):
|
||||||
else:
|
raise EntranceShuffleError(f'{entrance.replaces.name} replaced by an entrance with potential adult access')
|
||||||
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
|
else:
|
||||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as child')
|
if entrance.name in CHILD_FORBIDDEN and not entrance_unreachable_as(entrance, 'child', already_checked=[entrance.reverse]):
|
||||||
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 child')
|
||||||
raise EntranceShuffleError(f'{entrance.name} potentially accessible as adult')
|
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
|
# Check if all locations are reachable if not beatable-only or game is not yet complete
|
||||||
if locations_to_ensure_reachable:
|
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):
|
if not all_state.can_reach(loc, 'Location', player):
|
||||||
raise EntranceShuffleError(f'{loc} is unreachable')
|
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
|
# 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_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)
|
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):
|
def change_connections(entrance, target):
|
||||||
entrance.connect(target.disconnect())
|
entrance.connect(target.disconnect())
|
||||||
entrance.replaces = target.replaces
|
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.connect(entrance.reverse.assumed.disconnect())
|
||||||
target.replaces.reverse.replaces = entrance.reverse
|
target.replaces.reverse.replaces = entrance.reverse
|
||||||
|
|
||||||
def restore_connections(entrance, target):
|
def restore_connections(entrance, target):
|
||||||
target.connect(entrance.disconnect())
|
target.connect(entrance.disconnect())
|
||||||
entrance.replaces = None
|
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())
|
entrance.reverse.assumed.connect(target.replaces.reverse.disconnect())
|
||||||
target.replaces.reverse.replaces = None
|
target.replaces.reverse.replaces = None
|
||||||
|
|
||||||
|
@ -757,7 +828,7 @@ def check_entrances_compatibility(entrance, target, rollbacks):
|
||||||
def confirm_replacement(entrance, target):
|
def confirm_replacement(entrance, target):
|
||||||
delete_target_entrance(target)
|
delete_target_entrance(target)
|
||||||
logging.getLogger('').debug(f'Connected {entrance} to {entrance.connected_region}')
|
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
|
replaced_reverse = target.replaces.reverse
|
||||||
delete_target_entrance(entrance.reverse.assumed)
|
delete_target_entrance(entrance.reverse.assumed)
|
||||||
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')
|
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')
|
||||||
|
|
|
@ -11,7 +11,7 @@ import json
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from .HintList import getHint, getHintGroup, Hint, hintExclusions
|
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 .TextBox import line_wrap
|
||||||
from .Utils import data_path, read_json
|
from .Utils import data_path, read_json
|
||||||
|
|
||||||
|
@ -266,17 +266,6 @@ def getSimpleHintNoPrefix(item):
|
||||||
|
|
||||||
|
|
||||||
def colorText(gossip_text):
|
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
|
text = gossip_text.text
|
||||||
colors = list(gossip_text.colors) if gossip_text.colors is not None else []
|
colors = list(gossip_text.colors) if gossip_text.colors is not None else []
|
||||||
color = 'White'
|
color = 'White'
|
||||||
|
@ -292,7 +281,7 @@ def colorText(gossip_text):
|
||||||
splitText[1] = splitText[1][len(prefix):]
|
splitText[1] = splitText[1][len(prefix):]
|
||||||
break
|
break
|
||||||
|
|
||||||
splitText[1] = '\x05' + colorMap[color] + splitText[1] + '\x05\x40'
|
splitText[1] = '\x05' + COLOR_MAP[color] + splitText[1] + '\x05\x40'
|
||||||
text = ''.join(splitText)
|
text = ''.join(splitText)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
@ -649,9 +638,9 @@ def buildWorldGossipHints(world, checkedLocations=None):
|
||||||
if checkedLocations is None:
|
if checkedLocations is None:
|
||||||
checkedLocations = {player: set() for player in world.world.player_ids}
|
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
|
# 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:
|
try:
|
||||||
light_arrow_location = world.world.find_item("Light Arrows", world.player)
|
light_arrow_location = world.world.find_item("Light Arrows", world.player)
|
||||||
checkedLocations[light_arrow_location.player].add(light_arrow_location.name)
|
checkedLocations[light_arrow_location.player].add(light_arrow_location.name)
|
||||||
|
|
|
@ -1329,9 +1329,10 @@ def get_pool_core(world):
|
||||||
# We can resolve this by starting with some extra keys
|
# We can resolve this by starting with some extra keys
|
||||||
if world.dungeon_mq['Spirit Temple']:
|
if world.dungeon_mq['Spirit Temple']:
|
||||||
# Yes somehow you need 3 keys. This dungeon is bonkers
|
# Yes somehow you need 3 keys. This dungeon is bonkers
|
||||||
world.world.push_precollected(world.create_item('Small Key (Spirit Temple)'))
|
items = [world.create_item('Small Key (Spirit Temple)') for i in range(3)]
|
||||||
world.world.push_precollected(world.create_item('Small Key (Spirit Temple)'))
|
for item in items:
|
||||||
world.world.push_precollected(world.create_item('Small Key (Spirit Temple)'))
|
world.world.push_precollected(item)
|
||||||
|
world.remove_from_start_inventory.append(item.name)
|
||||||
#if not world.dungeon_mq['Fire Temple']:
|
#if not world.dungeon_mq['Fire Temple']:
|
||||||
# world.state.collect(ItemFactory('Small Key (Fire Temple)'))
|
# world.state.collect(ItemFactory('Small Key (Fire Temple)'))
|
||||||
if world.shuffle_bosskeys == 'vanilla':
|
if world.shuffle_bosskeys == 'vanilla':
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# text details: https://wiki.cloudmodding.com/oot/Text_Format
|
# text details: https://wiki.cloudmodding.com/oot/Text_Format
|
||||||
|
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
from .TextBox import line_wrap
|
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",
|
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 = {
|
MISC_MESSAGES = {
|
||||||
0x507B: (bytearray(
|
0x507B: (bytearray(
|
||||||
b"\x08I tell you, I saw him!\x04" \
|
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
|
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)
|
||||||
|
|
|
@ -96,6 +96,7 @@ class StartingAge(Choice):
|
||||||
|
|
||||||
class InteriorEntrances(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."""
|
"""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_off = 0
|
||||||
option_simple = 1
|
option_simple = 1
|
||||||
option_all = 2
|
option_all = 2
|
||||||
|
@ -105,26 +106,46 @@ class InteriorEntrances(Choice):
|
||||||
|
|
||||||
class GrottoEntrances(Toggle):
|
class GrottoEntrances(Toggle):
|
||||||
"""Shuffles grotto and grave entrances."""
|
"""Shuffles grotto and grave entrances."""
|
||||||
|
displayname = "Shuffle Grotto/Grave Entrances"
|
||||||
|
|
||||||
|
|
||||||
class DungeonEntrances(Toggle):
|
class DungeonEntrances(Toggle):
|
||||||
"""Shuffles dungeon entrances, excluding Ganon's Castle. Opens Deku, Fire and BotW to both ages."""
|
"""Shuffles dungeon entrances, excluding Ganon's Castle. Opens Deku, Fire and BotW to both ages."""
|
||||||
|
displayname = "Shuffle Dungeon Entrances"
|
||||||
|
|
||||||
|
|
||||||
class OverworldEntrances(Toggle):
|
class OverworldEntrances(Toggle):
|
||||||
"""Shuffles overworld loading zones."""
|
"""Shuffles overworld loading zones."""
|
||||||
|
displayname = "Shuffle Overworld Entrances"
|
||||||
|
|
||||||
|
|
||||||
class OwlDrops(Toggle):
|
class OwlDrops(Toggle):
|
||||||
"""Randomizes owl drops from Lake Hylia or Death Mountain Trail as child."""
|
"""Randomizes owl drops from Lake Hylia or Death Mountain Trail as child."""
|
||||||
|
displayname = "Randomize Owl Drops"
|
||||||
|
|
||||||
|
|
||||||
class WarpSongs(Toggle):
|
class WarpSongs(Toggle):
|
||||||
"""Randomizes warp song destinations."""
|
"""Randomizes warp song destinations."""
|
||||||
|
displayname = "Randomize Warp Songs"
|
||||||
|
|
||||||
|
|
||||||
class SpawnPositions(Toggle):
|
class SpawnPositions(Toggle):
|
||||||
"""Randomizes the starting position on loading a save. Consistent between savewarps."""
|
"""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):
|
class TriforceHunt(Toggle):
|
||||||
|
@ -170,6 +191,8 @@ world_options: typing.Dict[str, type(Option)] = {
|
||||||
"owl_drops": OwlDrops,
|
"owl_drops": OwlDrops,
|
||||||
"warp_songs": WarpSongs,
|
"warp_songs": WarpSongs,
|
||||||
"spawn_positions": SpawnPositions,
|
"spawn_positions": SpawnPositions,
|
||||||
|
"mix_entrance_pools": MixEntrancePools,
|
||||||
|
"decouple_entrances": DecoupleEntrances,
|
||||||
"triforce_hunt": TriforceHunt,
|
"triforce_hunt": TriforceHunt,
|
||||||
"triforce_goal": TriforceGoal,
|
"triforce_goal": TriforceGoal,
|
||||||
"extra_triforce_percentage": ExtraTriforces,
|
"extra_triforce_percentage": ExtraTriforces,
|
||||||
|
@ -540,6 +563,11 @@ class Hints(Choice):
|
||||||
alias_false = 0
|
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):
|
class HintDistribution(Choice):
|
||||||
"""Choose the hint distribution to use. Affects the frequency of strong hints, which items are always hinted, etc."""
|
"""Choose the hint distribution to use. Affects the frequency of strong hints, which items are always hinted, etc."""
|
||||||
displayname = "Hint Distribution"
|
displayname = "Hint Distribution"
|
||||||
|
@ -607,6 +635,7 @@ class RupeeStart(Toggle):
|
||||||
misc_options: typing.Dict[str, type(Option)] = {
|
misc_options: typing.Dict[str, type(Option)] = {
|
||||||
"correct_chest_sizes": CSMC,
|
"correct_chest_sizes": CSMC,
|
||||||
"hints": Hints,
|
"hints": Hints,
|
||||||
|
"misc_hints": MiscHints,
|
||||||
"hint_dist": HintDistribution,
|
"hint_dist": HintDistribution,
|
||||||
"text_shuffle": TextShuffle,
|
"text_shuffle": TextShuffle,
|
||||||
"damage_multiplier": DamageMultiplier,
|
"damage_multiplier": DamageMultiplier,
|
||||||
|
|
|
@ -9,7 +9,7 @@ from .LocationList import business_scrubs
|
||||||
from .Hints import writeGossipStoneHints, buildAltarHints, \
|
from .Hints import writeGossipStoneHints, buildAltarHints, \
|
||||||
buildGanonText, getSimpleHintNoPrefix
|
buildGanonText, getSimpleHintNoPrefix
|
||||||
from .Utils import data_path
|
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, \
|
write_shop_items, remove_unused_messages, make_player_message, \
|
||||||
add_item_messages, repack_messages, shuffle_messages, \
|
add_item_messages, repack_messages, shuffle_messages, \
|
||||||
get_message_by_id
|
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.
|
# 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
|
item = world.get_location('Song from Impa').item
|
||||||
save_context.give_raw_item(item.name)
|
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, 0x04) # "Obtained Malon's Item"
|
||||||
save_context.write_bits(0x0ED7, 0x08) # "Woke Talon in castle"
|
save_context.write_bits(0x0ED7, 0x08) # "Woke Talon in castle"
|
||||||
save_context.write_bits(0x0ED7, 0x10) # "Talon has fled 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 + 2, 0x0190) # X pos
|
||||||
rom.write_int16(chest_address + 6, 0xFABC) # Z pos
|
rom.write_int16(chest_address + 6, 0xFABC) # Z pos
|
||||||
else:
|
else:
|
||||||
if location.item.advancement:
|
if not location.item.advancement:
|
||||||
rom.write_int16(chest_address + 2, 0x0190) # X pos
|
rom.write_int16(chest_address + 2, 0x0190) # X pos
|
||||||
rom.write_int16(chest_address + 6, 0xFABC) # Z 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_0 + 6, 0x0172) # Z pos
|
||||||
rom.write_int16(chest_address_2 + 6, 0x0172) # Z pos
|
rom.write_int16(chest_address_2 + 6, 0x0172) # Z pos
|
||||||
else:
|
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_0 + 6, 0x0172) # Z pos
|
||||||
rom.write_int16(chest_address_2 + 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':
|
elif world.text_shuffle == 'complete':
|
||||||
permutation = shuffle_messages(messages, except_hints=False)
|
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)
|
repack_messages(rom, messages, permutation)
|
||||||
|
|
||||||
# output a text dump, for testing...
|
# output a text dump, for testing...
|
||||||
|
|
|
@ -38,6 +38,8 @@ class OOTRegion(Region):
|
||||||
self.provides_time = TimeOfDay.NONE
|
self.provides_time = TimeOfDay.NONE
|
||||||
self.scene = None
|
self.scene = None
|
||||||
self.dungeon = None
|
self.dungeon = None
|
||||||
|
self.pretty_name = None
|
||||||
|
self.font_color = None
|
||||||
|
|
||||||
def get_scene(self):
|
def get_scene(self):
|
||||||
if self.scene:
|
if self.scene:
|
||||||
|
|
|
@ -17,7 +17,7 @@ double_cache_prevention = threading.Lock()
|
||||||
class Rom(BigStream):
|
class Rom(BigStream):
|
||||||
original = None
|
original = None
|
||||||
|
|
||||||
def __init__(self, file=None):
|
def __init__(self, file=None, force_use=False):
|
||||||
super().__init__([])
|
super().__init__([])
|
||||||
|
|
||||||
self.changed_address = {}
|
self.changed_address = {}
|
||||||
|
@ -34,22 +34,25 @@ class Rom(BigStream):
|
||||||
self.symbols = {name: int(addr, 16) for name, addr in symbols.items()}
|
self.symbols = {name: int(addr, 16) for name, addr in symbols.items()}
|
||||||
|
|
||||||
# If decompressed file already exists, read from it
|
# If decompressed file already exists, read from it
|
||||||
if os.path.exists(decomp_file):
|
if not force_use:
|
||||||
file = decomp_file
|
if os.path.exists(decomp_file):
|
||||||
|
file = decomp_file
|
||||||
|
|
||||||
if file == '':
|
if file == '':
|
||||||
# if not specified, try to read from the previously decompressed rom
|
# if not specified, try to read from the previously decompressed rom
|
||||||
file = decomp_file
|
file = decomp_file
|
||||||
try:
|
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)
|
self.read_rom(file)
|
||||||
except FileNotFoundError:
|
|
||||||
# could not find the decompressed rom either
|
|
||||||
raise FileNotFoundError('Must specify path to base ROM')
|
|
||||||
else:
|
else:
|
||||||
self.read_rom(file)
|
self.read_rom(file)
|
||||||
|
|
||||||
# decompress rom, or check if it's already decompressed
|
# 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
|
# Add file to maximum size
|
||||||
self.buffer.extend(bytearray([0x00] * (0x4000000 - len(self.buffer))))
|
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)
|
new_rom.force_patch = copy.copy(self.force_patch)
|
||||||
return new_rom
|
return new_rom
|
||||||
|
|
||||||
def decompress_rom_file(self, file, decomp_file):
|
def decompress_rom_file(self, file, decomp_file, skip_crc_check):
|
||||||
validCRC = [
|
validCRC = [
|
||||||
[0xEC, 0x70, 0x11, 0xB7, 0x76, 0x16, 0xD7, 0x2B], # Compressed
|
[0xEC, 0x70, 0x11, 0xB7, 0x76, 0x16, 0xD7, 0x2B], # Compressed
|
||||||
[0x70, 0xEC, 0xB7, 0x11, 0x16, 0x76, 0x2B, 0xD7], # Byteswap compressed
|
[0x70, 0xEC, 0xB7, 0x11, 0x16, 0x76, 0x2B, 0xD7], # Byteswap compressed
|
||||||
|
@ -79,7 +82,7 @@ class Rom(BigStream):
|
||||||
# Validate ROM file
|
# Validate ROM file
|
||||||
file_name = os.path.splitext(file)
|
file_name = os.path.splitext(file)
|
||||||
romCRC = list(self.buffer[0x10:0x18])
|
romCRC = list(self.buffer[0x10:0x18])
|
||||||
if romCRC not in validCRC:
|
if romCRC not in validCRC and not skip_crc_check:
|
||||||
# Bad CRC validation
|
# Bad CRC validation
|
||||||
raise RuntimeError('ROM file %s is not a valid OoT 1.0 US ROM.' % file)
|
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',
|
elif len(self.buffer) < 0x2000000 or len(self.buffer) > (0x4000000) or file_name[1].lower() not in ['.z64',
|
||||||
|
|
|
@ -4,7 +4,7 @@ import subprocess
|
||||||
import Utils
|
import Utils
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
__version__ = Utils.__version__ + ' f.LUM'
|
__version__ = '6.1.0 f.LUM'
|
||||||
|
|
||||||
|
|
||||||
def data_path(*args):
|
def data_path(*args):
|
||||||
|
|
|
@ -191,7 +191,6 @@ class OOTWorld(World):
|
||||||
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
|
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
|
||||||
|
|
||||||
# Hint stuff
|
# 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.clearer_hints = True # this is being enforced since non-oot items do not have non-clear hint text
|
||||||
self.gossip_hints = {}
|
self.gossip_hints = {}
|
||||||
self.required_locations = []
|
self.required_locations = []
|
||||||
|
@ -276,6 +275,10 @@ class OOTWorld(World):
|
||||||
for region in region_json:
|
for region in region_json:
|
||||||
new_region = OOTRegion(region['region_name'], RegionType.Generic, None, self.player)
|
new_region = OOTRegion(region['region_name'], RegionType.Generic, None, self.player)
|
||||||
new_region.world = self.world
|
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:
|
if 'scene' in region:
|
||||||
new_region.scene = region['scene']
|
new_region.scene = region['scene']
|
||||||
if 'hint' in region:
|
if 'hint' in region:
|
||||||
|
@ -513,20 +516,6 @@ class OOTWorld(World):
|
||||||
else:
|
else:
|
||||||
break
|
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_rules(self)
|
||||||
set_entrances_based_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'))
|
create_patch_file(rom, output_path(output_directory, outfile_name + '.apz5'))
|
||||||
rom.restore()
|
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.
|
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
|
||||||
@classmethod
|
@classmethod
|
||||||
def stage_generate_output(cls, world: MultiWorld, output_directory: str):
|
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
|
@ -5,7 +5,7 @@
|
||||||
"AUDIO_THREAD_INFO": "03482FC0",
|
"AUDIO_THREAD_INFO": "03482FC0",
|
||||||
"AUDIO_THREAD_INFO_MEM_SIZE": "03482FDC",
|
"AUDIO_THREAD_INFO_MEM_SIZE": "03482FDC",
|
||||||
"AUDIO_THREAD_INFO_MEM_START": "03482FD8",
|
"AUDIO_THREAD_INFO_MEM_START": "03482FD8",
|
||||||
"AUDIO_THREAD_MEM_START": "0348EF50",
|
"AUDIO_THREAD_MEM_START": "0348EF80",
|
||||||
"BOMBCHUS_IN_LOGIC": "03480CBC",
|
"BOMBCHUS_IN_LOGIC": "03480CBC",
|
||||||
"CFG_A_BUTTON_COLOR": "03480854",
|
"CFG_A_BUTTON_COLOR": "03480854",
|
||||||
"CFG_A_NOTE_COLOR": "03480872",
|
"CFG_A_NOTE_COLOR": "03480872",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"CFG_TEXT_CURSOR_COLOR": "03480866",
|
"CFG_TEXT_CURSOR_COLOR": "03480866",
|
||||||
"CHAIN_HBA_REWARDS": "03483950",
|
"CHAIN_HBA_REWARDS": "03483950",
|
||||||
"CHEST_SIZE_MATCH_CONTENTS": "034826F0",
|
"CHEST_SIZE_MATCH_CONTENTS": "034826F0",
|
||||||
"COMPLETE_MASK_QUEST": "0348B1D1",
|
"COMPLETE_MASK_QUEST": "0348B201",
|
||||||
"COOP_CONTEXT": "03480020",
|
"COOP_CONTEXT": "03480020",
|
||||||
"COOP_VERSION": "03480020",
|
"COOP_VERSION": "03480020",
|
||||||
"COSMETIC_CONTEXT": "03480844",
|
"COSMETIC_CONTEXT": "03480844",
|
||||||
|
@ -47,13 +47,13 @@
|
||||||
"DEATH_LINK": "0348002A",
|
"DEATH_LINK": "0348002A",
|
||||||
"DEBUG_OFFSET": "034828A0",
|
"DEBUG_OFFSET": "034828A0",
|
||||||
"DISABLE_TIMERS": "03480CDC",
|
"DISABLE_TIMERS": "03480CDC",
|
||||||
"DPAD_TEXTURE": "0348D750",
|
"DPAD_TEXTURE": "0348D780",
|
||||||
"DUNGEONS_SHUFFLED": "03480CDE",
|
"DUNGEONS_SHUFFLED": "03480CDE",
|
||||||
"EXTENDED_OBJECT_TABLE": "03480C9C",
|
"EXTENDED_OBJECT_TABLE": "03480C9C",
|
||||||
"EXTERN_DAMAGE_MULTIPLYER": "03482CB1",
|
"EXTERN_DAMAGE_MULTIPLYER": "03482CB1",
|
||||||
"FAST_BUNNY_HOOD_ENABLED": "03480CE0",
|
"FAST_BUNNY_HOOD_ENABLED": "03480CE0",
|
||||||
"FAST_CHESTS": "03480CD6",
|
"FAST_CHESTS": "03480CD6",
|
||||||
"FONT_TEXTURE": "0348C288",
|
"FONT_TEXTURE": "0348C2B8",
|
||||||
"FREE_SCARECROW_ENABLED": "03480CCC",
|
"FREE_SCARECROW_ENABLED": "03480CCC",
|
||||||
"GET_CHEST_OVERRIDE_COLOR_WRAPPER": "03482720",
|
"GET_CHEST_OVERRIDE_COLOR_WRAPPER": "03482720",
|
||||||
"GET_CHEST_OVERRIDE_SIZE_WRAPPER": "034826F4",
|
"GET_CHEST_OVERRIDE_SIZE_WRAPPER": "034826F4",
|
||||||
|
@ -69,17 +69,17 @@
|
||||||
"LACS_CONDITION_COUNT": "03480CD2",
|
"LACS_CONDITION_COUNT": "03480CD2",
|
||||||
"MALON_GAVE_ICETRAP": "0348368C",
|
"MALON_GAVE_ICETRAP": "0348368C",
|
||||||
"MALON_TEXT_ID": "03480CDB",
|
"MALON_TEXT_ID": "03480CDB",
|
||||||
"MAX_RUPEES": "0348B1D3",
|
"MAX_RUPEES": "0348B203",
|
||||||
"MOVED_ADULT_KING_ZORA": "03482FFC",
|
"MOVED_ADULT_KING_ZORA": "03482FFC",
|
||||||
"NO_ESCAPE_SEQUENCE": "0348B19C",
|
"NO_ESCAPE_SEQUENCE": "0348B1CC",
|
||||||
"NO_FOG_STATE": "03480CDD",
|
"NO_FOG_STATE": "03480CDD",
|
||||||
"OCARINAS_SHUFFLED": "03480CD5",
|
"OCARINAS_SHUFFLED": "03480CD5",
|
||||||
"OPEN_KAKARIKO": "0348B1D2",
|
"OPEN_KAKARIKO": "0348B202",
|
||||||
"OUTGOING_ITEM": "03480030",
|
"OUTGOING_ITEM": "03480030",
|
||||||
"OUTGOING_KEY": "0348002C",
|
"OUTGOING_KEY": "0348002C",
|
||||||
"OUTGOING_PLAYER": "03480032",
|
"OUTGOING_PLAYER": "03480032",
|
||||||
"OVERWORLD_SHUFFLED": "03480CDF",
|
"OVERWORLD_SHUFFLED": "03480CDF",
|
||||||
"PAYLOAD_END": "0348EF50",
|
"PAYLOAD_END": "0348EF80",
|
||||||
"PAYLOAD_START": "03480000",
|
"PAYLOAD_START": "03480000",
|
||||||
"PLAYED_WARP_SONG": "03481210",
|
"PLAYED_WARP_SONG": "03481210",
|
||||||
"PLAYER_ID": "03480024",
|
"PLAYER_ID": "03480024",
|
||||||
|
@ -97,88 +97,88 @@
|
||||||
"SPEED_MULTIPLIER": "03482760",
|
"SPEED_MULTIPLIER": "03482760",
|
||||||
"START_TWINROVA_FIGHT": "0348307C",
|
"START_TWINROVA_FIGHT": "0348307C",
|
||||||
"TIME_TRAVEL_SAVED_EQUIPS": "03481A64",
|
"TIME_TRAVEL_SAVED_EQUIPS": "03481A64",
|
||||||
"TRIFORCE_ICON_TEXTURE": "0348DF50",
|
"TRIFORCE_ICON_TEXTURE": "0348DF80",
|
||||||
"TWINROVA_ACTION_TIMER": "03483080",
|
"TWINROVA_ACTION_TIMER": "03483080",
|
||||||
"WINDMILL_SONG_ID": "03480CD9",
|
"WINDMILL_SONG_ID": "03480CD9",
|
||||||
"WINDMILL_TEXT_ID": "03480CDA",
|
"WINDMILL_TEXT_ID": "03480CDA",
|
||||||
"a_button": "0348B160",
|
"a_button": "0348B190",
|
||||||
"a_note_b": "0348B14C",
|
"a_note_b": "0348B17C",
|
||||||
"a_note_font_glow_base": "0348B134",
|
"a_note_font_glow_base": "0348B164",
|
||||||
"a_note_font_glow_max": "0348B130",
|
"a_note_font_glow_max": "0348B160",
|
||||||
"a_note_g": "0348B150",
|
"a_note_g": "0348B180",
|
||||||
"a_note_glow_base": "0348B13C",
|
"a_note_glow_base": "0348B16C",
|
||||||
"a_note_glow_max": "0348B138",
|
"a_note_glow_max": "0348B168",
|
||||||
"a_note_r": "0348B154",
|
"a_note_r": "0348B184",
|
||||||
"active_item_action_id": "0348B1B4",
|
"active_item_action_id": "0348B1E4",
|
||||||
"active_item_fast_chest": "0348B1A4",
|
"active_item_fast_chest": "0348B1D4",
|
||||||
"active_item_graphic_id": "0348B1A8",
|
"active_item_graphic_id": "0348B1D8",
|
||||||
"active_item_object_id": "0348B1AC",
|
"active_item_object_id": "0348B1DC",
|
||||||
"active_item_row": "0348B1B8",
|
"active_item_row": "0348B1E8",
|
||||||
"active_item_text_id": "0348B1B0",
|
"active_item_text_id": "0348B1E0",
|
||||||
"active_override": "0348B1C0",
|
"active_override": "0348B1F0",
|
||||||
"active_override_is_outgoing": "0348B1BC",
|
"active_override_is_outgoing": "0348B1EC",
|
||||||
"b_button": "0348B15C",
|
"b_button": "0348B18C",
|
||||||
"beating_dd": "0348B168",
|
"beating_dd": "0348B198",
|
||||||
"beating_no_dd": "0348B170",
|
"beating_no_dd": "0348B1A0",
|
||||||
"c_button": "0348B158",
|
"c_button": "0348B188",
|
||||||
"c_note_b": "0348B140",
|
"c_note_b": "0348B170",
|
||||||
"c_note_font_glow_base": "0348B124",
|
"c_note_font_glow_base": "0348B154",
|
||||||
"c_note_font_glow_max": "0348B120",
|
"c_note_font_glow_max": "0348B150",
|
||||||
"c_note_g": "0348B144",
|
"c_note_g": "0348B174",
|
||||||
"c_note_glow_base": "0348B12C",
|
"c_note_glow_base": "0348B15C",
|
||||||
"c_note_glow_max": "0348B128",
|
"c_note_glow_max": "0348B158",
|
||||||
"c_note_r": "0348B148",
|
"c_note_r": "0348B178",
|
||||||
"cfg_dungeon_info_enable": "0348B0EC",
|
"cfg_dungeon_info_enable": "0348B11C",
|
||||||
"cfg_dungeon_info_mq_enable": "0348B190",
|
"cfg_dungeon_info_mq_enable": "0348B1C0",
|
||||||
"cfg_dungeon_info_mq_need_map": "0348B18C",
|
"cfg_dungeon_info_mq_need_map": "0348B1BC",
|
||||||
"cfg_dungeon_info_reward_enable": "0348B0E8",
|
"cfg_dungeon_info_reward_enable": "0348B118",
|
||||||
"cfg_dungeon_info_reward_need_altar": "0348B184",
|
"cfg_dungeon_info_reward_need_altar": "0348B1B4",
|
||||||
"cfg_dungeon_info_reward_need_compass": "0348B188",
|
"cfg_dungeon_info_reward_need_compass": "0348B1B8",
|
||||||
"cfg_dungeon_is_mq": "0348B1F0",
|
"cfg_dungeon_is_mq": "0348B220",
|
||||||
"cfg_dungeon_rewards": "03489EE4",
|
"cfg_dungeon_rewards": "03489F14",
|
||||||
"cfg_file_select_hash": "0348B198",
|
"cfg_file_select_hash": "0348B1C8",
|
||||||
"cfg_item_overrides": "0348B244",
|
"cfg_item_overrides": "0348B274",
|
||||||
"defaultDDHeart": "0348B174",
|
"defaultDDHeart": "0348B1A4",
|
||||||
"defaultHeart": "0348B17C",
|
"defaultHeart": "0348B1AC",
|
||||||
"dpad_sprite": "0348A058",
|
"dpad_sprite": "0348A088",
|
||||||
"dummy_actor": "0348B1C8",
|
"dummy_actor": "0348B1F8",
|
||||||
"dungeon_count": "0348B0F0",
|
"dungeon_count": "0348B120",
|
||||||
"dungeons": "03489F08",
|
"dungeons": "03489F38",
|
||||||
"empty_dlist": "0348B108",
|
"empty_dlist": "0348B138",
|
||||||
"extern_ctxt": "03489FA4",
|
"extern_ctxt": "03489FD4",
|
||||||
"font_sprite": "0348A068",
|
"font_sprite": "0348A098",
|
||||||
"freecam_modes": "03489C60",
|
"freecam_modes": "03489C90",
|
||||||
"hash_sprites": "0348B0FC",
|
"hash_sprites": "0348B12C",
|
||||||
"hash_symbols": "03489FB8",
|
"hash_symbols": "03489FE8",
|
||||||
"heap_next": "0348B1EC",
|
"heap_next": "0348B21C",
|
||||||
"heart_sprite": "03489FF8",
|
"heart_sprite": "0348A028",
|
||||||
"icon_sprites": "03489E24",
|
"icon_sprites": "03489E54",
|
||||||
"item_digit_sprite": "0348A018",
|
"item_digit_sprite": "0348A048",
|
||||||
"item_overrides_count": "0348B1CC",
|
"item_overrides_count": "0348B1FC",
|
||||||
"item_table": "0348A0E0",
|
"item_table": "0348A110",
|
||||||
"items_sprite": "0348A088",
|
"items_sprite": "0348A0B8",
|
||||||
"key_rupee_clock_sprite": "0348A028",
|
"key_rupee_clock_sprite": "0348A058",
|
||||||
"last_fog_distance": "0348B0F4",
|
"last_fog_distance": "0348B124",
|
||||||
"linkhead_skull_sprite": "0348A008",
|
"linkhead_skull_sprite": "0348A038",
|
||||||
"medal_colors": "03489EF4",
|
"medal_colors": "03489F24",
|
||||||
"medals_sprite": "0348A098",
|
"medals_sprite": "0348A0C8",
|
||||||
"normal_dd": "0348B164",
|
"normal_dd": "0348B194",
|
||||||
"normal_no_dd": "0348B16C",
|
"normal_no_dd": "0348B19C",
|
||||||
"object_slots": "0348C244",
|
"object_slots": "0348C274",
|
||||||
"pending_freezes": "0348B1D0",
|
"pending_freezes": "0348B200",
|
||||||
"pending_item_queue": "0348B22C",
|
"pending_item_queue": "0348B25C",
|
||||||
"quest_items_sprite": "0348A078",
|
"quest_items_sprite": "0348A0A8",
|
||||||
"rupee_colors": "03489E30",
|
"rupee_colors": "03489E60",
|
||||||
"satisified_pending_frames": "0348B1A0",
|
"satisified_pending_frames": "0348B1D0",
|
||||||
"scene_fog_distance": "0348B0F8",
|
"scene_fog_distance": "0348B128",
|
||||||
"setup_db": "0348A0B8",
|
"setup_db": "0348A0E8",
|
||||||
"song_note_sprite": "0348A038",
|
"song_note_sprite": "0348A068",
|
||||||
"stones_sprite": "0348A0A8",
|
"stones_sprite": "0348A0D8",
|
||||||
"text_cursor_border_base": "0348B114",
|
"text_cursor_border_base": "0348B144",
|
||||||
"text_cursor_border_max": "0348B110",
|
"text_cursor_border_max": "0348B140",
|
||||||
"text_cursor_inner_base": "0348B11C",
|
"text_cursor_inner_base": "0348B14C",
|
||||||
"text_cursor_inner_max": "0348B118",
|
"text_cursor_inner_max": "0348B148",
|
||||||
"triforce_hunt_enabled": "0348B1E0",
|
"triforce_hunt_enabled": "0348B210",
|
||||||
"triforce_pieces_requied": "0348B182",
|
"triforce_pieces_requied": "0348B1B2",
|
||||||
"triforce_sprite": "0348A048"
|
"triforce_sprite": "0348A078"
|
||||||
}
|
}
|
Loading…
Reference in New Issue