Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2025-11-08 11:18:52 -06:00
10 changed files with 99 additions and 59 deletions

View File

@@ -3274,16 +3274,20 @@ class Spoiler(object):
custom = self.metadata['custom_goals'][player]
if custom['gtentry'] and 'requirements' in custom['gtentry']:
outfile.write('GT Entry Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['gtentry']['goaltext'])
else:
outfile.write('GT Entry Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_gt_orig[player]))
if custom['ganongoal'] and 'requirements' in custom['ganongoal']:
outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['ganongoal']['goaltext'])
else:
outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player]))
if custom['pedgoal'] and 'requirements' in custom['pedgoal']:
outfile.write('Pedestal Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['pedgoal']['goaltext'])
if custom['murahgoal'] and 'requirements' in custom['murahgoal']:
outfile.write('Murahdahla Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['murahgoal']['goaltext'])
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
outfile.write('\n')
outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player])
@@ -3396,20 +3400,22 @@ class Spoiler(object):
player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')')
goal = self.world.custom_goals[player]['gtentry']
if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
outfile.write(str('GT Entry Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
pass
# outfile.write(str('GT Entry Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
elif self.world.crystals_gt_orig[player] == 'random':
outfile.write(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player])))
goal = self.world.custom_goals[player]['ganongoal']
if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
outfile.write(str('Ganon Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
pass
# outfile.write(str('Ganon Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
elif self.world.crystals_ganon_orig[player] == 'random':
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player])))
goal = self.world.custom_goals[player]['pedgoal']
if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
outfile.write(str('Pedestal Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
goal = self.world.custom_goals[player]['murahgoal']
if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
outfile.write(str('Murahdahla Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
# goal = self.world.custom_goals[player]['pedgoal']
# if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
# outfile.write(str('Pedestal Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
# goal = self.world.custom_goals[player]['murahgoal']
# if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
# outfile.write(str('Murahdahla Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
outfile.write('\n\nPrizes:\n\n')
for dungeon, prize in self.prizes.items():
outfile.write(str(dungeon + ':').ljust(line_width) + '%s\n' % prize)

View File

@@ -1,5 +1,11 @@
# Changelog
## 0.6.1.2
- Various fixes for Custom Goal Framework
- Added custom gfx for Pedestal and Murahdahla
- Re-fixed purple chest follower dupe
- Updated tournament winners texts
## 0.6.1.1
- Fixed issue with Bosses goals in Custom Goal Framework
- Fixed error when using Custom Goals with no extra values

14
Main.py
View File

@@ -645,23 +645,23 @@ def resolve_random_settings(world, args):
else:
raise Exception(f'Invalid {list(r.keys())[0]} requirement target for {goal_type}')
if req['condition'] & 0x7F == req_table['Pendants']:
goal['logic']['pendants'] = req['target'] or 3
goal['logic']['pendants'] = req['target'] = req.get('target', 3)
elif req['condition'] & 0x7F == req_table['Crystals']:
goal['logic']['crystals'] = req['target'] or 7
goal['logic']['crystals'] = req['target'] = req.get('target', 7)
elif req['condition'] & 0x7F == req_table['PendantBosses']:
goal['logic']['pendant_bosses'] = req['target'] or 3
goal['logic']['pendant_bosses'] = req['target'] = req.get('target', 3)
elif req['condition'] & 0x7F == req_table['CrystalBosses']:
goal['logic']['crystal_bosses'] = req['target'] or 7
goal['logic']['crystal_bosses'] = req['target'] = req.get('target', 7)
elif req['condition'] & 0x7F == req_table['PrizeBosses']:
goal['logic']['bosses'] = req['target'] or 10
goal['logic']['bosses'] = req['target'] = req.get('target', 10)
elif req['condition'] & 0x7F == req_table['Aga1']:
goal['logic']['aga1'] = True
elif req['condition'] & 0x7F == req_table['Aga2']:
goal['logic']['aga2'] = True
elif req['condition'] & 0x7F == req_table['TriforcePieces']:
goal['logic']['goal_items'] = req['target'] or None
goal['logic']['goal_items'] = req['target'] = req.get('target', None)
elif req['condition'] & 0x7F == req_table['CollectionRate']:
goal['logic']['collection'] = req['target'] or None
goal['logic']['collection'] = req['target'] = req.get('target', None)
goal['requirements'].append(req)
except KeyError:
raise KeyError(f'Invalid {goal_type} requirement: {r}')

View File

@@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
from OverworldGlitchRules import create_owg_connections
from Utils import bidict
version_number = '0.6.1.1'
version_number = '0.6.1.2'
# branch indicator is intentionally different across branches
version_branch = ''

36
Rom.py
View File

@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '72c4b2d00057d1faced32871d8081f3a'
RANDOMIZERBASEHASH = '2039c11b935d3b81f78810d9f4be19d6'
class JsonRom(object):
@@ -1270,8 +1270,11 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
goal_bytes += [req['target']]
else:
goal_bytes += int16_as_bytes(req['target'])
elif 'target' in req:
if req['condition'] & 0x7F < 0x08:
elif req['condition'] & 0x80 == 0:
if req['condition'] & 0x7F == 0x06 or req['condition'] & 0x7F == 0x07:
# agahnims have no target value
pass
elif req['condition'] & 0x7F < 0x08:
goal_bytes += [req['target']]
else:
goal_bytes += int16_as_bytes(req['target'])
@@ -1338,12 +1341,19 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
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])
# goal cutscene gfx
goals = {
#goal: gfx addr, palette addr
'gtentry': (0x3081D8, 0x3081E6),
'pedgoal': (0x3081ED, 0x3081F3),
'murahgoal': (0x3081F6, 0x3081FC),
}
for goal_type, gfx_addr in goals.items():
goal = world.custom_goals[player][goal_type]
if goal and 'cutscene_gfx' in goal:
gfx = goal['cutscene_gfx']
write_int16(rom, snes_to_pc(gfx_addr[0]), gfx[0])
rom.write_byte(snes_to_pc(gfx_addr[1]), 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'
@@ -1751,7 +1761,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
gen, seedstring = rom_header.split('|', 1)
gen = f'{gen:<3}'
seedstring = f'{int(seedstring):09}' if seedstring.isdigit() else seedstring[:9]
rom.name = bytearray(f'{gen}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
rom.name = bytearray(f'OR{gen}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
elif len(rom_header) <= 9:
seedstring = f'{int(rom_header):09}' if rom_header.isdigit() else rom_header
rom.name = bytearray(f'OR{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
@@ -2581,8 +2591,10 @@ def write_strings(rom, world, player, team):
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']
placeholder_count = goal_text.count('%d')
if placeholder_count > 0:
targets = [req['target'] for req in world.custom_goals[player][type]['requirements'] if 'target' in req][:placeholder_count]
return goal_text % tuple(targets)
return goal_text
if world.custom_goals[player]['gtentry'] and 'goaltext' in world.custom_goals[player]['gtentry']:

View File

@@ -1805,6 +1805,9 @@ def standard_rules(world, player):
add_rule(world.get_location('Hyrule Castle Tree', player), lambda state: state.has('Zelda Delivered', player))
add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player))
if not world.is_copied_world:
add_rule(world.get_location('Hyrule Castle Courtyard Tree Pull', player), lambda state: state.has('Zelda Delivered', player))
# don't allow bombs to get past here before zelda is rescued
set_rule(world.get_entrance('GT Hookshot South Entry to Ranged Crystal', player), lambda state: (state.can_use_bombs(player) and state.has('Zelda Delivered', player)) or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player)) # or state.has('Cane of Somaria', player))

View File

@@ -1782,6 +1782,8 @@ class TextTable(object):
text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^")
text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.")
text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert("Doors Async League winners\n{HARP}\n"
" ~~~2025~~~\nSchulzer\n\n"
" ~~~2024~~~\nhumbugh\n\n"
" ~~~2023~~~\nEriror\n\n"
" ~~~2022~~~\nAndy\n\n"
" ~~~2021~~~\nprdwong")
@@ -1795,7 +1797,8 @@ class TextTable(object):
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n"
" ~~~2023~~~\nnGanonsGoneWild\n\n"
" ~~~2024~~~\nGammachuu\n\n"
" ~~~2023~~~\nGanonsGoneWild\n\n"
" ~~~2022~~~\nObscure\n\n"
" ~~~2021~~~\nDaaanty\n\n"
" ~~~2019~~~\nJet082\n\n"
@@ -2029,6 +2032,7 @@ class TextTable(object):
text['ganon_phase_3_alt'] = CompressedTextMapper.convert("Got wax in your ears? I cannot die!")
# 190
text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n"
"~~~HMG 2025~~~\nSkele\n"
"~~~No Logic 2024~~~\ntam\n\n"
"~~~HMG 2023~~~\ntam\n\n"
"~~~No Logic 2022~~~\nChexhuman\n\n"

Binary file not shown.

View File

@@ -577,9 +577,13 @@ class CustomSettings(object):
def load_yaml(path):
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
try:
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
except yaml.YAMLError as e:
error_msg = f"Error parsing YAML file '{path}':\n{str(e)}"
raise ValueError(error_msg) from e

View File

@@ -77,32 +77,37 @@ def bottom_frame(self, parent, args=None):
argsDump['sprite'] = argsDump['sprite'].name
save_settings(parent, argsDump, "last.json")
guiargs = create_guiargs(parent)
# get default values for missing parameters
cliargs = ['--multi', str(guiargs.multi)]
if hasattr(guiargs, 'customizer'):
cliargs.extend(['--customizer', str(guiargs.customizer)])
for k,v in vars(parse_cli(cliargs)).items():
if k not in vars(guiargs):
setattr(guiargs, k, v)
elif type(v) is dict: # use same settings for every player
players = guiargs.multi if len(v) == 0 else len(v)
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, players + 1)})
argsDump = vars(guiargs)
needEnemizer = False
falsey = ["none", "default", False, 0]
for enemizerOption in ["shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health"]:
if enemizerOption in argsDump:
if isinstance(argsDump[enemizerOption], dict):
for playerID,playerSetting in argsDump[enemizerOption].items():
if not playerSetting in falsey:
needEnemizer = True
elif not argsDump[enemizerOption] in falsey:
needEnemizer = True
seeds = []
try:
guiargs = create_guiargs(parent)
# get default values for missing parameters
cliargs = ['--multi', str(guiargs.multi)]
if hasattr(guiargs, 'customizer'):
cliargs.extend(['--customizer', str(guiargs.customizer)])
for k,v in vars(parse_cli(cliargs)).items():
if k not in vars(guiargs):
setattr(guiargs, k, v)
elif type(v) is dict: # use same settings for every player
players = guiargs.multi if len(v) == 0 else len(v)
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, players + 1)})
if guiargs.multi == 1 and guiargs.names.endswith("=="):
# allow settings code thru player names entry
guiargs.code[1] = guiargs.names
guiargs.names = ""
argsDump = vars(guiargs)
needEnemizer = False
falsey = ["none", "default", False, 0]
for enemizerOption in ["shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health"]:
if enemizerOption in argsDump:
if isinstance(argsDump[enemizerOption], dict):
for playerID,playerSetting in argsDump[enemizerOption].items():
if not playerSetting in falsey:
needEnemizer = True
elif not argsDump[enemizerOption] in falsey:
needEnemizer = True
seeds = []
if guiargs.count is not None and guiargs.seed:
seed = guiargs.seed
for _ in range(guiargs.count):