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)
|
||||
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)
|
||||
|
|
|
@ -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/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
|
||||
|
|
|
@ -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
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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}')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
@ -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"
|
||||
}
|
Loading…
Reference in New Issue