Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2025-09-07 11:00:35 -05:00
13 changed files with 79 additions and 30 deletions
+2
View File
@@ -76,6 +76,7 @@ class World(object):
self.can_take_damage = True self.can_take_damage = True
self.hints = hints.copy() self.hints = hints.copy()
self.prizes = {} self.prizes = {}
self.default_zelda_region = {}
self.dynamic_regions = [] self.dynamic_regions = []
self.dynamic_locations = [] self.dynamic_locations = []
self.spoiler_mode = spoiler_mode self.spoiler_mode = spoiler_mode
@@ -185,6 +186,7 @@ class World(object):
set_player_attr('standardize_palettes', 'standardize') set_player_attr('standardize_palettes', 'standardize')
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}) set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []}) set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []})
set_player_attr('default_zelda_region', 'Hyrule Dungeon Cellblock')
set_player_attr('exp_cache', defaultdict(dict)) set_player_attr('exp_cache', defaultdict(dict))
set_player_attr('enabled_entrances', {}) set_player_attr('enabled_entrances', {})
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.6.0.8
- Re-fixed issue with Old Man spawning on pyramid
- Allowing Zelda to be in TT Prison for follower shuffle escape
- Fixed error with placing Old Man Cave in ER
- Fixed error when plando'ing followers at locations
## 0.6.0.7 ## 0.6.0.7
- Emergency fix for GT cutscene GFX - Emergency fix for GT cutscene GFX
+6 -2
View File
@@ -1302,8 +1302,12 @@ def create_doors(world, player):
world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain') world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain')
if world.mode[player] == 'standard' and 'Zelda Herself' not in [i.name for i in world.precollected_items if i.player == player]: if world.mode[player] == 'standard' and 'Zelda Herself' not in [i.name for i in world.precollected_items if i.player == player]:
world.get_door('Hyrule Castle Throne Room Tapestry', player).event('Zelda Pickup') if world.default_zelda_region[player] == 'Thieves Blind\'s Cell':
world.get_door('Hyrule Castle Tapestry Backwards', player).event('Zelda Pickup') zelda_location = 'Suspicious Maiden'
else:
zelda_location = 'Zelda Pickup'
world.get_door('Hyrule Castle Throne Room Tapestry', player).event(zelda_location)
world.get_door('Hyrule Castle Tapestry Backwards', player).event(zelda_location)
# crystal switches and barriers # crystal switches and barriers
world.get_door('Hera Lobby Crystal Exit', player).c_switch() world.get_door('Hera Lobby Crystal Exit', player).c_switch()
+6 -6
View File
@@ -586,13 +586,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
paths.append(portal.door.entrance.parent_region.name) paths.append(portal.door.entrance.parent_region.name)
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
if name == 'Hyrule Castle': if name == 'Hyrule Castle':
paths.append('Hyrule Dungeon Cellblock') paths.append(world.default_zelda_region[player])
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary')) paths.append((world.default_zelda_region[player], 'Sanctuary'))
if name == 'Hyrule Castle Sewers': if name == 'Hyrule Castle Sewers':
paths.append('Sanctuary') paths.append('Sanctuary')
if name == 'Hyrule Castle Dungeon': if name == 'Hyrule Castle Dungeon':
paths.append('Hyrule Dungeon Cellblock') paths.append(world.default_zelda_region[player])
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) paths.append((world.default_zelda_region[player], 'Hyrule Castle Throne Room'))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window') paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names: elif 'Thieves Attic Window' in all_r_names:
@@ -1322,7 +1322,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge
for r_name in dungeon_boss_sectors[key]: for r_name in dungeon_boss_sectors[key]:
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole) assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole)
if key == 'Hyrule Castle' and world.mode[player] == 'standard': if key == 'Hyrule Castle' and world.mode[player] == 'standard':
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda for r_name in [world.default_zelda_region[player], 'Sanctuary', 'Hyrule Castle Throne Room']: # need to deliver zelda
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
candidate_sectors, global_pole) candidate_sectors, global_pole)
if key == 'Thieves Town' and (world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind' if key == 'Thieves Town' and (world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind'
@@ -3091,7 +3091,7 @@ def split_dungeon_builder(builder, split_list, builder_info):
if builder.name == 'Hyrule Castle': if builder.name == 'Hyrule Castle':
assign_sector(find_sector('Hyrule Castle Throne Room', candidate_sectors), assign_sector(find_sector('Hyrule Castle Throne Room', candidate_sectors),
dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole) dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole)
assign_sector(find_sector('Hyrule Dungeon Cellblock', candidate_sectors), assign_sector(find_sector(world.default_zelda_region[player], candidate_sectors),
dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole) dungeon_map['Hyrule Castle Dungeon'], candidate_sectors, global_pole)
dungeon_map['Hyrule Castle Dungeon'].throne_door = world.get_door('Hyrule Castle Throne Room N', player) dungeon_map['Hyrule Castle Dungeon'].throne_door = world.get_door('Hyrule Castle Throne Room N', player)
dungeon_map['Hyrule Castle Sewers'].sewers_access = builder.throne_door dungeon_map['Hyrule Castle Sewers'].sewers_access = builder.throne_door
+13 -9
View File
@@ -1676,7 +1676,7 @@ def set_event_item(world, player, location_name, item_name=None):
def shuffle_event_items(world, player): def shuffle_event_items(world, player):
if (world.shuffle_followers[player]): if world.shuffle_followers[player]:
available_quests = follower_quests.copy() available_quests = follower_quests.copy()
available_pickups = [quests[0] for quests in available_quests.values()] available_pickups = [quests[0] for quests in available_quests.values()]
@@ -1688,11 +1688,14 @@ def shuffle_event_items(world, player):
available_pickups.remove(loc.item.name) available_pickups.remove(loc.item.name)
if world.mode[player] == 'standard': if world.mode[player] == 'standard' and 'Zelda Herself' in available_pickups:
if 'Zelda Herself' in available_pickups: zelda_dropoff = 'Zelda Pickup'
zelda_pickup = available_quests.pop('Zelda Pickup')[0] if world.default_zelda_region[player] == 'Thieves Blind\'s Cell':
available_pickups.remove(zelda_pickup) zelda_dropoff = 'Suspicious Maiden'
set_event_item(world, player, 'Zelda Pickup', zelda_pickup) available_quests.pop(zelda_dropoff)
zelda_pickup = 'Zelda Herself'
available_pickups.remove(zelda_pickup)
set_event_item(world, player, zelda_dropoff, zelda_pickup)
random.shuffle(available_pickups) random.shuffle(available_pickups)
@@ -1715,10 +1718,11 @@ def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_
item_player = player if len(item_parts) < 2 else int(item_parts[1]) item_player = player if len(item_parts) < 2 else int(item_parts[1])
item_name = item_parts[0] item_name = item_parts[0]
event_flag = False event_flag = False
if item_name in prize_set: if item_name in prize_set or item_name in follower_pickups:
item_player = player # prizes must be for that player item_player = player # must be for that player
item_to_place = ItemFactory(item_name, item_player) item_to_place = ItemFactory(item_name, item_player)
prize_pool.remove(item_name) if item_name in prize_set:
prize_pool.remove(item_name)
event_flag = True event_flag = True
elif is_dungeon_item(item_name, world, item_player): elif is_dungeon_item(item_name, world, item_player):
item_to_place = next(x for x in dungeon_pool item_to_place = next(x for x in dungeon_pool
+4 -1
View File
@@ -1805,7 +1805,10 @@ def imp_locations_factory(world, player):
return imp_locations return imp_locations
imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
imp_locations.append('Zelda Pickup') if world.default_zelda_region[player] == 'Thieves Blinds\' Cell':
imp_locations.append('Suspicious Maiden')
else:
imp_locations.append('Zelda Pickup')
imp_locations.append('Zelda Drop Off') imp_locations.append('Zelda Drop Off')
return imp_locations return imp_locations
+1 -1
View File
@@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
from OverworldGlitchRules import create_owg_connections from OverworldGlitchRules import create_owg_connections
from Utils import bidict from Utils import bidict
version_number = '0.6.0.7' version_number = '0.6.0.8'
# branch indicator is intentionally different across branches # branch indicator is intentionally different across branches
version_branch = '' version_branch = ''
+13 -1
View File
@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '93386b05ee4b5de6b9165941b9e14e97' RANDOMIZERBASEHASH = '65fae75651987228878051028da066ad'
class JsonRom(object): class JsonRom(object):
@@ -1581,6 +1581,18 @@ def patch_rom(world, rom, player, team, is_mystery=False):
rom.write_bytes(snes_to_pc(0x09A045), [0xEA, 0xEA]) # allow super bomb to follow into UW holes rom.write_bytes(snes_to_pc(0x09A045), [0xEA, 0xEA]) # allow super bomb to follow into UW holes
rom.write_byte(snes_to_pc(0x09ACDF), 0x6B) # allow kiki/locksmith to follow after screen transition rom.write_byte(snes_to_pc(0x09ACDF), 0x6B) # allow kiki/locksmith to follow after screen transition
if world.default_zelda_region[player] == 'Thieves Blind\'s Cell':
write_int16(rom, snes_to_pc(0x02D8D6), 0x45) # change zelda spawn point to maiden cell
rom.write_bytes(snes_to_pc(0x02D8F0), [0x08, 0x08, 0x08, 0x09, 0x0B, 0x0A, 0x0B, 0x0B])
write_int16(rom, snes_to_pc(0x02D91C), 0x0B00)
write_int16(rom, snes_to_pc(0x02D92A), 0x0800)
write_int16(rom, snes_to_pc(0x02D938), 0x0860)
write_int16(rom, snes_to_pc(0x02D946), 0x0B90)
write_int16(rom, snes_to_pc(0x02D954), 0x0078)
write_int16(rom, snes_to_pc(0x02D962), 0x017F)
rom.write_byte(snes_to_pc(0x02D975), 0x00)
rom.write_byte(snes_to_pc(0x02D98A), 0x02)
if world.enemy_shuffle[player] != 'none': if world.enemy_shuffle[player] != 'none':
# informs zelda and maiden to draw over gfx slots that are guaranteed unused # informs zelda and maiden to draw over gfx slots that are guaranteed unused
rom.write_bytes(0x1802C1, world.data_tables[player].room_headers[0x80].free_gfx[0:2]) rom.write_bytes(0x1802C1, world.data_tables[player].room_headers[0x80].free_gfx[0:2])
+1 -1
View File
@@ -1669,7 +1669,7 @@ def standard_rules(world, player):
def find_rules_for_zelda_delivery(world, player): def find_rules_for_zelda_delivery(world, player):
# path rules for backtracking # path rules for backtracking
start_region = world.get_region('Hyrule Dungeon Cellblock', player) start_region = world.get_region(world.default_zelda_region[player], player)
queue = deque([(start_region, [], [])]) queue = deque([(start_region, [], [])])
visited = {start_region} visited = {start_region}
blank_state = CollectionState(world) blank_state = CollectionState(world)
Binary file not shown.
+4 -4
View File
@@ -333,13 +333,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
if portal.destination: if portal.destination:
paths.append(portal.door.entrance.parent_region.name) paths.append(portal.door.entrance.parent_region.name)
if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon': if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon':
paths.append('Hyrule Dungeon Cellblock') paths.append(world.default_zelda_region[player])
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) paths.append((world.default_zelda_region[player], 'Hyrule Castle Throne Room'))
entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South') entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South')
# todo: in non-er, we can use the other portals too # todo: in non-er, we can use the other portals too
paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name)) paths.append((world.default_zelda_region[player], entrance.door.entrance.parent_region.name))
paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name, paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name,
'Hyrule Dungeon Cellblock'])) world.default_zelda_region[player]]))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window') paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names: elif 'Thieves Attic Window' in all_r_names:
+15
View File
@@ -521,6 +521,21 @@ def randomize_enemies(world, player):
green_mail, blue_mail, red_mail = original_table[idx] green_mail, blue_mail, red_mail = original_table[idx]
del original_table[idx] del original_table[idx]
world.data_tables[player].enemy_damage[i] = [green_mail, blue_mail, red_mail] world.data_tables[player].enemy_damage[i] = [green_mail, blue_mail, red_mail]
# determine default zelda follower location
if world.mode[player] == 'standard' and world.doorShuffle[player] == 'crossed' and world.shuffle_followers[player]:
def random_zelda():
world.default_zelda_region[player] = random.choice(['Hyrule Dungeon Cellblock', 'Thieves Blind\'s Cell'])
if world.customizer:
placements = world.customizer.get_placements()
if placements and player in placements and 'Zelda Herself' in placements[player].values():
location = [l for (l, item) in placements[player].items() if item == 'Zelda Herself'][0]
if location == 'Suspicious Maiden':
world.default_zelda_region[player] = 'Thieves Blind\'s Cell'
else:
random_zelda()
else:
random_zelda()
def write_enemy_shuffle_settings(world, player, rom): def write_enemy_shuffle_settings(world, player, rom):
+8 -5
View File
@@ -403,6 +403,7 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world):
else: else:
region_name = 'West Dark Death Mountain (Top)' region_name = 'West Dark Death Mountain (Top)'
om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True, True)) om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True, True))
om_cave_options = [e for e in om_cave_options if e in avail.entrances]
if avail.swapped: if avail.swapped:
om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances] om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances]
assert len(om_cave_options), 'No available entrances left to place Old Man Cave' assert len(om_cave_options), 'No available entrances left to place Old Man Cave'
@@ -643,7 +644,6 @@ def do_dark_sanc(entrances, exits, avail):
entrances.remove(choice) entrances.remove(choice)
exits.remove('Dark Sanctuary Hint') exits.remove('Dark Sanctuary Hint')
connect_entrance(choice, 'Dark Sanctuary Hint', avail) connect_entrance(choice, 'Dark Sanctuary Hint', avail)
ext.connect(avail.world.get_entrance(choice, avail.player).parent_region)
if not avail.coupled: if not avail.coupled:
avail.decoupled_entrances.remove(choice) avail.decoupled_entrances.remove(choice)
if avail.swapped and choice != 'Dark Sanctuary Hint': if avail.swapped and choice != 'Dark Sanctuary Hint':
@@ -651,8 +651,7 @@ def do_dark_sanc(entrances, exits, avail):
entrances.remove(swap_ent) entrances.remove(swap_ent)
exits.remove(swap_ext) exits.remove(swap_ext)
elif not ext.connected_region: elif not ext.connected_region:
# default to output to vanilla area, assume vanilla connection raise Exception('Dark Sanctuary Hint was placed earlier but its exit not properly connected')
ext.connect(avail.world.get_region('Dark Chapel Area', avail.player))
def do_links_house(entrances, exits, avail, cross_world): def do_links_house(entrances, exits, avail, cross_world):
@@ -722,8 +721,6 @@ def do_links_house(entrances, exits, avail, cross_world):
connect_two_way(links_house, lh_exit, avail) connect_two_way(links_house, lh_exit, avail)
else: else:
connect_entrance(links_house, lh_exit, avail) connect_entrance(links_house, lh_exit, avail)
ext = avail.world.get_entrance('Big Bomb Shop Exit', avail.player)
ext.connect(avail.world.get_entrance(links_house, avail.player).parent_region)
entrances.remove(links_house) entrances.remove(links_house)
exits.remove(lh_exit) exits.remove(lh_exit)
if not avail.coupled: if not avail.coupled:
@@ -1907,6 +1904,12 @@ def connect_entrance(entrancename, exit_name, avail):
avail.entrances.remove(entrancename) avail.entrances.remove(entrancename)
if avail.coupled: if avail.coupled:
avail.exits.remove(exit_name) avail.exits.remove(exit_name)
if exit_name == 'Big Bomb Shop' and avail.world.is_bombshop_start(avail.player):
ext = avail.world.get_entrance('Big Bomb Shop Exit', avail.player)
ext.connect(avail.world.get_entrance(entrancename, avail.player).parent_region)
if exit_name == 'Dark Sanctuary Hint' and avail.world.is_dark_chapel_start(avail.player):
ext = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player)
ext.connect(avail.world.get_entrance(entrancename, avail.player).parent_region)
world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player)
logging.getLogger('').debug(f'Connected (entr) {entrance.name} to {exit.name if exit is not None else region.name}') logging.getLogger('').debug(f'Connected (entr) {entrance.name} to {exit.name if exit is not None else region.name}')