Pull in OR 0.7.0.1 #2
@@ -31,6 +31,7 @@ weights/
|
||||
/QUsb2Snes/
|
||||
/output/
|
||||
/enemizer/
|
||||
visualizations/
|
||||
|
||||
resources/user/*
|
||||
!resources/user/.gitkeep
|
||||
|
||||
+51
-49
@@ -26,11 +26,12 @@ from Versions import DRVersion, GKVersion, ORVersion
|
||||
|
||||
class World(object):
|
||||
|
||||
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
||||
def __init__(self, players, owLayout, owParallel, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
||||
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints, spoiler_mode):
|
||||
self.players = players
|
||||
self.teams = 1
|
||||
self.owShuffle = owShuffle.copy()
|
||||
self.owLayout = owLayout.copy()
|
||||
self.owParallel = owParallel.copy()
|
||||
self.owTerrain = {}
|
||||
self.owKeepSimilar = {}
|
||||
self.owMixed = owMixed.copy()
|
||||
@@ -38,6 +39,7 @@ class World(object):
|
||||
self.owCrossed = self.owCrossed if self.owCrossed != 'polar' or self.owMixed else 'none'
|
||||
self.owWhirlpoolShuffle = {}
|
||||
self.owFluteShuffle = {}
|
||||
self.owFog = {}
|
||||
self.shuffle = shuffle.copy()
|
||||
self.doorShuffle = doorShuffle.copy()
|
||||
self.intensity = {}
|
||||
@@ -99,6 +101,9 @@ class World(object):
|
||||
self.owswaps = {}
|
||||
self.owcrossededges = {}
|
||||
self.owwhirlpools = {}
|
||||
self.owgrid = {}
|
||||
self.owlayoutmap_lw = {}
|
||||
self.owlayoutmap_dw = {}
|
||||
self.owflutespots = {}
|
||||
self.owsectors = {}
|
||||
self.allow_flip_sanc = {}
|
||||
@@ -131,6 +136,7 @@ class World(object):
|
||||
set_player_attr('owswaps', [[],[],[]])
|
||||
set_player_attr('owcrossededges', [])
|
||||
set_player_attr('owwhirlpools', [])
|
||||
set_player_attr('owgrid', None)
|
||||
set_player_attr('owsectors', None)
|
||||
set_player_attr('allow_flip_sanc', False)
|
||||
set_player_attr('remote_items', False)
|
||||
@@ -1747,12 +1753,12 @@ class Entrance(object):
|
||||
|
||||
def can_reach(self, state):
|
||||
# Destination Pickup OW Only No Ledges Can S&Q Allow Mirror
|
||||
multi_step_locations = { 'Pyramid Crack': ('Big Bomb', True, True, False, True),
|
||||
'Missing Smith': ('Frog', True, False, True, True),
|
||||
'Middle Aged Man': ('Dark Blacksmith Ruins', True, False, True, True),
|
||||
'Dark Palace Button':('Kiki', True, False, False, False),
|
||||
'Old Man Drop Off': ('Lost Old Man', True, False, False, False),
|
||||
'Revealing Light': ('Suspicious Maiden', False, False, False, False)
|
||||
multi_step_locations = { 'Pyramid Crack': ('Pick Up Big Bomb', True, True, False, True),
|
||||
'Missing Smith': ('Get Frog', True, False, True, True),
|
||||
'Middle Aged Man': ('Pick Up Purple Chest', True, False, True, True),
|
||||
'Dark Palace Button': ('Pick Up Kiki', True, False, False, False),
|
||||
'Old Man Drop Off': ('Escort Old Man', True, False, False, False),
|
||||
'Revealing Light': ('Maiden Rescued', False, False, False, False)
|
||||
}
|
||||
|
||||
if self.name in multi_step_locations:
|
||||
@@ -1761,7 +1767,10 @@ class Entrance(object):
|
||||
multi_step_loc = multi_step_locations[self.name]
|
||||
if world.shuffle_followers[self.player]:
|
||||
multi_step_loc = (multi_step_loc[0], self.name == 'Pyramid Crack', multi_step_loc[2], True, True)
|
||||
step_location = world.get_location(multi_step_loc[0], self.player)
|
||||
step_location = world.find_items(multi_step_loc[0], self.player)
|
||||
if len(step_location) == 0:
|
||||
return False
|
||||
step_location = step_location[0]
|
||||
if step_location.can_reach(state) and self.can_reach_thru(state, step_location, multi_step_loc[1], multi_step_loc[2], multi_step_loc[3], multi_step_loc[4]) and self.access_rule(state):
|
||||
if not self in state.path:
|
||||
path = state.path.get(step_location.parent_region, (step_location.parent_region.name, None))
|
||||
@@ -2401,12 +2410,11 @@ class OWEdge(object):
|
||||
def __init__(self, player, name, owIndex, direction, terrain, edge_id, owSlotIndex=0xff):
|
||||
self.player = player
|
||||
self.name = name
|
||||
self.type = DoorType.Open
|
||||
self.direction = direction
|
||||
self.terrain = terrain
|
||||
self.parallel = None
|
||||
self.specialEntrance = False
|
||||
self.specialExit = False
|
||||
self.deadEnd = False
|
||||
|
||||
# rom properties
|
||||
self.owIndex = owIndex
|
||||
@@ -2437,7 +2445,6 @@ class OWEdge(object):
|
||||
self.worldType = WorldType.Dark
|
||||
|
||||
# logical properties
|
||||
# self.connected = False # combine with Dest?
|
||||
self.dest = None
|
||||
self.dependents = []
|
||||
self.dead = False
|
||||
@@ -2454,9 +2461,6 @@ class OWEdge(object):
|
||||
def getTarget(self):
|
||||
return self.dest.specialID if self.dest.specialExit else self.dest.edge_id
|
||||
|
||||
def dead_end(self):
|
||||
self.deadEnd = True
|
||||
|
||||
def coordInfo(self, midpoint, vram_loc):
|
||||
self.midpoint = midpoint
|
||||
self.vramLoc = vram_loc
|
||||
@@ -3095,13 +3099,15 @@ class Spoiler(object):
|
||||
'bow_mode': self.world.bow_mode,
|
||||
'goal': self.world.goal,
|
||||
'custom_goals': self.world.custom_goals,
|
||||
'ow_shuffle': self.world.owShuffle,
|
||||
'ow_layout': self.world.owLayout,
|
||||
'ow_parallel': self.world.owParallel,
|
||||
'ow_terrain': self.world.owTerrain,
|
||||
'ow_crossed': self.world.owCrossed,
|
||||
'ow_keepsimilar': self.world.owKeepSimilar,
|
||||
'ow_mixed': self.world.owMixed,
|
||||
'ow_whirlpool': self.world.owWhirlpoolShuffle,
|
||||
'ow_fluteshuffle': self.world.owFluteShuffle,
|
||||
'ow_fog': self.world.owFog,
|
||||
'bonk_drops': self.world.shuffle_bonk_drops,
|
||||
'shuffle_followers': self.world.shuffle_followers,
|
||||
'shuffle': self.world.shuffle,
|
||||
@@ -3381,15 +3387,18 @@ class Spoiler(object):
|
||||
outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['dropshuffle'][player])
|
||||
outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player])
|
||||
outfile.write('\n')
|
||||
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
|
||||
if self.metadata['ow_shuffle'][player] != 'vanilla':
|
||||
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_layout'][player])
|
||||
if self.metadata['ow_layout'][player] != 'vanilla':
|
||||
outfile.write('Parallel OW:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_parallel'][player]))
|
||||
outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player]))
|
||||
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
|
||||
if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
|
||||
if self.metadata['ow_layout'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
|
||||
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
|
||||
outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
|
||||
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
|
||||
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
|
||||
if self.metadata['ow_layout'][player] == 'grid' or self.metadata['ow_mixed'][player]:
|
||||
outfile.write('Overworld Fog:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_fog'][player]))
|
||||
outfile.write('\n')
|
||||
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
|
||||
if self.metadata['shuffle'][player] != 'vanilla':
|
||||
@@ -3506,40 +3515,31 @@ class Spoiler(object):
|
||||
outfile.write(f'{fairy}: {bottle}\n')
|
||||
|
||||
if self.maps:
|
||||
if 'all' in self.settings or 'flute' in self.settings:
|
||||
# flute shuffle
|
||||
def write_map(type, title):
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('flute', player) in self.maps:
|
||||
outfile.write('\n\nFlute Spots:\n\n')
|
||||
if (type, player) in self.maps:
|
||||
outfile.write('\n\n' + title + '\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('flute', player) in self.maps:
|
||||
if (type, player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('flute', player)]['text'])
|
||||
outfile.write(self.maps[(type, player)]['text'])
|
||||
|
||||
if 'all' in self.settings or 'flute' in self.settings:
|
||||
# flute shuffle
|
||||
write_map('flute', 'Flute Spots:')
|
||||
|
||||
if 'all' in self.settings or 'overworld' in self.settings:
|
||||
# overworld tile flips
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('swaps', player) in self.maps:
|
||||
outfile.write('\n\nOW Tile Flips:\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('swaps', player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('swaps', player)]['text'])
|
||||
write_map('swaps', 'OW Tile Flips:')
|
||||
|
||||
# crossed groups
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('groups', player) in self.maps:
|
||||
outfile.write('\n\nOW Crossed Groups:\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('groups', player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('groups', player)]['text'])
|
||||
write_map('groups', 'OW Crossed Groups:')
|
||||
|
||||
# grid layout
|
||||
write_map('layout_grid_lw', 'Light World Layout:')
|
||||
write_map('layout_grid_dw', 'Dark World Layout:')
|
||||
|
||||
if self.overworlds and ('all' in self.settings or 'overworld' in self.settings):
|
||||
outfile.write('\n\nOverworld Edges:\n\n')
|
||||
@@ -3804,11 +3804,11 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique
|
||||
|
||||
# byte 10: settings_version
|
||||
|
||||
# byte 11: OOOT WCCC (OWR layout, free terrain, whirlpools, OWR crossed)
|
||||
or_mode = {"vanilla": 0, "parallel": 1, "full": 2}
|
||||
# byte 11: POOT WCCC (parallel, OWR layout, free terrain, whirlpools, OWR crossed)
|
||||
orlayout_mode = {"vanilla": 0, "grid": 1, "wild": 2}
|
||||
orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "unrestricted": 4}
|
||||
|
||||
# byte 12: KMBQ FF?? (keep similar, mixed/tile flip, bonk drops, follower quests, flute spots)
|
||||
# byte 12: KMBQ FFO? (keep similar, mixed/tile flip, bonk drops, follower quests, flute spots, fog)
|
||||
flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
|
||||
|
||||
# byte 13: FBBB TTPP (flute_mode, bow_mode, take_any, prize shuffle)
|
||||
@@ -3871,12 +3871,12 @@ class Settings(object):
|
||||
|
||||
settings_version,
|
||||
|
||||
(or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owTerrain[p] else 0)
|
||||
(0x80 if w.owParallel[p] else 0) | (orlayout_mode[w.owLayout[p]] << 5) | (0x10 if w.owTerrain[p] else 0)
|
||||
| (0x08 if w.owWhirlpoolShuffle[p] else 0) | orcrossed_mode[w.owCrossed[p]],
|
||||
|
||||
(0x80 if w.owKeepSimilar[p] else 0) | (0x40 if w.owMixed[p] else 0)
|
||||
| (0x20 if w.shuffle_bonk_drops[p] else 0) | (0x10 if w.shuffle_followers[p] else 0)
|
||||
| (flutespot_mode[w.owFluteShuffle[p]] << 4),
|
||||
| (flutespot_mode[w.owFluteShuffle[p]] << 4) | (0x02 if w.owFog[p] else 0),
|
||||
|
||||
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
|
||||
| take_any_mode[w.take_any[p]] << 2 | prizeshuffle_mode[w.prizeshuffle[p]]),
|
||||
@@ -3953,7 +3953,8 @@ class Settings(object):
|
||||
args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3]
|
||||
args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)]
|
||||
|
||||
args.ow_shuffle[p] = r(or_mode)[(settings[11] & 0xE0) >> 5]
|
||||
args.ow_parallel[p] = True if settings[11] & 0x80 else False
|
||||
args.ow_layout[p] = r(orlayout_mode)[(settings[11] & 0x60) >> 5]
|
||||
args.ow_terrain[p] = True if settings[11] & 0x10 else False
|
||||
args.ow_whirlpool[p] = True if settings[11] & 0x08 else False
|
||||
args.ow_crossed[p] = r(orcrossed_mode)[(settings[11] & 0x07)]
|
||||
@@ -3963,6 +3964,7 @@ class Settings(object):
|
||||
args.bonk_drops[p] = True if settings[12] & 0x20 else False
|
||||
args.shuffle_followers[p] = True if settings[12] & 0x10 else False
|
||||
args.ow_fluteshuffle[p] = r(flutespot_mode)[(settings[12] & 0x0C) >> 2]
|
||||
args.ow_fog[p] = True if settings[12] & 0x02 else False
|
||||
|
||||
if len(settings) > 13:
|
||||
args.flute_mode[p] = r(flute_mode)[(settings[13] & 0x80) >> 7]
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
# 0.7.0.1
|
||||
- Fixed buggy sprites in post-Aga Zora's Domain
|
||||
- Fixed L/R map switch when in special OW screens
|
||||
- Fixes issue not able to screen transition if bumped by enemy in water
|
||||
|
||||
# 0.7.0.0
|
||||
- New OW Layout Shuffle Mode: Grid
|
||||
- Implemented Fog of War for Tile Flip
|
||||
|
||||
# 0.6.1.11
|
||||
- Fixed bonk drops duplicate counting and potentially overwriting arbitrary values
|
||||
- Fixed boss icons on dungeon map check
|
||||
- Fixed dungeon counters to autotrack correctly
|
||||
- Key and chest counts in menu now display consistently (must have dungeon item or have visited dungeon to see the HUD)
|
||||
- Money balancing will fail in less scenarios
|
||||
- Fixed issue with Sanc pots not collecting
|
||||
- Enemizer now allows more enemies on water
|
||||
- Fix infinite pit fall issue with Old Man follower location
|
||||
- Fix bad overworld tilemap drawing on HC and Pyramid screens in OW Layout Shuffle
|
||||
|
||||
## 0.6.1.10
|
||||
- Emergency fix for bonk functionality
|
||||
|
||||
## 0.6.1.9
|
||||
- Fixed follower shuffle placement errors
|
||||
- Fixed pseudoboots ability to open Kings Tomb
|
||||
- Implemented better accurate coordinates on map check locations
|
||||
- Fixed janky icons on zoomed-in map check screen
|
||||
|
||||
## 0.6.1.8
|
||||
- Fixed follower placement and logic
|
||||
- Fixed error with HC Courtyard Tree Pull
|
||||
|
||||
## 0.6.1.7
|
||||
- \~Merged in DR v1.5.2~
|
||||
- Reverted key count update
|
||||
|
||||
@@ -120,6 +120,19 @@ def parse_cli(argv, no_defaults=False):
|
||||
ret.take_any = 'random' if ret.take_any == 'none' else ret.take_any
|
||||
ret.keyshuffle = 'universal'
|
||||
|
||||
if ret.ow_unparallel:
|
||||
ret.ow_parallel = False
|
||||
|
||||
if ret.ow_shuffle == 'parallel':
|
||||
ret.ow_layout = 'wild'
|
||||
ret.ow_parallel = True
|
||||
elif ret.ow_shuffle == 'full':
|
||||
ret.ow_layout = 'wild'
|
||||
ret.ow_parallel = False
|
||||
|
||||
if ret.ow_no_fog:
|
||||
ret.ow_fog = False
|
||||
|
||||
if player_num:
|
||||
defaults = copy.deepcopy(ret)
|
||||
for player in range(1, player_num + 1):
|
||||
@@ -130,23 +143,41 @@ def parse_cli(argv, no_defaults=False):
|
||||
for k, v in playersettings.items():
|
||||
setattr(playerargs, k, v)
|
||||
|
||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle',
|
||||
'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
|
||||
'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers',
|
||||
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'bosses_ganon', 'bosshunt_include_agas', 'ganon_item', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap', 'startinventory',
|
||||
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||
'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
|
||||
'skullwoods', 'linked_drops',
|
||||
'pseudoboots', 'mirrorscroll', 'dark_rooms', 'damage_challenge', 'shuffle_damage_table', 'crystal_book', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
|
||||
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_sfxinstruments',
|
||||
'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
|
||||
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'any_enemy_logic', 'aga_randomness',
|
||||
'money_balance']:
|
||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty',
|
||||
'item_functionality', 'ow_shuffle', 'ow_layout',
|
||||
'ow_parallel', 'ow_terrain', 'ow_crossed',
|
||||
'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool',
|
||||
'ow_fluteshuffle', 'ow_fog', 'flute_mode', 'bow_mode',
|
||||
'take_any', 'boots_hint', 'shuffle_followers',
|
||||
'shuffle', 'door_shuffle', 'intensity',
|
||||
'crystals_ganon', 'crystals_gt', 'bosses_ganon',
|
||||
'bosshunt_include_agas', 'ganon_item', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle',
|
||||
'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap',
|
||||
'startinventory', 'usestartinventory', 'bombbag',
|
||||
'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||
'triforce_max_difference', 'triforce_pool_min',
|
||||
'triforce_pool_max', 'triforce_goal_min',
|
||||
'triforce_goal_max', 'triforce_min_difference',
|
||||
'triforce_goal', 'triforce_pool', 'shufflelinks',
|
||||
'shuffletavern', 'skullwoods', 'linked_drops',
|
||||
'pseudoboots', 'mirrorscroll', 'dark_rooms',
|
||||
'damage_challenge', 'shuffle_damage_table',
|
||||
'crystal_book', 'retro', 'accessibility', 'hints',
|
||||
'beemizer', 'experimental', 'dungeon_counters',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health',
|
||||
'enemy_damage', 'shufflepots', 'ow_palettes',
|
||||
'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic',
|
||||
'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items', 'shopsanity', 'dropshuffle', 'pottery',
|
||||
'keydropshuffle', 'mixed_travel',
|
||||
'standardize_palettes', 'code', 'reduce_flashing',
|
||||
'shuffle_sfx', 'shuffle_sfxinstruments',
|
||||
'shuffle_songinstruments', 'msu_resume',
|
||||
'collection_rate', 'colorizepots', 'decoupledoors',
|
||||
'door_type_mode', 'bonk_drops', 'trap_door_mode',
|
||||
'key_logic_algorithm', 'door_self_loops',
|
||||
'any_enemy_logic', 'aga_randomness', 'money_balance']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
@@ -196,13 +227,18 @@ def parse_settings():
|
||||
# Shuffle Ganon defaults to TRUE
|
||||
"openpyramid": "auto",
|
||||
"shuffleganon": True,
|
||||
"ow_shuffle": "vanilla",
|
||||
"ow_shuffle": "vanilla", # for backwards compatibility
|
||||
"ow_layout": "vanilla",
|
||||
"ow_parallel": True,
|
||||
"ow_unparallel": False,
|
||||
"ow_terrain": False,
|
||||
"ow_crossed": "none",
|
||||
"ow_keepsimilar": False,
|
||||
"ow_mixed": False,
|
||||
"ow_whirlpool": False,
|
||||
"ow_fluteshuffle": "vanilla",
|
||||
"ow_fog": True,
|
||||
"ow_no_fog": False,
|
||||
"shuffle_followers": False,
|
||||
"bonk_drops": False,
|
||||
"shuffle": "vanilla",
|
||||
|
||||
@@ -1030,7 +1030,10 @@ def balance_money_progression(world):
|
||||
'Rupees (100)': 100, 'Rupees (300)': 300}
|
||||
rupee_rooms = {'Eastern Rupees': 90, 'Mire Key Rupees': 45, 'Mire Shooter Rupees': 90,
|
||||
'TR Rupees': 270, 'PoD Dark Basement': 270}
|
||||
acceptable_balancers = ['Bombs (3)', 'Arrows (10)', 'Bombs (10)']
|
||||
acceptable_balancers = ['Single Bomb', 'Bombs (3)', 'Bombs (10)',
|
||||
'Single Arrow', 'Arrows (5)', 'Arrows (10)',
|
||||
'Small Magic', 'Big Magic', 'Small Heart',
|
||||
'Fairy', 'Chicken', 'Nothing']
|
||||
|
||||
base_value = sum(rupee_rooms.values())
|
||||
available_money = {player: base_value for player in range(1, world.players+1)}
|
||||
@@ -1060,6 +1063,8 @@ def balance_money_progression(world):
|
||||
return [loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)]
|
||||
|
||||
def interesting_item(location, item, world, player):
|
||||
if location.event or location.locked:
|
||||
return True
|
||||
if item.advancement:
|
||||
return True
|
||||
if item.type is not None or item.name.startswith('Rupee'):
|
||||
@@ -1129,10 +1134,11 @@ def balance_money_progression(world):
|
||||
unchecked_locations.remove(location)
|
||||
if location.item:
|
||||
if location.item.name.startswith('Rupee'):
|
||||
wallet[location.item.player] += rupee_chart[location.item.name]
|
||||
if location.item.name != 'Rupees (300)':
|
||||
balance_locations[location.item.player].add(location)
|
||||
if interesting_item(location, location.item, world, location.item.player):
|
||||
if not (location.item.name == 'Rupee (1)' and world.algorithm != 'district'):
|
||||
wallet[location.item.player] += rupee_chart[location.item.name]
|
||||
if location.item.name != 'Rupees (300)':
|
||||
balance_locations[location.item.player].add(location)
|
||||
elif interesting_item(location, location.item, world, location.item.player):
|
||||
checked_locations.append(location)
|
||||
elif location.item.name in acceptable_balancers:
|
||||
balance_locations[location.item.player].add(location)
|
||||
@@ -1176,7 +1182,11 @@ def balance_money_progression(world):
|
||||
if len(increase_targets) == 0:
|
||||
increase_targets = [x for x in balance_locations[target_player] if (rupee_chart[x.item.name] if x.item.name in rupee_chart else 0) < best_value]
|
||||
if len(increase_targets) == 0:
|
||||
raise Exception('No early sphere swaps for rupees - money grind would be required - bailing for now')
|
||||
if state.can_farm_rupees(target_player):
|
||||
logger.warning(f'No more swap targets available. Short by {difference} rupees, but continuing (player can farm)')
|
||||
break
|
||||
else:
|
||||
raise Exception(f'No early sphere swaps for rupees - money grind would be required - bailing for now')
|
||||
best_target = min(increase_targets, key=lambda t: rupee_chart[t.item.name] if t.item.name in rupee_chart else 0)
|
||||
make_item_free = wallet[target_player] < 20
|
||||
old_value = 0 if make_item_free else (rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0)
|
||||
|
||||
+32
-13
@@ -1727,6 +1727,7 @@ def shuffle_event_items(world, player):
|
||||
available_quests = follower_quests.copy()
|
||||
available_pickups = [quests[0] for quests in available_quests.values()]
|
||||
|
||||
# finalize customizer followers first
|
||||
for loc_name in follower_quests.keys():
|
||||
loc = world.get_location(loc_name, player)
|
||||
if loc.item:
|
||||
@@ -1734,7 +1735,6 @@ def shuffle_event_items(world, player):
|
||||
available_quests.pop(loc_name)
|
||||
available_pickups.remove(loc.item.name)
|
||||
|
||||
|
||||
if world.mode[player] == 'standard' and 'Zelda Herself' in available_pickups:
|
||||
zelda_dropoff = 'Zelda Pickup'
|
||||
if world.default_zelda_region[player] == 'Thieves Blind\'s Cell':
|
||||
@@ -1744,20 +1744,39 @@ def shuffle_event_items(world, player):
|
||||
available_pickups.remove(zelda_pickup)
|
||||
set_event_item(world, player, zelda_dropoff, zelda_pickup)
|
||||
|
||||
random.shuffle(available_pickups)
|
||||
follower_locations = [world.get_location(loc_name, player) for loc_name in available_quests.keys()]
|
||||
|
||||
restricted_pickups = { 'Get Frog': 'Dark Blacksmith Ruins'}
|
||||
for pickup in restricted_pickups:
|
||||
restricted_quests = [q for q in available_quests.keys() if q not in restricted_pickups[pickup]]
|
||||
random.shuffle(restricted_quests)
|
||||
quest = restricted_quests.pop()
|
||||
available_quests.pop(quest)
|
||||
available_pickups.remove(pickup)
|
||||
set_event_item(world, player, quest, pickup)
|
||||
attempts = 10
|
||||
for attempt in range(attempts):
|
||||
try:
|
||||
all_state = world.get_all_state(keys=True)
|
||||
if world.prizeshuffle[player] != 'wild':
|
||||
from Items import prize_item_table
|
||||
prizes = ItemFactory(list(prize_item_table.keys()), player)
|
||||
for prize in prizes:
|
||||
all_state.collect(prize, True)
|
||||
|
||||
for pickup in available_pickups:
|
||||
quest, _ = available_quests.popitem()
|
||||
set_event_item(world, player, quest, pickup)
|
||||
# randomize the follower pickups, but ensure that the last items are the unrestrictive ones
|
||||
unrestrictive_pickups = ItemFactory([p for p in ['Zelda Herself', 'Sign Vandalized'] if p in available_pickups], player)
|
||||
restrictive_pickups = ItemFactory([p for p in available_pickups if p not in unrestrictive_pickups], player)
|
||||
random.shuffle(restrictive_pickups)
|
||||
random.shuffle(unrestrictive_pickups)
|
||||
pickup_items = unrestrictive_pickups + restrictive_pickups
|
||||
random.shuffle(follower_locations)
|
||||
|
||||
fill_restrictive(world, all_state, follower_locations, pickup_items, single_player_placement=True)
|
||||
for loc_name in available_quests.keys():
|
||||
loc = world.get_location(loc_name, player)
|
||||
if loc.item:
|
||||
set_event_item(world, player, loc_name)
|
||||
except FillError as e:
|
||||
logging.getLogger('').info("Failed to place followers (%s). Will retry %s more times", e, attempts - attempt - 1)
|
||||
for loc in follower_locations:
|
||||
loc.item = None
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise FillError(f'Unable to place followers: {", ".join(list(map(lambda d: d.hint_text, follower_locations)))}')
|
||||
|
||||
|
||||
def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool):
|
||||
|
||||
@@ -503,7 +503,7 @@ def init_world(args, fish):
|
||||
customized.load_yaml(args.customizer)
|
||||
customized.adjust_args(args, False)
|
||||
|
||||
world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
|
||||
world = World(args.multi, args.ow_layout, args.ow_parallel, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
|
||||
args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm,
|
||||
args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints, args.spoiler)
|
||||
|
||||
@@ -529,6 +529,7 @@ def init_world(args, fish):
|
||||
world.owKeepSimilar = args.ow_keepsimilar.copy()
|
||||
world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
|
||||
world.owFluteShuffle = args.ow_fluteshuffle.copy()
|
||||
world.owFog = args.ow_fog.copy()
|
||||
world.shuffle_followers = args.shuffle_followers.copy()
|
||||
world.shuffle_bonk_drops = args.bonk_drops.copy()
|
||||
world.open_pyramid = args.openpyramid.copy()
|
||||
@@ -806,7 +807,7 @@ def set_starting_inventory(world, args):
|
||||
|
||||
def copy_world(world):
|
||||
# ToDo: Not good yet
|
||||
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
||||
ret = World(world.players, world.owLayout, world.owParallel, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
||||
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
||||
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
|
||||
ret.teams = world.teams
|
||||
@@ -852,6 +853,7 @@ def copy_world(world):
|
||||
ret.owKeepSimilar = world.owKeepSimilar.copy()
|
||||
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
|
||||
ret.owFluteShuffle = world.owFluteShuffle.copy()
|
||||
ret.owFog = world.owFog.copy()
|
||||
ret.shuffle_followers = world.shuffle_followers.copy()
|
||||
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
|
||||
ret.open_pyramid = world.open_pyramid.copy()
|
||||
@@ -1037,7 +1039,7 @@ def copy_world(world):
|
||||
|
||||
def copy_world_premature(world, player, create_flute_exits=True):
|
||||
# ToDo: Not good yet
|
||||
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
||||
ret = World(world.players, world.owLayout, world.owParallel, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
||||
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
||||
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
|
||||
ret.teams = world.teams
|
||||
@@ -1083,6 +1085,7 @@ def copy_world_premature(world, player, create_flute_exits=True):
|
||||
ret.owKeepSimilar = world.owKeepSimilar.copy()
|
||||
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
|
||||
ret.owFluteShuffle = world.owFluteShuffle.copy()
|
||||
ret.owFog = world.owFog.copy()
|
||||
ret.shuffle_followers = world.shuffle_followers.copy()
|
||||
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
|
||||
ret.open_pyramid = world.open_pyramid.copy()
|
||||
|
||||
@@ -326,12 +326,20 @@ def create_owedges(world, player):
|
||||
|
||||
world.owedges += edges
|
||||
world.initialize_owedges(edges)
|
||||
set_parallel_owedge_links(world, player, edges)
|
||||
|
||||
def create_owedge(player, name, owIndex, direction, terrain, edge_id, owSlotIndex=0xff):
|
||||
if name not in OWExitTypes['OWEdge']:
|
||||
OWExitTypes['OWEdge'].append(name)
|
||||
return OWEdge(player, name, owIndex, direction, terrain, edge_id, owSlotIndex)
|
||||
|
||||
def set_parallel_owedge_links(world, player, edges):
|
||||
for edge in edges:
|
||||
if edge.name in parallel_links:
|
||||
dw_edge = world.get_owedge(parallel_links[edge.name], player)
|
||||
edge.parallel = dw_edge
|
||||
dw_edge.parallel = edge
|
||||
|
||||
|
||||
OWEdgeGroups = {
|
||||
#(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup, CustomizerGroup)
|
||||
|
||||
+178
-85
@@ -127,37 +127,38 @@ def link_overworld(world, player):
|
||||
|
||||
# restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship
|
||||
parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken)
|
||||
if world.owKeepSimilar[player]:
|
||||
del parallel_links_new['Maze Race ES']
|
||||
del parallel_links_new['Kakariko Suburb WS']
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, parallel, _, custom) = group
|
||||
if parallel == IsParallel.Yes:
|
||||
if world.owLayout[player] != 'grid':
|
||||
if world.owKeepSimilar[player]:
|
||||
del parallel_links_new['Maze Race ES']
|
||||
del parallel_links_new['Kakariko Suburb WS']
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, parallel, _, custom) = group
|
||||
if parallel == IsParallel.Yes:
|
||||
(forward_edges, back_edges) = trimmed_groups[group]
|
||||
if ['Maze Race ES'] in forward_edges:
|
||||
forward_edges.remove(['Maze Race ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES'])
|
||||
if ['Kakariko Suburb WS'] in back_edges:
|
||||
back_edges.remove(['Kakariko Suburb WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS'])
|
||||
trimmed_groups[group] = (forward_edges, back_edges)
|
||||
else:
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, _, _, custom) = group
|
||||
(forward_edges, back_edges) = trimmed_groups[group]
|
||||
if ['Maze Race ES'] in forward_edges:
|
||||
forward_edges.remove(['Maze Race ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES'])
|
||||
if ['Kakariko Suburb WS'] in back_edges:
|
||||
back_edges.remove(['Kakariko Suburb WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS'])
|
||||
if ['Dig Game EC', 'Dig Game ES'] in forward_edges:
|
||||
forward_edges.remove(['Dig Game EC', 'Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC'])
|
||||
if ['Frog WC', 'Frog WS'] in back_edges:
|
||||
back_edges.remove(['Frog WC', 'Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC'])
|
||||
trimmed_groups[group] = (forward_edges, back_edges)
|
||||
else:
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, _, _, custom) = group
|
||||
(forward_edges, back_edges) = trimmed_groups[group]
|
||||
if ['Dig Game EC', 'Dig Game ES'] in forward_edges:
|
||||
forward_edges.remove(['Dig Game EC', 'Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC'])
|
||||
if ['Frog WC', 'Frog WS'] in back_edges:
|
||||
back_edges.remove(['Frog WC', 'Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC'])
|
||||
trimmed_groups[group] = (forward_edges, back_edges)
|
||||
parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})}
|
||||
|
||||
connected_edges = []
|
||||
if world.owShuffle[player] != 'vanilla':
|
||||
if world.owLayout[player] != 'vanilla':
|
||||
trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
|
||||
trimmed_groups = reorganize_groups(world, trimmed_groups, player)
|
||||
|
||||
@@ -245,7 +246,7 @@ def link_overworld(world, player):
|
||||
if 'undefined_chance' in custom_crossed:
|
||||
undefined_chance = custom_crossed['undefined_chance']
|
||||
|
||||
if limited_crossed > -1:
|
||||
if limited_crossed > -1 and world.owLayout[player] != 'grid':
|
||||
# connect forced crossed non-parallel edges based on previously determined tile flips
|
||||
for edge in swapped_edges:
|
||||
if edge not in parallel_links_new:
|
||||
@@ -277,10 +278,10 @@ def link_overworld(world, player):
|
||||
s[0x30], s[0x35],
|
||||
s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
||||
world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player)
|
||||
elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'):
|
||||
elif (limited_crossed > -1 and world.owLayout[player] != 'grid') or (world.owLayout[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'):
|
||||
crossed_candidates = list()
|
||||
for group in trimmed_groups.keys():
|
||||
(mode, wrld, dir, terrain, parallel, count, _) = group
|
||||
(mode, wrld, _, terrain, parallel, _, _) = group
|
||||
if wrld == WorldType.Light and mode != OpenStd.Standard:
|
||||
for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]):
|
||||
if forward_set[0] in parallel_links_new:
|
||||
@@ -291,7 +292,7 @@ def link_overworld(world, player):
|
||||
combine_set = forward_combine+back_combine
|
||||
|
||||
skip_forward = False
|
||||
if world.owShuffle[player] == 'vanilla':
|
||||
if world.owLayout[player] == 'vanilla':
|
||||
if any(edge in force_crossed for edge in combine_set):
|
||||
if not any(edge in force_noncrossed for edge in combine_set):
|
||||
if any(edge in force_crossed for edge in forward_combine):
|
||||
@@ -425,7 +426,7 @@ def link_overworld(world, player):
|
||||
# layout shuffle
|
||||
logging.getLogger('').debug('Shuffling overworld layout')
|
||||
|
||||
if world.owShuffle[player] == 'vanilla':
|
||||
if world.owLayout[player] == 'vanilla':
|
||||
# apply outstanding flips
|
||||
trimmed_groups = performSwap(trimmed_groups, edges_to_swap)
|
||||
assert len(edges_to_swap) == 0, 'Not all edges were flipped successfully: ' + ', '.join(edges_to_swap)
|
||||
@@ -438,8 +439,15 @@ def link_overworld(world, player):
|
||||
assert len(forward_set) == len(back_set)
|
||||
for (forward_edge, back_edge) in zip(forward_set, back_set):
|
||||
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
||||
elif world.owLayout[player] == 'grid':
|
||||
from source.overworld.LayoutGenerator import generate_random_grid_layout
|
||||
|
||||
for exitname, destname in special_screen_connections:
|
||||
connect_two_way(world, exitname, destname, player, connected_edges)
|
||||
|
||||
generate_random_grid_layout(world, player, connected_edges, ow_crossed_tiles if world.owCrossed[player] == 'grouped' else [], force_noncrossed, force_crossed, limited_crossed, undefined_chance / 100)
|
||||
else:
|
||||
if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel':
|
||||
if world.owKeepSimilar[player] and world.owParallel[player]:
|
||||
for exitname, destname in parallelsimilar_connections:
|
||||
connect_two_way(world, exitname, destname, player, connected_edges)
|
||||
|
||||
@@ -589,7 +597,10 @@ def link_overworld(world, player):
|
||||
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
||||
|
||||
if world.owFluteShuffle[player] == 'vanilla':
|
||||
connect_flutes(default_flute_connections)
|
||||
flute_spots = default_flute_connections.copy()
|
||||
sort_flute_spots(world, player, flute_spots)
|
||||
world.owflutespots[player] = flute_spots
|
||||
connect_flutes(flute_spots)
|
||||
else:
|
||||
flute_spots = 8
|
||||
flute_pool = list(flute_data.keys())
|
||||
@@ -716,7 +727,7 @@ def link_overworld(world, player):
|
||||
flute_spots -= spots_to_place
|
||||
|
||||
# connect new flute spots
|
||||
new_spots.sort()
|
||||
sort_flute_spots(world, player, new_spots)
|
||||
world.owflutespots[player] = new_spots
|
||||
connect_flutes(new_spots)
|
||||
|
||||
@@ -835,7 +846,7 @@ def connect_custom(world, connected_edges, groups, forced, player):
|
||||
remove_pair_from_pool(edge1.name, edge2.name, is_crossed)
|
||||
connect_two_way(world, edge1.name, edge2.name, player, connected_edges)
|
||||
# resolve parallel
|
||||
if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new:
|
||||
if world.owParallel[player] and edge1.name in parallel_links_new:
|
||||
parallel_forward_edge = parallel_links_new[edge1.name]
|
||||
parallel_back_edge = parallel_links_new[edge2.name]
|
||||
if validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed):
|
||||
@@ -851,13 +862,13 @@ def connect_custom(world, connected_edges, groups, forced, player):
|
||||
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
||||
else:
|
||||
raise GenerationException('Violation of force crossed rules on unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge)
|
||||
if world.owShuffle[player] == 'parallel' and forward_edge in parallel_links_new:
|
||||
if world.owParallel[player] and forward_edge in parallel_links_new:
|
||||
parallel_forward_edge = parallel_links_new[forward_edge]
|
||||
parallel_back_edge = parallel_links_new[back_edge]
|
||||
if not validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed):
|
||||
raise GenerationException('Violation of force crossed rules on parallel unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge)
|
||||
|
||||
def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
|
||||
def connect_two_way(world, edgename1, edgename2, player, connected_edges=None, set_spoiler=True):
|
||||
edge1 = world.get_entrance(edgename1, player)
|
||||
edge2 = world.get_entrance(edgename2, player)
|
||||
x = world.get_owedge(edgename1, player)
|
||||
@@ -881,7 +892,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
|
||||
x.dest = y
|
||||
y.dest = x
|
||||
|
||||
if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none':
|
||||
if set_spoiler and (world.owLayout[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none'):
|
||||
world.spoiler.set_overworld(edgename2, edgename1, 'both', player)
|
||||
|
||||
if connected_edges is not None:
|
||||
@@ -889,7 +900,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
|
||||
connected_edges.append(edgename2)
|
||||
|
||||
# connecting parallel connections
|
||||
if world.owShuffle[player] in ['vanilla', 'parallel']:
|
||||
if world.owLayout[player] == 'vanilla' or world.owParallel[player]:
|
||||
if edgename1 in parallel_links_new:
|
||||
try:
|
||||
parallel_forward_edge = parallel_links_new[edgename1]
|
||||
@@ -978,7 +989,7 @@ def determine_forced_flips(world, tile_ow_groups, do_grouped, player):
|
||||
for whirl1, whirl2 in custom_whirlpools.items():
|
||||
if [whirlpool_map[whirl1], whirlpool_map[whirl2]] not in merged_owids and should_merge_group(whirlpool_map[whirl1], whirlpool_map[whirl2]):
|
||||
merged_owids.append([whirlpool_map[whirl1], whirlpool_map[whirl2]])
|
||||
if world.owShuffle[player] != 'vanilla':
|
||||
if world.owLayout[player] != 'vanilla':
|
||||
custom_edges = world.customizer.get_owedges()
|
||||
if custom_edges and player in custom_edges:
|
||||
custom_edges = custom_edges[player]
|
||||
@@ -1084,6 +1095,9 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player):
|
||||
exist_dw_regions.extend(dw_regions)
|
||||
|
||||
parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)]
|
||||
if world.owLayout[player] == 'grid':
|
||||
parity[1] = 0
|
||||
parity[2] = 0
|
||||
if not world.owKeepSimilar[player]:
|
||||
parity[1] += 2*parity[2]
|
||||
parity[2] = 0
|
||||
@@ -1177,12 +1191,16 @@ def define_tile_groups(world, do_grouped, player):
|
||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']:
|
||||
merge_groups([[0x05, 0x07]])
|
||||
|
||||
# all non-parallel screens
|
||||
if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
|
||||
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
|
||||
# special screens
|
||||
if world.owLayout[player] != 'wild' and (world.owCrossed[player] == 'none' or do_grouped):
|
||||
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81]])
|
||||
|
||||
# remaining non-parallel edges
|
||||
if world.owLayout[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
|
||||
merge_groups([[0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
|
||||
|
||||
# special case: non-parallel keep similar
|
||||
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
||||
if world.owLayout[player] == 'wild' and world.owParallel[player] and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
||||
merge_groups([[0x28, 0x29]])
|
||||
|
||||
# whirlpool screens
|
||||
@@ -1238,7 +1256,7 @@ def reorganize_groups(world, groups, player):
|
||||
new_group[0] = None
|
||||
if world.owTerrain[player]:
|
||||
new_group[3] = None
|
||||
if world.owShuffle[player] != 'parallel':
|
||||
if not world.owParallel[player]:
|
||||
new_group[4] = None
|
||||
if not world.owKeepSimilar[player]:
|
||||
new_group[5] = None
|
||||
@@ -1307,6 +1325,15 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
|
||||
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
||||
return groups
|
||||
|
||||
def sort_flute_spots(world, player, flute_spots):
|
||||
if world.owLayout[player] != 'grid':
|
||||
flute_spots.sort(key=lambda id: flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04)
|
||||
else:
|
||||
world_layout = world.owgrid[player][0] if world.mode[player] != 'inverted' else world.owgrid[player][1]
|
||||
layout_list = sum(world_layout, [])
|
||||
layout_map = {id & 0xBF: i for i, id in enumerate(layout_list)}
|
||||
flute_spots.sort(key=lambda id: layout_map[flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04])
|
||||
|
||||
def create_dynamic_flute_exits(world, player):
|
||||
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
||||
if not flute_in_pool:
|
||||
@@ -2347,6 +2374,11 @@ parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'),
|
||||
('Dig Game ES', 'Frog WS')
|
||||
]
|
||||
|
||||
special_screen_connections = [('Lost Woods NW', 'Master Sword Meadow SC'),
|
||||
('Stone Bridge WC', 'Hobo EC'),
|
||||
('Zora Waterfall NE', 'Zoras Domain SW')
|
||||
]
|
||||
|
||||
# non shuffled overworld
|
||||
default_connections = [('Lost Woods NW', 'Master Sword Meadow SC'),
|
||||
('Lost Woods SW', 'Lost Woods Pass NW'),
|
||||
@@ -2560,46 +2592,107 @@ isolated_regions = [
|
||||
flute_data = {
|
||||
#OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
|
||||
0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
|
||||
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550),
|
||||
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
||||
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8),
|
||||
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20),
|
||||
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488),
|
||||
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x02d0, 0x0f38),
|
||||
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x0448, 0x0058),
|
||||
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x0588, 0x0318),
|
||||
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x0578, 0x04f8),
|
||||
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x0508, 0x0758),
|
||||
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x0580, 0x0928, 0x0580, 0x0948),
|
||||
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x04f8, 0x0b60),
|
||||
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x0598, 0x0ccf),
|
||||
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x04e0, 0x0f68),
|
||||
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x07c0, 0x0210, 0x07c8, 0x01f8),
|
||||
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0548),
|
||||
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07d8, 0x07f8),
|
||||
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0720, 0x0a80),
|
||||
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80),
|
||||
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0908, 0x04d8),
|
||||
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x08b8, 0x0b08),
|
||||
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0b88, 0x00b8),
|
||||
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0ae0, 0x02c0),
|
||||
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b10, 0x04e8),
|
||||
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b30, 0x0770),
|
||||
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0b20, 0x08b8),
|
||||
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0),
|
||||
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58),
|
||||
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30),
|
||||
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070),
|
||||
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0cd6, 0x0568),
|
||||
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0c80, 0x0628),
|
||||
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0d60, 0x08d8),
|
||||
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88),
|
||||
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0f90, 0x0da4),
|
||||
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d48, 0x0ed0),
|
||||
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0e70, 0x0540),
|
||||
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0f30, 0x0770),
|
||||
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0f38, 0x0898),
|
||||
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0f80, 0x0ef0)
|
||||
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x01d8, 0x0518),
|
||||
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
||||
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98),
|
||||
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0),
|
||||
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8),
|
||||
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58),
|
||||
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088),
|
||||
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318),
|
||||
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8),
|
||||
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738),
|
||||
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948),
|
||||
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78),
|
||||
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f),
|
||||
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48),
|
||||
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8),
|
||||
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518),
|
||||
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8),
|
||||
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70),
|
||||
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0),
|
||||
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8),
|
||||
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18),
|
||||
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8),
|
||||
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0),
|
||||
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8),
|
||||
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760),
|
||||
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8),
|
||||
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8),
|
||||
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70),
|
||||
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00),
|
||||
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070),
|
||||
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528),
|
||||
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688),
|
||||
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8),
|
||||
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90),
|
||||
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8),
|
||||
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0),
|
||||
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570),
|
||||
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770),
|
||||
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898),
|
||||
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0)
|
||||
}
|
||||
|
||||
ow_loc_prize_table = {
|
||||
'Master Sword Pedestal': (0x06d, 0x070),
|
||||
'Hobo': (0xb80, 0xb90),
|
||||
'Mushroom': (0x180, 0x140),
|
||||
'Lost Woods Hideout Tree': (0x200, 0x320),
|
||||
'Ether Tablet': (0x728, 0x017),
|
||||
'Spectacle Rock': (0x7F8, 0x128),
|
||||
'Old Man': (0x6A8, 0x3FF),
|
||||
'Floating Island': (0xD38, 0x038),
|
||||
'Death Mountain Bonk Rocks': (0xD48, 0x107),
|
||||
'Mountain Pass Pull Tree': (0x4E0, 0x380),
|
||||
'Mountain Pass Southeast Tree': (0x5A0, 0x3FF),
|
||||
'Lost Woods Pass West Tree': (0x080, 0x540),
|
||||
'Kakariko Portal Tree': (0x190, 0x540),
|
||||
'Fortune Bonk Rocks': (0x288, 0x558),
|
||||
'Bonk Rocks Tree': (0x680, 0x520),
|
||||
'Sanctuary Tree': (0x788, 0x594),
|
||||
'River Bend West Tree': (0xA38, 0x5FF),
|
||||
'River Bend East Tree': (0xB88, 0x558),
|
||||
'Bottle Merchant': (0x178, 0x7E8),
|
||||
'Blinds Hideout Tree': (0x118, 0x6F7),
|
||||
'Kakariko Welcome Tree': (0x358, 0x988),
|
||||
'Hyrule Castle Tree': (0x730, 0x780),
|
||||
'Wooden Bridge Tree': (0xA78, 0x6F0),
|
||||
'Eastern Palace Tree': (0xEC0, 0x968),
|
||||
'Maze Race': (0x0C0, 0xB00),
|
||||
'Flute Spot': (0x480, 0xAC0),
|
||||
'Flute Boy East Tree': (0x540, 0xB80),
|
||||
'Flute Boy South Tree': (0x540, 0xB80),
|
||||
'Central Bonk Rocks Tree': (0x768, 0xB37),
|
||||
'Tree Line Tree 2': (0xCD8, 0xB00),
|
||||
'Tree Line Tree 4': (0xCD8, 0xB00),
|
||||
'Desert Ledge': (0x018, 0xE88),
|
||||
'Bombos Tablet': (0x3E8, 0xEF8),
|
||||
'Flute Boy Approach North Tree': (0x588, 0xD07),
|
||||
'Flute Boy Approach South Tree': (0x588, 0xD07),
|
||||
'Lake Hylia Island': (0xB98, 0xD88),
|
||||
'Purple Chest': (0x588, 0xE96),
|
||||
'Sunken Treasure': (0x6F8, 0xF48),
|
||||
|
||||
'Dark Lumberjack Tree': (0x438, 0x1F7),
|
||||
'Bumper Cave Ledge': (0x538, 0x277),
|
||||
'Catfish': (0xE80, 0x368),
|
||||
'Dark Fortune Bonk Rocks (Drop 1)': (0x288, 0x558),
|
||||
'Dark Fortune Bonk Rocks (Drop 2)': (0x288, 0x558),
|
||||
'Dark Graveyard West Bonk Rocks': (0x900, 0x580),
|
||||
'Dark Graveyard North Bonk Rocks': (0x900, 0x580),
|
||||
'Dark Graveyard Tomb Bonk Rocks': (0x900, 0x580),
|
||||
'Qirn Jump West Tree': (0xA98, 0x5FF),
|
||||
'Qirn Jump East Tree': (0xB88, 0x558),
|
||||
'Dark Witch Tree': (0xC28, 0x558),
|
||||
'Pyramid': (0x998, 0x778),
|
||||
'Pyramid Tree': (0x738, 0x908),
|
||||
'Palace of Darkness Tree': (0xEC0, 0x968),
|
||||
'Digging Game': (0x0C0, 0xB00),
|
||||
'Dark Tree Line Tree 2': (0xCD8, 0xB00),
|
||||
'Dark Tree Line Tree 3': (0xCD8, 0xB00),
|
||||
'Dark Tree Line Tree 4': (0xCD8, 0xB00),
|
||||
'Hype Cave Statue': (0x900, 0xDB0)
|
||||
}
|
||||
|
||||
tile_swap_spoiler_table = \
|
||||
|
||||
@@ -35,7 +35,7 @@ def main(args):
|
||||
start_time = time.process_time()
|
||||
|
||||
# initialize the world
|
||||
world = World(1, 'vanilla', 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False)
|
||||
world = World(1, 'vanilla', True, 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False)
|
||||
world.player_names[1].append("Player 1")
|
||||
logger = logging.getLogger('')
|
||||
|
||||
@@ -168,9 +168,9 @@ def prefill_world(world, plando, text_patches):
|
||||
elif line.startswith('!goal'):
|
||||
_, goalstr = line.split(':', 1)
|
||||
world.goal = {1: goalstr.strip()}
|
||||
elif line.startswith('!owShuffle'):
|
||||
elif line.startswith('!owLayout'):
|
||||
_, modestr = line.split(':', 1)
|
||||
world.owShuffle = {1: modestr.strip()}
|
||||
world.owLayout = {1: modestr.strip()}
|
||||
elif line.startswith('!owCrossed'):
|
||||
_, modestr = line.split(':', 1)
|
||||
world.owCrossed = {1: modestr.strip()}
|
||||
|
||||
@@ -244,7 +244,7 @@ Ganons Tower - Validation Chest: Nothing
|
||||
Ganon: Triforce
|
||||
|
||||
# set Overworld connections (lines starting with $, separate edges with =)
|
||||
!owShuffle: parallel
|
||||
!owLayout: wild
|
||||
#!owMixed: true # Mixed OW not supported yet
|
||||
!owCrossed: none
|
||||
!owKeepSimilar: true
|
||||
|
||||
@@ -132,20 +132,24 @@ Note: These changes do impact the logic. If you use `CodeTracker`, these Inverte
|
||||
|
||||
Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
|
||||
|
||||
## Overworld Layout Shuffle (--ow_shuffle)
|
||||
## Overworld Layout Shuffle (--ow_layout)
|
||||
OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://zelda.codemann8.com/images/shared/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Flip)
|
||||
|
||||
### Vanilla
|
||||
|
||||
OW Transitions are not shuffled.
|
||||
|
||||
### Parallel
|
||||
### Grid
|
||||
|
||||
OW Transitions are shuffled, but both worlds will have a matching layout, similar to that of vanilla.
|
||||
OW Screens are shuffled in such a way that they are still arranged on an 8x8 grid for each world like vanilla, and the OW Transitions are based on that arrangement.
|
||||
|
||||
### Full
|
||||
### Wild
|
||||
|
||||
OW Transitions are shuffled within each world separately.
|
||||
OW Transitions are shuffled with no respect to geometric coherence.
|
||||
|
||||
## Parallel (--ow_unparallel to disable)
|
||||
|
||||
With OW Layout Shuffle, this forces both worlds to have a matching layout.
|
||||
|
||||
## Free Terrain (--ow_terrain)
|
||||
|
||||
@@ -389,11 +393,17 @@ Districts are a concept originally conceived by Aerinon in the Door Randomizer,
|
||||
Show the help message and exit.
|
||||
|
||||
```
|
||||
--ow_shuffle <mode>
|
||||
--ow_layout <mode>
|
||||
```
|
||||
|
||||
For specifying the overworld layout shuffle you want as above. (default: vanilla)
|
||||
|
||||
```
|
||||
--ow_unparallel
|
||||
```
|
||||
|
||||
With OW Layout Shuffle, this no longer forces both worlds to have a matching layout.
|
||||
|
||||
```
|
||||
--ow_terrain
|
||||
```
|
||||
@@ -424,6 +434,12 @@ This gives each OW tile a random chance to be flipped to the opposite world
|
||||
|
||||
For randomizing the flute spots around the overworld
|
||||
|
||||
```
|
||||
--ow_no_fog
|
||||
```
|
||||
|
||||
With OW Grid Layout Shuffle or Mixed, this disables the fog that prevents you from seeing unvisited screens on the overworld map.
|
||||
|
||||
```
|
||||
--shuffle_followers
|
||||
```
|
||||
|
||||
@@ -84,7 +84,7 @@ from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc
|
||||
from Versions import DRVersion, GKVersion, ORVersion
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '2647cc28bca3675152576dd1f5ea0bab'
|
||||
RANDOMIZERBASEHASH = '33cd5e308266cf2273c80de1b1df3dac'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -530,14 +530,15 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
|
||||
# patch flute spots
|
||||
owFlags = 0
|
||||
if world.owFluteShuffle[player] == 'vanilla':
|
||||
owFog = 0
|
||||
if world.owFluteShuffle[player] == 'vanilla' and world.owLayout[player] != 'grid':
|
||||
flute_spots = default_flute_connections
|
||||
else:
|
||||
flute_spots = world.owflutespots[player]
|
||||
owFlags |= 0x0100
|
||||
write_int16(rom, snes_to_pc(0x0AB7F7), 0xEAEA)
|
||||
|
||||
flute_writes = sorted([(f, flute_data[f][1]) for f in flute_spots], key = lambda f: f[1])
|
||||
flute_writes = [(f, flute_data[f][1]) for f in flute_spots]
|
||||
for o in range(0, len(flute_writes)):
|
||||
owid = flute_writes[o][0]
|
||||
offset = 0
|
||||
@@ -550,46 +551,64 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
write_int16(rom, snes_to_pc(0x02E8D1 + (o * 2)), data[13] if offset > 0 and len(data) > 13 else data[5]) # link Y
|
||||
write_int16(rom, snes_to_pc(0x02E8F3 + (o * 2)), data[14] if offset > 0 and len(data) > 13 else data[6]) # link X
|
||||
|
||||
if offset == 0 or len(data) <= 15:
|
||||
write_int16(rom, snes_to_pc(0x02E86B + (o * 2)), data[2]) # vram
|
||||
write_int16(rom, snes_to_pc(0x02E88D + (o * 2)), data[3]) # BG scroll Y
|
||||
write_int16(rom, snes_to_pc(0x02E8AF + (o * 2)), data[4]) # BG scroll X
|
||||
write_int16(rom, snes_to_pc(0x02E915 + (o * 2)), data[7]) # cam Y
|
||||
write_int16(rom, snes_to_pc(0x02E937 + (o * 2)), data[8]) # cam X
|
||||
write_int16(rom, snes_to_pc(0x02E959 + (o * 2)), data[9]) # unknown 1
|
||||
write_int16(rom, snes_to_pc(0x02E97B + (o * 2)), data[10]) # unknown 2
|
||||
rom.write_byte(snes_to_pc(0x0AB783 + o), data[12] & 0xff) # flute menu blip - X low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB78B + o), data[12] // 0x100) # flute menu blip - X high byte
|
||||
rom.write_byte(snes_to_pc(0x0AB793 + o), data[11] & 0xff) # flute menu blip - Y low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB79B + o), data[11] // 0x100) # flute menu blip - Y high byte
|
||||
else: # use alternate flute data
|
||||
write_int16(rom, snes_to_pc(0x02E86B + (o * 2)), data[15]) # vram
|
||||
write_int16(rom, snes_to_pc(0x02E88D + (o * 2)), data[16]) # BG scroll Y
|
||||
write_int16(rom, snes_to_pc(0x02E8AF + (o * 2)), data[17]) # BG scroll X
|
||||
write_int16(rom, snes_to_pc(0x02E915 + (o * 2)), data[18]) # cam Y
|
||||
write_int16(rom, snes_to_pc(0x02E937 + (o * 2)), data[19]) # cam X
|
||||
write_int16(rom, snes_to_pc(0x02E959 + (o * 2)), data[20]) # unknown 1
|
||||
write_int16(rom, snes_to_pc(0x02E97B + (o * 2)), data[21]) # unknown 2
|
||||
rom.write_byte(snes_to_pc(0x0AB783 + o), data[23] & 0xff) # flute menu blip - X low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB78B + o), data[23] // 0x100) # flute menu blip - X high byte
|
||||
rom.write_byte(snes_to_pc(0x0AB793 + o), data[22] & 0xff) # flute menu blip - Y low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB79B + o), data[22] // 0x100) # flute menu blip - Y high byte
|
||||
base_index = 0
|
||||
if not (offset == 0 or len(data) <= 15):
|
||||
base_index = 13 # use alternate flute data
|
||||
write_int16(rom, snes_to_pc(0x02E86B + (o * 2)), data[base_index + 2]) # vram
|
||||
write_int16(rom, snes_to_pc(0x02E88D + (o * 2)), data[base_index + 3]) # BG scroll Y
|
||||
write_int16(rom, snes_to_pc(0x02E8AF + (o * 2)), data[base_index + 4]) # BG scroll X
|
||||
if base_index > 0:
|
||||
base_index -= 2
|
||||
write_int16(rom, snes_to_pc(0x02E915 + (o * 2)), data[base_index + 7]) # cam Y
|
||||
write_int16(rom, snes_to_pc(0x02E937 + (o * 2)), data[base_index + 8]) # cam X
|
||||
write_int16(rom, snes_to_pc(0x02E959 + (o * 2)), data[base_index + 9]) # unknown 1
|
||||
write_int16(rom, snes_to_pc(0x02E97B + (o * 2)), data[base_index + 10]) # unknown 2
|
||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, data[base_index + 12], data[base_index + 11], world.mode[player] == 'inverted')
|
||||
rom.write_byte(snes_to_pc(0x0AB783 + o), map_x & 0xff) # flute menu blip - X low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB78B + o), map_x // 0x100) # flute menu blip - X high byte
|
||||
rom.write_byte(snes_to_pc(0x0AB793 + o), map_y & 0xff) # flute menu blip - Y low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB79B + o), map_y // 0x100) # flute menu blip - Y high byte
|
||||
|
||||
# patch whirlpools
|
||||
if world.owWhirlpoolShuffle[player]:
|
||||
owFlags |= 0x01
|
||||
write_int16s(rom, snes_to_pc(0x02EA5C), world.owwhirlpools[player])
|
||||
|
||||
# set custom overworld map layout and fog
|
||||
if world.owLayout[player] == 'grid':
|
||||
owFlags |= 0x06
|
||||
owFog = 1 if world.owParallel[player] else 2
|
||||
grid = world.owgrid[player]
|
||||
all_rows = grid[0] + grid[1]
|
||||
all_cells = sum(all_rows, [])
|
||||
rom.write_bytes(0x153C80, all_cells)
|
||||
for pos, cell_id in enumerate(sum(grid[0], [])):
|
||||
rom.write_byte(0x153D00 + cell_id % 0x40, pos)
|
||||
for pos, cell_id in enumerate(sum(grid[1], [])):
|
||||
rom.write_byte(0x153D40 + cell_id % 0x40, pos)
|
||||
elif world.owMixed[player]:
|
||||
owFlags |= 0x02
|
||||
owFog = 1
|
||||
large_screen_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35, 0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
||||
for cell_id in range(0x80):
|
||||
if cell_id - 0x01 in large_screen_ids:
|
||||
screen_id = cell_id - 0x01
|
||||
elif cell_id - 0x08 in large_screen_ids:
|
||||
screen_id = cell_id - 0x08
|
||||
elif cell_id - 0x09 in large_screen_ids:
|
||||
screen_id = cell_id - 0x09
|
||||
else:
|
||||
screen_id = cell_id
|
||||
world_flag = 0x40 if screen_id in world.owswaps[player][0] else 0x00
|
||||
rom.write_byte(0x153C80 + cell_id, cell_id ^ world_flag)
|
||||
|
||||
# patch overworld edges
|
||||
inverted_buffer = [0] * 0x82
|
||||
owMode = 0
|
||||
if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] not in ['none', 'polar'] or world.owMixed[player]:
|
||||
if world.owShuffle[player] == 'parallel':
|
||||
owMode = 1
|
||||
elif world.owShuffle[player] == 'full':
|
||||
owMode = 2
|
||||
|
||||
if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'):
|
||||
if world.owLayout[player] != 'vanilla' or world.owCrossed[player] not in ['none', 'polar'] or world.owMixed[player]:
|
||||
if world.owLayout[player] != 'vanilla':
|
||||
owMode = 1 if world.owParallel[player] else 2
|
||||
if world.owKeepSimilar[player] and (world.owLayout[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'):
|
||||
owMode |= 0x0100
|
||||
if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]):
|
||||
owMode |= 0x0200
|
||||
@@ -624,10 +643,11 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(0x1539B0 + b + 9, world_flag)
|
||||
|
||||
for edge in world.owedges:
|
||||
if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player:
|
||||
if edge.player == player:
|
||||
write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc)
|
||||
if not edge.specialExit:
|
||||
rom.write_byte(0x1539A0 + (edge.specialID - 0x80) * 2 if edge.specialEntrance else edge.getAddress() + 0x0e, edge.getTarget())
|
||||
destination = edge.getTarget() if edge.dest is not None and isinstance(edge.dest, OWEdge) else 0xFF
|
||||
rom.write_byte(0x1539A0 + (edge.specialID - 0x80) * 2 if edge.specialEntrance else edge.getAddress() + 0x0e, destination)
|
||||
|
||||
# patch bonk prizes
|
||||
if world.shuffle_bonk_drops[player]:
|
||||
@@ -656,6 +676,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
|
||||
write_int16(rom, 0x150002, owMode)
|
||||
write_int16(rom, 0x150004, owFlags)
|
||||
write_int16(rom, 0x150008, owFog if world.owFog[player] else 0x00)
|
||||
|
||||
# patch entrance/exits/holes
|
||||
for region in world.regions:
|
||||
@@ -1317,8 +1338,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
else:
|
||||
goal_bytes += int16_as_bytes(req['target'])
|
||||
elif req['condition'] & 0x80 == 0:
|
||||
if req['condition'] & 0x7F == 0x06 or req['condition'] & 0x7F == 0x07:
|
||||
# agahnims have no target value
|
||||
if req['condition'] & 0x7F in [0x00, 0x06, 0x07]:
|
||||
# no target value
|
||||
pass
|
||||
elif req['condition'] & 0x7F < 0x08:
|
||||
goal_bytes += [req['target']]
|
||||
@@ -1459,9 +1480,15 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
| (0x04 if world.mapshuffle[player] != 'none' else 0x00)
|
||||
| (0x08 if world.bigkeyshuffle[player] != 'none' else 0x00))) # free roaming item text boxes
|
||||
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] not in ['none', 'nearby'] else 0x00) # maps showing crystals on overworld
|
||||
if world.keyshuffle[player] != 'universal' and (world.mapshuffle[player] not in ['none', 'nearby'] or world.doorShuffle[player] != 'vanilla'
|
||||
or world.dropshuffle[player] != 'none' or world.pottery[player] not in ['none', 'cave']):
|
||||
rom.write_byte(0x18003A, 0x01) # show key counts on map pickup
|
||||
map_hud_mode = 0x00
|
||||
if world.dungeon_counters[player] == 'on':
|
||||
map_hud_mode = 0x02 # always on
|
||||
elif world.dungeon_counters[player] == 'off':
|
||||
pass
|
||||
elif world.keyshuffle[player] != 'universal' and (world.mapshuffle[player] not in ['none', 'nearby'] or world.doorShuffle[player] != 'vanilla'
|
||||
or world.dropshuffle[player] != 'none' or world.pottery[player] not in ['none', 'cave'] or world.dungeon_counters[player] == 'pickup'):
|
||||
map_hud_mode = 0x01 # show on pickup
|
||||
rom.write_byte(0x18003A, map_hud_mode)
|
||||
|
||||
loot_source = 0x09
|
||||
if world.prizeshuffle[player] != 'none':
|
||||
@@ -1535,36 +1562,53 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(0x18003C, compass_mode)
|
||||
|
||||
def get_entrance_coords(ent):
|
||||
if ent is None:
|
||||
owid_map = [0x1E, 0x30, 0xFF, 0x7B, 0x5E, 0x70, 0x40, 0x75, 0x03, 0x58, 0x47]
|
||||
x_map_position_generic = [0x03c0, 0x0740, 0xff00, 0x03c0, 0x01c0, 0x0bc0, 0x05c0, 0x09c0, 0x0ac0, 0x07c0, 0x0dc0]
|
||||
y_map_position_generic = [0xff00, 0xff00, 0xff00, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0xff00, 0x0fc0, 0x0fc0]
|
||||
world_indicator = 0x0000
|
||||
idx = int((map_index-2)/2)
|
||||
if ent is None or isinstance(ent, int):
|
||||
owid_map = [ 0x1E, 0x30, 0xFF, 0x7B, 0x5E, 0x70, 0x40, 0x75, 0x03, 0x58, 0x47 ]
|
||||
coord_flags = 0x0000
|
||||
if ent is None:
|
||||
# HUD-style dislocated icons
|
||||
x_map_position = [0x0050, 0x0080, 0xFF00, 0x0040, 0x0020, 0x00C0, 0x0060, 0x00A0, 0x00B0, 0x0080, 0x00E0]
|
||||
y_map_position = [0x000C, 0x000C, 0xFF00, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x000C, 0x00D4, 0x00D4]
|
||||
coord_flags = 0x4000 # raw OAM coord flag
|
||||
idx = int((map_index-2)/2)
|
||||
elif isinstance(ent, int):
|
||||
# vanilla icon positions
|
||||
x_map_position = [0x0F30, 0x0170, 0xFF00, 0x0790, 0x0F30, 0x0160, 0x00F0, 0x0CB0, 0x0900, 0x0240, 0x0F30]
|
||||
y_map_position = [0x06E0, 0x0E50, 0xFF00, 0x0FD0, 0x06E0, 0x0D80, 0x0160, 0x0E80, 0x0130, 0x0840, 0x01B0]
|
||||
idx = ent
|
||||
owid = owid_map[idx]
|
||||
map_x = x_map_position[idx]
|
||||
map_y = y_map_position[idx]
|
||||
if owid != 0xFF:
|
||||
if (owid < 0x40) == (world.is_tile_swapped(owid, player)):
|
||||
world_indicator = 0x8000
|
||||
return [world_indicator | x_map_position_generic[idx], y_map_position_generic[idx]]
|
||||
if type(ent) is Location:
|
||||
from OverworldShuffle import OWTileRegions
|
||||
if ent.name == 'Hobo':
|
||||
coords = (0xb80, 0xb80)
|
||||
elif ent.name == 'Master Sword Pedestal':
|
||||
coords = (0x06d, 0x070)
|
||||
coord_flags |= 0x8000 # world indicator flag
|
||||
if coord_flags & 0x4000 == 0:
|
||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0)
|
||||
return (coord_flags | map_x, map_y)
|
||||
elif type(ent) is Location:
|
||||
from OverworldShuffle import OWTileRegions, ow_loc_prize_table
|
||||
if ent.name in ow_loc_prize_table:
|
||||
coords = ow_loc_prize_table[ent.name]
|
||||
else:
|
||||
owid = OWTileRegions[ent.parent_region.name]
|
||||
if owid == 0x81:
|
||||
coords = (0x220, 0xf40)
|
||||
coords = (0xF60, 0x280)
|
||||
else:
|
||||
owid = owid % 0x40
|
||||
coords = (0x200 * (owid % 0x08) + 0x100, 0x200 * int(owid / 0x08) + 0x100)
|
||||
if owid in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]:
|
||||
if owid in [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35]:
|
||||
coords = (coords[0] + 0x100, coords[1] + 0x100)
|
||||
else:
|
||||
coords = ow_prize_table[ent.name]
|
||||
coords = ((0x8000 if ent.parent_region.type == RegionType.DarkWorld else 0x0000) | coords[0], coords[1])
|
||||
if ent.name in ow_prize_table:
|
||||
coords = ow_prize_table[ent.name]
|
||||
elif door_addresses[ent.name][1] is not None:
|
||||
coords = (door_addresses[ent.name][1][6], door_addresses[ent.name][1][5])
|
||||
else:
|
||||
raise Exception(f"No overworld map coordinates for entrance {ent.name}")
|
||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, coords[0], coords[1], ent.parent_region.type == RegionType.DarkWorld)
|
||||
coords = ((0x8000 if ent.parent_region.type == RegionType.DarkWorld else 0x0000) | map_x, map_y)
|
||||
return coords
|
||||
|
||||
if world.overworld_map[player] == 'default':
|
||||
# disable HC/AT/GT icons
|
||||
if not world.owMixed[player]:
|
||||
@@ -1591,16 +1635,16 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
# prize location
|
||||
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords)
|
||||
if world.shuffle[player] == 'vanilla' or world.overworld_map[player] == 'default':
|
||||
# TODO: I think this is logically the same as some of the vanilla stuff below
|
||||
vanilla_entrances = { 'Hyrule Castle': 'Hyrule Castle Entrance (South)',
|
||||
'Desert Palace': 'Desert Palace Entrance (North)',
|
||||
'Skull Woods': 'Skull Woods Final Section' }
|
||||
entrance_name = vanilla_entrances[dungeon] if dungeon in vanilla_entrances else dungeon
|
||||
if world.is_atgt_swapped(player):
|
||||
swap_entrances = { 'Agahnims Tower': 'Ganons Tower',
|
||||
'Ganons Tower': 'Agahnims Tower' }
|
||||
entrance_name = swap_entrances[dungeon] if dungeon in swap_entrances else entrance_name
|
||||
entrance = world.get_entrance(entrance_name, player)
|
||||
swap_entrances = { 'Agahnims Tower': 'Ganons Tower',
|
||||
'Ganons Tower': 'Agahnims Tower' }
|
||||
if dungeon in swap_entrances:
|
||||
entrance_name = dungeon
|
||||
if world.is_atgt_swapped(player):
|
||||
entrance_name = swap_entrances[dungeon]
|
||||
entrance = world.get_entrance(entrance_name, player)
|
||||
coords = get_entrance_coords(entrance)
|
||||
else:
|
||||
coords = get_entrance_coords(int((map_index-2)/2))
|
||||
else:
|
||||
if len(portal_list) == 1:
|
||||
portal_idx = 0
|
||||
@@ -1620,7 +1664,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
offset += 4
|
||||
portal = world.get_portal(portal_list[portal_idx], player)
|
||||
entrance = portal.find_portal_entrance()
|
||||
coords = get_entrance_coords(entrance)
|
||||
coords = get_entrance_coords(entrance)
|
||||
|
||||
# figure out compass entrances and what world (light/dark)
|
||||
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6), coords)
|
||||
@@ -2542,7 +2586,7 @@ def write_strings(rom, world, player, team):
|
||||
if world.is_tile_swapped(0x18, player) or world.flute_mode[player] in ['active', 'pseudo']:
|
||||
items_to_hint.remove(flute_item)
|
||||
flute_item = 'Ocarina (Activated)'
|
||||
if world.owShuffle[player] != 'vanilla' or world.owMixed[player]:
|
||||
if world.owLayout[player] != 'vanilla' or world.owMixed[player]:
|
||||
# Adding a guaranteed hint for the Flute in overworld shuffle.
|
||||
this_location = world.find_items_not_key_only(flute_item, player)
|
||||
if this_location and this_location not in hinted_locations:
|
||||
@@ -2560,7 +2604,7 @@ def write_strings(rom, world, player, team):
|
||||
random.shuffle(items_to_hint)
|
||||
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8
|
||||
hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
|
||||
hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0
|
||||
hint_count += 1 if world.owLayout[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0
|
||||
while hint_count > 0 and len(items_to_hint) > 0:
|
||||
this_item = items_to_hint.pop(0)
|
||||
this_location = world.find_items_not_key_only(this_item, player)
|
||||
@@ -3194,6 +3238,13 @@ def update_compasses(rom, dungeon_locations, world, player):
|
||||
if not provided_dungeon:
|
||||
rom.write_byte(0x186FFF, 0xff)
|
||||
|
||||
def adjust_ow_coordinates_to_layout(world, player, x, y, dw_flag):
|
||||
if world.owLayout[player] != 'grid':
|
||||
return (x, y)
|
||||
layout_map = world.owlayoutmap_dw[player] if dw_flag else world.owlayoutmap_lw[player]
|
||||
original_slot_id = ((y // 0x0200) % 0x08) * 0x08 + ((x // 0x0200) % 0x08)
|
||||
new_slot_id = layout_map[original_slot_id]
|
||||
return ((new_slot_id % 0x08) * 0x0200 + x % 0x0200, ((new_slot_id // 0x08) % 0x08) * 0x0200 + y % 0x0200)
|
||||
|
||||
|
||||
InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main',
|
||||
|
||||
@@ -1832,7 +1832,9 @@ def standard_rules(world, player):
|
||||
add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player))
|
||||
|
||||
if not world.is_premature_copied_world:
|
||||
add_rule(world.get_location('Hyrule Castle Courtyard Tree Pull', player), lambda state: state.has('Zelda Delivered', player))
|
||||
loc = world.get_location_unsafe('Hyrule Castle Courtyard Tree Pull', player)
|
||||
if loc:
|
||||
add_rule(loc, 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))
|
||||
|
||||
@@ -356,6 +356,26 @@ def update_deprecated_args(args):
|
||||
else:
|
||||
args.shuffleganon = not args.no_shuffleganon in truthy
|
||||
|
||||
# OW Parallel defaults to TRUE
|
||||
# Don't do: Yes
|
||||
# Do: No
|
||||
if "ow_unparallel" in argVars:
|
||||
if isinstance(args.ow_parallel, dict):
|
||||
for player in range(1, players + 1):
|
||||
args.ow_parallel[player] = not args.ow_unparallel in truthy
|
||||
else:
|
||||
args.ow_parallel = not args.ow_unparallel in truthy
|
||||
|
||||
# OW Fog defaults to TRUE
|
||||
# Don't do: Yes
|
||||
# Do: No
|
||||
if "ow_no_fog" in argVars:
|
||||
if isinstance(args.ow_fog, dict):
|
||||
for player in range(1, players + 1):
|
||||
args.ow_fog[player] = not args.ow_no_fog in truthy
|
||||
else:
|
||||
args.ow_fog = not args.ow_no_fog in truthy
|
||||
|
||||
# Playthrough defaults to TRUE
|
||||
# Don't do: Yes
|
||||
# Do: No
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
GKVersion = '1.0.0'
|
||||
ORVersion = '0.6.1.7'
|
||||
DRVersion = '1.5.0-u'
|
||||
ORVersion = '0.7.0.1'
|
||||
DRVersion = '1.5.2-u'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 324 KiB |
+9
-1
@@ -234,7 +234,7 @@ You may define a list of items and a list of locations. Those items will be cons
|
||||
|
||||
### ow-edges
|
||||
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`.
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_layout` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`.
|
||||
|
||||
#### two-way
|
||||
|
||||
@@ -253,6 +253,14 @@ someDescription:
|
||||
- Stone Bridge WS*
|
||||
```
|
||||
|
||||
### ow-grid
|
||||
|
||||
`grid` contains additional options that only have an effect when `ow_layout` is set to `grid`.
|
||||
|
||||
#### wrap_horizontal / wrap_vertical
|
||||
|
||||
Set these to `true` to allow for overworld edge transitions to wrap from one side of a world to the opposite side. With `wrap_horizontal`, there can be east transitions on the eastern edge of the world map that send the player to the western edge of the world. With `wrap_vertical`, there can be south transitions on the southern edge of the world map that send the player to the northern edge of the world.
|
||||
|
||||
### ow-crossed
|
||||
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_crossed`, `force_noncrossed`, `limit_crossed`, and `undefined_chance`. There are also
|
||||
|
||||
@@ -20,7 +20,8 @@ settings:
|
||||
shuffle_followers: true
|
||||
shuffle: crossed
|
||||
shufflelinks: true
|
||||
ow_shuffle: parallel
|
||||
ow_layout: wild
|
||||
ow_parallel: true
|
||||
ow_terrain: true
|
||||
ow_crossed: grouped
|
||||
ow_keepsimilar: true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_keepsimilar: false
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_keepsimilar: false
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_keepsimilar: false
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_terrain: true
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -177,6 +177,22 @@
|
||||
"full"
|
||||
]
|
||||
},
|
||||
"ow_layout": {
|
||||
"choices": [
|
||||
"vanilla",
|
||||
"grid",
|
||||
"wild"
|
||||
]
|
||||
},
|
||||
"ow_parallel": {
|
||||
"action": "store_true",
|
||||
"help": "suppress",
|
||||
"type": "bool"
|
||||
},
|
||||
"ow_unparallel": {
|
||||
"action": "store_true",
|
||||
"dest": "ow_parallel"
|
||||
},
|
||||
"ow_terrain": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
@@ -212,6 +228,15 @@
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"ow_fog": {
|
||||
"action": "store_true",
|
||||
"help": "suppress",
|
||||
"type": "bool"
|
||||
},
|
||||
"ow_no_fog": {
|
||||
"action": "store_true",
|
||||
"dest": "ow_fog"
|
||||
},
|
||||
"shuffle": {
|
||||
"choices": [
|
||||
"vanilla",
|
||||
|
||||
@@ -235,14 +235,18 @@
|
||||
"the entrances vanilla."
|
||||
],
|
||||
"ow_shuffle": [
|
||||
"Deprecated, use ow_layout and ow_unparallel instead."
|
||||
],
|
||||
"ow_layout": [
|
||||
"This shuffles the layout of the overworld.",
|
||||
"Vanilla: All overworld transitions are connected the same",
|
||||
" way they were in the base game.",
|
||||
"Parallel: Overworld transitions are shuffled, but both worlds",
|
||||
" will have the same pattern/shape.",
|
||||
"Full: Overworld transitions are shuffled, but both worlds",
|
||||
" will have an independent map shape."
|
||||
"Grid: OW Screens are arranged on 8x8 grids and OW Transitions",
|
||||
" work according to this arrangement.",
|
||||
"Wild: OW Transitions are shuffled with no respect to geometric coherence."
|
||||
],
|
||||
"ow_unparallel": [
|
||||
"With OW Layout Shuffle, this no longer forces both worlds to have a matching layout." ],
|
||||
"ow_terrain": [
|
||||
"With OW Layout Shuffle, this allows land and water edges to be connected." ],
|
||||
"ow_crossed": [
|
||||
@@ -277,6 +281,9 @@
|
||||
" spots from being on any adjacent screen.",
|
||||
"Random: New flute spots will be generated with minimal bias."
|
||||
],
|
||||
"ow_no_fog": [
|
||||
"With OW Grid Layout Shuffle or Mixed, this disables the fog that prevents",
|
||||
"you from seeing unvisited screens on the overworld map." ],
|
||||
"door_shuffle": [
|
||||
"Select Door Shuffling Algorithm. (default: %(default)s)",
|
||||
"Basic: Doors are mixed within a single dungeon.",
|
||||
|
||||
@@ -157,10 +157,12 @@
|
||||
"randomizer.enemizer.enemylogic.allow_all": "Allow special enemies anywhere",
|
||||
|
||||
|
||||
"randomizer.overworld.overworldshuffle": "Layout Shuffle",
|
||||
"randomizer.overworld.overworldshuffle.vanilla": "Vanilla",
|
||||
"randomizer.overworld.overworldshuffle.parallel": "Parallel",
|
||||
"randomizer.overworld.overworldshuffle.full": "Full",
|
||||
"randomizer.overworld.layout": "Layout Shuffle",
|
||||
"randomizer.overworld.layout.vanilla": "Vanilla",
|
||||
"randomizer.overworld.layout.grid": "Grid",
|
||||
"randomizer.overworld.layout.wild": "Wild",
|
||||
|
||||
"randomizer.overworld.parallel": "Keep Worlds Parallel",
|
||||
|
||||
"randomizer.overworld.terrain": "Free Terrain",
|
||||
|
||||
@@ -181,6 +183,8 @@
|
||||
"randomizer.overworld.overworldflute.balanced": "Balanced",
|
||||
"randomizer.overworld.overworldflute.random": "Random",
|
||||
|
||||
"randomizer.overworld.fog": "Overworld Map Fog",
|
||||
|
||||
|
||||
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
|
||||
"randomizer.entrance.openpyramid.auto": "Auto",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"topOverworldFrame": {},
|
||||
"leftOverworldFrame": {
|
||||
"overworldshuffle": {
|
||||
"layout": {
|
||||
"type": "selectbox",
|
||||
"default": "vanilla",
|
||||
"options": [
|
||||
"vanilla",
|
||||
"parallel",
|
||||
"full"
|
||||
"grid",
|
||||
"wild"
|
||||
]
|
||||
},
|
||||
"crossed": {
|
||||
@@ -18,7 +18,10 @@
|
||||
"grouped",
|
||||
"polar",
|
||||
"unrestricted"
|
||||
]
|
||||
],
|
||||
"config": {
|
||||
"pady": [16,0]
|
||||
}
|
||||
},
|
||||
"mixed": {
|
||||
"type": "checkbox",
|
||||
@@ -45,22 +48,27 @@
|
||||
"config": {
|
||||
"pady": [20,0]
|
||||
}
|
||||
},
|
||||
"fog": {
|
||||
"type": "checkbox",
|
||||
"default": true,
|
||||
"config": {
|
||||
"pady": [20,0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightOverworldFrame": {
|
||||
"parallel": {
|
||||
"type": "checkbox",
|
||||
"default": true
|
||||
},
|
||||
"terrain": {
|
||||
"type": "checkbox",
|
||||
"default": false,
|
||||
"config": {
|
||||
"pady": [3,0]
|
||||
}
|
||||
"default": false
|
||||
},
|
||||
"keepsimilar": {
|
||||
"type": "checkbox",
|
||||
"default": false,
|
||||
"config": {
|
||||
"pady": [6,0]
|
||||
}
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,8 @@ class CustomSettings(object):
|
||||
args.mystery = True
|
||||
else:
|
||||
settings = defaultdict(lambda: None, player_setting)
|
||||
args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p])
|
||||
args.ow_layout[p] = get_setting(settings['ow_layout'], args.ow_layout[p])
|
||||
args.ow_parallel[p] = get_setting(settings['ow_parallel'], args.ow_parallel[p])
|
||||
args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p])
|
||||
args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p])
|
||||
if args.ow_crossed[p] == 'chaos':
|
||||
@@ -101,6 +102,7 @@ class CustomSettings(object):
|
||||
args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p])
|
||||
args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p])
|
||||
args.ow_fluteshuffle[p] = get_setting(settings['ow_fluteshuffle'], args.ow_fluteshuffle[p])
|
||||
args.ow_fog[p] = get_setting(settings['ow_fog'], args.ow_fog[p])
|
||||
args.shuffle_followers[p] = get_setting(settings['shuffle_followers'], args.shuffle_followers[p])
|
||||
args.bonk_drops[p] = get_setting(settings['bonk_drops'], args.bonk_drops[p])
|
||||
args.shuffle[p] = get_setting(settings['shuffle'], args.shuffle[p])
|
||||
@@ -136,6 +138,14 @@ class CustomSettings(object):
|
||||
args.take_any[p] = 'random' if args.take_any[p] == 'none' else args.take_any[p]
|
||||
args.keyshuffle[p] = 'universal'
|
||||
|
||||
ow_shuffle = get_setting(settings['ow_shuffle'], args.ow_shuffle[p])
|
||||
if ow_shuffle == 'parallel':
|
||||
args.ow_layout = 'wild'
|
||||
args.ow_parallel = True
|
||||
elif ow_shuffle == 'full':
|
||||
args.ow_layout = 'wild'
|
||||
args.ow_parallel = False
|
||||
|
||||
args.mixed_travel[p] = get_setting(settings['mixed_travel'], args.mixed_travel[p])
|
||||
args.standardize_palettes[p] = get_setting(settings['standardize_palettes'],
|
||||
args.standardize_palettes[p])
|
||||
@@ -254,6 +264,11 @@ class CustomSettings(object):
|
||||
return self.file_source['ow-edges']
|
||||
return None
|
||||
|
||||
def get_owgrid(self):
|
||||
if 'ow-grid' in self.file_source:
|
||||
return self.file_source['ow-grid']
|
||||
return None
|
||||
|
||||
def get_owcrossed(self):
|
||||
if 'ow-crossed' in self.file_source:
|
||||
return self.file_source['ow-crossed']
|
||||
@@ -357,13 +372,15 @@ class CustomSettings(object):
|
||||
self.world_rep['start_inventory'] = start_inv = {}
|
||||
for p in self.player_range:
|
||||
settings_dict[p] = {}
|
||||
settings_dict[p]['ow_shuffle'] = world.owShuffle[p]
|
||||
settings_dict[p]['ow_layout'] = world.owLayout[p]
|
||||
settings_dict[p]['ow_parallel'] = world.owParallel[p]
|
||||
settings_dict[p]['ow_terrain'] = world.owTerrain[p]
|
||||
settings_dict[p]['ow_crossed'] = world.owCrossed[p]
|
||||
settings_dict[p]['ow_keepsimilar'] = world.owKeepSimilar[p]
|
||||
settings_dict[p]['ow_mixed'] = world.owMixed[p]
|
||||
settings_dict[p]['ow_whirlpool'] = world.owWhirlpoolShuffle[p]
|
||||
settings_dict[p]['ow_fluteshuffle'] = world.owFluteShuffle[p]
|
||||
settings_dict[p]['ow_fog'] = world.owFog[p]
|
||||
settings_dict[p]['shuffle_followers'] = world.shuffle_followers[p]
|
||||
settings_dict[p]['bonk_drops'] = world.shuffle_bonk_drops[p]
|
||||
settings_dict[p]['shuffle'] = world.shuffle[p]
|
||||
|
||||
@@ -92,13 +92,15 @@ SETTINGSTOPROCESS = {
|
||||
"bombbag": "bombbag"
|
||||
},
|
||||
"overworld": {
|
||||
"overworldshuffle": "ow_shuffle",
|
||||
"layout": "ow_layout",
|
||||
"parallel": "ow_parallel",
|
||||
"terrain": "ow_terrain",
|
||||
"crossed": "ow_crossed",
|
||||
"keepsimilar": "ow_keepsimilar",
|
||||
"mixed": "ow_mixed",
|
||||
"whirlpool": "ow_whirlpool",
|
||||
"overworldflute": "ow_fluteshuffle"
|
||||
"overworldflute": "ow_fluteshuffle",
|
||||
"fog": "ow_fog"
|
||||
},
|
||||
"entrance": {
|
||||
"entranceshuffle": "shuffle",
|
||||
|
||||
@@ -2116,8 +2116,13 @@ class EnemyTable:
|
||||
self.room_map = defaultdict(list)
|
||||
self.special_bitmasks = None
|
||||
|
||||
def write_sprite_data_to_rom(self, rom):
|
||||
pointer_address = snes_to_pc(0x09D62E)
|
||||
def write_sprite_data_to_rom(self, rom, pointer_table):
|
||||
ow_data_end = pointer_table['ow_sprites'][0] + pointer_table['ow_sprites'][1]
|
||||
if ow_data_end > pointer_table['uw_sprites'][2]:
|
||||
# moving pointer table down
|
||||
pointer_table['uw_sprites'][2] = ow_data_end
|
||||
rom.write_bytes(snes_to_pc(pointer_table['uw_sprites'][3]), int16_as_bytes(ow_data_end & 0xFFFF))
|
||||
pointer_address = snes_to_pc(pointer_table['uw_sprites'][2])
|
||||
data_pointer = snes_to_pc(0x288000)
|
||||
empty_pointer = pc_to_snes(data_pointer) & 0xFFFF
|
||||
rom.write_bytes(data_pointer, [0x00, 0xff])
|
||||
|
||||
@@ -183,9 +183,9 @@ def init_sprite_requirements():
|
||||
SpriteRequirement(EnemySprite.Vulture).no_drop().sub_group(2, 0x12).exclude(NoFlyingRooms),
|
||||
SpriteRequirement(EnemySprite.CorrectPullSwitch).affix().sub_group(3, [0x52, 0x53]),
|
||||
SpriteRequirement(EnemySprite.WrongPullSwitch).affix().sub_group(3, [0x52, 0x53]),
|
||||
SpriteRequirement(EnemySprite.Octorok).aquaphobia().sub_group(2, [0xc, 0x18]),
|
||||
SpriteRequirement(EnemySprite.Octorok).sub_group(2, [0xc, 0x18]),
|
||||
SpriteRequirement(EnemySprite.Moldorm).exalt().sub_group(2, 0x30),
|
||||
SpriteRequirement(EnemySprite.Octorok4Way).aquaphobia().sub_group(2, 0xc),
|
||||
SpriteRequirement(EnemySprite.Octorok4Way).sub_group(2, 0xc),
|
||||
SpriteRequirement(EnemySprite.Cucco).immune().sub_group(3, [0x15, 0x50]).exclude(NoFlyingRooms),
|
||||
SpriteRequirement(EnemySprite.Buzzblob).sub_group(3, 0x11),
|
||||
SpriteRequirement(EnemySprite.Snapdragon).sub_group(0, 0x16).sub_group(2, 0x17),
|
||||
@@ -197,7 +197,7 @@ def init_sprite_requirements():
|
||||
.exclude(NoFlyingRooms).exclude({0x40}), # no anti-fairies in aga tower bridge room
|
||||
SpriteRequirement(EnemySprite.Wiseman).affix().sub_group(2, 0x4c),
|
||||
SpriteRequirement(EnemySprite.Hoarder).sub_group(3, 0x11).exclude({0x10c}),
|
||||
SpriteRequirement(EnemySprite.MiniMoldorm).aquaphobia().sub_group(1, 0x1e),
|
||||
SpriteRequirement(EnemySprite.MiniMoldorm).sub_group(1, 0x1e),
|
||||
SpriteRequirement(EnemySprite.Poe).no_drop().sub_group(3, 0x15).exclude(NoFlyingRooms),
|
||||
SpriteRequirement(EnemySprite.Smithy).affix().sub_group(1, 0x1d).sub_group(3, 0x15),
|
||||
SpriteRequirement(EnemySprite.Statue).stasis().immune().sub_group(3, [0x52, 0x53]),
|
||||
@@ -231,12 +231,12 @@ def init_sprite_requirements():
|
||||
SpriteRequirement(EnemySprite.Hoarder2).sub_group(3, 0x11).exclude({0x10c}),
|
||||
SpriteRequirement(EnemySprite.TutorialGuard).affix(),
|
||||
SpriteRequirement(EnemySprite.LightningGate).affix().sub_group(3, 0x3f),
|
||||
SpriteRequirement(EnemySprite.BlueGuard).aquaphobia().sub_group(1, [0xd, 0x49]).exclude(PitRooms),
|
||||
SpriteRequirement(EnemySprite.BlueGuard).aquaphobia().sub_group(1, [0xd, 0x49]).sub_group(2, [0x29, 0x13]),
|
||||
SpriteRequirement(EnemySprite.GreenGuard).aquaphobia().sub_group(1, 0x49).exclude(PitRooms),
|
||||
SpriteRequirement(EnemySprite.GreenGuard).aquaphobia().sub_group(1, 0x49).sub_group(2, 0x13),
|
||||
SpriteRequirement(EnemySprite.RedSpearGuard).aquaphobia().sub_group(1, [0xd, 0x49]).exclude(PitRooms),
|
||||
SpriteRequirement(EnemySprite.RedSpearGuard).aquaphobia().sub_group(1, [0xd, 0x49]).sub_group(2, [0x29, 0x13]),
|
||||
SpriteRequirement(EnemySprite.BlueGuard).sub_group(1, [0xd, 0x49]).exclude(PitRooms),
|
||||
SpriteRequirement(EnemySprite.BlueGuard).sub_group(1, [0xd, 0x49]).sub_group(2, [0x29, 0x13]),
|
||||
SpriteRequirement(EnemySprite.GreenGuard).sub_group(1, 0x49).exclude(PitRooms),
|
||||
SpriteRequirement(EnemySprite.GreenGuard).sub_group(1, 0x49).sub_group(2, 0x13),
|
||||
SpriteRequirement(EnemySprite.RedSpearGuard).sub_group(1, [0xd, 0x49]).exclude(PitRooms),
|
||||
SpriteRequirement(EnemySprite.RedSpearGuard).sub_group(1, [0xd, 0x49]).sub_group(2, [0x29, 0x13]),
|
||||
SpriteRequirement(EnemySprite.BluesainBolt).aquaphobia().sub_group(0, 0x46).sub_group(1, [0xd, 0x49]),
|
||||
SpriteRequirement(EnemySprite.UsainBolt).aquaphobia().sub_group(1, [0xd, 0x49]),
|
||||
SpriteRequirement(EnemySprite.BlueArcher).sub_group(0, 0x48).sub_group(1, 0x49),
|
||||
|
||||
+142
-132
@@ -147,139 +147,149 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c
|
||||
'Lake Hylia Fortune Teller': (0x72, (0x0122, 0x35, 0x0380, 0x0c6a, 0x0a00, 0x0cb8, 0x0a58, 0x0cd7, 0x0a85, 0x06, 0xfa, 0x0000, 0x0000), 0x00),
|
||||
'Kakariko Gamble Game': (0x66, (0x0118, 0x29, 0x069e, 0x0ac4, 0x02ea, 0x0b18, 0x0368, 0x0b33, 0x036f, 0x0a, 0xf6, 0x09AC, 0x0000), 0x00)}
|
||||
|
||||
ow_prize_table = {'Links House': (0x8b1, 0xb2d),
|
||||
'Desert Palace Entrance (South)': (0x108, 0xd70), 'Desert Palace Entrance (West)': (0x031, 0xca0),
|
||||
'Desert Palace Entrance (North)': (0x0e1, 0xba0), 'Desert Palace Entrance (East)': (0x191, 0xca0),
|
||||
'Eastern Palace': (0xf31, 0x620), 'Tower of Hera': (0x8D0, 0x080),
|
||||
'Hyrule Castle Entrance (South)': (0x820, 0x730), 'Hyrule Castle Entrance (West)': (0x740, 0x5D0),
|
||||
'Hyrule Castle Entrance (East)': (0x8f0, 0x5D0),
|
||||
'Agahnims Tower': (0x820, 0x5D0),
|
||||
'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x2e0, 0x280),
|
||||
'Skull Woods Second Section Door (East)': (0x200, 0x240),
|
||||
'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0),
|
||||
'Skull Woods Final Section': (0x082, 0x0b0),
|
||||
'Skull Woods First Section Hole (West)': (0x200, 0x2b0),
|
||||
'Skull Woods First Section Hole (East)': (0x340, 0x2e0),
|
||||
'Skull Woods First Section Hole (North)': (0x320, 0x1e0),
|
||||
'Skull Woods Second Section Hole': (0x0f0, 0x0b0),
|
||||
'Ice Palace': (0xca0, 0xda0),
|
||||
'Misery Mire': (0x100, 0xca0),
|
||||
'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0),
|
||||
'Turtle Rock': (0xf11, 0x103),
|
||||
'Dark Death Mountain Ledge (West)': (0xb80, 0x180),
|
||||
'Dark Death Mountain Ledge (East)': (0xc80, 0x180),
|
||||
'Turtle Rock Isolated Ledge Entrance': (0xc00, 0x240),
|
||||
ow_prize_table = {'Lost Woods Gamble': (0x2A0, 0x080),
|
||||
'Lost Woods Hideout Drop': (0x338, 0x218),
|
||||
'Lost Woods Hideout Stump': (0x2A0, 0x2E0),
|
||||
'Lumberjack House': (0x538, 0x158),
|
||||
'Lumberjack Tree Tree': (0x4A0, 0x160),
|
||||
'Lumberjack Tree Cave': (0x550, 0x004),
|
||||
'Tower of Hera': (0x900, 0x0E0),
|
||||
'Spectacle Rock Cave Peak': (0x758, 0x1B7),
|
||||
'Spectacle Rock Cave (Bottom)': (0x6F0, 0x257),
|
||||
'Spectacle Rock Cave': (0x7C8, 0x257),
|
||||
'Death Mountain Return Cave (East)': (0x648, 0x278),
|
||||
'Old Man Cave (East)': (0x658, 0x387),
|
||||
'Old Man House (Top)': (0x8C8, 0x2D8),
|
||||
'Old Man House (Bottom)': (0x728, 0x3A7),
|
||||
'Paradox Cave (Top)': (0xDE0, 0x100),
|
||||
'Paradox Cave (Bottom)': (0xDC8, 0x358),
|
||||
'Paradox Cave (Middle)': (0xDDF, 0x3FF),
|
||||
'Fairy Ascension Cave (Top)': (0xCD8, 0x2B8),
|
||||
'Fairy Ascension Cave (Bottom)': (0xC78, 0x358),
|
||||
'Spiral Cave': (0xC78, 0x207),
|
||||
'Spiral Cave (Bottom)': (0xBC8, 0x2F7),
|
||||
'Mimic Cave': (0xD38, 0x207),
|
||||
'Hookshot Fairy': (0xD38, 0x358),
|
||||
'Death Mountain Return Cave (West)': (0x5B8, 0x247),
|
||||
'Old Man Cave (West)': (0x5B8, 0x348),
|
||||
'Waterfall of Wishing': (0xE80, 0x2C7),
|
||||
'Fortune Teller (Light)': (0x308, 0x5A8),
|
||||
'Bonk Rock Cave': (0x600, 0x517),
|
||||
'Sanctuary': (0x748, 0x4F4),
|
||||
'Sanctuary Grave': (0x860, 0x550),
|
||||
'Graveyard Cave': (0x8F8, 0x4A6),
|
||||
'Kings Grave': (0x978, 0x566),
|
||||
'North Fairy Cave Drop': (0xA40, 0x580),
|
||||
'North Fairy Cave': (0xAA8, 0x4A8),
|
||||
'Potion Shop': (0xCB8, 0x598),
|
||||
'Kakariko Well Drop': (0x030, 0x6C0),
|
||||
'Kakariko Well Cave': (0x0D8, 0x6F7),
|
||||
'Blinds Hideout': (0x1B8, 0x6F7),
|
||||
'Elder House (West)': (0x258, 0x6F7),
|
||||
'Elder House (East)': (0x2E8, 0x6F7),
|
||||
'Snitch Lady (West)': (0x0C8, 0x7E8),
|
||||
'Snitch Lady (East)': (0x338, 0x7E8),
|
||||
'Chicken House': (0x188, 0x8E8),
|
||||
'Sick Kids House': (0x278, 0x8E8),
|
||||
'Bush Covered House': (0x338, 0x8E8),
|
||||
'Light World Bomb Hut': (0x068, 0x9D7),
|
||||
'Kakariko Shop': (0x1C8, 0x9D7),
|
||||
'Tavern North': (0x2B8, 0x977),
|
||||
'Tavern (Front)': (0x2B8, 0x9F7),
|
||||
'Agahnims Tower': (0x7F0, 0x630),
|
||||
'Hyrule Castle Entrance (South)': (0x7F0, 0x750),
|
||||
'Hyrule Castle Entrance (West)': (0x758, 0x644),
|
||||
'Hyrule Castle Entrance (East)': (0x8E8, 0x644),
|
||||
'Hyrule Castle Secret Entrance Drop': (0x9D0, 0x680),
|
||||
'Hyrule Castle Secret Entrance Stairs': (0x8D0, 0x700),
|
||||
'Kakariko Well Drop': (0x030, 0x680),
|
||||
'Kakariko Well Cave': (0x060, 0x680),
|
||||
'Bat Cave Drop': (0x520, 0x8f0),
|
||||
'Bat Cave Cave': (0x560, 0x940),
|
||||
'Elder House (East)': (0x2b0, 0x6a0),
|
||||
'Elder House (West)': (0x230, 0x6a0),
|
||||
'North Fairy Cave Drop': (0xa40, 0x500),
|
||||
'North Fairy Cave': (0xa80, 0x440),
|
||||
'Lost Woods Hideout Drop': (0x290, 0x200),
|
||||
'Lost Woods Hideout Stump': (0x240, 0x280),
|
||||
'Lumberjack Tree Tree': (0x4e0, 0x140),
|
||||
'Lumberjack Tree Cave': (0x560, 0x004),
|
||||
'Two Brothers House (East)': (0x200, 0x0b60),
|
||||
'Two Brothers House (West)': (0x180, 0x0b60),
|
||||
'Sanctuary Grave': (0x820, 0x4c0),
|
||||
'Sanctuary': (0x720, 0x4a0),
|
||||
'Old Man Cave (West)': (0x580, 0x2c0),
|
||||
'Old Man Cave (East)': (0x620, 0x2c0),
|
||||
'Old Man House (Bottom)': (0x720, 0x320),
|
||||
'Old Man House (Top)': (0x820, 0x220),
|
||||
'Death Mountain Return Cave (East)': (0x600, 0x220),
|
||||
'Death Mountain Return Cave (West)': (0x500, 0x1c0),
|
||||
'Spectacle Rock Cave Peak': (0x720, 0x0a0),
|
||||
'Spectacle Rock Cave': (0x790, 0x1a0),
|
||||
'Spectacle Rock Cave (Bottom)': (0x710, 0x0a0),
|
||||
'Paradox Cave (Bottom)': (0xd80, 0x180),
|
||||
'Paradox Cave (Middle)': (0xd80, 0x380),
|
||||
'Paradox Cave (Top)': (0xd80, 0x020),
|
||||
'Fairy Ascension Cave (Bottom)': (0xcc8, 0x2a0),
|
||||
'Fairy Ascension Cave (Top)': (0xc00, 0x240),
|
||||
'Spiral Cave': (0xb80, 0x180),
|
||||
'Spiral Cave (Bottom)': (0xb80, 0x2c0),
|
||||
'Bumper Cave (Bottom)': (0x580, 0x2c0),
|
||||
'Bumper Cave (Top)': (0x500, 0x1c0),
|
||||
'Superbunny Cave (Top)': (0xd80, 0x020),
|
||||
'Superbunny Cave (Bottom)': (0xd00, 0x180),
|
||||
'Hookshot Cave': (0xc80, 0x0c0),
|
||||
'Hookshot Cave Back Entrance': (0xcf0, 0x004),
|
||||
'Ganons Tower': (0x8D0, 0x080),
|
||||
'Pyramid Hole': (0x820, 0x680),
|
||||
'Inverted Pyramid Hole': (0x820, 0x680),
|
||||
'Pyramid Entrance': (0x640, 0x7c0),
|
||||
'Inverted Pyramid Entrance': (0x6C0, 0x5D0),
|
||||
'Waterfall of Wishing': (0xe80, 0x280),
|
||||
'Dam': (0x759, 0xED0),
|
||||
'Blinds Hideout': (0x190, 0x6c0),
|
||||
'Bonk Fairy (Light)': (0x740, 0xa80),
|
||||
'Lake Hylia Fairy': (0xd40, 0x9f0),
|
||||
'Light Hype Fairy': (0x940, 0xc80),
|
||||
'Desert Fairy': (0x420, 0xe00),
|
||||
'Kings Grave': (0x920, 0x520),
|
||||
'Tavern North': (0x270, 0x900),
|
||||
'Chicken House': (0x120, 0x880),
|
||||
'Aginahs Cave': (0x2e0, 0xd00),
|
||||
'Sahasrahlas Hut': (0xcf0, 0x6c0),
|
||||
'Lake Hylia Shop': (0xbc0, 0xc00),
|
||||
'Capacity Upgrade': (0xca0, 0xda0),
|
||||
'Blacksmiths Hut': (0x4a0, 0x880),
|
||||
'Sick Kids House': (0x220, 0x880),
|
||||
'Lost Woods Gamble': (0x240, 0x080),
|
||||
'Fortune Teller (Light)': (0x2c0, 0x4c0),
|
||||
'Snitch Lady (East)': (0x310, 0x7a0),
|
||||
'Snitch Lady (West)': (0x080, 0x7a0),
|
||||
'Bush Covered House': (0x2e0, 0x880),
|
||||
'Tavern (Front)': (0x270, 0x980),
|
||||
'Light World Bomb Hut': (0x070, 0x980),
|
||||
'Kakariko Shop': (0x170, 0x980),
|
||||
'Cave 45': (0x440, 0xca0), 'Graveyard Cave': (0x8f0, 0x430),
|
||||
'Checkerboard Cave': (0x260, 0xc00),
|
||||
'Mini Moldorm Cave': (0xa40, 0xe80),
|
||||
'Long Fairy Cave': (0xf60, 0xb00),
|
||||
'Good Bee Cave': (0xec0, 0xc00),
|
||||
'20 Rupee Cave': (0xe80, 0xca0),
|
||||
'50 Rupee Cave': (0x4d0, 0xed0),
|
||||
'Ice Rod Cave': (0xe00, 0xc00),
|
||||
'Bonk Rock Cave': (0x5f0, 0x460),
|
||||
'Library': (0x270, 0xaa0),
|
||||
'Potion Shop': (0xc80, 0x4c0),
|
||||
'Hookshot Fairy': (0xd00, 0x180),
|
||||
'Pyramid Fairy': (0x740, 0x740),
|
||||
'East Dark World Hint': (0xf60, 0xb00),
|
||||
'Palace of Darkness Hint': (0xd60, 0x7c0),
|
||||
'Dark Lake Hylia Fairy': (0xd40, 0x9f0),
|
||||
'Dark Lake Hylia Ledge Fairy': (0xe00, 0xc00),
|
||||
'Dark Lake Hylia Ledge Spike Cave': (0xe80, 0xca0),
|
||||
'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00),
|
||||
'Hype Cave': (0x940, 0xc80),
|
||||
'Bonk Fairy (Dark)': (0x740, 0xa80),
|
||||
'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x080, 0x7a0),
|
||||
'Hammer Peg Cave': (0x4c0, 0x940),
|
||||
'Red Shield Shop': (0x500, 0x680),
|
||||
'Dark Sanctuary Hint': (0x720, 0x4a0),
|
||||
'Fortune Teller (Dark)': (0x2c0, 0x4c0),
|
||||
'Dark World Shop': (0x2e0, 0x880),
|
||||
'Dark Lumberjack Shop': (0x4e0, 0x0d0),
|
||||
'Dark Potion Shop': (0xc80, 0x4c0),
|
||||
'Archery Game': (0x2f0, 0xaf0),
|
||||
'Mire Shed': (0x060, 0xc90),
|
||||
'Mire Hint': (0x2e0, 0xd00),
|
||||
'Mire Fairy': (0x1c0, 0xc90),
|
||||
'Spike Cave': (0x860, 0x180),
|
||||
'Dark Death Mountain Shop': (0xd80, 0x180),
|
||||
'Dark Death Mountain Fairy': (0x620, 0x2c0),
|
||||
'Mimic Cave': (0xc80, 0x180),
|
||||
'Big Bomb Shop': (0x8b1, 0xb2d),
|
||||
'Dark Lake Hylia Shop': (0xa40, 0xc40),
|
||||
'Lumberjack House': (0x580, 0x100),
|
||||
'Lake Hylia Fortune Teller': (0xa40, 0xc40),
|
||||
'Kakariko Gamble Game': (0x2f0, 0xaf0)}
|
||||
'Hyrule Castle Secret Entrance Stairs': (0x8C8, 0x718),
|
||||
'Inverted Pyramid Hole': (0x7F0, 0x6B0),
|
||||
'Inverted Pyramid Entrance': (0x6E8, 0x644),
|
||||
'Sahasrahlas Hut': (0xCF0, 0x747),
|
||||
'Eastern Palace': (0xF40, 0x680),
|
||||
'Blacksmiths Hut': (0x4E8, 0x8C8),
|
||||
'Bat Cave Drop': (0x538, 0x9A8),
|
||||
'Bat Cave Cave': (0x510, 0x930),
|
||||
'Two Brothers House (West)': (0x1B8, 0xB88),
|
||||
'Two Brothers House (East)': (0x238, 0xB88),
|
||||
'Library': (0x2B8, 0xAA7),
|
||||
'Kakariko Gamble Game': (0x348, 0xB78),
|
||||
'Bonk Fairy (Light)': (0x788, 0xA87),
|
||||
'Links House': (0x8B8, 0xB58),
|
||||
'Lake Hylia Fairy': (0xD58, 0xA57),
|
||||
'Long Fairy Cave': (0xF68, 0xB58),
|
||||
'Desert Palace Entrance (North)': (0x148, 0xC28),
|
||||
'Desert Palace Entrance (West)': (0x088, 0xCF8),
|
||||
'Desert Palace Entrance (East)': (0x1E8, 0xCF8),
|
||||
'Desert Palace Entrance (South)': (0x138, 0xD18),
|
||||
'Checkerboard Cave': (0x2E8, 0xCF7),
|
||||
'Aginahs Cave': (0x388, 0xDE8),
|
||||
'Cave 45': (0x448, 0xD86),
|
||||
'Light Hype Fairy': (0x988, 0xCF8),
|
||||
'Lake Hylia Fortune Teller': (0xA68, 0xCC8),
|
||||
'Lake Hylia Shop': (0xBF8, 0xC66),
|
||||
'Capacity Upgrade': (0xCC8, 0xDE8),
|
||||
'Mini Moldorm Cave': (0xA88, 0xEF8),
|
||||
'Ice Rod Cave': (0xE68, 0xC37),
|
||||
'Good Bee Cave': (0xEF8, 0xC36),
|
||||
'20 Rupee Cave': (0xEB8, 0xCC6),
|
||||
'Desert Fairy': (0x488, 0xE76),
|
||||
'50 Rupee Cave': (0x4F8, 0xF47),
|
||||
'Dam': (0x778, 0xF68),
|
||||
|
||||
'Skull Woods Final Section': (0x078, 0x0A8),
|
||||
'Skull Woods Second Section Door (West)': (0x0E0, 0x1C0),
|
||||
'Skull Woods Second Section Hole': (0x150, 0x120),
|
||||
'Skull Woods Second Section Door (East)': (0x208, 0x208),
|
||||
'Skull Woods First Section Door': (0x2C8, 0x2A8),
|
||||
'Skull Woods First Section Hole (North)': (0x320, 0x1e0),
|
||||
'Skull Woods First Section Hole (West)': (0x240, 0x380),
|
||||
'Skull Woods First Section Hole (East)': (0x340, 0x380),
|
||||
'Dark Lumberjack Shop': (0x538, 0x177),
|
||||
'Ganons Tower': (0x900, 0x0E0),
|
||||
'Dark Death Mountain Fairy': (0x658, 0x387),
|
||||
'Spike Cave': (0x9F8, 0x268),
|
||||
'Hookshot Cave Back Entrance': (0xD38, 0x038),
|
||||
'Hookshot Cave': (0xD48, 0x107),
|
||||
'Superbunny Cave (Top)': (0xDE0, 0x100),
|
||||
'Superbunny Cave (Bottom)': (0xD38, 0x358),
|
||||
'Dark Death Mountain Ledge (West)': (0xC78, 0x207),
|
||||
'Dark Death Mountain Ledge (East)': (0xD38, 0x207),
|
||||
'Turtle Rock Isolated Ledge Entrance': (0xCD8, 0x2B8),
|
||||
'Dark Death Mountain Shop': (0xDC8, 0x358),
|
||||
'Turtle Rock': (0xF48, 0x108),
|
||||
'Bumper Cave (Top)': (0x5B8, 0x247),
|
||||
'Bumper Cave (Bottom)': (0x5B8, 0x348),
|
||||
'Fortune Teller (Dark)': (0x308, 0x5A8),
|
||||
'Dark Sanctuary Hint': (0x748, 0x4F4),
|
||||
'Dark Potion Shop': (0xCB8, 0x598),
|
||||
'Chest Game': (0x0C8, 0x7E8),
|
||||
'Thieves Town': (0x1F0, 0x7E8),
|
||||
'C-Shaped House': (0x338, 0x7E8),
|
||||
'Brewery': (0x1C8, 0x9D7),
|
||||
'Dark World Shop': (0x338, 0x8E8),
|
||||
'Red Shield Shop': (0x538, 0x778),
|
||||
'Pyramid Hole': (0x800, 0x680),
|
||||
'Pyramid Entrance': (0x718, 0x7A8),
|
||||
'Pyramid Fairy': (0x7B8, 0x7A8),
|
||||
'Palace of Darkness': (0xF40, 0x680),
|
||||
'Palace of Darkness Hint': (0xD70, 0x878),
|
||||
'Hammer Peg Cave': (0x528, 0x9B6),
|
||||
'Archery Game': (0x348, 0xB78),
|
||||
'Bonk Fairy (Dark)': (0x788, 0xA87),
|
||||
'Big Bomb Shop': (0x8B8, 0xB58),
|
||||
'Dark Lake Hylia Fairy': (0xD58, 0xA57),
|
||||
'East Dark World Hint': (0xF68, 0xB58),
|
||||
'Mire Shed': (0x0A8, 0xCC7),
|
||||
'Misery Mire': (0x148, 0xCC7),
|
||||
'Mire Fairy': (0x1E8, 0xCC7),
|
||||
'Mire Hint': (0x388, 0xDE8),
|
||||
'Hype Cave': (0x988, 0xCF8),
|
||||
'Dark Lake Hylia Shop': (0xA68, 0xCC8),
|
||||
'Ice Palace': (0xCA8, 0xE28),
|
||||
'Dark Lake Hylia Ledge Fairy': (0xE68, 0xC37),
|
||||
'Dark Lake Hylia Ledge Hint': (0xEF8, 0xC36),
|
||||
'Dark Lake Hylia Ledge Spike Cave': (0xEB8, 0xCC6),
|
||||
'Swamp Palace': (0x778, 0xF68)}
|
||||
|
||||
default_connector_connections = [('Death Mountain Return Cave (West)', 'Death Mountain Return Cave Exit (West)'),
|
||||
('Death Mountain Return Cave (East)', 'Death Mountain Return Cave Exit (East)'),
|
||||
|
||||
@@ -634,7 +634,7 @@ def do_dark_sanc(entrances, exits, avail):
|
||||
forbidden.append('Links House')
|
||||
else:
|
||||
forbidden.append('Big Bomb Shop')
|
||||
if avail.world.owShuffle[avail.player] == 'vanilla':
|
||||
if avail.world.owLayout[avail.player] == 'vanilla':
|
||||
choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances]
|
||||
else:
|
||||
choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances]
|
||||
@@ -678,7 +678,7 @@ def do_links_house(entrances, exits, avail, cross_world):
|
||||
forbidden.append(links_house_vanilla)
|
||||
forbidden.extend(Forbidden_Swap_Entrances)
|
||||
shuffle_mode = avail.world.shuffle[avail.player]
|
||||
if avail.world.owShuffle[avail.player] == 'vanilla':
|
||||
if avail.world.owLayout[avail.player] == 'vanilla':
|
||||
# simple shuffle -
|
||||
if shuffle_mode == 'simple':
|
||||
avail.links_on_mountain = True # taken care of by the logic below
|
||||
@@ -732,7 +732,7 @@ def do_links_house(entrances, exits, avail, cross_world):
|
||||
|
||||
# links on dm
|
||||
dm_spots = LH_DM_Connector_List.union(LH_DM_Exit_Forbidden)
|
||||
if links_house in dm_spots and avail.world.owShuffle[avail.player] == 'vanilla':
|
||||
if links_house in dm_spots and avail.world.owLayout[avail.player] == 'vanilla':
|
||||
if avail.links_on_mountain:
|
||||
return # connector is fine
|
||||
logging.getLogger('').warning(f'Links House is placed in tight area and is now unhandled. Report any errors that occur from here.')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,473 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Dict, List
|
||||
from PIL import Image, ImageDraw
|
||||
from BaseClasses import Direction, OWEdge
|
||||
from source.overworld.LayoutGenerator import Screen
|
||||
|
||||
def get_edge_lists(grid: List[List[List[int]]],
|
||||
overworld_screens: Dict[int, Screen],
|
||||
large_screen_quadrant_info: Dict[int, Dict]) -> Dict:
|
||||
"""
|
||||
Get list of edges for each cell and direction.
|
||||
|
||||
Args:
|
||||
grid: 3D list [world][row][col] containing screen IDs
|
||||
overworld_screens: Dict of screen_id -> Screen objects
|
||||
large_screen_quadrant_info: Dict of screen_id -> quadrant info for large screens
|
||||
|
||||
Returns:
|
||||
Dict mapping (world, row, col, direction) -> list of edges
|
||||
Each edge has a .dest property (None if unconnected)
|
||||
"""
|
||||
GRID_SIZE = 8
|
||||
edge_lists = {}
|
||||
|
||||
# Large screen base IDs
|
||||
large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35,
|
||||
0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
||||
|
||||
for world_idx in range(2):
|
||||
# Build a map of screen_id -> list of (row, col) positions for large screens
|
||||
large_screen_positions = {}
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
||||
if screen_id not in large_screen_positions:
|
||||
large_screen_positions[screen_id] = []
|
||||
large_screen_positions[screen_id].append((row, col))
|
||||
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
|
||||
if screen_id == -1:
|
||||
# Empty cell - no edges
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edge_lists[(world_idx, row, col, direction)] = []
|
||||
continue
|
||||
|
||||
screen = overworld_screens.get(screen_id)
|
||||
if not screen:
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edge_lists[(world_idx, row, col, direction)] = []
|
||||
continue
|
||||
|
||||
is_large = screen_id in large_screen_base_ids
|
||||
|
||||
if is_large:
|
||||
# For large screens, determine which quadrant this cell is
|
||||
# Find all positions of this large screen and determine quadrant
|
||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
||||
|
||||
# Determine quadrant by finding relative position
|
||||
# The quadrant is determined by which cells are adjacent
|
||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
||||
|
||||
# Get edges for this quadrant
|
||||
if screen_id in large_screen_quadrant_info:
|
||||
quad_info = large_screen_quadrant_info[screen_id]
|
||||
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edges = quad_info.get(quadrant, {}).get(direction, [])
|
||||
edge_lists[(world_idx, row, col, direction)] = edges
|
||||
else:
|
||||
# No quadrant info - no edges
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edge_lists[(world_idx, row, col, direction)] = []
|
||||
else:
|
||||
# Small screen - get edges directly
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edges_in_dir = [e for e in screen.edges.values() if e.direction == direction]
|
||||
edge_lists[(world_idx, row, col, direction)] = edges_in_dir
|
||||
|
||||
return edge_lists
|
||||
|
||||
def determine_large_screen_quadrant(row: int, col: int, positions: List[tuple], grid_size: int) -> str:
|
||||
"""
|
||||
Determine which quadrant (NW, NE, SW, SE) a cell is in for a large screen.
|
||||
Handles wrapping correctly by checking adjacency patterns.
|
||||
|
||||
Args:
|
||||
row: Current cell row
|
||||
col: Current cell column
|
||||
positions: List of all (row, col) positions for this large screen
|
||||
grid_size: Size of the grid (8)
|
||||
|
||||
Returns:
|
||||
Quadrant string: "NW", "NE", "SW", or "SE"
|
||||
"""
|
||||
positions_set = set(positions)
|
||||
|
||||
# Check which adjacent cells also belong to this large screen
|
||||
has_right = ((row, (col + 1) % grid_size) in positions_set)
|
||||
has_below = (((row + 1) % grid_size, col) in positions_set)
|
||||
has_left = ((row, (col - 1) % grid_size) in positions_set)
|
||||
has_above = (((row - 1) % grid_size, col) in positions_set)
|
||||
|
||||
# Determine quadrant based on adjacency
|
||||
# NW: has right and below neighbors
|
||||
# NE: has left and below neighbors
|
||||
# SW: has right and above neighbors
|
||||
# SE: has left and above neighbors
|
||||
|
||||
if has_right and has_below:
|
||||
return "NW"
|
||||
elif has_left and has_below:
|
||||
return "NE"
|
||||
elif has_right and has_above:
|
||||
return "SW"
|
||||
elif has_left and has_above:
|
||||
return "SE"
|
||||
else:
|
||||
raise Exception("?")
|
||||
|
||||
def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool:
|
||||
if edge.dest is None:
|
||||
return False
|
||||
|
||||
source_screen = overworld_screens.get(edge.owIndex)
|
||||
dest_screen = overworld_screens.get(edge.dest.owIndex)
|
||||
return source_screen.dark_world != dest_screen.dark_world
|
||||
|
||||
def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
||||
overworld_screens: Dict[int, Screen],
|
||||
large_screen_quadrant_info: Dict[int, Dict]) -> None:
|
||||
# Constants
|
||||
GRID_SIZE = 8
|
||||
BORDER_WIDTH = 1
|
||||
OUTPUT_CELL_SIZE = 64 # Each cell in output is always 64x64 pixels
|
||||
|
||||
# Load the world images
|
||||
try:
|
||||
lightworld_img = Image.open("data/overworld/lightworld.png")
|
||||
darkworld_img = Image.open("data/overworld/darkworld.png")
|
||||
except FileNotFoundError as e:
|
||||
raise FileNotFoundError(f"World image not found: {e}. Ensure lightworld.png and darkworld.png are in the data/overworld directory.")
|
||||
|
||||
# Calculate source cell size from the base images
|
||||
# Each world image is 8x8 screens, so divide by 8 to get source cell size
|
||||
img_width, _ = lightworld_img.size
|
||||
SOURCE_CELL_SIZE = img_width // GRID_SIZE # Size of each cell in the source image
|
||||
|
||||
# Calculate dimensions for the output (always based on 64x64 cells)
|
||||
world_width = GRID_SIZE * OUTPUT_CELL_SIZE
|
||||
world_height = GRID_SIZE * OUTPUT_CELL_SIZE
|
||||
|
||||
# Create output image (two worlds side by side with a small gap)
|
||||
gap = 32
|
||||
output_width = world_width * 2 + gap
|
||||
output_height = world_height
|
||||
output_img = Image.new('RGB', (output_width, output_height), color='black')
|
||||
|
||||
# Large screen base IDs (defined once for reuse)
|
||||
large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35,
|
||||
0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
||||
|
||||
# Process both worlds
|
||||
for world_idx in range(2):
|
||||
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
||||
|
||||
# Build a map of screen_id -> list of (row, col) positions for large screens
|
||||
large_screen_positions = {}
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
||||
if screen_id not in large_screen_positions:
|
||||
large_screen_positions[screen_id] = []
|
||||
large_screen_positions[screen_id].append((row, col))
|
||||
|
||||
# Process each cell in the grid individually
|
||||
# This handles wrapped large screens correctly by drawing each quadrant separately
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
|
||||
if screen_id == -1:
|
||||
# Empty cell - fill with black (already black from initialization)
|
||||
continue
|
||||
|
||||
is_large = screen_id in large_screen_base_ids
|
||||
|
||||
# Calculate source position in the world image
|
||||
source_row = (screen_id % 0x40) >> 3
|
||||
source_col = screen_id % 0x08
|
||||
world_img = lightworld_img if screen_id < 0x40 else darkworld_img
|
||||
|
||||
if is_large:
|
||||
# For large screens, determine which quadrant this cell represents
|
||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
||||
|
||||
# Map quadrant to source offset within the 2x2 large screen
|
||||
quadrant_offsets = {
|
||||
"NW": (0, 0),
|
||||
"NE": (1, 0),
|
||||
"SW": (0, 1),
|
||||
"SE": (1, 1)
|
||||
}
|
||||
q_col_offset, q_row_offset = quadrant_offsets[quadrant]
|
||||
|
||||
# Calculate source position for this quadrant
|
||||
source_x = (source_col + q_col_offset) * SOURCE_CELL_SIZE
|
||||
source_y = (source_row + q_row_offset) * SOURCE_CELL_SIZE
|
||||
|
||||
# Crop single cell from source (the specific quadrant)
|
||||
cropped = world_img.crop((
|
||||
source_x,
|
||||
source_y,
|
||||
source_x + SOURCE_CELL_SIZE,
|
||||
source_y + SOURCE_CELL_SIZE
|
||||
))
|
||||
else:
|
||||
# Small screen (1x1)
|
||||
source_x = source_col * SOURCE_CELL_SIZE
|
||||
source_y = source_row * SOURCE_CELL_SIZE
|
||||
|
||||
# Crop single cell from source
|
||||
cropped = world_img.crop((
|
||||
source_x,
|
||||
source_y,
|
||||
source_x + SOURCE_CELL_SIZE,
|
||||
source_y + SOURCE_CELL_SIZE
|
||||
))
|
||||
|
||||
# Resize to output size (64x64 pixels)
|
||||
resized = cropped.resize(
|
||||
(OUTPUT_CELL_SIZE, OUTPUT_CELL_SIZE),
|
||||
Image.LANCZOS
|
||||
)
|
||||
|
||||
# Paste into output at grid position
|
||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||
dest_y = row * OUTPUT_CELL_SIZE
|
||||
output_img.paste(resized, (dest_x, dest_y))
|
||||
|
||||
edge_lists = get_edge_lists(grid, overworld_screens, large_screen_quadrant_info)
|
||||
|
||||
# Draw borders and edge connection indicators after all screens are placed
|
||||
draw = ImageDraw.Draw(output_img)
|
||||
|
||||
# Size of the indicator squares
|
||||
INDICATOR_SIZE = 12
|
||||
|
||||
for world_idx in range(2):
|
||||
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
||||
|
||||
# Build large screen positions map for this world
|
||||
large_screen_positions = {}
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
||||
if screen_id not in large_screen_positions:
|
||||
large_screen_positions[screen_id] = []
|
||||
large_screen_positions[screen_id].append((row, col))
|
||||
|
||||
# Draw borders for each cell
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
|
||||
if screen_id == -1:
|
||||
continue
|
||||
|
||||
is_large = screen_id in large_screen_base_ids
|
||||
|
||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||
dest_y = row * OUTPUT_CELL_SIZE
|
||||
|
||||
if is_large:
|
||||
# For large screens, determine which quadrant this cell is
|
||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
||||
|
||||
# Draw border only on the outer edges of the large screen
|
||||
# (not on internal edges between quadrants)
|
||||
# NW: draw top and left borders
|
||||
# NE: draw top and right borders
|
||||
# SW: draw bottom and left borders
|
||||
# SE: draw bottom and right borders
|
||||
|
||||
if quadrant in ["NW", "NE"]:
|
||||
# Draw top border
|
||||
draw.line([(dest_x, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y)], fill='black', width=BORDER_WIDTH)
|
||||
if quadrant in ["SW", "SE"]:
|
||||
# Draw bottom border
|
||||
draw.line([(dest_x, dest_y + OUTPUT_CELL_SIZE - 1), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||
if quadrant in ["NW", "SW"]:
|
||||
# Draw left border
|
||||
draw.line([(dest_x, dest_y), (dest_x, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||
if quadrant in ["NE", "SE"]:
|
||||
# Draw right border
|
||||
draw.line([(dest_x + OUTPUT_CELL_SIZE - 1, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||
else:
|
||||
# Small screen - draw border around single cell
|
||||
draw.rectangle(
|
||||
[dest_x, dest_y, dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1],
|
||||
outline='black',
|
||||
width=BORDER_WIDTH
|
||||
)
|
||||
|
||||
# Draw edge connection indicators for each cell
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id == -1:
|
||||
continue
|
||||
|
||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||
dest_y = row * OUTPUT_CELL_SIZE
|
||||
|
||||
# Draw indicator for each direction (only if edges exist)
|
||||
# Use bright colors for visibility
|
||||
GREEN = (0, 255, 0) # Bright green
|
||||
YELLOW = (255, 255, 0) # Bright yellow
|
||||
RED = (255, 0, 0) # Bright red
|
||||
|
||||
# North indicators - positioned based on edge midpoint
|
||||
north_edges = edge_lists.get((world_idx, row, col, Direction.North), [])
|
||||
if north_edges:
|
||||
north_y = dest_y # Touch the top border
|
||||
|
||||
for edge in north_edges:
|
||||
# For north/south edges, midpoint gives the X coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_x_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_x = dest_x + edge_x_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in north_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[edge_x, north_y, edge_x + INDICATOR_SIZE - 1, north_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[edge_x, north_y, edge_x + INDICATOR_SIZE - 1, north_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[edge_x + INDICATOR_SIZE - 1, north_y, edge_x, north_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# South indicators - positioned based on edge midpoint
|
||||
south_edges = edge_lists.get((world_idx, row, col, Direction.South), [])
|
||||
if south_edges:
|
||||
south_y = dest_y + OUTPUT_CELL_SIZE - INDICATOR_SIZE # Touch the bottom border
|
||||
|
||||
for edge in south_edges:
|
||||
# For north/south edges, midpoint gives the X coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_x_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_x = dest_x + edge_x_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in south_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[edge_x, south_y, edge_x + INDICATOR_SIZE - 1, south_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[edge_x, south_y, edge_x + INDICATOR_SIZE - 1, south_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[edge_x + INDICATOR_SIZE - 1, south_y, edge_x, south_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# West indicators - positioned based on edge midpoint
|
||||
west_edges = edge_lists.get((world_idx, row, col, Direction.West), [])
|
||||
if west_edges:
|
||||
west_x = dest_x # Touch the left border
|
||||
|
||||
for edge in west_edges:
|
||||
# For west/east edges, midpoint gives the Y coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_y_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_y = dest_y + edge_y_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in west_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[west_x, edge_y, west_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[west_x, edge_y, west_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[west_x + INDICATOR_SIZE - 1, edge_y, west_x, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# East indicators - positioned based on edge midpoint
|
||||
east_edges = edge_lists.get((world_idx, row, col, Direction.East), [])
|
||||
if east_edges:
|
||||
east_x = dest_x + OUTPUT_CELL_SIZE - INDICATOR_SIZE # Touch the right border
|
||||
|
||||
for edge in east_edges:
|
||||
# For west/east edges, midpoint gives the Y coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_y_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_y = dest_y + edge_y_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in east_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[east_x, edge_y, east_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[east_x, edge_y, east_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[east_x + INDICATOR_SIZE - 1, edge_y, east_x, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"layout_{timestamp}.png"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
# Save the image
|
||||
output_img.save(filepath, "PNG")
|
||||
logging.getLogger('').info(f"Layout visualization saved to {filepath}")
|
||||
+22
-12
@@ -44,6 +44,11 @@ class DataTables:
|
||||
self.ow_enemy_table = None
|
||||
self.pot_secret_table = None
|
||||
self.overworld_sprite_sheets = None
|
||||
self.pointer_addresses = {
|
||||
# table: [data_start, data_size, pointer_table, references]
|
||||
'ow_sprites': [ 0x09CB41, None, (0x09C881, 0x09C901, 0x09CA21), None ],
|
||||
'uw_sprites': [ 0x09D92E, None, 0x09D62E, 0x09C298 ],
|
||||
}
|
||||
|
||||
# associated data
|
||||
self.sprite_requirements = None
|
||||
@@ -92,13 +97,6 @@ class DataTables:
|
||||
# bank 0A uses 372A bytes
|
||||
# bank 1F uses 77CE bytes: total is about a bank and a half
|
||||
# probably should reuse bank 1F if writing all the rooms out
|
||||
for sheet in self.sprite_sheets.values():
|
||||
sheet.write_to_rom(rom, snes_to_pc(0x00DB97)) # bank 00, SheetsTable_AA3
|
||||
if self.uw_enemy_table.size() > 0x2800:
|
||||
raise Exception('Sprite table is too big for current area')
|
||||
self.uw_enemy_table.write_sprite_data_to_rom(rom)
|
||||
self.uw_enemy_table.check_special_bitmasks_size()
|
||||
self.uw_enemy_table.write_special_bitmask_table(rom)
|
||||
for area_id, sheet in self.overworld_sprite_sheets.items():
|
||||
if area_id in [0x80, 0x81]:
|
||||
offset = area_id - 0x80 # 02E575 for special areas?
|
||||
@@ -110,7 +108,14 @@ class DataTables:
|
||||
# _00FAC1 is LW post-aga
|
||||
# _00FB01 is DW
|
||||
# _00FA41 is rain state
|
||||
for sheet in self.sprite_sheets.values():
|
||||
sheet.write_to_rom(rom, snes_to_pc(0x00DB97)) # bank 00, SheetsTable_AA3
|
||||
self.write_ow_sprite_data_to_rom(rom)
|
||||
if self.uw_enemy_table.size() > 0x2800:
|
||||
raise Exception('Sprite table is too big for current area')
|
||||
self.uw_enemy_table.write_sprite_data_to_rom(rom, self.pointer_addresses)
|
||||
self.uw_enemy_table.check_special_bitmasks_size()
|
||||
self.uw_enemy_table.write_special_bitmask_table(rom)
|
||||
for sprite, stats in self.enemy_stats.items():
|
||||
# write health to rom
|
||||
if stats.health is not None:
|
||||
@@ -148,13 +153,14 @@ class DataTables:
|
||||
|
||||
def write_ow_sprite_data_to_rom(self, rom):
|
||||
# calculate how big this table is going to be?
|
||||
# bytes = sum(1+len(x)*3 for x in self.ow_enemy_table.values() if len(x) > 0)+1
|
||||
bytes = sum(1+len(x)*3 for x in self.ow_enemy_table.values() if len(x) > 0)+1
|
||||
self.pointer_addresses['ow_sprites'][1] = bytes
|
||||
# ending_byte = 0x09CB3B + bytes
|
||||
max_per_state = {0: 0x40, 1: 0x90, 2: 0x8B} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla, extra tutorial guard)
|
||||
max_per_state = {0: 0x40, 1: 0x90, 2: 0x82} # dropped max on state 2 to steal space for extra sprites (Murahdahla, extra tutorial guard)
|
||||
|
||||
pointer_address = snes_to_pc(0x09C881)
|
||||
# currently borrowed 10 bytes, used 9 (2xMurah + TutorialGuard)
|
||||
data_pointer = snes_to_pc(0x09CB38) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla, extra tutorial guard)
|
||||
pointer_address = snes_to_pc(self.pointer_addresses['ow_sprites'][2][0])
|
||||
self.pointer_addresses['ow_sprites'][0] = pointer_address + ((max_per_state[0] + max_per_state[1] + max_per_state[2]) * 2)
|
||||
data_pointer = self.pointer_addresses['ow_sprites'][0]
|
||||
empty_pointer = pc_to_snes(data_pointer) & 0xFFFF
|
||||
rom.write_byte(data_pointer, 0xff)
|
||||
cached_dark_world = {}
|
||||
@@ -187,6 +193,10 @@ class DataTables:
|
||||
data_pointer += len(data)
|
||||
rom.write_byte(data_pointer, 0xff)
|
||||
data_pointer += 1
|
||||
# Check if OW sprite data has overwritten the UW sprite pointer table
|
||||
max_allowed_address = snes_to_pc(0x09D62E)
|
||||
if data_pointer > max_allowed_address:
|
||||
raise Exception(f'OW sprite data will cause the UW sprite pointer table to overwrite the pots pointer table. Data end: {hex(pc_to_snes(data_pointer))}, Max allowed: $09D62E')
|
||||
|
||||
|
||||
special_health_table = {
|
||||
|
||||
@@ -121,8 +121,16 @@ def roll_settings(weights):
|
||||
ret.accessibility = get_choice('accessibility')
|
||||
ret.restrict_boss_items = get_choice('restrict_boss_items')
|
||||
|
||||
overworld_layout = get_choice('overworld_layout')
|
||||
ret.ow_layout = overworld_layout if overworld_layout != 'none' else 'vanilla'
|
||||
ret.ow_parallel = get_choice_bool('overworld_parallel')
|
||||
overworld_shuffle = get_choice('overworld_shuffle')
|
||||
ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla'
|
||||
if overworld_shuffle == 'parallel':
|
||||
ret.ow_layout = 'wild'
|
||||
ret.ow_parallel = True
|
||||
elif overworld_shuffle == 'full':
|
||||
ret.ow_layout = 'wild'
|
||||
ret.ow_parallel = False
|
||||
ret.ow_terrain = get_choice_bool('overworld_terrain')
|
||||
valid_options = {'none': 'none', 'polar': 'polar', 'grouped': 'polar', 'chaos': 'unrestricted', 'unrestricted': 'unrestricted'}
|
||||
ret.ow_crossed = get_choice('overworld_crossed')
|
||||
@@ -132,6 +140,7 @@ def roll_settings(weights):
|
||||
ret.ow_whirlpool = get_choice_bool('whirlpool_shuffle')
|
||||
overworld_flute = get_choice('flute_shuffle')
|
||||
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
|
||||
ret.ow_fog = get_choice_bool('overworld_fog')
|
||||
ret.shuffle_followers = get_choice_bool('shuffle_followers')
|
||||
ret.bonk_drops = get_choice_bool('bonk_drops')
|
||||
entrance_shuffle = get_choice('entrance_shuffle')
|
||||
@@ -148,11 +157,7 @@ def roll_settings(weights):
|
||||
ret.door_self_loops = get_choice_bool('door_self_loops')
|
||||
ret.experimental = get_choice_bool('experimental')
|
||||
ret.collection_rate = get_choice_bool('collection_rate')
|
||||
|
||||
ret.dungeon_counters = get_choice_non_bool('dungeon_counters') if 'dungeon_counters' in weights else 'default'
|
||||
if ret.dungeon_counters == 'default':
|
||||
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle != 'none' else 'off'
|
||||
|
||||
ret.pseudoboots = get_choice_bool('pseudoboots')
|
||||
ret.mirrorscroll = get_choice_bool('mirrorscroll')
|
||||
ret.shopsanity = get_choice_bool('shopsanity')
|
||||
|
||||
Reference in New Issue
Block a user