Merge branch 'OverworldShuffle' of github.com:codemann8/ALttPDoorRandomizer into OverworldShuffle

This commit is contained in:
2021-11-08 00:23:30 -08:00
27 changed files with 393 additions and 560 deletions

View File

@@ -110,6 +110,7 @@ class World(object):
set_player_attr('owwhirlpools', [])
set_player_attr('remote_items', False)
set_player_attr('required_medallions', ['Ether', 'Quake'])
set_player_attr('bottle_refills', ['Bottle (Green Potion)', 'Bottle (Green Potion)'])
set_player_attr('swamp_patch_required', False)
set_player_attr('powder_patch_required', False)
set_player_attr('ganon_at_pyramid', True)
@@ -954,7 +955,7 @@ class CollectionState(object):
# try to resolve a name
if resolution_hint == 'Location':
spot = self.world.get_location(spot, player)
elif resolution_hint in ['Entrance', 'OWEdge']:
elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'Ledge', 'Portal', 'Whirlpool', 'Mirror', 'Flute']:
spot = self.world.get_entrance(spot, player)
else:
# default to Region
@@ -1699,15 +1700,71 @@ class Entrance(object):
self.player = player
self.door = None
self.hide_path = False
self.temp_path = []
def can_reach(self, state):
if self.parent_region.can_reach(state) and self.access_rule(state):
if not self.hide_path and not self in state.path:
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
return True
# Destination Pickup OW Only No Ledges Can S&Q
multi_step_locations = { 'Pyramid Crack': ('Big Bomb', True, True, False),
'Missing Smith': ('Frog', True, False, True),
'Middle Aged Man': ('Dark Blacksmith Ruins', True, False, True) }
if self.name in multi_step_locations:
if self not in state.path:
world = self.parent_region.world if self.parent_region else None
step_location = world.get_location(multi_step_locations[self.name][0], self.player)
if step_location.can_reach(state) and self.can_reach_thru(state, step_location.parent_region, multi_step_locations[self.name][1], multi_step_locations[self.name][2], multi_step_locations[self.name][3]) 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))
item_name = step_location.item.name if step_location.item else 'Pick Up Item'
path = (f'{step_location.parent_region.name} Exit', (item_name, path))
while len(self.temp_path):
exit = self.temp_path.pop(0)
path = (exit.name, (exit.parent_region.name, path))
item_name = self.connected_region.locations[0].item.name if self.connected_region.locations[0].item else 'Deliver Item'
path = (item_name, (self.parent_region.name, path))
state.path[self] = (self.name, path)
return True
else:
return True
else:
if self.parent_region.can_reach(state) and self.access_rule(state):
if not self.hide_path and not self in state.path:
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
return True
return False
def can_reach_thru(self, state, start_region, ignore_underworld=False, ignore_ledges=False, allow_save_quit=False):
def explore_region(region, path = []):
nonlocal found
if region not in explored_regions or len(explored_regions[region]) > len(path):
explored_regions[region] = path
for exit in region.exits:
if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \
and exit.connected_region.name not in ['Dig Game Area'] \
and exit.access_rule(state):
if exit.connected_region == self.parent_region:
found = True
explored_regions[self.parent_region] = path + [exit]
elif not ignore_underworld or region.type == exit.connected_region.type or exit.connected_region.type not in [RegionType.Cave, RegionType.Dungeon]:
explore_region(exit.connected_region, path + [exit])
found = False
explored_regions = {}
explore_region(start_region.entrances[0].parent_region)
if found:
self.temp_path = explored_regions[self.parent_region]
elif allow_save_quit:
world = self.parent_region.world if self.parent_region else None
exit = world.get_entrance('Links House S&Q', self.player)
explore_region(exit.connected_region, [exit])
if found:
self.temp_path = explored_regions[self.parent_region]
#TODO: Implement residual mirror portal placing for the previous leg, to be used for the final destination
return found
def connect(self, region, addresses=None, target=None, vanilla=None):
self.connected_region = region
self.target = target
@@ -2680,6 +2737,7 @@ class Spoiler(object):
self.doorTypes = {}
self.lobbies = {}
self.medallions = {}
self.bottles = {}
self.playthrough = {}
self.unreachables = []
self.startinventory = []
@@ -2780,6 +2838,15 @@ class Spoiler(object):
self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0]
self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1]
self.bottles = OrderedDict()
if self.world.players == 1:
self.bottles['Waterfall Bottle'] = self.world.bottle_refills[1][0]
self.bottles['Pyramid Bottle'] = self.world.bottle_refills[1][1]
else:
for player in range(1, self.world.players + 1):
self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0]
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
self.locations = OrderedDict()
listed_locations = set()
@@ -2862,6 +2929,7 @@ class Spoiler(object):
out.update(self.locations)
out['Starting Inventory'] = self.startinventory
out['Special'] = self.medallions
out['Bottles'] = self.bottles
if self.hashes:
out['Hashes'] = {f"{self.world.player_names[player][team]} (Team {team+1})": hash for (player, team), hash in self.hashes.items()}
if self.shops:
@@ -2911,12 +2979,14 @@ class Spoiler(object):
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'))
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shufflelinks'][player] else 'No'))
if self.metadata['shuffle'][player] != 'vanilla':
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shuffleganon'][player] else 'No'))
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shufflelinks'][player] else 'No'))
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
if self.metadata['door_shuffle'][player] != 'vanilla':
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
outfile.write('Pot Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['potshuffle'][player] else 'No'))
outfile.write('Key Drop Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No'))
@@ -2931,7 +3001,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'.ljust(line_width+1).join(self.startinventory) + '\n')
def to_file(self, filename):
self.parse_data()
@@ -2945,6 +3015,7 @@ class Spoiler(object):
if len(self.hashes) > 0:
for team in range(self.world.teams):
outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team]))
outfile.write('\n\nRequirements:\n\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
@@ -2955,6 +3026,10 @@ class Spoiler(object):
if self.world.crystals_ganon_orig[player] == 'random':
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player])))
outfile.write('\n\nBottle Refills:\n\n')
for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n')
if self.overworlds:
# overworlds: overworld transitions;
outfile.write('\n\nOverworld:\n\n')
@@ -3133,7 +3208,7 @@ access_mode = {"items": 0, "locations": 1, "none": 2}
boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3}
enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
# byte 7: HHHD DPR? (enemy_health, enemy_dmg, potshuffle, retro, ?)
# byte 7: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
@@ -3163,7 +3238,8 @@ class Settings(object):
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
| (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0) | (0x2 if w.retro[p] else 0)])
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0)])
return base64.b64encode(code, "+-".encode()).decode()
@staticmethod
@@ -3208,6 +3284,8 @@ class Settings(object):
args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5]
args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3]
args.shufflepots[p] = True if settings[7] & 0x4 else False
args.bombbag[p] = True if settings[7] & 0x2 else False
args.shufflelinks[p] = True if settings[7] & 0x1 else False
class KeyRuleType(FastEnum):