Merge branch 'OverworldShuffle' of github.com:codemann8/ALttPDoorRandomizer into OverworldShuffle
This commit is contained in:
201
BaseClasses.py
201
BaseClasses.py
@@ -25,6 +25,7 @@ class World(object):
|
||||
self.players = players
|
||||
self.teams = 1
|
||||
self.owShuffle = owShuffle.copy()
|
||||
self.owTerrain = {}
|
||||
self.owCrossed = owCrossed.copy()
|
||||
self.owKeepSimilar = {}
|
||||
self.owMixed = owMixed.copy()
|
||||
@@ -888,7 +889,21 @@ class CollectionState(object):
|
||||
else:
|
||||
door_candidates.append(door.name)
|
||||
return door_candidates
|
||||
return None
|
||||
door_candidates, skip = [], set()
|
||||
if state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name in state.world.key_logic[player]:
|
||||
key_logic = state.world.key_logic[player][dungeon_name]
|
||||
for door, paired in key_logic.sm_doors.items():
|
||||
if door.name in key_logic.door_rules:
|
||||
rule = key_logic.door_rules[door.name]
|
||||
key = KeyRuleType.AllowSmall
|
||||
if (key in rule.new_rules and key_total >= rule.new_rules[key] and door.name not in skip
|
||||
and door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]):
|
||||
if paired:
|
||||
door_candidates.append((door.name, paired.name))
|
||||
skip.add(paired.name)
|
||||
else:
|
||||
door_candidates.append(door.name)
|
||||
return door_candidates if door_candidates else None
|
||||
|
||||
@staticmethod
|
||||
def print_rrp(rrp):
|
||||
@@ -1027,29 +1042,30 @@ class CollectionState(object):
|
||||
checked_locations = set([l for l in locations if l in self.locations_checked])
|
||||
reachable_events = [location for location in locations if location.event and location.can_reach(self)]
|
||||
reachable_events = self._do_not_flood_the_keys(reachable_events)
|
||||
found_new = False
|
||||
for event in reachable_events:
|
||||
if event not in checked_locations:
|
||||
self.events.append((event.name, event.player))
|
||||
self.collect(event.item, True, event)
|
||||
return len(reachable_events) > len(checked_locations)
|
||||
found_new = True
|
||||
return found_new
|
||||
|
||||
def sweep_for_events(self, key_only=False, locations=None):
|
||||
# this may need improvement
|
||||
if locations is None:
|
||||
locations = self.world.get_filled_locations()
|
||||
new_locations = True
|
||||
checked_locations = 0
|
||||
while new_locations:
|
||||
reachable_events = [location for location in locations if location.event and
|
||||
(not key_only or (not self.world.keyshuffle[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey))
|
||||
and location.can_reach(self)]
|
||||
reachable_events = self._do_not_flood_the_keys(reachable_events)
|
||||
new_locations = False
|
||||
for event in reachable_events:
|
||||
if (event.name, event.player) not in self.events:
|
||||
self.events.append((event.name, event.player))
|
||||
self.collect(event.item, True, event)
|
||||
new_locations = len(reachable_events) > checked_locations
|
||||
checked_locations = len(reachable_events)
|
||||
new_locations = True
|
||||
|
||||
|
||||
def can_reach_blue(self, region, player):
|
||||
@@ -1119,150 +1135,14 @@ class CollectionState(object):
|
||||
return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))
|
||||
|
||||
def can_farm_rupees(self, player):
|
||||
tree_pulls = ['Lost Woods East Area',
|
||||
'Snitch Lady (East)',
|
||||
'Turtle Rock Area',
|
||||
'Pyramid Area',
|
||||
'Hype Cave Area',
|
||||
'Dark South Pass Area',
|
||||
'Bumper Cave Area']
|
||||
pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area']
|
||||
post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area']
|
||||
return self.has('Farmable Rupees', player)
|
||||
|
||||
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):
|
||||
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))
|
||||
|
||||
for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
# 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:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has_beaten_aga(player):
|
||||
for region in pre_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
else:
|
||||
for region in post_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
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_beaten_aga(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
|
||||
|
||||
def can_farm_bombs(self, player):
|
||||
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
||||
return True
|
||||
|
||||
bush_bombs = ['Flute Boy Approach Area',
|
||||
'Kakariko Area',
|
||||
'Village of Outcasts Area',
|
||||
'Forgotten Forest Area',
|
||||
'Bat Cave Ledge',
|
||||
'East Dark Death Mountain (Bottom)']
|
||||
rock_bombs = ['Links House Area',
|
||||
'Dark Chapel Area',
|
||||
'Wooden Bridge Area',
|
||||
'Ice Cave Area',
|
||||
'Eastern Nook Area',
|
||||
'West Death Mountain (Bottom)',
|
||||
'Kakariko Fortune Area',
|
||||
'Skull Woods Forest',
|
||||
'Catfish Area',
|
||||
'Dark Fortune Area',
|
||||
'Qirn Jump Area',
|
||||
'Shield Shop Area',
|
||||
'Palace of Darkness Nook Area',
|
||||
'Swamp Nook Area',
|
||||
'Dark South Pass Area']
|
||||
bonk_bombs = ['Kakariko Fortune Area', 'Dark Graveyard Area'] #TODO: Flute Boy Approach Area and Bonk Rock Ledge are available post-Aga
|
||||
bomb_caves = ['Graveyard Cave', 'Light World Bomb Hut']
|
||||
|
||||
tree_pulls = ['Lost Woods East Area',
|
||||
'Snitch Lady (East)',
|
||||
'Turtle Rock Area',
|
||||
'Pyramid Area',
|
||||
'Hype Cave Area',
|
||||
'Dark South Pass Area',
|
||||
'Bumper Cave Area']
|
||||
pre_aga_tree_pulls = ['Hyrule Castle Courtyard', 'Mountain Entry Area']
|
||||
post_aga_tree_pulls = ['Statues Area', 'Eastern Palace Area']
|
||||
|
||||
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):
|
||||
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))
|
||||
|
||||
# bomb pickups
|
||||
for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []):
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
if self.can_lift_rocks(player):
|
||||
for region in rock_bombs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
if not self.world.shuffle_bonk_drops[player] and self.can_collect_bonkdrops(player):
|
||||
for region in bonk_bombs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
# tree pulls
|
||||
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:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has_beaten_aga(player):
|
||||
for region in pre_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
else:
|
||||
for region in post_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
# bush crabs (final item isn't considered)
|
||||
if self.world.enemy_shuffle[player] == 'none':
|
||||
if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]:
|
||||
for region in bush_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has_beaten_aga(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 [0xdc, 0xdd, 0xde]:
|
||||
for region in rock_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if self.has('Farmable Bombs', player):
|
||||
return True
|
||||
|
||||
# stun prize
|
||||
if self.can_stun_enemies(player) and self.world.prizes[player]['stun'] in [0xdc, 0xdd, 0xde]:
|
||||
@@ -1271,6 +1151,7 @@ class CollectionState(object):
|
||||
# bomb purchases
|
||||
if self.can_farm_rupees(player) and (self.can_buy_unlimited('Bombs (10)', player) or self.can_reach('Big Bomb Shop', None, player)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def item_count(self, item, player):
|
||||
@@ -1516,10 +1397,7 @@ class CollectionState(object):
|
||||
return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player)
|
||||
|
||||
def is_not_bunny(self, region, player):
|
||||
if self.has_Pearl(player):
|
||||
return True
|
||||
|
||||
return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world
|
||||
return self.has_Pearl(player) or not region.can_cause_bunny(player)
|
||||
|
||||
def can_reach_light_world(self, player):
|
||||
if True in [i.is_light_world for i in self.reachable_regions[player]]:
|
||||
@@ -1814,7 +1692,7 @@ class Region(object):
|
||||
def can_reach(self, state):
|
||||
from Utils import stack_size3a
|
||||
from DungeonGenerator import GenerationException
|
||||
if stack_size3a() > self.world.players * 500:
|
||||
if stack_size3a() > self.world.players * 1000:
|
||||
raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'')
|
||||
|
||||
if state.stale[self.player]:
|
||||
@@ -1840,6 +1718,12 @@ class Region(object):
|
||||
|
||||
return True
|
||||
|
||||
def can_cause_bunny(self, player):
|
||||
if 'Moon Pearl' in list(map(str, [i for i in self.world.precollected_items if i.player == player])):
|
||||
return False
|
||||
|
||||
return self.is_dark_world if self.world.mode[player] != 'inverted' else self.is_light_world
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
@@ -2035,6 +1919,9 @@ class Entrance(object):
|
||||
|
||||
return found
|
||||
|
||||
def can_cause_bunny(self, player):
|
||||
return self.parent_region.can_cause_bunny(player)
|
||||
|
||||
def connect(self, region, addresses=None, target=None, vanilla=None):
|
||||
self.connected_region = region
|
||||
self.target = target
|
||||
@@ -2886,6 +2773,9 @@ class Location(object):
|
||||
name += f' ({world.get_player_names(self.player)})'
|
||||
return name
|
||||
|
||||
def can_cause_bunny(self, player):
|
||||
return self.parent_region.can_cause_bunny(player)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
@@ -3118,6 +3008,7 @@ class Spoiler(object):
|
||||
'weapons': self.world.swords,
|
||||
'goal': self.world.goal,
|
||||
'ow_shuffle': self.world.owShuffle,
|
||||
'ow_terrain': self.world.owTerrain,
|
||||
'ow_crossed': self.world.owCrossed,
|
||||
'ow_keepsimilar': self.world.owKeepSimilar,
|
||||
'ow_mixed': self.world.owMixed,
|
||||
@@ -3192,11 +3083,11 @@ class Spoiler(object):
|
||||
self.locations = OrderedDict()
|
||||
listed_locations = set()
|
||||
|
||||
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld]
|
||||
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and not loc.skip]
|
||||
self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations])
|
||||
listed_locations.update(lw_locations)
|
||||
|
||||
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld]
|
||||
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and not loc.skip]
|
||||
self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations])
|
||||
listed_locations.update(dw_locations)
|
||||
|
||||
@@ -3332,10 +3223,12 @@ class Spoiler(object):
|
||||
outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player]))
|
||||
outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player]))
|
||||
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
|
||||
if self.metadata['ow_shuffle'][player] != 'vanilla':
|
||||
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':
|
||||
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
|
||||
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][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])
|
||||
outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player]))
|
||||
@@ -3427,10 +3320,10 @@ class Spoiler(object):
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('flute', player)]['text'] + '\n\n')
|
||||
|
||||
# overworld tile swaps
|
||||
# overworld tile flips
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('swaps', player) in self.maps:
|
||||
outfile.write('OW Tile Swaps:\n')
|
||||
outfile.write('OW Tile Flips:\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('swaps', player) in self.maps:
|
||||
|
||||
Reference in New Issue
Block a user