815 lines
33 KiB
Python
815 lines
33 KiB
Python
|
from .Utils import data_path, __version__
|
||
|
from .Colors import *
|
||
|
import logging
|
||
|
import worlds.oot.Music as music
|
||
|
import worlds.oot.Sounds as sfx
|
||
|
import worlds.oot.IconManip as icon
|
||
|
from .JSONDump import dump_obj, CollapseList, CollapseDict, AlignedDict, SortedDict
|
||
|
import json
|
||
|
|
||
|
logger = logging.getLogger('')
|
||
|
|
||
|
# Options are all lowercase and have underscores instead of spaces
|
||
|
# this needs to be undone for the oot generator
|
||
|
def format_cosmetic_option_result(option_result):
|
||
|
def format_word(word):
|
||
|
special_words = {
|
||
|
'nes': 'NES',
|
||
|
'gamecube': 'GameCube',
|
||
|
'of': 'of'
|
||
|
}
|
||
|
return special_words.get(word, word.capitalize())
|
||
|
words = option_result.split('_')
|
||
|
return ' '.join([format_word(word) for word in words])
|
||
|
|
||
|
|
||
|
def patch_targeting(rom, ootworld, symbols):
|
||
|
# Set default targeting option to Hold
|
||
|
if ootworld.default_targeting == 'hold':
|
||
|
rom.write_byte(0xB71E6D, 0x01)
|
||
|
else:
|
||
|
rom.write_byte(0xB71E6D, 0x00)
|
||
|
|
||
|
|
||
|
def patch_dpad(rom, ootworld, symbols):
|
||
|
# Display D-Pad HUD
|
||
|
if ootworld.display_dpad:
|
||
|
rom.write_byte(symbols['CFG_DISPLAY_DPAD'], 0x01)
|
||
|
else:
|
||
|
rom.write_byte(symbols['CFG_DISPLAY_DPAD'], 0x00)
|
||
|
|
||
|
|
||
|
|
||
|
def patch_music(rom, ootworld, symbols):
|
||
|
# patch music
|
||
|
if ootworld.background_music != 'normal' or ootworld.fanfares != 'normal':
|
||
|
music.restore_music(rom)
|
||
|
log, errors = music.randomize_music(rom, ootworld, {})
|
||
|
if errors:
|
||
|
logger.error(errors)
|
||
|
else:
|
||
|
music.restore_music(rom)
|
||
|
|
||
|
|
||
|
def patch_model_colors(rom, color, model_addresses):
|
||
|
main_addresses, dark_addresses = model_addresses
|
||
|
|
||
|
if color is None:
|
||
|
for address in main_addresses + dark_addresses:
|
||
|
original = rom.original.read_bytes(address, 3)
|
||
|
rom.write_bytes(address, original)
|
||
|
return
|
||
|
|
||
|
for address in main_addresses:
|
||
|
rom.write_bytes(address, color)
|
||
|
|
||
|
darkened_color = list(map(lambda light: int(max((light - 0x32) * 0.6, 0)), color))
|
||
|
for address in dark_addresses:
|
||
|
rom.write_bytes(address, darkened_color)
|
||
|
|
||
|
|
||
|
def patch_tunic_icon(rom, tunic, color):
|
||
|
# patch tunic icon colors
|
||
|
icon_locations = {
|
||
|
'Kokiri Tunic': 0x007FE000,
|
||
|
'Goron Tunic': 0x007FF000,
|
||
|
'Zora Tunic': 0x00800000,
|
||
|
}
|
||
|
|
||
|
if color is not None:
|
||
|
tunic_icon = icon.generate_tunic_icon(color)
|
||
|
else:
|
||
|
tunic_icon = rom.original.read_bytes(icon_locations[tunic], 0x1000)
|
||
|
|
||
|
rom.write_bytes(icon_locations[tunic], tunic_icon)
|
||
|
|
||
|
|
||
|
def patch_tunic_colors(rom, ootworld, symbols):
|
||
|
# patch tunic colors
|
||
|
tunics = [
|
||
|
('Kokiri Tunic', 'kokiri_color', 0x00B6DA38),
|
||
|
('Goron Tunic', 'goron_color', 0x00B6DA3B),
|
||
|
('Zora Tunic', 'zora_color', 0x00B6DA3E),
|
||
|
]
|
||
|
tunic_color_list = get_tunic_colors()
|
||
|
|
||
|
for tunic, tunic_setting, address in tunics:
|
||
|
tunic_option = format_cosmetic_option_result(ootworld.__dict__[tunic_setting])
|
||
|
|
||
|
# handle random
|
||
|
if tunic_option == 'Random Choice':
|
||
|
tunic_option = random.choice(tunic_color_list)
|
||
|
# handle completely random
|
||
|
if tunic_option == 'Completely Random':
|
||
|
color = generate_random_color()
|
||
|
# grab the color from the list
|
||
|
elif tunic_option in tunic_colors:
|
||
|
color = list(tunic_colors[tunic_option])
|
||
|
# build color from hex code
|
||
|
else:
|
||
|
color = hex_to_color(tunic_option)
|
||
|
tunic_option = 'Custom'
|
||
|
# "Weird" weirdshots will crash if the Kokiri Tunic Green value is > 0x99. Brickwall it.
|
||
|
if ootworld.logic_rules != 'glitchless' and tunic == 'Kokiri Tunic':
|
||
|
color[1] = min(color[1],0x98)
|
||
|
rom.write_bytes(address, color)
|
||
|
|
||
|
# patch the tunic icon
|
||
|
if [tunic, tunic_option] not in [['Kokiri Tunic', 'Kokiri Green'], ['Goron Tunic', 'Goron Red'], ['Zora Tunic', 'Zora Blue']]:
|
||
|
patch_tunic_icon(rom, tunic, color)
|
||
|
else:
|
||
|
patch_tunic_icon(rom, tunic, None)
|
||
|
|
||
|
|
||
|
def patch_navi_colors(rom, ootworld, symbols):
|
||
|
# patch navi colors
|
||
|
navi = [
|
||
|
# colors for Navi
|
||
|
('Navi Idle', 'navi_color_default',
|
||
|
[0x00B5E184], # Default (Player)
|
||
|
symbols.get('CFG_RAINBOW_NAVI_IDLE_INNER_ENABLED', None), symbols.get('CFG_RAINBOW_NAVI_IDLE_OUTER_ENABLED', None)),
|
||
|
('Navi Targeting Enemy', 'navi_color_enemy',
|
||
|
[0x00B5E19C, 0x00B5E1BC], # Enemy, Boss
|
||
|
symbols.get('CFG_RAINBOW_NAVI_ENEMY_INNER_ENABLED', None), symbols.get('CFG_RAINBOW_NAVI_ENEMY_OUTER_ENABLED', None)),
|
||
|
('Navi Targeting NPC', 'navi_color_npc',
|
||
|
[0x00B5E194], # NPC
|
||
|
symbols.get('CFG_RAINBOW_NAVI_NPC_INNER_ENABLED', None), symbols.get('CFG_RAINBOW_NAVI_NPC_OUTER_ENABLED', None)),
|
||
|
('Navi Targeting Prop', 'navi_color_prop',
|
||
|
[0x00B5E174, 0x00B5E17C, 0x00B5E18C, 0x00B5E1A4, 0x00B5E1AC,
|
||
|
0x00B5E1B4, 0x00B5E1C4, 0x00B5E1CC, 0x00B5E1D4], # Everything else
|
||
|
symbols.get('CFG_RAINBOW_NAVI_PROP_INNER_ENABLED', None), symbols.get('CFG_RAINBOW_NAVI_PROP_OUTER_ENABLED', None)),
|
||
|
]
|
||
|
|
||
|
navi_color_list = get_navi_colors()
|
||
|
rainbow_error = None
|
||
|
|
||
|
for navi_action, navi_setting, navi_addresses, rainbow_inner_symbol, rainbow_outer_symbol in navi:
|
||
|
navi_option_inner = format_cosmetic_option_result(ootworld.__dict__[navi_setting+'_inner'])
|
||
|
navi_option_outer = format_cosmetic_option_result(ootworld.__dict__[navi_setting+'_outer'])
|
||
|
|
||
|
# choose a random choice for the whole group
|
||
|
if navi_option_inner == 'Random Choice':
|
||
|
navi_option_inner = random.choice(navi_color_list)
|
||
|
if navi_option_outer == 'Random Choice':
|
||
|
navi_option_outer = random.choice(navi_color_list)
|
||
|
|
||
|
if navi_option_outer == 'Match Inner':
|
||
|
navi_option_outer = navi_option_inner
|
||
|
|
||
|
colors = []
|
||
|
option_dict = {}
|
||
|
for address_index, address in enumerate(navi_addresses):
|
||
|
address_colors = {}
|
||
|
colors.append(address_colors)
|
||
|
for index, (navi_part, option, rainbow_symbol) in enumerate([
|
||
|
('inner', navi_option_inner, rainbow_inner_symbol),
|
||
|
('outer', navi_option_outer, rainbow_outer_symbol),
|
||
|
]):
|
||
|
color = None
|
||
|
|
||
|
# set rainbow option
|
||
|
if rainbow_symbol is not None and option == 'Rainbow':
|
||
|
rom.write_byte(rainbow_symbol, 0x01)
|
||
|
color = [0x00, 0x00, 0x00]
|
||
|
elif rainbow_symbol is not None:
|
||
|
rom.write_byte(rainbow_symbol, 0x00)
|
||
|
elif option == 'Rainbow':
|
||
|
rainbow_error = "Rainbow Navi is not supported by this patch version. Using 'Completely Random' as a substitute."
|
||
|
option = 'Completely Random'
|
||
|
|
||
|
# completely random is random for every subgroup
|
||
|
if color is None and option == 'Completely Random':
|
||
|
color = generate_random_color()
|
||
|
|
||
|
# grab the color from the list
|
||
|
if color is None and option in NaviColors:
|
||
|
color = list(NaviColors[option][index])
|
||
|
|
||
|
# build color from hex code
|
||
|
if color is None:
|
||
|
color = hex_to_color(option)
|
||
|
option = 'Custom'
|
||
|
|
||
|
# Check color validity
|
||
|
if color is None:
|
||
|
raise Exception(f'Invalid {navi_part} color {option} for {navi_action}')
|
||
|
|
||
|
address_colors[navi_part] = color
|
||
|
option_dict[navi_part] = option
|
||
|
|
||
|
# write color
|
||
|
color = address_colors['inner'] + [0xFF] + address_colors['outer'] + [0xFF]
|
||
|
rom.write_bytes(address, color)
|
||
|
|
||
|
|
||
|
if rainbow_error:
|
||
|
logger.error(rainbow_error)
|
||
|
|
||
|
|
||
|
def patch_sword_trails(rom, ootworld, symbols):
|
||
|
# patch sword trail duration
|
||
|
rom.write_byte(0x00BEFF8C, ootworld.sword_trail_duration)
|
||
|
|
||
|
# patch sword trail colors
|
||
|
sword_trails = [
|
||
|
('Sword Trail', 'sword_trail_color',
|
||
|
[(0x00BEFF7C, 0xB0, 0x40, 0xB0, 0xFF), (0x00BEFF84, 0x20, 0x00, 0x10, 0x00)],
|
||
|
symbols.get('CFG_RAINBOW_SWORD_INNER_ENABLED', None), symbols.get('CFG_RAINBOW_SWORD_OUTER_ENABLED', None)),
|
||
|
]
|
||
|
|
||
|
sword_trail_color_list = get_sword_trail_colors()
|
||
|
rainbow_error = None
|
||
|
|
||
|
for trail_name, trail_setting, trail_addresses, rainbow_inner_symbol, rainbow_outer_symbol in sword_trails:
|
||
|
option_inner = format_cosmetic_option_result(ootworld.__dict__[trail_setting+'_inner'])
|
||
|
option_outer = format_cosmetic_option_result(ootworld.__dict__[trail_setting+'_outer'])
|
||
|
|
||
|
# handle random choice
|
||
|
if option_inner == 'Random Choice':
|
||
|
option_inner = random.choice(sword_trail_color_list)
|
||
|
if option_outer == 'Random Choice':
|
||
|
option_outer = random.choice(sword_trail_color_list)
|
||
|
|
||
|
if option_outer == 'Match Inner':
|
||
|
option_outer = option_inner
|
||
|
|
||
|
colors = []
|
||
|
option_dict = {}
|
||
|
for address_index, (address, inner_transparency, inner_white_transparency, outer_transparency, outer_white_transparency) in enumerate(trail_addresses):
|
||
|
address_colors = {}
|
||
|
colors.append(address_colors)
|
||
|
transparency_dict = {}
|
||
|
for index, (trail_part, option, rainbow_symbol, white_transparency, transparency) in enumerate([
|
||
|
('inner', option_inner, rainbow_inner_symbol, inner_white_transparency, inner_transparency),
|
||
|
('outer', option_outer, rainbow_outer_symbol, outer_white_transparency, outer_transparency),
|
||
|
]):
|
||
|
color = None
|
||
|
|
||
|
# set rainbow option
|
||
|
if rainbow_symbol is not None and option == 'Rainbow':
|
||
|
rom.write_byte(rainbow_symbol, 0x01)
|
||
|
color = [0x00, 0x00, 0x00]
|
||
|
elif rainbow_symbol is not None:
|
||
|
rom.write_byte(rainbow_symbol, 0x00)
|
||
|
elif option == 'Rainbow':
|
||
|
rainbow_error = "Rainbow Sword Trail is not supported by this patch version. Using 'Completely Random' as a substitute."
|
||
|
option = 'Completely Random'
|
||
|
|
||
|
# completely random is random for every subgroup
|
||
|
if color is None and option == 'Completely Random':
|
||
|
color = generate_random_color()
|
||
|
|
||
|
# grab the color from the list
|
||
|
if color is None and option in sword_trail_colors:
|
||
|
color = list(sword_trail_colors[option])
|
||
|
|
||
|
# build color from hex code
|
||
|
if color is None:
|
||
|
color = hex_to_color(option)
|
||
|
option = 'Custom'
|
||
|
|
||
|
# Check color validity
|
||
|
if color is None:
|
||
|
raise Exception(f'Invalid {trail_part} color {option} for {trail_name}')
|
||
|
|
||
|
# handle white transparency
|
||
|
if option == 'White':
|
||
|
transparency_dict[trail_part] = white_transparency
|
||
|
else:
|
||
|
transparency_dict[trail_part] = transparency
|
||
|
|
||
|
address_colors[trail_part] = color
|
||
|
option_dict[trail_part] = option
|
||
|
|
||
|
# write color
|
||
|
color = address_colors['outer'] + [transparency_dict['outer']] + address_colors['inner'] + [transparency_dict['inner']]
|
||
|
rom.write_bytes(address, color)
|
||
|
|
||
|
if rainbow_error:
|
||
|
logger.error(rainbow_error)
|
||
|
|
||
|
|
||
|
def patch_bombchu_trails(rom, ootworld, symbols):
|
||
|
# patch bombchu trail colors
|
||
|
bombchu_trails = [
|
||
|
('Bombchu Trail', 'bombchu_trail_color', get_bombchu_trail_colors(), bombchu_trail_colors,
|
||
|
(symbols['CFG_BOMBCHU_TRAIL_INNER_COLOR'], symbols['CFG_BOMBCHU_TRAIL_OUTER_COLOR'],
|
||
|
symbols['CFG_RAINBOW_BOMBCHU_TRAIL_INNER_ENABLED'], symbols['CFG_RAINBOW_BOMBCHU_TRAIL_OUTER_ENABLED'])),
|
||
|
]
|
||
|
|
||
|
patch_trails(rom, ootworld, bombchu_trails)
|
||
|
|
||
|
|
||
|
def patch_boomerang_trails(rom, ootworld, symbols):
|
||
|
# patch boomerang trail colors
|
||
|
boomerang_trails = [
|
||
|
('Boomerang Trail', 'boomerang_trail_color', get_boomerang_trail_colors(), boomerang_trail_colors,
|
||
|
(symbols['CFG_BOOM_TRAIL_INNER_COLOR'], symbols['CFG_BOOM_TRAIL_OUTER_COLOR'],
|
||
|
symbols['CFG_RAINBOW_BOOM_TRAIL_INNER_ENABLED'], symbols['CFG_RAINBOW_BOOM_TRAIL_OUTER_ENABLED'])),
|
||
|
]
|
||
|
|
||
|
patch_trails(rom, ootworld, boomerang_trails)
|
||
|
|
||
|
|
||
|
def patch_trails(rom, ootworld, trails):
|
||
|
for trail_name, trail_setting, trail_color_list, trail_color_dict, trail_symbols in trails:
|
||
|
color_inner_symbol, color_outer_symbol, rainbow_inner_symbol, rainbow_outer_symbol = trail_symbols
|
||
|
option_inner = format_cosmetic_option_result(ootworld.__dict__[trail_setting+'_inner'])
|
||
|
option_outer = format_cosmetic_option_result(ootworld.__dict__[trail_setting+'_outer'])
|
||
|
|
||
|
# handle random choice
|
||
|
if option_inner == 'Random Choice':
|
||
|
option_inner = random.choice(trail_color_list)
|
||
|
if option_outer == 'Random Choice':
|
||
|
option_outer = random.choice(trail_color_list)
|
||
|
|
||
|
if option_outer == 'Match Inner':
|
||
|
option_outer = option_inner
|
||
|
|
||
|
option_dict = {}
|
||
|
colors = {}
|
||
|
|
||
|
for index, (trail_part, option, rainbow_symbol, color_symbol) in enumerate([
|
||
|
('inner', option_inner, rainbow_inner_symbol, color_inner_symbol),
|
||
|
('outer', option_outer, rainbow_outer_symbol, color_outer_symbol),
|
||
|
]):
|
||
|
color = None
|
||
|
|
||
|
# set rainbow option
|
||
|
if option == 'Rainbow':
|
||
|
rom.write_byte(rainbow_symbol, 0x01)
|
||
|
color = [0x00, 0x00, 0x00]
|
||
|
else:
|
||
|
rom.write_byte(rainbow_symbol, 0x00)
|
||
|
|
||
|
# handle completely random
|
||
|
if color is None and option == 'Completely Random':
|
||
|
# Specific handling for inner bombchu trails for contrast purposes.
|
||
|
if trail_name == 'Bombchu Trail' and trail_part == 'inner':
|
||
|
fixed_dark_color = [0, 0, 0]
|
||
|
color = [0, 0, 0]
|
||
|
# Avoid colors which have a low contrast so the bombchu ticking is still visible
|
||
|
while contrast_ratio(color, fixed_dark_color) <= 4:
|
||
|
color = generate_random_color()
|
||
|
else:
|
||
|
color = generate_random_color()
|
||
|
|
||
|
# grab the color from the list
|
||
|
if color is None and option in trail_color_dict:
|
||
|
color = list(trail_color_dict[option])
|
||
|
|
||
|
# build color from hex code
|
||
|
if color is None:
|
||
|
color = hex_to_color(option)
|
||
|
option = 'Custom'
|
||
|
|
||
|
option_dict[trail_part] = option
|
||
|
colors[trail_part] = color
|
||
|
|
||
|
# write color
|
||
|
rom.write_bytes(color_symbol, color)
|
||
|
|
||
|
|
||
|
|
||
|
def patch_gauntlet_colors(rom, ootworld, symbols):
|
||
|
# patch gauntlet colors
|
||
|
gauntlets = [
|
||
|
('Silver Gauntlets', 'silver_gauntlets_color', 0x00B6DA44,
|
||
|
([0x173B4CC], [0x173B4D4, 0x173B50C, 0x173B514])), # GI Model DList colors
|
||
|
('Gold Gauntlets', 'golden_gauntlets_color', 0x00B6DA47,
|
||
|
([0x173B4EC], [0x173B4F4, 0x173B52C, 0x173B534])), # GI Model DList colors
|
||
|
]
|
||
|
gauntlet_color_list = get_gauntlet_colors()
|
||
|
|
||
|
for gauntlet, gauntlet_setting, address, model_addresses in gauntlets:
|
||
|
gauntlet_option = format_cosmetic_option_result(ootworld.__dict__[gauntlet_setting])
|
||
|
|
||
|
# handle random
|
||
|
if gauntlet_option == 'Random Choice':
|
||
|
gauntlet_option = random.choice(gauntlet_color_list)
|
||
|
# handle completely random
|
||
|
if gauntlet_option == 'Completely Random':
|
||
|
color = generate_random_color()
|
||
|
# grab the color from the list
|
||
|
elif gauntlet_option in gauntlet_colors:
|
||
|
color = list(gauntlet_colors[gauntlet_option])
|
||
|
# build color from hex code
|
||
|
else:
|
||
|
color = hex_to_color(gauntlet_option)
|
||
|
gauntlet_option = 'Custom'
|
||
|
rom.write_bytes(address, color)
|
||
|
if ootworld.correct_model_colors:
|
||
|
patch_model_colors(rom, color, model_addresses)
|
||
|
else:
|
||
|
patch_model_colors(rom, None, model_addresses)
|
||
|
|
||
|
def patch_shield_frame_colors(rom, ootworld, symbols):
|
||
|
# patch shield frame colors
|
||
|
shield_frames = [
|
||
|
('Mirror Shield Frame', 'mirror_shield_frame_color',
|
||
|
[0xFA7274, 0xFA776C, 0xFAA27C, 0xFAC564, 0xFAC984, 0xFAEDD4],
|
||
|
([0x1616FCC], [0x1616FD4])),
|
||
|
]
|
||
|
shield_frame_color_list = get_shield_frame_colors()
|
||
|
|
||
|
for shield_frame, shield_frame_setting, addresses, model_addresses in shield_frames:
|
||
|
shield_frame_option = format_cosmetic_option_result(ootworld.__dict__[shield_frame_setting])
|
||
|
|
||
|
# handle random
|
||
|
if shield_frame_option == 'Random Choice':
|
||
|
shield_frame_option = random.choice(shield_frame_color_list)
|
||
|
# handle completely random
|
||
|
if shield_frame_option == 'Completely Random':
|
||
|
color = [random.getrandbits(8), random.getrandbits(8), random.getrandbits(8)]
|
||
|
# grab the color from the list
|
||
|
elif shield_frame_option in shield_frame_colors:
|
||
|
color = list(shield_frame_colors[shield_frame_option])
|
||
|
# build color from hex code
|
||
|
else:
|
||
|
color = hex_to_color(shield_frame_option)
|
||
|
shield_frame_option = 'Custom'
|
||
|
for address in addresses:
|
||
|
rom.write_bytes(address, color)
|
||
|
if ootworld.correct_model_colors and shield_frame_option != 'Red':
|
||
|
patch_model_colors(rom, color, model_addresses)
|
||
|
else:
|
||
|
patch_model_colors(rom, None, model_addresses)
|
||
|
|
||
|
|
||
|
def patch_heart_colors(rom, ootworld, symbols):
|
||
|
# patch heart colors
|
||
|
hearts = [
|
||
|
('Heart Color', 'heart_color', symbols['CFG_HEART_COLOR'], 0xBB0994,
|
||
|
([0x14DA474, 0x14DA594, 0x14B701C, 0x14B70DC],
|
||
|
[0x14B70FC, 0x14DA494, 0x14DA5B4, 0x14B700C, 0x14B702C, 0x14B703C, 0x14B704C, 0x14B705C,
|
||
|
0x14B706C, 0x14B707C, 0x14B708C, 0x14B709C, 0x14B70AC, 0x14B70BC, 0x14B70CC])), # GI Model DList colors
|
||
|
]
|
||
|
heart_color_list = get_heart_colors()
|
||
|
|
||
|
for heart, heart_setting, symbol, file_select_address, model_addresses in hearts:
|
||
|
heart_option = format_cosmetic_option_result(ootworld.__dict__[heart_setting])
|
||
|
|
||
|
# handle random
|
||
|
if heart_option == 'Random Choice':
|
||
|
heart_option = random.choice(heart_color_list)
|
||
|
# handle completely random
|
||
|
if heart_option == 'Completely Random':
|
||
|
color = generate_random_color()
|
||
|
# grab the color from the list
|
||
|
elif heart_option in heart_colors:
|
||
|
color = list(heart_colors[heart_option])
|
||
|
# build color from hex code
|
||
|
else:
|
||
|
color = hex_to_color(heart_option)
|
||
|
heart_option = 'Custom'
|
||
|
rom.write_int16s(symbol, color) # symbol for ingame HUD
|
||
|
rom.write_int16s(file_select_address, color) # file select normal hearts
|
||
|
if heart_option != 'Red':
|
||
|
rom.write_int16s(file_select_address + 6, color) # file select DD hearts
|
||
|
else:
|
||
|
original_dd_color = rom.original.read_bytes(file_select_address + 6, 6)
|
||
|
rom.write_bytes(file_select_address + 6, original_dd_color)
|
||
|
if ootworld.correct_model_colors and heart_option != 'Red':
|
||
|
patch_model_colors(rom, color, model_addresses) # heart model colors
|
||
|
icon.patch_overworld_icon(rom, color, 0xF43D80) # Overworld Heart Icon
|
||
|
else:
|
||
|
patch_model_colors(rom, None, model_addresses)
|
||
|
icon.patch_overworld_icon(rom, None, 0xF43D80)
|
||
|
|
||
|
def patch_magic_colors(rom, ootworld, symbols):
|
||
|
# patch magic colors
|
||
|
magic = [
|
||
|
('Magic Meter Color', 'magic_color', symbols["CFG_MAGIC_COLOR"],
|
||
|
([0x154C654, 0x154CFB4], [0x154C65C, 0x154CFBC])), # GI Model DList colors
|
||
|
]
|
||
|
magic_color_list = get_magic_colors()
|
||
|
|
||
|
for magic_color, magic_setting, symbol, model_addresses in magic:
|
||
|
magic_option = format_cosmetic_option_result(ootworld.__dict__[magic_setting])
|
||
|
|
||
|
if magic_option == 'Random Choice':
|
||
|
magic_option = random.choice(magic_color_list)
|
||
|
|
||
|
if magic_option == 'Completely Random':
|
||
|
color = generate_random_color()
|
||
|
elif magic_option in magic_colors:
|
||
|
color = list(magic_colors[magic_option])
|
||
|
else:
|
||
|
color = hex_to_color(magic_option)
|
||
|
magic_option = 'Custom'
|
||
|
rom.write_int16s(symbol, color)
|
||
|
if magic_option != 'Green' and ootworld.correct_model_colors:
|
||
|
patch_model_colors(rom, color, model_addresses)
|
||
|
icon.patch_overworld_icon(rom, color, 0xF45650, data_path('icons/magicSmallExtras.raw')) # Overworld Small Pot
|
||
|
icon.patch_overworld_icon(rom, color, 0xF47650, data_path('icons/magicLargeExtras.raw')) # Overworld Big Pot
|
||
|
else:
|
||
|
patch_model_colors(rom, None, model_addresses)
|
||
|
icon.patch_overworld_icon(rom, None, 0xF45650)
|
||
|
icon.patch_overworld_icon(rom, None, 0xF47650)
|
||
|
|
||
|
def patch_button_colors(rom, ootworld, symbols):
|
||
|
buttons = [
|
||
|
('A Button Color', 'a_button_color', a_button_colors,
|
||
|
[('A Button Color', symbols['CFG_A_BUTTON_COLOR'],
|
||
|
None),
|
||
|
('Text Cursor Color', symbols['CFG_TEXT_CURSOR_COLOR'],
|
||
|
[(0xB88E81, 0xB88E85, 0xB88E9)]), # Initial Inner Color
|
||
|
('Shop Cursor Color', symbols['CFG_SHOP_CURSOR_COLOR'],
|
||
|
None),
|
||
|
('Save/Death Cursor Color', None,
|
||
|
[(0xBBEBC2, 0xBBEBC3, 0xBBEBD6), (0xBBEDDA, 0xBBEDDB, 0xBBEDDE)]), # Save Cursor / Death Cursor
|
||
|
('Pause Menu A Cursor Color', None,
|
||
|
[(0xBC7849, 0xBC784B, 0xBC784D), (0xBC78A9, 0xBC78AB, 0xBC78AD), (0xBC78BB, 0xBC78BD, 0xBC78BF)]), # Inner / Pulse 1 / Pulse 2
|
||
|
('Pause Menu A Icon Color', None,
|
||
|
[(0x845754, 0x845755, 0x845756)]),
|
||
|
('A Note Color', symbols['CFG_A_NOTE_COLOR'], # For Textbox Song Display
|
||
|
[(0xBB299A, 0xBB299B, 0xBB299E), (0xBB2C8E, 0xBB2C8F, 0xBB2C92), (0xBB2F8A, 0xBB2F8B, 0xBB2F96)]), # Pause Menu Song Display
|
||
|
]),
|
||
|
('B Button Color', 'b_button_color', b_button_colors,
|
||
|
[('B Button Color', symbols['CFG_B_BUTTON_COLOR'],
|
||
|
None),
|
||
|
]),
|
||
|
('C Button Color', 'c_button_color', c_button_colors,
|
||
|
[('C Button Color', symbols['CFG_C_BUTTON_COLOR'],
|
||
|
None),
|
||
|
('Pause Menu C Cursor Color', None,
|
||
|
[(0xBC7843, 0xBC7845, 0xBC7847), (0xBC7891, 0xBC7893, 0xBC7895), (0xBC78A3, 0xBC78A5, 0xBC78A7)]), # Inner / Pulse 1 / Pulse 2
|
||
|
('Pause Menu C Icon Color', None,
|
||
|
[(0x8456FC, 0x8456FD, 0x8456FE)]),
|
||
|
('C Note Color', symbols['CFG_C_NOTE_COLOR'], # For Textbox Song Display
|
||
|
[(0xBB2996, 0xBB2997, 0xBB29A2), (0xBB2C8A, 0xBB2C8B, 0xBB2C96), (0xBB2F86, 0xBB2F87, 0xBB2F9A)]), # Pause Menu Song Display
|
||
|
]),
|
||
|
('Start Button Color', 'start_button_color', start_button_colors,
|
||
|
[('Start Button Color', None,
|
||
|
[(0xAE9EC6, 0xAE9EC7, 0xAE9EDA)]),
|
||
|
]),
|
||
|
]
|
||
|
|
||
|
for button, button_setting, button_colors, patches in buttons:
|
||
|
button_option = format_cosmetic_option_result(ootworld.__dict__[button_setting])
|
||
|
color_set = None
|
||
|
colors = {}
|
||
|
|
||
|
# handle random
|
||
|
if button_option == 'Random Choice':
|
||
|
button_option = random.choice(list(button_colors.keys()))
|
||
|
# handle completely random
|
||
|
if button_option == 'Completely Random':
|
||
|
fixed_font_color = [10, 10, 10]
|
||
|
color = [0, 0, 0]
|
||
|
# Avoid colors which have a low contrast with the font inside buttons (eg. the A letter)
|
||
|
while contrast_ratio(color, fixed_font_color) <= 3:
|
||
|
color = generate_random_color()
|
||
|
# grab the color from the list
|
||
|
elif button_option in button_colors:
|
||
|
color_set = [button_colors[button_option]] if isinstance(button_colors[button_option][0], int) else list(button_colors[button_option])
|
||
|
color = color_set[0]
|
||
|
# build color from hex code
|
||
|
else:
|
||
|
color = hex_to_color(button_option)
|
||
|
button_option = 'Custom'
|
||
|
|
||
|
# apply all button color patches
|
||
|
for i, (patch, symbol, byte_addresses) in enumerate(patches):
|
||
|
if color_set is not None and len(color_set) > i and color_set[i]:
|
||
|
colors[patch] = color_set[i]
|
||
|
else:
|
||
|
colors[patch] = color
|
||
|
|
||
|
if symbol:
|
||
|
rom.write_int16s(symbol, colors[patch])
|
||
|
|
||
|
if byte_addresses:
|
||
|
for r_addr, g_addr, b_addr in byte_addresses:
|
||
|
rom.write_byte(r_addr, colors[patch][0])
|
||
|
rom.write_byte(g_addr, colors[patch][1])
|
||
|
rom.write_byte(b_addr, colors[patch][2])
|
||
|
|
||
|
|
||
|
def patch_sfx(rom, ootworld, symbols):
|
||
|
# Configurable Sound Effects
|
||
|
sfx_config = [
|
||
|
('sfx_navi_overworld', sfx.SoundHooks.NAVI_OVERWORLD),
|
||
|
('sfx_navi_enemy', sfx.SoundHooks.NAVI_ENEMY),
|
||
|
('sfx_low_hp', sfx.SoundHooks.HP_LOW),
|
||
|
('sfx_menu_cursor', sfx.SoundHooks.MENU_CURSOR),
|
||
|
('sfx_menu_select', sfx.SoundHooks.MENU_SELECT),
|
||
|
('sfx_nightfall', sfx.SoundHooks.NIGHTFALL),
|
||
|
('sfx_horse_neigh', sfx.SoundHooks.HORSE_NEIGH),
|
||
|
('sfx_hover_boots', sfx.SoundHooks.BOOTS_HOVER),
|
||
|
]
|
||
|
sound_dict = sfx.get_patch_dict()
|
||
|
sounds_keyword_label = {sound.value.keyword: sound.value.label for sound in sfx.Sounds}
|
||
|
sounds_label_keyword = {sound.value.label: sound.value.keyword for sound in sfx.Sounds}
|
||
|
|
||
|
for setting, hook in sfx_config:
|
||
|
selection = ootworld.__dict__[setting].replace('_', '-')
|
||
|
|
||
|
if selection == 'default':
|
||
|
for loc in hook.value.locations:
|
||
|
sound_id = rom.original.read_int16(loc)
|
||
|
rom.write_int16(loc, sound_id)
|
||
|
else:
|
||
|
if selection == 'random-choice':
|
||
|
selection = random.choice(sfx.get_hook_pool(hook)).value.keyword
|
||
|
elif selection == 'random-ear-safe':
|
||
|
selection = random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword
|
||
|
elif selection == 'completely-random':
|
||
|
selection = random.choice(sfx.standard).value.keyword
|
||
|
sound_id = sound_dict[selection]
|
||
|
for loc in hook.value.locations:
|
||
|
rom.write_int16(loc, sound_id)
|
||
|
|
||
|
|
||
|
|
||
|
def patch_instrument(rom, ootworld, symbols):
|
||
|
# Player Instrument
|
||
|
instruments = {
|
||
|
#'none': 0x00,
|
||
|
'ocarina': 0x01,
|
||
|
'malon': 0x02,
|
||
|
'whistle': 0x03,
|
||
|
'harp': 0x04,
|
||
|
'grind_organ': 0x05,
|
||
|
'flute': 0x06,
|
||
|
#'another_ocarina': 0x07,
|
||
|
}
|
||
|
|
||
|
choice = ootworld.sfx_ocarina
|
||
|
if choice == 'random-choice':
|
||
|
choice = random.choice(list(instruments.keys()))
|
||
|
|
||
|
rom.write_byte(0x00B53C7B, instruments[choice])
|
||
|
rom.write_byte(0x00B4BF6F, instruments[choice]) # For Lost Woods Skull Kids' minigame in Lost Woods
|
||
|
|
||
|
|
||
|
legacy_cosmetic_data_headers = [
|
||
|
0x03481000,
|
||
|
0x03480810,
|
||
|
]
|
||
|
|
||
|
global_patch_sets = [
|
||
|
patch_targeting,
|
||
|
patch_music,
|
||
|
patch_tunic_colors,
|
||
|
patch_navi_colors,
|
||
|
patch_sword_trails,
|
||
|
patch_gauntlet_colors,
|
||
|
patch_shield_frame_colors,
|
||
|
patch_sfx,
|
||
|
patch_instrument,
|
||
|
]
|
||
|
|
||
|
patch_sets = {
|
||
|
0x1F04FA62: {
|
||
|
"patches": [
|
||
|
patch_dpad,
|
||
|
patch_sword_trails,
|
||
|
],
|
||
|
"symbols": {
|
||
|
"CFG_DISPLAY_DPAD": 0x0004,
|
||
|
"CFG_RAINBOW_SWORD_INNER_ENABLED": 0x0005,
|
||
|
"CFG_RAINBOW_SWORD_OUTER_ENABLED": 0x0006,
|
||
|
},
|
||
|
},
|
||
|
0x1F05D3F9: {
|
||
|
"patches": [
|
||
|
patch_dpad,
|
||
|
patch_sword_trails,
|
||
|
],
|
||
|
"symbols": {
|
||
|
"CFG_DISPLAY_DPAD": 0x0004,
|
||
|
"CFG_RAINBOW_SWORD_INNER_ENABLED": 0x0005,
|
||
|
"CFG_RAINBOW_SWORD_OUTER_ENABLED": 0x0006,
|
||
|
},
|
||
|
},
|
||
|
0x1F0693FB: {
|
||
|
"patches": [
|
||
|
patch_dpad,
|
||
|
patch_sword_trails,
|
||
|
patch_heart_colors,
|
||
|
patch_magic_colors,
|
||
|
],
|
||
|
"symbols": {
|
||
|
"CFG_MAGIC_COLOR": 0x0004,
|
||
|
"CFG_HEART_COLOR": 0x000A,
|
||
|
"CFG_DISPLAY_DPAD": 0x0010,
|
||
|
"CFG_RAINBOW_SWORD_INNER_ENABLED": 0x0011,
|
||
|
"CFG_RAINBOW_SWORD_OUTER_ENABLED": 0x0012,
|
||
|
}
|
||
|
},
|
||
|
0x1F073FC9: {
|
||
|
"patches": [
|
||
|
patch_dpad,
|
||
|
patch_sword_trails,
|
||
|
patch_heart_colors,
|
||
|
patch_magic_colors,
|
||
|
patch_button_colors,
|
||
|
],
|
||
|
"symbols": {
|
||
|
"CFG_MAGIC_COLOR": 0x0004,
|
||
|
"CFG_HEART_COLOR": 0x000A,
|
||
|
"CFG_A_BUTTON_COLOR": 0x0010,
|
||
|
"CFG_B_BUTTON_COLOR": 0x0016,
|
||
|
"CFG_C_BUTTON_COLOR": 0x001C,
|
||
|
"CFG_TEXT_CURSOR_COLOR": 0x0022,
|
||
|
"CFG_SHOP_CURSOR_COLOR": 0x0028,
|
||
|
"CFG_A_NOTE_COLOR": 0x002E,
|
||
|
"CFG_C_NOTE_COLOR": 0x0034,
|
||
|
"CFG_DISPLAY_DPAD": 0x003A,
|
||
|
"CFG_RAINBOW_SWORD_INNER_ENABLED": 0x003B,
|
||
|
"CFG_RAINBOW_SWORD_OUTER_ENABLED": 0x003C,
|
||
|
}
|
||
|
},
|
||
|
0x1F073FD8: {
|
||
|
"patches": [
|
||
|
patch_dpad,
|
||
|
patch_navi_colors,
|
||
|
patch_sword_trails,
|
||
|
patch_heart_colors,
|
||
|
patch_magic_colors,
|
||
|
patch_button_colors,
|
||
|
patch_boomerang_trails,
|
||
|
patch_bombchu_trails,
|
||
|
],
|
||
|
"symbols": {
|
||
|
"CFG_MAGIC_COLOR": 0x0004,
|
||
|
"CFG_HEART_COLOR": 0x000A,
|
||
|
"CFG_A_BUTTON_COLOR": 0x0010,
|
||
|
"CFG_B_BUTTON_COLOR": 0x0016,
|
||
|
"CFG_C_BUTTON_COLOR": 0x001C,
|
||
|
"CFG_TEXT_CURSOR_COLOR": 0x0022,
|
||
|
"CFG_SHOP_CURSOR_COLOR": 0x0028,
|
||
|
"CFG_A_NOTE_COLOR": 0x002E,
|
||
|
"CFG_C_NOTE_COLOR": 0x0034,
|
||
|
"CFG_BOOM_TRAIL_INNER_COLOR": 0x003A,
|
||
|
"CFG_BOOM_TRAIL_OUTER_COLOR": 0x003D,
|
||
|
"CFG_BOMBCHU_TRAIL_INNER_COLOR": 0x0040,
|
||
|
"CFG_BOMBCHU_TRAIL_OUTER_COLOR": 0x0043,
|
||
|
"CFG_DISPLAY_DPAD": 0x0046,
|
||
|
"CFG_RAINBOW_SWORD_INNER_ENABLED": 0x0047,
|
||
|
"CFG_RAINBOW_SWORD_OUTER_ENABLED": 0x0048,
|
||
|
"CFG_RAINBOW_BOOM_TRAIL_INNER_ENABLED": 0x0049,
|
||
|
"CFG_RAINBOW_BOOM_TRAIL_OUTER_ENABLED": 0x004A,
|
||
|
"CFG_RAINBOW_BOMBCHU_TRAIL_INNER_ENABLED": 0x004B,
|
||
|
"CFG_RAINBOW_BOMBCHU_TRAIL_OUTER_ENABLED": 0x004C,
|
||
|
"CFG_RAINBOW_NAVI_IDLE_INNER_ENABLED": 0x004D,
|
||
|
"CFG_RAINBOW_NAVI_IDLE_OUTER_ENABLED": 0x004E,
|
||
|
"CFG_RAINBOW_NAVI_ENEMY_INNER_ENABLED": 0x004F,
|
||
|
"CFG_RAINBOW_NAVI_ENEMY_OUTER_ENABLED": 0x0050,
|
||
|
"CFG_RAINBOW_NAVI_NPC_INNER_ENABLED": 0x0051,
|
||
|
"CFG_RAINBOW_NAVI_NPC_OUTER_ENABLED": 0x0052,
|
||
|
"CFG_RAINBOW_NAVI_PROP_INNER_ENABLED": 0x0053,
|
||
|
"CFG_RAINBOW_NAVI_PROP_OUTER_ENABLED": 0x0054,
|
||
|
}
|
||
|
},
|
||
|
}
|
||
|
|
||
|
|
||
|
def patch_cosmetics(ootworld, rom):
|
||
|
# Use the world's slot seed for cosmetics
|
||
|
random.seed(ootworld.world.slot_seeds[ootworld.player])
|
||
|
|
||
|
# try to detect the cosmetic patch data format
|
||
|
versioned_patch_set = None
|
||
|
cosmetic_context = rom.read_int32(rom.sym('RANDO_CONTEXT') + 4)
|
||
|
if cosmetic_context >= 0x80000000 and cosmetic_context <= 0x80F7FFFC:
|
||
|
cosmetic_context = (cosmetic_context - 0x80400000) + 0x3480000 # convert from RAM to ROM address
|
||
|
cosmetic_version = rom.read_int32(cosmetic_context)
|
||
|
versioned_patch_set = patch_sets.get(cosmetic_version)
|
||
|
else:
|
||
|
# If cosmetic_context is not a valid pointer, then try to
|
||
|
# search over all possible legacy header locations.
|
||
|
for header in legacy_cosmetic_data_headers:
|
||
|
cosmetic_context = header
|
||
|
cosmetic_version = rom.read_int32(cosmetic_context)
|
||
|
if cosmetic_version in patch_sets:
|
||
|
versioned_patch_set = patch_sets[cosmetic_version]
|
||
|
break
|
||
|
|
||
|
# patch version specific patches
|
||
|
if versioned_patch_set:
|
||
|
# offset the cosmetic_context struct for absolute addressing
|
||
|
cosmetic_context_symbols = {
|
||
|
sym: address + cosmetic_context
|
||
|
for sym, address in versioned_patch_set['symbols'].items()
|
||
|
}
|
||
|
|
||
|
# warn if patching a legacy format
|
||
|
if cosmetic_version != rom.read_int32(rom.sym('COSMETIC_FORMAT_VERSION')):
|
||
|
logger.error("ROM uses old cosmetic patch format.")
|
||
|
|
||
|
# patch cosmetics that use vanilla oot data, and always compatible
|
||
|
for patch_func in [patch for patch in global_patch_sets if patch not in versioned_patch_set['patches']]:
|
||
|
patch_func(rom, ootworld, {})
|
||
|
|
||
|
for patch_func in versioned_patch_set['patches']:
|
||
|
patch_func(rom, ootworld, cosmetic_context_symbols)
|
||
|
else:
|
||
|
# patch cosmetics that use vanilla oot data, and always compatible
|
||
|
for patch_func in global_patch_sets:
|
||
|
patch_func(rom, ootworld, {})
|
||
|
|
||
|
# Unknown patch format
|
||
|
logger.error("Unable to patch some cosmetics. ROM uses unknown cosmetic patch format.")
|