Implemented Custom Goal Framework

This commit is contained in:
codemann8
2025-10-29 00:20:41 -05:00
parent ba444f4bbc
commit fdbe9cf9fd
9 changed files with 629 additions and 100 deletions

152
Rom.py
View File

@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'e20f407ef55da945f893d32ee6fc541d'
RANDOMIZERBASEHASH = 'b7817fb00fb0a918a7fa275ff8f4c3be'
class JsonRom(object):
@@ -1218,8 +1218,6 @@ def patch_rom(world, rom, player, team, is_mystery=False):
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
if world.is_pyramid_open(player):
rom.initial_sram.pre_open_pyramid_hole()
if world.crystals_needed_for_gt[player] == 0:
rom.initial_sram.pre_open_ganons_tower()
rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped
rom.write_byte(0xF5D73, 0xF0) # bees are catchable
rom.write_byte(0xF5F10, 0xF0) # bees are catchable
@@ -1247,22 +1245,105 @@ def patch_rom(world, rom, player, team, is_mystery=False):
(0x02 if 'bombs' in world.escape_assist[player] else 0x00) |
(0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist
if world.goal[player] in ['pedestal', 'triforcehunt']:
rom.write_byte(0x1801A8, 0x01) # make ganon invincible
elif world.goal[player] in ['dungeons']:
rom.write_byte(0x1801A8, 0x02) # make ganon invincible until all dungeons are beat
elif world.goal[player] in ['crystals', 'trinity']:
rom.write_byte(0x1801A8, 0x04) # make ganon invincible until all crystals
elif world.goal[player] in ['ganonhunt']:
rom.write_byte(0x1801A8, 0x05) # make ganon invincible until all triforce pieces collected
elif world.goal[player] in ['completionist']:
rom.write_byte(0x1801A8, 0x0B) # make ganon invincible until everything is collected
else:
rom.write_byte(0x1801A8, 0x03) # make ganon invincible until all crystals and aga 2 are collected
gt_entry, ped_pull, ganon_goal, murah_goal = [], [], [], []
# 00: Invulnerable
# 01: All pendants
# 02: All crystals
# 03: Pendant bosses
# 04: Crystal bosses
# 05: Prize bosses
# 06: Agahnim 1 defeated
# 07: Agahnim 2 defeated
# 08: Goal items collected (ie. Triforce Pieces)
# 09: Max collection rate
# 0A: Custom goal
rom.write_byte(0x18019A, world.crystals_needed_for_gt[player])
rom.write_byte(0x1801A6, world.crystals_needed_for_ganon[player])
rom.write_byte(0x1801A2, 0x00) # ped requirement is vanilla, set to 0x1 for special requirements
def get_goal_bytes(type):
goal_bytes = []
for req in world.custom_goals[player][type]['requirements']:
goal_bytes += [req['condition']]
if req['condition'] == 0x0A:
# custom goal
goal_bytes += [req['options']]
goal_bytes += int16_as_bytes(req['address'])
if 0x08 & req['options'] == 0:
goal_bytes += [req['target']]
else:
goal_bytes += int16_as_bytes(req['target'])
elif 'target' in req:
if req['condition'] & 0x7F < 0x08:
goal_bytes += [req['target']]
else:
goal_bytes += int16_as_bytes(req['target'])
return goal_bytes
if world.custom_goals[player]['gtentry'] and 'requirements' in world.custom_goals[player]['gtentry']:
gt_entry += get_goal_bytes('gtentry')
else:
gt_entry += [0x02, world.crystals_needed_for_gt[player]]
if len(gt_entry) == 0 or gt_entry == [0x02, 0x00]:
rom.initial_sram.pre_open_ganons_tower()
if world.custom_goals[player]['pedgoal'] and 'requirements' in world.custom_goals[player]['pedgoal']:
ped_pull += get_goal_bytes('pedgoal')
else:
ped_pull += [0x81]
if world.custom_goals[player]['murahgoal'] and 'requirements' in world.custom_goals[player]['murahgoal']:
murah_goal += get_goal_bytes('murahgoal')
else:
if world.goal[player] in ['triforcehunt', 'trinity']:
murah_goal += [0x88]
else:
murah_goal += [0x00]
if world.custom_goals[player]['ganongoal'] and 'requirements' in world.custom_goals[player]['ganongoal']:
ganon_goal += get_goal_bytes('ganongoal')
else:
if world.goal[player] in ['pedestal', 'triforcehunt']:
ganon_goal = [0x00]
elif world.goal[player] in ['dungeons']:
ganon_goal += [0x81, 0x82, 0x06, 0x07] # pendants, crystals, and agas
elif world.goal[player] in ['crystals', 'trinity']:
ganon_goal += [0x02, world.crystals_needed_for_ganon[player]]
elif world.goal[player] in ['ganonhunt']:
ganon_goal += [0x88] # triforce pieces
elif world.goal[player] in ['completionist']:
ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate
else:
ganon_goal += [0x02, world.crystals_needed_for_ganon[player], 0x07] # crystals and aga2
gt_entry += [0xFF]
ped_pull += [0xFF]
ganon_goal += [0xFF]
murah_goal += [0xFF]
start_address = 0x8198 + 8
write_int16(rom, 0x180198, start_address)
rom.write_bytes(snes_to_pc(0xB00000 + start_address), gt_entry)
start_address += len(gt_entry)
write_int16(rom, 0x18019A, start_address)
rom.write_bytes(snes_to_pc(0xB00000 + start_address), ganon_goal)
start_address += len(ganon_goal)
write_int16(rom, 0x18019C, start_address)
rom.write_bytes(snes_to_pc(0xB00000 + start_address), ped_pull)
start_address += len(ped_pull)
write_int16(rom, 0x18019E, start_address)
rom.write_bytes(snes_to_pc(0xB00000 + start_address), murah_goal)
start_address += len(murah_goal)
if start_address > 0x81D8:
raise Exception("Custom Goal data too long to fit in allocated space, try reducing the amount of requirements.")
# gt entry
gtentry = world.custom_goals[player]['gtentry']
if gtentry and 'cutscene_gfx' in gtentry:
gfx = gtentry['cutscene_gfx']
write_int16(rom, snes_to_pc(0x3081D8), gfx[0])
rom.write_byte(snes_to_pc(0x3081E6), gfx[1])
# block HC upstairs doors in rain state in standard mode
prevent_rain = world.mode[player] == 'standard' and world.shuffle[player] != 'vanilla' and world.logic[player] != 'nologic'
@@ -1654,26 +1735,6 @@ def patch_rom(world, rom, player, team, is_mystery=False):
write_enemizer_tweaks(rom, world, player)
write_strings(rom, world, player, team)
# gt entry
if world.customizer:
gtentry = world.customizer.get_gtentry()
if gtentry and player in gtentry:
gtentry = gtentry[player]
if 'cutscene_gfx' in gtentry:
gfx = gtentry['cutscene_gfx']
if type(gfx) is str:
from Tables import item_gfx_table
if gfx.lower() == 'random':
gfx = random.choice(list(item_gfx_table.keys()))
if gfx in item_gfx_table:
write_int16(rom, snes_to_pc(0x3081AA), item_gfx_table[gfx][1] + (0x8000 if not item_gfx_table[gfx][0] else 0))
rom.write_byte(snes_to_pc(0x3081AC), item_gfx_table[gfx][2])
else:
logging.getLogger('').warning('Invalid name "%s" in customized GT entry cutscene gfx', gfx)
else:
write_int16(rom, snes_to_pc(0x3081AA), gfx[0])
rom.write_byte(snes_to_pc(0x3081AC), gfx[1])
# write initial sram
rom.write_initial_sram()
@@ -2502,6 +2563,21 @@ def write_strings(rom, world, player, team):
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
def get_custom_goal_text(type):
goal_text = world.custom_goals[player][type]['goaltext']
if '%d' in goal_text:
return goal_text % world.custom_goals[player][type]['requirements'][0]['target']
return goal_text
if world.custom_goals[player]['gtentry'] and 'goaltext' in world.custom_goals[player]['gtentry']:
tt['sign_ganons_tower'] = get_custom_goal_text('gtentry')
if world.custom_goals[player]['ganongoal'] and 'goaltext' in world.custom_goals[player]['ganongoal']:
tt['sign_ganon'] = get_custom_goal_text('ganongoal')
if world.custom_goals[player]['pedgoal'] and 'goaltext' in world.custom_goals[player]['pedgoal']:
tt['mastersword_pedestal_goal'] = get_custom_goal_text('pedgoal')
if world.custom_goals[player]['murahgoal'] and 'goaltext' in world.custom_goals[player]['murahgoal']:
tt['murahdahla'] = get_custom_goal_text('murahgoal')
tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)]