Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2022-06-10 17:54:15 -05:00
11 changed files with 365 additions and 411 deletions

View File

@@ -1089,6 +1089,10 @@ class CollectionState(object):
rupee_farms = ['Archery Game', '50 Rupee Cave', '20 Rupee Cave'] rupee_farms = ['Archery Game', '50 Rupee Cave', '20 Rupee Cave']
bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area']
pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area']
rock_crabs = ['Desert Pass Area']
def can_reach_non_bunny(regionname): def can_reach_non_bunny(regionname):
region = self.world.get_region(regionname, player) region = self.world.get_region(regionname, player)
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player)) return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
@@ -1097,7 +1101,8 @@ class CollectionState(object):
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
if any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']): # tree pulls
if self.can_kill_most_things(player) and any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']):
for region in tree_pulls: for region in tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1109,6 +1114,22 @@ class CollectionState(object):
for region in post_aga_tree_pulls: for region in post_aga_tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
# bush crabs (final item isn't considered)
if self.world.enemy_shuffle[player] == 'none':
if self.world.prizes[player]['crab'][0] in [0xda, 0xdb]:
for region in bush_crabs:
if can_reach_non_bunny(region):
return True
if not self.has('Beat Agahnim 1', player):
for region in pre_aga_bush_crabs:
if can_reach_non_bunny(region):
return True
if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xda, 0xdb]:
for region in rock_crabs:
if can_reach_non_bunny(region):
return True
return False return False
def can_farm_bombs(self, player): def can_farm_bombs(self, player):
@@ -1173,7 +1194,7 @@ class CollectionState(object):
return True return True
# tree pulls # tree pulls
if any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']): if self.can_kill_most_things(player) and any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']):
for region in tree_pulls: for region in tree_pulls:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
return True return True
@@ -1187,7 +1208,7 @@ class CollectionState(object):
return True return True
# bush crabs (final item isn't considered) # bush crabs (final item isn't considered)
if self.world.enemy_shuffle[player] != 'none': if self.world.enemy_shuffle[player] == 'none':
if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]: if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]:
for region in bush_crabs: for region in bush_crabs:
if can_reach_non_bunny(region): if can_reach_non_bunny(region):
@@ -3141,7 +3162,18 @@ class Spoiler(object):
if self.world.players > 1: if self.world.players > 1:
outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('swaps', player)]['text'] + '\n\n') outfile.write(self.maps[('swaps', player)]['text'] + '\n\n')
# crossed groups
for player in range(1, self.world.players + 1):
if ('groups', player) in self.maps:
outfile.write('OW Crossed Groups:\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'] + '\n\n')
if self.overworlds: if self.overworlds:
# overworld transitions # overworld transitions
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))

View File

@@ -1,7 +1,15 @@
# Changelog # Changelog
### 0.2.7.3
- Restructured OWR algorithm to include some additional scenarios not previously allowed
- Added new Inverted D-pad controls for Social Distorion (ie. Mirror Mode) support
- Crossed OWR/Special OW Areas are now included in the spoiler log
- Fixed default TF pieces with Trinity in Mystery
- Added bush crabs to rupee farm logic (only in non-enemizer)
- Updated tree pull logic to also require ability to kill most things
### 0.2.7.2 ### 0.2.7.2
- Special OW Area are now shuffled in Layout Shuffle (Zora/Hobo/Pedestal) - Special OW Areas are now shuffled in Layout Shuffle (Zora/Hobo/Pedestal)
- Fixed some broken water region graph modelling, fixed some reachability logic - Fixed some broken water region graph modelling, fixed some reachability logic
- Some minor code simplifications - Some minor code simplifications

View File

@@ -759,9 +759,11 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
placed_items = {} placed_items = {}
precollected_items = [] precollected_items = []
clock_mode = None clock_mode = None
if goal in ['triforcehunt', 'trinity']: if treasure_hunt_total == 0:
if treasure_hunt_total == 0: if goal == 'triforcehunt':
treasure_hunt_total = 30 treasure_hunt_total = 30
elif goal == 'trinity':
treasure_hunt_total = 10
triforcepool = ['Triforce Piece'] * int(treasure_hunt_total) triforcepool = ['Triforce Piece'] * int(treasure_hunt_total)
pool.extend(alwaysitems) pool.extend(alwaysitems)

View File

@@ -42,7 +42,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'), 'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'), 'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'), 'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'), 'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'), 'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'), 'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'), 'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),

View File

