Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2021-10-27 01:42:46 -05:00
18 changed files with 280 additions and 113 deletions

View File

@@ -26,6 +26,7 @@ class World(object):
self.owCrossed = owCrossed.copy()
self.owKeepSimilar = {}
self.owMixed = owMixed.copy()
self.owWhirlpoolShuffle = {}
self.owFluteShuffle = {}
self.shuffle = shuffle.copy()
self.doorShuffle = doorShuffle.copy()
@@ -76,6 +77,7 @@ class World(object):
self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1
self.owswaps = {}
self.owwhirlpools = {}
self.owedges = []
self._owedge_cache = {}
self.owflutespots = {}
@@ -105,6 +107,7 @@ class World(object):
set_player_attr('_region_cache', {})
set_player_attr('player_names', [])
set_player_attr('owswaps', [[],[],[]])
set_player_attr('owwhirlpools', [])
set_player_attr('remote_items', False)
set_player_attr('required_medallions', ['Ether', 'Quake'])
set_player_attr('swamp_patch_required', False)
@@ -112,7 +115,7 @@ class World(object):
set_player_attr('ganon_at_pyramid', True)
set_player_attr('ganonstower_vanilla', True)
set_player_attr('sewer_light_cone', self.mode[player] == 'standard')
set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or ((self.mode[player] == 'inverted') != (0x05 in self.owswaps[player][0] and self.owMixed[player])))
set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or ((self.mode[player] == 'inverted') != 0x05 in self.owswaps[player][0]))
set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] or self.doorShuffle[player] not in ['vanilla'])
set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
set_player_attr('fix_trock_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
@@ -120,7 +123,7 @@ class World(object):
set_player_attr('can_access_trock_front', None)
set_player_attr('can_access_trock_big_chest', None)
set_player_attr('can_access_trock_middle', None)
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lite', 'lean', 'crossed', 'insanity', 'madness_legacy'])
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lean', 'crossed', 'insanity', 'madness_legacy'])
set_player_attr('mapshuffle', False)
set_player_attr('compassshuffle', False)
set_player_attr('keyshuffle', False)
@@ -2693,6 +2696,7 @@ class Spoiler(object):
'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,
'shuffle': self.world.shuffle,
'shuffleganon': self.world.shuffle_ganon,
@@ -2784,6 +2788,7 @@ class Spoiler(object):
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No'))
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No'))
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_whirlpool'][player] else 'No'))
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shuffleganon'][player] else 'No'))
@@ -2807,6 +2812,7 @@ class Spoiler(object):
if self.startinventory:
outfile.write('Starting Inventory:'.ljust(line_width))
outfile.write('\n'.ljust(line_width+1).join(self.startinventory))
outfile.write('\n\nRequirements:\n\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)

View File

@@ -1,5 +1,8 @@
# Changelog
### 0.2.1.0
- Implemented Whirlpool Shuffle
### 0.2.0.0
- Massive overhaul of ER algorithm
- Added 2 new ER modes (Lite and Lean)

3
CLI.py
View File

@@ -94,7 +94,7 @@ def parse_cli(argv, no_defaults=False):
playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True)
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_fluteshuffle',
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'bombbag', 'shuffleganon',
@@ -149,6 +149,7 @@ def parse_settings():
"ow_crossed": "none",
"ow_keepsimilar": False,
"ow_mixed": False,
"ow_whirlpool": False,
"ow_fluteshuffle": "vanilla",
"shuffle": "vanilla",
"shufflelinks": False,

View File

@@ -87,6 +87,7 @@ def main(args, seed=None, fish=None):
world.crystals_ganon_orig = args.crystals_ganon.copy()
world.crystals_gt_orig = args.crystals_gt.copy()
world.owKeepSimilar = args.ow_keepsimilar.copy()
world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
world.owFluteShuffle = args.ow_fluteshuffle.copy()
world.open_pyramid = args.openpyramid.copy()
world.boss_shuffle = args.shufflebosses.copy()
@@ -406,6 +407,7 @@ def copy_world(world):
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy()
ret.owKeepSimilar = world.owKeepSimilar.copy()
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
ret.owFluteShuffle = world.owFluteShuffle.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.boss_shuffle = world.boss_shuffle.copy()

View File

@@ -138,6 +138,7 @@ def roll_settings(weights):
ret.ow_crossed = get_choice('overworld_crossed')
ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on'
ret.ow_mixed = get_choice('overworld_swap') == 'on'
ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on'
overworld_flute = get_choice('flute_shuffle')
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
entrance_shuffle = get_choice('entrance_shuffle')

View File

@@ -969,7 +969,7 @@ OWTileRegions = bidict({
})
OWTileGroups = {
("Woods", "Regular"): (
("Woods", "Regular", "None"): (
[
0x00, 0x2d, 0x80
],
@@ -977,7 +977,7 @@ OWTileGroups = {
0x40, 0x6d
]
),
("Lumberjack", "Regular"): (
("Lumberjack", "Regular", "None"): (
[
0x02
],
@@ -985,7 +985,7 @@ OWTileGroups = {
0x42
]
),
("West Mountain", "Regular"): (
("West Mountain", "Regular", "None"): (
[
0x03
],
@@ -993,7 +993,7 @@ OWTileGroups = {
0x43
]
),
("East Mountain", "Regular"): (
("East Mountain", "Regular", "None"): (
[
0x05
],
@@ -1001,23 +1001,31 @@ OWTileGroups = {
0x45
]
),
("East Mountain", "Entrance"): (
("East Mountain", "Entrance", "None"): (
[
0x07,
0x07
],
[
0x47
]
),
("Lake", "Regular"): (
("Lake", "Regular", "Zora"): (
[
0x0f, 0x35, 0x81
0x0f, 0x81
],
[
0x4f, 0x75
0x4f
]
),
("Mountain Entry", "Regular"): (
("Lake", "Regular", "Lake"): (
[
0x35
],
[
0x75
]
),
("Mountain Entry", "Regular", "None"): (
[
0x0a
],
@@ -1025,7 +1033,7 @@ OWTileGroups = {
0x4a
]
),
("Woods Pass", "Regular"): (
("Woods Pass", "Regular", "None"): (
[
0x10
],
@@ -1033,7 +1041,7 @@ OWTileGroups = {
0x50
]
),
("Fortune", "Regular"): (
("Fortune", "Regular", "None"): (
[
0x11
],
@@ -1041,15 +1049,39 @@ OWTileGroups = {
0x51
]
),
("Whirlpools", "Regular"): (
("Whirlpools", "Regular", "Pond"): (
[
0x12, 0x15, 0x33, 0x3f
0x12
],
[
0x52, 0x55, 0x73, 0x7f
0x52
]
),
("Castle", "Entrance"): (
("Whirlpools", "Regular", "Witch"): (
[
0x15
],
[
0x55
]
),
("Whirlpools", "Regular", "CWhirlpool"): (
[
0x33
],
[
0x73
]
),
("Whirlpools", "Regular", "Southeast"): (
[
0x3f
],
[
0x7f
]
),
("Castle", "Entrance", "None"): (
[
0x13, 0x14
],
@@ -1057,7 +1089,7 @@ OWTileGroups = {
0x53, 0x54
]
),
("Castle", "Regular"): (
("Castle", "Regular", "None"): (
[
0x1a, 0x1b
],
@@ -1065,7 +1097,7 @@ OWTileGroups = {
0x5a, 0x5b
]
),
("Witch", "Regular"): (
("Witch", "Regular", "None"): (
[
0x16
],
@@ -1073,7 +1105,7 @@ OWTileGroups = {
0x56
]
),
("Water Approach", "Regular"): (
("Water Approach", "Regular", "None"): (
[
0x17
],
@@ -1081,7 +1113,7 @@ OWTileGroups = {
0x57
]
),
("Village", "Regular"): (
("Village", "Regular", "None"): (
[
0x18
],
@@ -1089,7 +1121,7 @@ OWTileGroups = {
0x58
]
),
("Wooden Bridge", "Regular"): (
("Wooden Bridge", "Regular", "None"): (
[
0x1d
],
@@ -1097,7 +1129,7 @@ OWTileGroups = {
0x5d
]
),
("Eastern", "Regular"): (
("Eastern", "Regular", "None"): (
[
0x1e
],
@@ -1105,7 +1137,7 @@ OWTileGroups = {
0x5e
]
),
("Blacksmith", "Regular"): (
("Blacksmith", "Regular", "None"): (
[
0x22
],
@@ -1113,7 +1145,7 @@ OWTileGroups = {
0x62
]
),
("Dunes", "Regular"): (
("Dunes", "Regular", "None"): (
[
0x25
],
@@ -1121,7 +1153,7 @@ OWTileGroups = {
0x65
]
),
("Game", "Regular"): (
("Game", "Regular", "None"): (
[
0x28, 0x29
],
@@ -1129,7 +1161,7 @@ OWTileGroups = {
0x68, 0x69
]
),
("Grove", "Regular"): (
("Grove", "Regular", "None"): (
[
0x2a
],
@@ -1137,7 +1169,7 @@ OWTileGroups = {
0x6a
]
),
("Central Bonk Rocks", "Regular"): (
("Central Bonk Rocks", "Regular", "None"): (
[
0x2b
],
@@ -1145,7 +1177,7 @@ OWTileGroups = {
0x6b
]
),
# ("Links", "Regular"): (
# ("Links", "Regular", "None"): (
# [
# 0x2c
# ],
@@ -1153,7 +1185,7 @@ OWTileGroups = {
# 0x6c
# ]
# ),
("Tree Line", "Regular"): (
("Tree Line", "Regular", "None"): (
[
0x2e
],
@@ -1161,7 +1193,7 @@ OWTileGroups = {
0x6e
]
),
("Nook", "Regular"): (
("Nook", "Regular", "None"): (
[
0x2f
],
@@ -1169,7 +1201,7 @@ OWTileGroups = {
0x6f
]
),
("Desert", "Regular"): (
("Desert", "Regular", "None"): (
[
0x30, 0x3a
],
@@ -1177,7 +1209,7 @@ OWTileGroups = {
0x70, 0x7a
]
),
("Grove Approach", "Regular"): (
("Grove Approach", "Regular", "None"): (
[
0x32
],
@@ -1185,7 +1217,7 @@ OWTileGroups = {
0x72
]
),
("Hype", "Regular"): (
("Hype", "Regular", "None"): (
[
0x34
],
@@ -1193,7 +1225,7 @@ OWTileGroups = {
0x74
]
),
("Shopping Mall", "Regular"): (
("Shopping Mall", "Regular", "None"): (
[
0x37
],
@@ -1201,7 +1233,7 @@ OWTileGroups = {
0x77
]
),
("Swamp", "Regular"): (
("Swamp", "Regular", "None"): (
[
0x3b
],
@@ -1209,7 +1241,7 @@ OWTileGroups = {
0x7b
]
),
("South Pass", "Regular"): (
("South Pass", "Regular", "None"): (
[
0x3c
],

View File

@@ -3,7 +3,7 @@ from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSl
from Regions import mark_dark_world_regions, mark_light_world_regions
from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
__version__ = '0.2.0.0-u'
__version__ = '0.2.1.0-u'
def link_overworld(world, player):
# setup mandatory connections
@@ -187,6 +187,44 @@ def link_overworld(world, player):
trimmed_groups = performSwap(trimmed_groups, crossed_edges)
assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges)
# whirlpool shuffle
logging.getLogger('').debug('Shuffling whirlpools')
if not world.owWhirlpoolShuffle[player]:
for (_, from_whirlpool, from_region), (_, to_whirlpool, to_region) in default_whirlpool_connections:
connect_simple(world, from_whirlpool, to_region, player)
connect_simple(world, to_whirlpool, from_region, player)
else:
whirlpool_candidates = [[],[]]
for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections:
if world.owCrossed[player] != 'none':
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
else:
if world.get_region(from_region, player).type == RegionType.LightWorld:
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
else:
whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region)))
if world.get_region(to_region, player).type == RegionType.LightWorld:
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
else:
whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region)))
# shuffle happens here
world.owwhirlpools[player] = [None] * 8
whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ]
for whirlpools in whirlpool_candidates:
random.shuffle(whirlpools)
while len(whirlpools):
from_owid, from_whirlpool, from_region = whirlpools.pop()
to_owid, to_whirlpool, to_region = whirlpools.pop()
connect_simple(world, from_whirlpool, to_region, player)
connect_simple(world, to_whirlpool, from_region, player)
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid
world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player)
# layout shuffle
logging.getLogger('').debug('Shuffling overworld layout')
connected_edges = []
@@ -387,22 +425,33 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
def shuffle_tiles(world, groups, result_list, player):
swapped_edges = list()
valid_whirlpool_parity = False
# tile shuffle happens here
removed = list()
for group in groups.keys():
if random.randint(0, 1):
removed.append(group)
# save shuffled tiles to list
for group in groups.keys():
if group not in removed:
(owids, lw_regions, dw_regions) = groups[group]
(exist_owids, exist_lw_regions, exist_dw_regions) = result_list
exist_owids.extend(owids)
exist_lw_regions.extend(lw_regions)
exist_dw_regions.extend(dw_regions)
result_list = [exist_owids, exist_lw_regions, exist_dw_regions]
while not valid_whirlpool_parity:
# tile shuffle happens here
removed = list()
for group in groups.keys():
# if group[0] in ['Links', 'Central Bonk Rocks', 'Castle']: # TODO: Standard + Inverted
if random.randint(0, 1):
removed.append(group)
# save shuffled tiles to list
new_results = [[],[],[]]
for group in groups.keys():
if group not in removed:
(owids, lw_regions, dw_regions) = groups[group]
(exist_owids, exist_lw_regions, exist_dw_regions) = new_results
exist_owids.extend(owids)
exist_lw_regions.extend(lw_regions)
exist_dw_regions.extend(dw_regions)
# check whirlpool parity
valid_whirlpool_parity = world.owCrossed[player] != 'none' or len(set(new_results[0]) & set({0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f})) % 2 == 0
(exist_owids, exist_lw_regions, exist_dw_regions) = result_list
exist_owids.extend(new_results[0])
exist_lw_regions.extend(new_results[1])
exist_dw_regions.extend(new_results[2])
# replace LW edges with DW
ignore_list = list() #TODO: Remove ignore_list when special OW areas are included in pool
@@ -426,36 +475,62 @@ def shuffle_tiles(world, groups, result_list, player):
def reorganize_tile_groups(world, player):
groups = {}
for (name, groupType) in OWTileGroups.keys():
for (name, groupType, whirlpoolGroup) in OWTileGroups.keys():
if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \
or (world.mode[player] == 'standard' and world.shuffle[player] in ['lite', 'lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'):
or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']:
groups[(name,)] = ([], [], [])
if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
groups[(name, whirlpoolGroup)] = ([], [], [])
else:
groups[(name,)] = ([], [], [])
else:
groups[(name, groupType)] = ([], [], [])
if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
groups[(name, groupType, whirlpoolGroup)] = ([], [], [])
else:
groups[(name, groupType)] = ([], [], [])
for (name, groupType) in OWTileGroups.keys():
for (name, groupType, whirlpoolGroup) in OWTileGroups.keys():
if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks'] \
or (world.mode[player] == 'standard' and world.shuffle[player] in ['lite', 'lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'):
(lw_owids, dw_owids) = OWTileGroups[(name, groupType,)]
or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance'):
(lw_owids, dw_owids) = OWTileGroups[(name, groupType, whirlpoolGroup)]
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']:
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name,)]
exist_owids.extend(lw_owids)
exist_owids.extend(dw_owids)
for owid in lw_owids:
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids:
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions)
if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, whirlpoolGroup)]
exist_owids.extend(lw_owids)
exist_owids.extend(dw_owids)
for owid in lw_owids:
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids:
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[(name, whirlpoolGroup)] = (exist_owids, exist_lw_regions, exist_dw_regions)
else:
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name,)]
exist_owids.extend(lw_owids)
exist_owids.extend(dw_owids)
for owid in lw_owids:
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids:
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[(name,)] = (exist_owids, exist_lw_regions, exist_dw_regions)
else:
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType)]
exist_owids.extend(lw_owids)
exist_owids.extend(dw_owids)
for owid in lw_owids:
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids:
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions)
if world.owWhirlpoolShuffle[player] or world.owCrossed[player] != 'none':
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType, whirlpoolGroup)]
exist_owids.extend(lw_owids)
exist_owids.extend(dw_owids)
for owid in lw_owids:
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids:
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[(name, groupType, whirlpoolGroup)] = (exist_owids, exist_lw_regions, exist_dw_regions)
else:
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[(name, groupType)]
exist_owids.extend(lw_owids)
exist_owids.extend(dw_owids)
for owid in lw_owids:
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids:
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions)
return groups
def remove_reserved(world, groupedlist, connected_edges, player):
@@ -733,17 +808,7 @@ temporary_mandatory_connections = [
]
# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
mandatory_connections = [# Whirlpool Connections
('C Whirlpool', 'River Bend Water'),
('River Bend Whirlpool', 'C Whirlpool Water'),
('Lake Hylia Whirlpool', 'Zora Waterfall Water'),
('Zora Whirlpool', 'Lake Hylia Water'),
('Kakariko Pond Whirlpool', 'Octoballoon Water'),
('Octoballoon Whirlpool', 'Kakariko Pond Area'),
('Qirn Jump Whirlpool', 'Bomber Corner Water'),
('Bomber Corner Whirlpool', 'Qirn Jump Water'),
# Intra-tile OW Connections
mandatory_connections = [# Intra-tile OW Connections
('Lost Woods Bush (West)', 'Lost Woods East Area'), #pearl
('Lost Woods Bush (East)', 'Lost Woods West Area'), #pearl
('West Death Mountain Drop', 'West Death Mountain (Bottom)'),
@@ -967,6 +1032,13 @@ mandatory_connections = [# Whirlpool Connections
('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') #fake flipper
]
default_whirlpool_connections = [
((0x33, 'C Whirlpool', 'C Whirlpool Water'), (0x15, 'River Bend Whirlpool', 'River Bend Water')),
((0x35, 'Lake Hylia Whirlpool', 'Lake Hylia Water'), (0x0f, 'Zora Whirlpool', 'Zora Waterfall Water')),
((0x12, 'Kakariko Pond Whirlpool', 'Kakariko Pond Area'), (0x3f, 'Octoballoon Whirlpool', 'Octoballoon Water')),
((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water'))
]
default_flute_connections = [
0x0b, 0x16, 0x18, 0x2c, 0x2f, 0x38, 0x3b, 0x3f
]

View File

@@ -93,6 +93,8 @@ Every transition independently is a candidate to be chosen as a cross-world conn
Note: Only parallel connections (a connection that also exists in the opposite world) are considered for cross-world connections, which means that the same connection in the opposite world will also connect cross-world.
Note: If Whirlpool Shuffle is enabled, those connections can be cross-world but do not count towards the 9 transitions that are crossed.
Motive: Why 9 connections? To imitate the effect of the 9 standard portals that exist.
### Chaos
@@ -111,6 +113,10 @@ OW tiles are randomly chosen to become a part of the opposite world. When on the
Note: Tiles are put into groups that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to another tile, those tiles must swap together; (an exception to this is the Old Man Rescue cave which has been modified similar to how Inverted modifies it, Old Man Rescue is ALWAYS accessible from the Light World)
## Whirlpool Shuffle (--ow_whirlpool)
When enabled, the whirlpool connections are shuffled. If Crossed OW is enabled, the whirlpools can also be cross-world as well. For Limited Crossed OW, this doesn't count towards the limited number of crossed edge transitions.
## Flute Shuffle (--ow_fluteshuffle)
When enabled, new flute spots are generated and gives the player the option to cancel out of the flute menu by pressing X.

7
Rom.py
View File

@@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'c6c2a2d5d89a3c84871f58806bbb3acf'
RANDOMIZERBASEHASH = 'e9dea70e0a0b15bfa0ff7ecd63228a0c'
class JsonRom(object):
@@ -644,6 +644,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(snes_to_pc(0x0AB793 + o), data[11] & 0xff) # Y low byte
rom.write_byte(snes_to_pc(0x0AB79B + o), data[11] // 0x100) # Y high byte
# patch whirlpools
if world.owWhirlpoolShuffle[player]:
owFlags |= 0x01
write_int16s(rom, snes_to_pc(0x02EA5C), world.owwhirlpools[player])
# patch overworld edges
inverted_buffer = [0] * 0x82
owMode = 0

View File

@@ -14,6 +14,10 @@ jsl OWEdgeTransition : nop #4 ;LDA $02A4E3,X : ORA $7EF3CA
;org $02e238 ;LDX #$9E : - DEX : DEX : CMP $DAEE,X : BNE -
;jsl OWSpecialTransition : nop #5
; whirlpool shuffle cross world change
org $02b3bd
jsl OWWhirlpoolUpdate ;JSL $02EA6C
; flute menu cancel
org $0ab7af ;LDA $F2 : ORA $F0 : AND #$C0
jml OWFluteCancel2 : nop
@@ -123,6 +127,14 @@ OWWorldCheck16:
plx : and.w #$00ff : rtl
}
OWWhirlpoolUpdate:
{
jsl $02ea6c ; what we wrote over
lda.l OWFlags : and #$01 : beq +
ldx $8a : jsr OWWorldUpdate
+ rtl
}
OWFluteCancel:
{
lda.l OWFlags+1 : and #$01 : bne +
@@ -341,32 +353,39 @@ OWNewDestination:
sep #$30 : lda OWOppSlotOffset,y : !add $04 : asl : and #$7f : sta $700
; crossed OW shuffle
LDA.l OWMode+1 : AND.b #!FLAG_OW_CROSSED : beq .return
ldx $05 : lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return
sta.l $7ef3ca ; change world
lda #$38 : sta $012f ; play sfx - #$3b is an alternative
; toggle bunny mode
+ lda $7ef357 : bne .nobunny
lda.l InvertedMode : bne .inverted
lda $7ef3ca : and.b #$40 : bra +
.inverted lda $7ef3ca : and.b #$40 : eor #$40
+ cmp #$40 : bne .nobunny
; turn into bunny
lda $5d : cmp #$04 : beq + ; if swimming, continue
lda #$17 : sta $5d
+ lda #$01 : sta $02e0 : sta $56
bra .return
.nobunny
lda $5d : cmp #$17 : bne + ; retain current state unless bunny
stz $5d
+ stz $02e0 : stz $56
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return
ldx $05 : jsr OWWorldUpdate
.return
lda $05 : sta $8a
rep #$30 : rts
}
OWWorldUpdate: ; x = owid of destination screen
{
lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return
sta.l $7ef3ca ; change world
lda #$38 : sta $012f ; play sfx - #$3b is an alternative
; toggle bunny mode
+ lda $7ef357 : bne .nobunny
lda.l InvertedMode : bne .inverted
lda $7ef3ca : and.b #$40 : bra +
.inverted lda $7ef3ca : and.b #$40 : eor #$40
+ cmp #$40 : bne .nobunny
; turn into bunny
lda $5d : cmp #$04 : beq + ; if swimming, continue
lda #$17 : sta $5d
+ lda #$01 : sta $02e0 : sta $56
bra .return
.nobunny
lda $5d : cmp #$17 : bne + ; retain current state unless bunny
stz $5d
+ stz $02e0 : stz $56
.return
rts
}
OWSpecialTransition:
{
LDX #$9E
@@ -531,7 +550,7 @@ OWNorthEdges:
; Min Max Width Mid OW Slot/OWID VRAM *FREE* Dest Index
dw $00a0, $00a0, $0000, $00a0, $0000, $0000, $0000, $0040 ;Lost Woods
dw $0458, $0540, $00e8, $04cc, $0a0a, $0000, $0000, $0000
dw $0f70, $0f90, $0020, $0f80, $0f0f, $0000, $0000, $0041
dw $0f38, $0f60, $0028, $0f4c, $0f0f, $0000, $0000, $0041
dw $0058, $0058, $0000, $0058, $1010, $0000, $0000, $0001
dw $0178, $0178, $0000, $0178, $1010, $0000, $0000, $0002
dw $0388, $0388, $0000, $0388, $1111, $0000, $0000, $0003

Binary file not shown.

View File

@@ -15,6 +15,9 @@
overworld_swap:
on: 1
off: 1
whirlpool_shuffle:
on: 1
off: 1
flute_shuffle:
vanilla: 0
balanced: 1

View File

@@ -133,6 +133,10 @@
"action": "store_true",
"type": "bool"
},
"ow_whirlpool": {
"action": "store_true",
"type": "bool"
},
"ow_fluteshuffle": {
"choices": [
"vanilla",

View File

@@ -221,6 +221,9 @@
"ow_mixed": [
"Overworld tiles are randomly chosen to become part of the opposite world."
],
"ow_whirlpool": [
"Whirlpools will be shuffled and paired together."
],
"ow_fluteshuffle": [
"This randomizes the flute spot destinations.",
"Vanilla: All flute spots remain unchanged.",

View File

@@ -123,13 +123,18 @@
"randomizer.overworld.crossed.grouped": "Grouped",
"randomizer.overworld.crossed.limited": "Limited",
"randomizer.overworld.crossed.chaos": "Chaos",
"randomizer.overworld.keepsimilar": "Keep Similar Edges Together",
"randomizer.overworld.mixed": "Tile Swap (Mixed)",
"randomizer.overworld.whirlpool": "Whirlpool Shuffle",
"randomizer.overworld.overworldflute": "Flute Shuffle",
"randomizer.overworld.overworldflute.vanilla": "Vanilla",
"randomizer.overworld.overworldflute.balanced": "Balanced",
"randomizer.overworld.overworldflute.random": "Random",
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
"randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool",

View File

@@ -24,6 +24,10 @@
"type": "checkbox",
"default": true
},
"whirlpool": {
"type": "checkbox",
"default": false
},
"overworldflute": {
"type": "selectbox",
"default": "vanilla",

View File

@@ -79,6 +79,7 @@ SETTINGSTOPROCESS = {
"crossed": "ow_crossed",
"keepsimilar": "ow_keepsimilar",
"mixed": "ow_mixed",
"whirlpool": "ow_whirlpool",
"overworldflute": "ow_fluteshuffle"
},
"entrance": {

View File

@@ -33,7 +33,7 @@ def overworld_page(parent):
packAttrs = {"side":LEFT, "pady":(18,0)}
elif key == "overworldflute":
packAttrs["pady"] = (20,0)
elif key == "mixed":
elif key in ["whirlpool", "mixed"]:
packAttrs = {"anchor":W, "padx":(79,0)}
self.widgets[key].pack(packAttrs)