@@ -135,7 +135,7 @@ def main(args, seed=None, fish=None):
world.player_names[player].append(name) world.player_names[player].append(name)
logger.info('') logger.info('')
if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] not in ['none', 'polar'] or world.owMixed[1] or str(args.outputname).startswith('M'): if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] not in ['none', 'polar'] or world.owMixed[1] or world.owWhirlpoolShuffle[1] or world.owFluteShuffle[1] != 'vanilla' or str(args.outputname).startswith('M'):
outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' outfilebase = f'OR_{args.outputname if args.outputname else world.seed}'
else: else:
outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'

View File

@@ -208,12 +208,14 @@ def roll_settings(weights):
ret.crystals_gt = get_choice('tower_open') ret.crystals_gt = get_choice('tower_open')
ret.crystals_ganon = get_choice('ganon_open') ret.crystals_ganon = get_choice('ganon_open')
goal_min = get_choice_default('triforce_goal_min', default=20) from ItemList import set_default_triforce
goal_max = get_choice_default('triforce_goal_max', default=20) default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
pool_min = get_choice_default('triforce_pool_min', default=30) goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
pool_max = get_choice_default('triforce_pool_max', default=30) goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
ret.triforce_goal = random.randint(int(goal_min), int(goal_max)) ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
min_diff = get_choice_default('triforce_min_difference', default=10) min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal))
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max)) ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.mode = get_choice('world_state') ret.mode = get_choice('world_state')

View File

@@ -984,289 +984,6 @@ OWTileRegions = bidict({
'Zoras Domain': 0x81 'Zoras Domain': 0x81
}) })
OWTileGroups = {
("Woods", "Regular", "None"): (
[
0x00, 0x2d, 0x80
],
[
0x40, 0x6d
]
),
("Lumberjack", "Regular", "None"): (
[
0x02
],
[
0x42
]
),
("Mountain Entry", "Entrance", "None"): (
[
0x03
],
[
0x43
]
),
("East Mountain", "Regular", "None"): (
[
0x05
],
[
0x45
]
),
("East Mountain", "Entrance", "None"): (
[
0x07
],
[
0x47
]
),
("Lake", "Regular", "Zora"): (
[
0x0f, 0x81
],
[
0x4f
]
),
("Lake", "Regular", "Lake"): (
[
0x35
],
[
0x75
]
),
("Mountain Entry", "Regular", "None"): (
[
0x0a
],
[
0x4a
]
),
("Woods Pass", "Regular", "None"): (
[
0x10
],
[
0x50
]
),
("Fortune", "Regular", "None"): (
[
0x11
],
[
0x51
]
),
("Whirlpools", "Regular", "Pond"): (
[
0x12
],
[
0x52
]
),
("Whirlpools", "Regular", "Witch"): (
[
0x15
],
[
0x55
]
),
("Whirlpools", "Regular", "CWhirlpool"): (
[
0x33
],
[
0x73
]
),
("Whirlpools", "Regular", "Southeast"): (
[
0x3f
],
[
0x7f
]
),
("Castle", "Entrance", "None"): (
[
0x13, 0x14
],
[
0x53, 0x54
]
),
("Castle", "Regular", "None"): (
[
0x1a, 0x1b
],
[
0x5a, 0x5b
]
),
("Witch", "Regular", "None"): (
[
0x16
],
[
0x56
]
),
("Water Approach", "Regular", "None"): (
[
0x17
],
[
0x57
]
),
("Village", "Regular", "None"): (
[
0x18
],
[
0x58
]
),
("Wooden Bridge", "Regular", "None"): (
[
0x1d
],
[
0x5d
]
),
("Eastern", "Regular", "None"): (
[
0x1e
],
[
0x5e
]
),
("Blacksmith", "Regular", "None"): (
[
0x22
],
[
0x62
]
),
("Dunes", "Regular", "None"): (
[
0x25
],
[
0x65
]
),
("Game", "Regular", "None"): (
[
0x28, 0x29
],
[
0x68, 0x69
]
),
("Grove", "Regular", "None"): (
[
0x2a
],
[
0x6a
]
),
("Central Bonk Rocks", "Regular", "None"): (
[
0x2b
],
[
0x6b
]
),
("Links", "Regular", "None"): (
[
0x2c
],
[
0x6c
]
),
("Tree Line", "Regular", "None"): (
[
0x2e
],
[
0x6e
]
),
("Nook", "Regular", "None"): (
[
0x2f
],
[
0x6f
]
),
("Desert", "Regular", "None"): (
[
0x30, 0x3a
],
[
0x70, 0x7a
]
),
("Grove Approach", "Regular", "None"): (
[
0x32
],
[
0x72
]
),
("Hype", "Regular", "None"): (
[
0x34
],
[
0x74
]
),
("Shopping Mall", "Regular", "None"): (
[
0x37
],
[
0x77
]
),
("Swamp", "Regular", "None"): (
[
0x3b
],
[
0x7b
]
),
("South Pass", "Regular", "None"): (
[
0x3c
],
[
0x7c
]
)
}
parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW', parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW',
'Lost Woods SC': 'Skull Woods SC', 'Lost Woods SC': 'Skull Woods SC',
'Lost Woods SE': 'Skull Woods SE', 'Lost Woods SE': 'Skull Woods SE',

View File

@@ -1,11 +1,12 @@
import RaceRandom as random, logging, copy import RaceRandom as random, logging, copy
from collections import OrderedDict from collections import OrderedDict, defaultdict
from DungeonGenerator import GenerationException from DungeonGenerator import GenerationException
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
from Regions import mark_dark_world_regions, mark_light_world_regions from Regions import mark_dark_world_regions, mark_light_world_regions
from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
from Utils import bidict
version_number = '0.2.7.2' version_number = '0.2.7.3'
version_branch = '' version_branch = ''
__version__ = '%s%s' % (version_number, version_branch) __version__ = '%s%s' % (version_number, version_branch)
@@ -101,8 +102,8 @@ def link_overworld(world, player):
else: else:
raise NotImplementedError('Invalid OW Edge swap scenario') raise NotImplementedError('Invalid OW Edge swap scenario')
return new_groups return new_groups
tile_groups = reorganize_tile_groups(world, player) tile_groups = define_tile_groups(world, player, False)
trimmed_groups = copy.deepcopy(OWEdgeGroups) trimmed_groups = copy.deepcopy(OWEdgeGroups)
swapped_edges = list() swapped_edges = list()
@@ -133,19 +134,20 @@ def link_overworld(world, player):
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC']) trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC'])
trimmed_groups[group] = (forward_edges, back_edges) trimmed_groups[group] = (forward_edges, back_edges)
connected_edges = []
if world.owShuffle[player] != 'vanilla':
trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
trimmed_groups = reorganize_groups(world, trimmed_groups, player)
# tile shuffle # tile shuffle
logging.getLogger('').debug('Swapping overworld tiles') logging.getLogger('').debug('Swapping overworld tiles')
if world.owMixed[player]: if world.owMixed[player]:
swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], player) swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player)
# move swapped regions/edges to other world
trimmed_groups = performSwap(trimmed_groups, swapped_edges)
assert len(swapped_edges) == 0, 'Not all edges were swapped successfully: ' + ', '.join(swapped_edges )
update_world_regions(world, player) update_world_regions(world, player)
# update spoiler # update spoiler
s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40)])) s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40, 0x82)]))
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05], s[0x00], s[0x03], s[0x05],
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
@@ -157,9 +159,9 @@ def link_overworld(world, player):
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25], s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
s[0x3a],s[0x3b],s[0x3c], s[0x3f], s[0x3a],s[0x3b],s[0x3c], s[0x3f],
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
s[0x32],s[0x33],s[0x34], s[0x37], s[0x40], s[0x32],s[0x33],s[0x34], s[0x37],
s[0x30], s[0x35], s[0x30], s[0x35],
s[0x3a],s[0x3b],s[0x3c], s[0x3f]) s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
world.spoiler.set_map('swaps', text_output, world.owswaps[player][0], player) world.spoiler.set_map('swaps', text_output, world.owswaps[player][0], player)
# apply tile logical connections # apply tile logical connections
@@ -175,42 +177,77 @@ def link_overworld(world, player):
# crossed shuffle # crossed shuffle
logging.getLogger('').debug('Crossing overworld edges') logging.getLogger('').debug('Crossing overworld edges')
if world.owCrossed[player] in ['grouped', 'limited', 'chaos']: crossed_edges = list()
# more Maze Race/Suburb/Frog/Dig Game fixes
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']
#TODO: Revisit with changes to Limited/Allowed
if world.owCrossed[player] not in ['none', 'grouped', 'polar', 'chaos']:
for edge in swapped_edges:
if edge not in parallel_links_new and edge not in parallel_links_new.inverse:
crossed_edges.append(edge)
if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'):
if world.owCrossed[player] == 'grouped': if world.owCrossed[player] == 'grouped':
ow_crossed_tiles = [[],[],[]] # the idea is to XOR the new swaps with the ones from Mixed so that non-parallel edges still work
crossed_edges = shuffle_tiles(world, tile_groups, ow_crossed_tiles, player) # Polar corresponds to Grouped with no swaps in ow_crossed_tiles_mask
elif world.owCrossed[player] in ['limited', 'chaos']: ow_crossed_tiles_mask = [[],[],[]]
crossed_edges = list() crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player)
ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])]
# update spoiler
s = list(map(lambda x: 'O' if x not in ow_crossed_tiles else 'X', [i for i in range(0x40, 0x82)]))
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05],
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
s[0x0a], s[0x0f],
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
s[0x22], s[0x25], s[0x1a], s[0x1d],
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
s[0x40], s[0x32],s[0x33],s[0x34], s[0x37],
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)
else:
crossed_candidates = list() crossed_candidates = list()
for group in trimmed_groups.keys(): for group in trimmed_groups.keys():
(mode, wrld, dir, terrain, parallel, count) = group (mode, wrld, dir, terrain, parallel, count) = group
if parallel == IsParallel.Yes and wrld == WorldType.Light and (mode == OpenStd.Open or world.mode[player] != 'standard'): if wrld == WorldType.Light and mode != OpenStd.Standard:
for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]):
if world.owKeepSimilar[player]: if forward_set[0] in parallel_links_new or forward_set[0] in parallel_links_new.inverse:
if world.owCrossed[player] == 'chaos' and random.randint(0, 1): if world.owKeepSimilar[player]:
for edge in forward_set:
crossed_edges.append(edge)
elif world.owCrossed[player] == 'limited':
crossed_candidates.append(forward_set)
else:
for edge in forward_set:
if world.owCrossed[player] == 'chaos' and random.randint(0, 1): if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
crossed_edges.append(edge) for edge in forward_set:
crossed_edges.append(edge)
elif world.owCrossed[player] == 'limited': elif world.owCrossed[player] == 'limited':
crossed_candidates.append([edge]) crossed_candidates.append(forward_set)
else:
for edge in forward_set:
if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
crossed_edges.append(edge)
elif world.owCrossed[player] == 'limited':
crossed_candidates.append([edge])
if world.owCrossed[player] == 'limited': if world.owCrossed[player] == 'limited':
random.shuffle(crossed_candidates) random.shuffle(crossed_candidates)
for edge_set in crossed_candidates[:9]: for edge_set in crossed_candidates[:9]:
for edge in edge_set: for edge in edge_set:
crossed_edges.append(edge) crossed_edges.append(edge)
for edge in copy.deepcopy(crossed_edges): for edge in copy.deepcopy(crossed_edges):
if edge in parallel_links: if edge in parallel_links_new:
crossed_edges.append(parallel_links[edge]) crossed_edges.append(parallel_links_new[edge])
elif edge in parallel_links.inverse: elif edge in parallel_links_new.inverse:
crossed_edges.append(parallel_links.inverse[edge][0]) crossed_edges.append(parallel_links_new.inverse[edge][0])
trimmed_groups = performSwap(trimmed_groups, crossed_edges) # after tile swap and crossed, determine edges that need to swap
assert len(crossed_edges) == 0, 'Not all edges were crossed successfully: ' + ', '.join(crossed_edges) edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)]
# whirlpool shuffle # whirlpool shuffle
logging.getLogger('').debug('Shuffling whirlpools') logging.getLogger('').debug('Shuffling whirlpools')
@@ -233,14 +270,14 @@ def link_overworld(world, player):
else: else:
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \ if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \ or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
or (world.owCrossed[player] == 'grouped' and ((from_owid < 0x40) == (from_owid not in ow_crossed_tiles[0]))): or (world.owCrossed[player] == 'grouped' and ((world.get_region(from_region, player).type == RegionType.LightWorld) == (from_owid not in ow_crossed_tiles))):
whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region))) whirlpool_candidates[0].append(tuple((from_owid, from_whirlpool, from_region)))
else: else:
whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region))) whirlpool_candidates[1].append(tuple((from_owid, from_whirlpool, from_region)))
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(to_region, player).type == RegionType.LightWorld)) \ if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(to_region, player).type == RegionType.LightWorld)) \
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \ or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
or (world.owCrossed[player] == 'grouped' and ((to_owid < 0x40) == (to_owid not in ow_crossed_tiles[0]))): or (world.owCrossed[player] == 'grouped' and ((world.get_region(to_region, player).type == RegionType.LightWorld) == (to_owid not in ow_crossed_tiles))):
whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region))) whirlpool_candidates[0].append(tuple((to_owid, to_whirlpool, to_region)))
else: else:
whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region))) whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region)))
@@ -260,9 +297,12 @@ def link_overworld(world, player):
# layout shuffle # layout shuffle
logging.getLogger('').debug('Shuffling overworld layout') logging.getLogger('').debug('Shuffling overworld layout')
connected_edges = []
if world.owShuffle[player] == 'vanilla': if world.owShuffle[player] == 'vanilla':
# apply outstanding swaps
trimmed_groups = performSwap(trimmed_groups, edges_to_swap)
assert len(edges_to_swap) == 0, 'Not all edges were swapped successfully: ' + ', '.join(edges_to_swap)
# vanilla transitions # vanilla transitions
groups = list(trimmed_groups.values()) groups = list(trimmed_groups.values())
for (forward_edge_sets, back_edge_sets) in groups: for (forward_edge_sets, back_edge_sets) in groups:
@@ -285,8 +325,7 @@ def link_overworld(world, player):
connect_custom(world, connected_edges, player) connect_custom(world, connected_edges, player)
# layout shuffle # layout shuffle
trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
groups = reorganize_groups(world, trimmed_groups, player)
tries = 20 tries = 20
valid_layout = False valid_layout = False
@@ -429,7 +468,7 @@ def link_overworld(world, player):
# update spoiler # update spoiler
new_spots = list(map(lambda o: flute_data[o][1], new_spots)) new_spots = list(map(lambda o: flute_data[o][1], new_spots))
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)])) s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05], s[0x00], s[0x03], s[0x05],
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
s[0x0a], s[0x0f], s[0x0a], s[0x0f],
@@ -505,33 +544,94 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
if not (parallel_forward_edge in connected_edges) and not (parallel_back_edge in connected_edges): if not (parallel_forward_edge in connected_edges) and not (parallel_back_edge in connected_edges):
connect_two_way(world, parallel_forward_edge, parallel_back_edge, player, connected_edges) connect_two_way(world, parallel_forward_edge, parallel_back_edge, player, connected_edges)
except KeyError: except KeyError:
# TODO: Figure out why non-parallel edges are getting into parallel groups
raise KeyError('No parallel edge for edge %s' % edgename2) raise KeyError('No parallel edge for edge %s' % edgename2)
def shuffle_tiles(world, groups, result_list, player): def shuffle_tiles(world, groups, result_list, do_grouped, player):
swapped_edges = list() swapped_edges = list()
valid_whirlpool_parity = False group_parity = {}
for group_data in groups:
group = group_data[0]
parity = [0, 0, 0, 0, 0]
# vertical land
if 0x00 in group:
parity[0] += 1
if 0x0f in group:
parity[0] += 1
if 0x80 in group:
parity[0] -= 1
if 0x81 in group:
parity[0] -= 1
# horizontal land
if 0x1a in group:
parity[1] -= 1
if 0x1b in group:
parity[1] += 1
if 0x28 in group:
parity[1] += 1
if 0x29 in group:
parity[1] -= 1
if 0x30 in group:
parity[1] -= 2
if 0x3a in group:
parity[1] += 2
# horizontal water
if 0x2d in group:
parity[2] += 1
if 0x80 in group:
parity[2] -= 1
# whirlpool
if 0x0f in group:
parity[3] += 1
if 0x12 in group:
parity[3] += 1
if 0x33 in group:
parity[3] += 1
if 0x35 in group:
parity[3] += 1
# dropdown exit
if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group:
parity[4] += 1
if 0x1b in group and world.mode[player] != 'standard':
parity[4] += 1
if 0x1b in group and world.shuffle_ganon:
parity[4] -= 1
group_parity[group[0]] = parity
attempts = 1000
while True:
if attempts == 0: # expected to only occur with custom swaps
raise GenerationException('Could not find valid tile swaps')
while not valid_whirlpool_parity:
# tile shuffle happens here # tile shuffle happens here
removed = list() removed = list()
for group in groups.keys(): for group in groups:
# if group[0] in ['Links', 'Central Bonk Rocks', 'Castle']: # TODO: Standard + Inverted # if 0x1b in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted
if random.randint(0, 1): if random.randint(0, 1):
removed.append(group) removed.append(group)
# save shuffled tiles to list # save shuffled tiles to list
new_results = [[],[],[]] new_results = [[],[],[]]
for group in groups.keys(): for group in groups:
if group not in removed: if group not in removed:
(owids, lw_regions, dw_regions) = groups[group] (owids, lw_regions, dw_regions) = group
(exist_owids, exist_lw_regions, exist_dw_regions) = new_results (exist_owids, exist_lw_regions, exist_dw_regions) = new_results
exist_owids.extend(owids) exist_owids.extend(owids)
exist_lw_regions.extend(lw_regions) exist_lw_regions.extend(lw_regions)
exist_dw_regions.extend(dw_regions) exist_dw_regions.extend(dw_regions)
# check whirlpool parity parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(5)]
valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len([o for o in new_results[0] if o in [0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f]]) % 2 == 0 parity[3] %= 2 # actual parity
if (world.owCrossed[player] == 'none' or do_grouped) and parity[:4] != [0, 0, 0, 0]:
attempts -= 1
continue
# ensure sanc can be placed in LW in certain modes
if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
free_dw_drops = parity[4] + (1 if world.shuffle_ganon else 0)
free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0)
if free_dw_drops == free_drops:
attempts -= 1
continue
break
(exist_owids, exist_lw_regions, exist_dw_regions) = result_list (exist_owids, exist_lw_regions, exist_dw_regions) = result_list
exist_owids.extend(new_results[0]) exist_owids.extend(new_results[0])
@@ -539,7 +639,7 @@ def shuffle_tiles(world, groups, result_list, player):
exist_dw_regions.extend(new_results[2]) exist_dw_regions.extend(new_results[2])
# replace LW edges with DW # replace LW edges with DW
if world.owCrossed[player] != 'polar': if world.owCrossed[player] not in ['polar', 'grouped', 'chaos'] or do_grouped:
# in polar, the actual edge connections remain vanilla # in polar, the actual edge connections remain vanilla
def getSwappedEdges(world, lst, player): def getSwappedEdges(world, lst, player):
for regionname in lst: for regionname in lst:
@@ -553,43 +653,75 @@ def shuffle_tiles(world, groups, result_list, player):
return swapped_edges return swapped_edges
def reorganize_tile_groups(world, player): def define_tile_groups(world, player, do_grouped):
def get_group_key(group): groups = [[i, i + 0x40] for i in range(0x40)]
#(name, groupType, whirlpoolGroup) = group
new_group = list(group) def get_group(id):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted']: for group in groups:
new_group[1] = None if id in group:
if not world.owWhirlpoolShuffle[player] and world.owCrossed[player] == 'none': return group
new_group[2] = None
return tuple(new_group) def merge_groups(tile_links):
for link in tile_links:
merged_group = []
for id in link:
if id not in merged_group:
group = get_group(id)
groups.remove(group)
merged_group += group
groups.append(merged_group)
def can_shuffle_group(group): def can_shuffle_group(group):
(name, groupType, whirlpoolGroup) = group # escape sequence should stay normal in standard
return name not in ['Castle', 'Links', 'Central Bonk Rocks'] \ if world.mode[player] == 'standard' and (0x1b in group or 0x2b in group or 0x2c in group):
or (world.mode[player] != 'standard' and (name != 'Castle' \ return False
or world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
or (world.mode[player] == 'open' and world.doorShuffle[player] == 'crossed') \ # sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen
or world.owCrossed[player] in ['grouped', 'polar', 'chaos'])) \ if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
or (world.mode[player] == 'standard' and world.shuffle[player] in ['lean', 'crossed', 'insanity'] and name == 'Castle' and groupType == 'Entrance') and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \
or (world.shuffle[player] == 'lite' and world.mode[player] == 'inverted')):
groups = {} return False
for group in OWTileGroups.keys():
if can_shuffle_group(group): return True
groups[get_group_key(group)] = ([], [], [])
for group in OWTileGroups.keys(): for i in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]:
groups.remove(get_group(i + 1))
groups.remove(get_group(i + 8))
groups.remove(get_group(i + 9))
groups.append([0x80])
groups.append([0x81])
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple']:
merge_groups([[0x03, 0x0a], [0x28, 0x29]])
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']:
merge_groups([[0x13, 0x14]])
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted']:
merge_groups([[0x05, 0x07]])
if world.shuffle[player] == 'vanilla' or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']):
merge_groups([[0x13, 0x14, 0x1b]])
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]])
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and world.owCrossed[player] == 'none':
merge_groups([[0x28, 0x29]])
if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]])
tile_groups = []
for group in groups:
if can_shuffle_group(group): if can_shuffle_group(group):
(lw_owids, dw_owids) = OWTileGroups[group] lw_regions = []
(exist_owids, exist_lw_regions, exist_dw_regions) = groups[get_group_key(group)] dw_regions = []
exist_owids.extend(lw_owids) for id in group:
exist_owids.extend(dw_owids) (lw_regions if id < 0x40 or id >= 0x80 else dw_regions).extend(OWTileRegions.inverse[id])
for owid in lw_owids: tile_groups.append((group, lw_regions, dw_regions))
exist_lw_regions.extend(OWTileRegions.inverse[owid])
for owid in dw_owids: return tile_groups
exist_dw_regions.extend(OWTileRegions.inverse[owid])
groups[get_group_key(group)] = (exist_owids, exist_lw_regions, exist_dw_regions)
return groups
def remove_reserved(world, groupedlist, connected_edges, player): def remove_reserved(world, groupedlist, connected_edges, player):
new_grouping = {} new_grouping = {}
@@ -597,7 +729,6 @@ def remove_reserved(world, groupedlist, connected_edges, player):
new_grouping[group] = ([], []) new_grouping[group] = ([], [])
for group in groupedlist.keys(): for group in groupedlist.keys():
(_, region, _, _, _, _) = group
(forward_edges, back_edges) = groupedlist[group] (forward_edges, back_edges) = groupedlist[group]
# remove edges already connected (thru plando and other forced connections) # remove edges already connected (thru plando and other forced connections)
@@ -605,15 +736,6 @@ def remove_reserved(world, groupedlist, connected_edges, player):
forward_edges = list(list(filter((edge).__ne__, i)) for i in forward_edges) forward_edges = list(list(filter((edge).__ne__, i)) for i in forward_edges)
back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges) back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges)
# remove parallel edges from pool, since they get added during shuffle
if world.owShuffle[player] == 'parallel' and region == WorldType.Dark:
for edge in parallel_links:
forward_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in forward_edges)
back_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in back_edges)
for edge in parallel_links.inverse:
forward_edges = list(list(filter((parallel_links.inverse[edge][0]).__ne__, i)) for i in forward_edges)
back_edges = list(list(filter((parallel_links.inverse[edge][0]).__ne__, i)) for i in back_edges)
forward_edges = list(filter(([]).__ne__, forward_edges)) forward_edges = list(filter(([]).__ne__, forward_edges))
back_edges = list(filter(([]).__ne__, back_edges)) back_edges = list(filter(([]).__ne__, back_edges))
@@ -656,7 +778,26 @@ def reorganize_groups(world, groups, player):
exist_back_edges.extend(back_edges) exist_back_edges.extend(back_edges)
new_grouping[new_group] = (exist_forward_edges, exist_back_edges) new_grouping[new_group] = (exist_forward_edges, exist_back_edges)
return list(new_grouping.values()) return new_grouping
def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
groups = defaultdict(lambda: ([],[]))
for (key, group) in trimmed_groups.items():
(mode, wrld, dir, terrain, parallel, count) = key
if mode == OpenStd.Standard:
groups[key] = group
else:
if world.owCrossed[player] == 'chaos':
groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0])
groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1])
else:
for i in range(2):
for edge_set in group[i]:
new_world = int(wrld)
if edge_set[0] in edges_to_swap:
new_world += 1
groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count)][i].append(edge_set)
return list(groups.values())
def create_flute_exits(world, player): def create_flute_exits(world, player):
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']): for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
@@ -1807,6 +1948,26 @@ D(18)|s ss ss | +-+-+-+-+-+-+-+-+
E(20)| s s | D(18)| |s| |s| | E(20)| s s | D(18)| |s| |s| |
F(28)|ssssssss| | s +-+ s +-+ s | F(28)|ssssssss| | s +-+ s +-+ s |
G(30)|s ssss s| E(20)| |s| |s| | G(30)|s ssss s| E(20)| |s| |s| |
H(38)| sss s| +-+-+-+-+-+-+-+-+
+--------+ F(28)|s|s|s|s|s|s|s|s|
+-+ +-+-+-+-+-+-+-+-+
Ped/Hobo: |s| G(30)| |s|s|s| |s|
+-+ | s +-+-+-+ s +-+
Zora: |s| H(38)| |s|s|s| |s|
+-+ +---+-+-+-+---+-+"""
flute_spoiler_table = \
""" 0 1 2 3 4 5 6 7
+---+-+---+---+-+
01234567 A(00)| |s| | |s|
+--------+ | s +-+ s | s +-+
A(00)|s ss s s| B(08)| |s| | |s|
B(08)| s s| +-+-+-+-+-+-+-+-+
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
E(20)| s s | D(18)| |s| |s| |
F(28)|ssssssss| | s +-+ s +-+ s |
G(30)|s ssss s| E(20)| |s| |s| |
H(38)| sss s| +-+-+-+-+-+-+-+-+ H(38)| sss s| +-+-+-+-+-+-+-+-+
+--------+ F(28)|s|s|s|s|s|s|s|s| +--------+ F(28)|s|s|s|s|s|s|s|s|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+

2
Rom.py
View File

@@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '538bf256ff03bb7576991114395eeccc' RANDOMIZERBASEHASH = 'fc6f1d6ba782d08ac92600c31cc7ee43'
class JsonRom(object): class JsonRom(object):

View File

@@ -39,8 +39,17 @@ org $04E8B4
Overworld_LoadSpecialOverworld: Overworld_LoadSpecialOverworld:
org $05af75 ; mirror hooks
org $02FBAB
JSL OWMirrorSpriteRestore : NOP
org $05AF75
Sprite_6C_MirrorPortal:
jsl OWPreserveMirrorSprite : nop #2 ; LDA $7EF3CA : BNE $05AFDF jsl OWPreserveMirrorSprite : nop #2 ; LDA $7EF3CA : BNE $05AFDF
org $05AFDF
Sprite_6C_MirrorPortal_missing_mirror:
JML OWMirrorSpriteDelete : NOP ; STZ $0DD0,X : BRA $05AFF1
org $0ABFBF
JSL OWMirrorSpriteOnMap : BRA + : NOP #6 : +
; whirlpool shuffle cross world change ; whirlpool shuffle cross world change
org $02b3bd org $02b3bd
@@ -196,38 +205,61 @@ OWWhirlpoolUpdate:
rtl rtl
} }
OWMirrorSpriteOnMap:
{
lda.w $1ac0,x : bit.b #$f0 : beq .continue
lda.b #$00 : rtl
.continue
ora.w $1ab0,x
ora.w $1ad0,x
ora.w $1ae0,x
rtl
}
OWPreserveMirrorSprite: OWPreserveMirrorSprite:
{ {
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla ; if OW Crossed, skip world check and continue
rtl ; if OW Crossed, skip world check and continue lda $10 : cmp.b #$0f : beq .vanilla
rtl
.vanilla .vanilla
lda InvertedMode : beq + lda.l InvertedMode : beq +
lda $7ef3ca : beq .deleteMirror lda.l $7ef3ca : beq .deleteMirror
rtl rtl
+ lda $7ef3ca : bne .deleteMirror + lda.l $7ef3ca : bne .deleteMirror
rtl rtl
.deleteMirror .deleteMirror
pla : lda #$de : pha ; in vanilla, if in dark world, jump to $05afdf lda.b $10 : cmp.b #$0f : bne +
rtl jsr.w OWMirrorSpriteMove ; if performing mirror superbunny
+ pla : pla : pla : jml Sprite_6C_MirrorPortal_missing_mirror
} }
OWMirrorSpriteMove: OWMirrorSpriteMove:
{ {
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq +
lda $1acf : eor #$80 : sta $1acf lda.w $1acf : ora.b #$40 : sta.w $1acf
+ lda #$2c : jml.l $07A985 ; what we wrote over + rts
}
OWMirrorSpriteBonk:
{
jsr.w OWMirrorSpriteMove
lda.b #$2c : jml.l SetGameModeLikeMirror ; what we wrote over
}
OWMirrorSpriteDelete:
{
stz.w $0dd0,x ; what we wrote over
jsr.w OWMirrorSpriteMove
jml Sprite_6C_MirrorPortal_dont_do_warp
} }
OWMirrorSpriteRestore: OWMirrorSpriteRestore:
{ {
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return
lda InvertedMode : beq + lda.l InvertedMode : beq +
lda $7ef3ca : beq .return lda.l $7ef3ca : beq .return
bra .restorePortal bra .restorePortal
+ lda $7ef3ca : bne .return + lda.l $7ef3ca : bne .return
.restorePortal .restorePortal
lda $1acf : and #$0f : sta $1acf lda.w $1acf : and.b #$0f : sta.w $1acf
.return .return
rep #$30 : lda.w $04AC ; what we wrote over rep #$30 : lda.w $04AC ; what we wrote over
@@ -603,7 +635,7 @@ OWWorldUpdate: ; x = owid of destination screen
cmp #0 : beq + : lda #1 cmp #0 : beq + : lda #1
+ cmp.l InvertedMode : bne + + cmp.l InvertedMode : bne +
lda $1acf : and #$0f : sta $1acf : bra .playSfx ; bring portal back into position lda $1acf : and #$0f : sta $1acf : bra .playSfx ; bring portal back into position
+ lda $1acf : eor #$80 : sta $1acf ; move portal off screen + lda $1acf : ora #$40 : sta $1acf ; move portal off screen
.playSfx .playSfx
lda #$38 : sta $012f ; play sfx - #$3b is an alternative lda #$38 : sta $012f ; play sfx - #$3b is an alternative

Binary file not shown.