From 1a62b1da282de6fbbcfe2553681b2b6ebc768a4e Mon Sep 17 00:00:00 2001 From: Bonta-kun <40473493+Bonta0@users.noreply.github.com> Date: Thu, 18 Apr 2019 11:23:24 +0200 Subject: [PATCH] Multiworld core implementation By Bonta0 Does not include the server/client code or the rom writes specific to it. Indeed it cannot write multiworld roms at all right now, pending addition future updates to support the official ALTTPR Multiworld client. Includes some GUI changes by Alaszun Co-authored-by: Alaszun --- .gitignore | 3 + AdjusterMain.py | 2 +- BaseClasses.py | 461 ++++++++++++----------- Bosses.py | 104 +++--- Dungeons.py | 70 ++-- EntranceRandomizer.py | 9 +- EntranceShuffle.py | 498 ++++++++++++------------- Fill.py | 162 ++++++-- Gui.py | 16 +- ItemList.py | 146 ++++---- Items.py | 4 +- Main.py | 125 ++++--- Plando.py | 36 +- Regions.py | 846 +++++++++++++++++++++--------------------- Rom.py | 175 +++++---- Rules.py | 775 +++++++++++++++++++------------------- 16 files changed, 1821 insertions(+), 1611 deletions(-) diff --git a/.gitignore b/.gitignore index af03169d..4ac9fd67 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ build bundle/components.wxs dist README.html +.vs/ +*multidata +*multisave diff --git a/AdjusterMain.py b/AdjusterMain.py index 346e9301..63466142 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -21,7 +21,7 @@ def adjust(args): outfilebase = os.path.basename(args.rom)[:-4] + '_adjusted' - if os.stat(args.rom).st_size == 2097152 and os.path.splitext(args.rom)[-1].lower() == '.sfc': + if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc': rom = LocalRom(args.rom, False) else: raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.') diff --git a/BaseClasses.py b/BaseClasses.py index bacf3368..93612364 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -7,7 +7,8 @@ from Utils import int16_as_bytes class World(object): - def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): + def __init__(self, players, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): + self.players = players self.shuffle = shuffle self.logic = logic self.mode = mode @@ -22,7 +23,7 @@ class World(object): self.itempool = [] self.seed = None self.state = CollectionState(self) - self.required_medallions = ['Ether', 'Quake'] + self.required_medallions = dict([(player, ['Ether', 'Quake']) for player in range(1, players + 1)]) self._cached_entrances = None self._cached_locations = None self._entrance_cache = {} @@ -32,10 +33,10 @@ class World(object): self.required_locations = [] self.place_dungeon_items = place_dungeon_items # configurable in future self.shuffle_bonk_prizes = False - self.swamp_patch_required = False - self.powder_patch_required = False - self.ganon_at_pyramid = True - self.ganonstower_vanilla = True + self.swamp_patch_required = {player: False for player in range(1, players + 1)} + self.powder_patch_required = {player: False for player in range(1, players + 1)} + self.ganon_at_pyramid = {player: True for player in range(1, players + 1)} + self.ganonstower_vanilla = {player: True for player in range(1, players + 1)} self.sewer_light_cone = mode == 'standard' self.light_world_light_cone = False self.dark_world_light_cone = False @@ -78,52 +79,52 @@ class World(object): for region in self.regions: region.world = self - def get_region(self, regionname): + def get_region(self, regionname, player): if isinstance(regionname, Region): return regionname try: - return self._region_cache[regionname] + return self._region_cache[(regionname, player)] except KeyError: for region in self.regions: - if region.name == regionname: - self._region_cache[regionname] = region + if region.name == regionname and region.player == player: + self._region_cache[(regionname, player)] = region return region - raise RuntimeError('No such region %s' % regionname) + raise RuntimeError('No such region %s for player %d' % (regionname, player)) - def get_entrance(self, entrance): + def get_entrance(self, entrance, player): if isinstance(entrance, Entrance): return entrance try: - return self._entrance_cache[entrance] + return self._entrance_cache[(entrance, player)] except KeyError: for region in self.regions: for exit in region.exits: - if exit.name == entrance: - self._entrance_cache[entrance] = exit + if exit.name == entrance and exit.player == player: + self._entrance_cache[(entrance, player)] = exit return exit - raise RuntimeError('No such entrance %s' % entrance) + raise RuntimeError('No such entrance %s for player %d' % (entrance, player)) - def get_location(self, location): + def get_location(self, location, player): if isinstance(location, Location): return location try: - return self._location_cache[location] + return self._location_cache[(location, player)] except KeyError: for region in self.regions: for r_location in region.locations: - if r_location.name == location: - self._location_cache[location] = r_location + if r_location.name == location and r_location.player == player: + self._location_cache[(location, player)] = r_location return r_location - raise RuntimeError('No such location %s' % location) + raise RuntimeError('No such location %s for player %d' % (location, player)) - def get_dungeon(self, dungeonname): + def get_dungeon(self, dungeonname, player): if isinstance(dungeonname, Dungeon): return dungeonname for dungeon in self.dungeons: - if dungeon.name == dungeonname: + if dungeon.name == dungeonname and dungeon.player == player: return dungeon - raise RuntimeError('No such dungeon %s' % dungeonname) + raise RuntimeError('No such dungeon %s for player %d' % (dungeonname, player)) def get_all_state(self, keys=False): ret = CollectionState(self) @@ -131,58 +132,61 @@ class World(object): def soft_collect(item): if item.name.startswith('Progressive '): if 'Sword' in item.name: - if ret.has('Golden Sword'): + if ret.has('Golden Sword', item.player): pass - elif ret.has('Tempered Sword') and self.difficulty_requirements.progressive_sword_limit >= 4: - ret.prog_items.append('Golden Sword') - elif ret.has('Master Sword') and self.difficulty_requirements.progressive_sword_limit >= 3: - ret.prog_items.append('Tempered Sword') - elif ret.has('Fighter Sword') and self.difficulty_requirements.progressive_sword_limit >= 2: - ret.prog_items.append('Master Sword') + elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements.progressive_sword_limit >= 4: + ret.prog_items.append(('Golden Sword', item.player)) + elif ret.has('Master Sword', item.player) and self.difficulty_requirements.progressive_sword_limit >= 3: + ret.prog_items.append(('Tempered Sword', item.player)) + elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements.progressive_sword_limit >= 2: + ret.prog_items.append(('Master Sword', item.player)) elif self.difficulty_requirements.progressive_sword_limit >= 1: - ret.prog_items.append('Fighter Sword') + ret.prog_items.append(('Fighter Sword', item.player)) elif 'Glove' in item.name: - if ret.has('Titans Mitts'): + if ret.has('Titans Mitts', item.player): pass - elif ret.has('Power Glove'): - ret.prog_items.append('Titans Mitts') + elif ret.has('Power Glove', item.player): + ret.prog_items.append(('Titans Mitts', item.player)) else: - ret.prog_items.append('Power Glove') + ret.prog_items.append(('Power Glove', item.player)) elif 'Shield' in item.name: - if ret.has('Mirror Shield'): + if ret.has('Mirror Shield', item.player): pass - elif ret.has('Red Shield') and self.difficulty_requirements.progressive_shield_limit >= 3: - ret.prog_items.append('Mirror Shield') - elif ret.has('Blue Shield') and self.difficulty_requirements.progressive_shield_limit >= 2: - ret.prog_items.append('Red Shield') + elif ret.has('Red Shield', item.player) and self.difficulty_requirements.progressive_shield_limit >= 3: + ret.prog_items.append(('Mirror Shield', item.player)) + elif ret.has('Blue Shield', item.player) and self.difficulty_requirements.progressive_shield_limit >= 2: + ret.prog_items.append(('Red Shield', item.player)) elif self.difficulty_requirements.progressive_shield_limit >= 1: - ret.prog_items.append('Blue Shield') + ret.prog_items.append(('Blue Shield', item.player)) elif item.name.startswith('Bottle'): - if ret.bottle_count() < self.difficulty_requirements.progressive_bottle_limit: - ret.prog_items.append(item.name) + if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit: + ret.prog_items.append((item.name, item.player)) elif item.advancement or item.key: - ret.prog_items.append(item.name) + ret.prog_items.append((item.name, item.player)) for item in self.itempool: soft_collect(item) + if keys: - from Items import ItemFactory - for item in ItemFactory(['Small Key (Escape)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', - 'Big Key (Palace of Darkness)'] + ['Small Key (Palace of Darkness)'] * 6 + ['Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Skull Woods)'] + ['Small Key (Skull Woods)'] * 3 + ['Big Key (Swamp Palace)', - 'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + ['Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', 'Big Key (Ganons Tower)'] + ['Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + ['Small Key (Ganons Tower)'] * 4): - soft_collect(item) + for p in range(1, self.players + 1): + from Items import ItemFactory + for item in ItemFactory(['Small Key (Escape)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', + 'Big Key (Palace of Darkness)'] + ['Small Key (Palace of Darkness)'] * 6 + ['Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Skull Woods)'] + ['Small Key (Skull Woods)'] * 3 + ['Big Key (Swamp Palace)', + 'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + ['Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', 'Big Key (Ganons Tower)'] + ['Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + ['Small Key (Ganons Tower)'] * 4, + p): + soft_collect(item) ret.sweep_for_events() return ret def get_items(self): return [loc.item for loc in self.get_filled_locations()] + self.itempool - def find_items(self, item): - return [location for location in self.get_locations() if location.item is not None and location.item.name == item] + def find_items(self, item, player): + return [location for location in self.get_locations() if location.item is not None and location.item.name == item and location.item.player == player] def push_item(self, location, item, collect=True): if not isinstance(location, Location): - location = self.get_location(location) + raise RuntimeError('Cannot assign item %s to location %s (player %d).' % (item, location, item.player)) if location.can_fill(self.state, item, False): location.item = item @@ -214,21 +218,21 @@ class World(object): def clear_location_cache(self): self._cached_locations = None - def get_unfilled_locations(self): - return [location for location in self.get_locations() if location.item is None] + def get_unfilled_locations(self, player=None): + return [location for location in self.get_locations() if (player is None or location.player == player) and location.item is None] - def get_filled_locations(self): - return [location for location in self.get_locations() if location.item is not None] + def get_filled_locations(self, player=None): + return [location for location in self.get_locations() if (player is None or location.player == player) and location.item is not None] - def get_reachable_locations(self, state=None): + def get_reachable_locations(self, state=None, player=None): if state is None: state = self.state - return [location for location in self.get_locations() if state.can_reach(location)] + return [location for location in self.get_locations() if (player is None or location.player == player) and state.can_reach(location)] - def get_placeable_locations(self, state=None): + def get_placeable_locations(self, state=None, player=None): if state is None: state = self.state - return [location for location in self.get_locations() if location.item is None and state.can_reach(location)] + return [location for location in self.get_locations() if (player is None or location.player == player) and location.item is None and state.can_reach(location)] def unlocks_new_location(self, item): temp_state = self.state.copy() @@ -241,11 +245,10 @@ class World(object): return False def has_beaten_game(self, state): - if state.has('Triforce'): + if all([state.has('Triforce', player) for player in range(1, self.players + 1)]): + return True + if self.goal in ['triforcehunt'] and all([((state.item_count('Triforce Piece', player) + state.item_count('Power Star', player)) > self.treasure_hunt_count) for player in range(1, self.players + 1)]): return True - if self.goal in ['triforcehunt']: - if state.item_count('Triforce Piece') + state.item_count('Power Star') > self.treasure_hunt_count: - return True return False def can_beat_game(self, starting_state=None): @@ -259,17 +262,21 @@ class World(object): prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked] - treasure_pieces_collected = state.item_count('Triforce Piece') + state.item_count('Power Star') + treasure_pieces_collected = dict([(player, state.item_count('Triforce Piece', player) + state.item_count('Power Star', player)) for player in range(1, self.players + 1)]) + triforces_collected = dict([(player, state.has('Triforce', player)) for player in range(1, self.players + 1)]) + while prog_locations: sphere = [] # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres for location in prog_locations: if state.can_reach(location): if location.item.name == 'Triforce': - return True + triforces_collected[location.item.player] = True + if all(triforces_collected.values()): + return True elif location.item.name in ['Triforce Piece', 'Power Star']: - treasure_pieces_collected += 1 - if self.goal in ['triforcehunt'] and treasure_pieces_collected >= self.treasure_hunt_count: + treasure_pieces_collected[location.item.player] += 1 + if self.goal in ['triforcehunt'] and all([treasure_pieces_collected[player] >= self.treasure_hunt_count for player in range(1, self.players + 1)]): return True sphere.append(location) @@ -283,8 +290,7 @@ class World(object): return False - @property - def option_identifier(self): + def option_identifier(self, maxbytes, player): id_value = 0 id_value_max = 1 @@ -309,10 +315,11 @@ class World(object): markbool(self.shuffle_ganon) markbool(self.keysanity) markbool(self.retro) - assert id_value_max <= 0xFFFFFFFF + marksequence(range(1, 256), self.players) + marksequence(range(1, self.players + 1), player) + assert id_value_max < (1 << (maxbytes * 8)) return id_value - class CollectionState(object): def __init__(self, parent): @@ -326,7 +333,6 @@ class CollectionState(object): def update_reachable_regions(self): self.stale=False - new_regions = True reachable_regions_count = len(self.reachable_regions) while new_regions: @@ -347,149 +353,149 @@ class CollectionState(object): ret.stale = True return ret - def can_reach(self, spot, resolution_hint=None): + def can_reach(self, spot, resolution_hint=None, player=None): try: spot_type = spot.spot_type except AttributeError: # try to resolve a name if resolution_hint == 'Location': - spot = self.world.get_location(spot) + spot = self.world.get_location(spot, player) elif resolution_hint == 'Entrance': - spot = self.world.get_entrance(spot) + spot = self.world.get_entrance(spot, player) else: # default to Region - spot = self.world.get_region(spot) + spot = self.world.get_region(spot, player) - return spot.can_reach(self) - def sweep_for_events(self, key_only=False): + def sweep_for_events(self, key_only=False, locations=None): # this may need improvement new_locations = True checked_locations = 0 while new_locations: - reachable_events = [location for location in self.world.get_filled_locations() if location.event and (not key_only or location.item.key) and self.can_reach(location)] + if locations is None: + locations = self.world.get_filled_locations() + reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and self.can_reach(location)] for event in reachable_events: - if event.name not in self.events: - self.events.append(event.name) + 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) - - def has(self, item, count=1): + def has(self, item, player, count=1): if count == 1: - return item in self.prog_items - return self.item_count(item) >= count + return (item, player) in self.prog_items + return self.item_count(item, player) >= count - def has_key(self, item, count=1): + def has_key(self, item, player, count=1): if self.world.retro: - return self.can_buy_unlimited('Small Key (Universal)') + return self.can_buy_unlimited('Small Key (Universal)', player) if count == 1: - return item in self.prog_items - return self.item_count(item) >= count + return (item, player) in self.prog_items + return self.item_count(item, player) >= count - def can_buy_unlimited(self, item): + def can_buy_unlimited(self, item, player): for shop in self.world.shops: - if shop.has_unlimited(item) and shop.region.can_reach(self): + if shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self): return True return False - def item_count(self, item): - return len([pritem for pritem in self.prog_items if pritem == item]) + def item_count(self, item, player): + return len([pritem for pritem in self.prog_items if pritem == (item, player)]) - def can_lift_rocks(self): - return self.has('Power Glove') or self.has('Titans Mitts') + def can_lift_rocks(self, player): + return self.has('Power Glove', player) or self.has('Titans Mitts', player) - def has_bottle(self): - return self.bottle_count() > 0 + def has_bottle(self, player): + return self.bottle_count(player) > 0 - def bottle_count(self): - return len([pritem for pritem in self.prog_items if pritem.startswith('Bottle')]) + def bottle_count(self, player): + return len([pritem for pritem in self.prog_items if pritem[0].startswith('Bottle') and pritem[1] == player]) - def has_hearts(self, count): + def has_hearts(self, player, count): # Warning: This only considers items that are marked as advancement items - return self.heart_count() >= count + return self.heart_count(player) >= count - def heart_count(self): + def heart_count(self, player): # Warning: This only considers items that are marked as advancement items return ( - self.item_count('Boss Heart Container') - + self.item_count('Sanctuary Heart Container') - + self.item_count('Piece of Heart') // 4 + self.item_count('Boss Heart Container', player) + + self.item_count('Sanctuary Heart Container', player) + + self.item_count('Piece of Heart', player) // 4 + 3 # starting hearts ) - def can_lift_heavy_rocks(self): - return self.has('Titans Mitts') + def can_lift_heavy_rocks(self, player): + return self.has('Titans Mitts', player) - def can_extend_magic(self, smallmagic=16, fullrefill=False): #This reflects the total magic Link has, not the total extra he has. + def can_extend_magic(self, player, smallmagic=16, fullrefill=False): #This reflects the total magic Link has, not the total extra he has. basemagic = 8 - if self.has('Quarter Magic'): + if self.has('Quarter Magic', player): basemagic = 32 - elif self.has('Half Magic'): + elif self.has('Half Magic', player): basemagic = 16 - if self.can_buy_unlimited('Green Potion') or self.can_buy_unlimited('Blue Potion'): + if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player): if self.world.difficulty == 'hard' and not fullrefill: - basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count()) + basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player)) elif self.world.difficulty == 'expert' and not fullrefill: - basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count()) + basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player)) elif self.world.difficulty == 'insane' and not fullrefill: basemagic = basemagic else: - basemagic = basemagic + basemagic * self.bottle_count() + basemagic = basemagic + basemagic * self.bottle_count(player) return basemagic >= smallmagic - def can_kill_most_things(self, enemies=5): - return (self.has_blunt_weapon() - or self.has('Cane of Somaria') - or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_magic())) - or self.can_shoot_arrows() - or self.has('Fire Rod') + def can_kill_most_things(self, player, enemies=5): + return (self.has_blunt_weapon(player) + or self.has('Cane of Somaria', player) + or (self.has('Cane of Byrna', player) and (enemies < 6 or self.can_extend_magic(player))) + or self.can_shoot_arrows(player) + or self.has('Fire Rod', player) ) - def can_shoot_arrows(self): + def can_shoot_arrows(self, player): if self.world.retro: #TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location) #FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe - return self.has('Bow') and (self.has('Silver Arrows') or self.can_buy_unlimited('Single Arrow')) - return self.has('Bow') + return self.has('Bow', player) and (self.has('Silver Arrows', player) or self.can_buy_unlimited('Single Arrow', player)) + return self.has('Bow', player) - def can_get_good_bee(self): - cave = self.world.get_region('Good Bee Cave') + def can_get_good_bee(self, player): + cave = self.world.get_region('Good Bee Cave', player) return ( - self.has_bottle() and - self.has('Bug Catching Net') and - (self.has_Boots() or (self.has_sword() and self.has('Quake'))) and + self.has_bottle(player) and + self.has('Bug Catching Net', player) and + (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and cave.can_reach(self) and - (cave.is_light_world or self.has_Pearl()) + (cave.is_light_world or self.has_Pearl(player)) ) - def has_sword(self): - return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword') + def has_sword(self, player): + return self.has('Fighter Sword', player) or self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) - def has_beam_sword(self): - return self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword') + def has_beam_sword(self, player): + return self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) - def has_blunt_weapon(self): - return self.has_sword() or self.has('Hammer') + def has_blunt_weapon(self, player): + return self.has_sword(player) or self.has('Hammer', player) - def has_Mirror(self): - return self.has('Magic Mirror') + def has_Mirror(self, player): + return self.has('Magic Mirror', player) - def has_Boots(self): - return self.has('Pegasus Boots') + def has_Boots(self, player): + return self.has('Pegasus Boots', player) - def has_Pearl(self): - return self.has('Moon Pearl') + def has_Pearl(self, player): + return self.has('Moon Pearl', player) - def has_fire_source(self): - return self.has('Fire Rod') or self.has('Lamp') + def has_fire_source(self, player): + return self.has('Fire Rod', player) or self.has('Lamp', player) - def has_misery_mire_medallion(self): - return self.has(self.world.required_medallions[0]) + def has_misery_mire_medallion(self, player): + return self.has(self.world.required_medallions[player][0], player) - def has_turtle_rock_medallion(self): - return self.has(self.world.required_medallions[1]) + def has_turtle_rock_medallion(self, player): + return self.has(self.world.required_medallions[player][1], player) def collect(self, item, event=False, location=None): if location: @@ -497,47 +503,47 @@ class CollectionState(object): changed = False if item.name.startswith('Progressive '): if 'Sword' in item.name: - if self.has('Golden Sword'): + if self.has('Golden Sword', item.player): pass - elif self.has('Tempered Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 4: - self.prog_items.append('Golden Sword') + elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements.progressive_sword_limit >= 4: + self.prog_items.append(('Golden Sword', item.player)) changed = True - elif self.has('Master Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 3: - self.prog_items.append('Tempered Sword') + elif self.has('Master Sword', item.player) and self.world.difficulty_requirements.progressive_sword_limit >= 3: + self.prog_items.append(('Tempered Sword', item.player)) changed = True - elif self.has('Fighter Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 2: - self.prog_items.append('Master Sword') + elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements.progressive_sword_limit >= 2: + self.prog_items.append(('Master Sword', item.player)) changed = True elif self.world.difficulty_requirements.progressive_sword_limit >= 1: - self.prog_items.append('Fighter Sword') + self.prog_items.append(('Fighter Sword', item.player)) changed = True elif 'Glove' in item.name: - if self.has('Titans Mitts'): + if self.has('Titans Mitts', item.player): pass - elif self.has('Power Glove'): - self.prog_items.append('Titans Mitts') + elif self.has('Power Glove', item.player): + self.prog_items.append(('Titans Mitts', item.player)) changed = True else: - self.prog_items.append('Power Glove') + self.prog_items.append(('Power Glove', item.player)) changed = True elif 'Shield' in item.name: - if self.has('Mirror Shield'): + if self.has('Mirror Shield', item.player): pass - elif self.has('Red Shield') and self.world.difficulty_requirements.progressive_shield_limit >= 3: - self.prog_items.append('Mirror Shield') + elif self.has('Red Shield', item.player) and self.world.difficulty_requirements.progressive_shield_limit >= 3: + self.prog_items.append(('Mirror Shield', item.player)) changed = True - elif self.has('Blue Shield') and self.world.difficulty_requirements.progressive_shield_limit >= 2: - self.prog_items.append('Red Shield') + elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements.progressive_shield_limit >= 2: + self.prog_items.append(('Red Shield', item.player)) changed = True elif self.world.difficulty_requirements.progressive_shield_limit >= 1: - self.prog_items.append('Blue Shield') + self.prog_items.append(('Blue Shield', item.player)) changed = True elif item.name.startswith('Bottle'): - if self.bottle_count() < self.world.difficulty_requirements.progressive_bottle_limit: - self.prog_items.append(item.name) + if self.bottle_count(item.player) < self.world.difficulty_requirements.progressive_bottle_limit: + self.prog_items.append((item.name, item.player)) changed = True elif event or item.advancement: - self.prog_items.append(item.name) + self.prog_items.append((item.name, item.player)) changed = True self.stale = True @@ -551,27 +557,27 @@ class CollectionState(object): to_remove = item.name if to_remove.startswith('Progressive '): if 'Sword' in to_remove: - if self.has('Golden Sword'): + if self.has('Golden Sword', item.player): to_remove = 'Golden Sword' - elif self.has('Tempered Sword'): + elif self.has('Tempered Sword', item.player): to_remove = 'Tempered Sword' - elif self.has('Master Sword'): + elif self.has('Master Sword', item.player): to_remove = 'Master Sword' - elif self.has('Fighter Sword'): + elif self.has('Fighter Sword', item.player): to_remove = 'Fighter Sword' else: to_remove = None elif 'Glove' in item.name: - if self.has('Titans Mitts'): + if self.has('Titans Mitts', item.player): to_remove = 'Titans Mitts' - elif self.has('Power Glove'): + elif self.has('Power Glove', item.player): to_remove = 'Power Glove' else: to_remove = None if to_remove is not None: try: - self.prog_items.remove(to_remove) + self.prog_items.remove((to_remove, item.player)) except ValueError: return @@ -582,8 +588,8 @@ class CollectionState(object): def __getattr__(self, item): if item.startswith('can_reach_'): return self.can_reach(item[10]) - elif item.startswith('has_'): - return self.has(item[4]) + #elif item.startswith('has_'): + # return self.has(item[4]) raise RuntimeError('Cannot parse %s.' % item) @@ -602,7 +608,7 @@ class RegionType(Enum): class Region(object): - def __init__(self, name, type, hint): + def __init__(self, name, type, hint, player): self.name = name self.type = type self.entrances = [] @@ -616,6 +622,7 @@ class Region(object): self.spot_type = 'Region' self.hint_text = hint self.recursion_count = 0 + self.player = player def can_reach(self, state): if state.stale: @@ -634,7 +641,7 @@ class Region(object): is_dungeon_item = item.key or item.map or item.compass sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)' if sewer_hack or (is_dungeon_item and not self.world.keysanity): - return self.dungeon and self.dungeon.is_dungeon_item(item) + return self.dungeon and self.dungeon.is_dungeon_item(item) and (item.player == self.player or self.world.keysanity) return True @@ -642,12 +649,12 @@ class Region(object): return str(self.__unicode__()) def __unicode__(self): - return '%s' % self.name + return '%s (Player %d)' % (self.name, self.player) class Entrance(object): - def __init__(self, name='', parent=None): + def __init__(self, player, name='', parent=None): self.name = name self.parent_region = parent self.connected_region = None @@ -657,6 +664,7 @@ class Entrance(object): self.recursion_count = 0 self.vanilla = None self.access_rule = lambda state: True + self.player = player def can_reach(self, state): if state.can_reach(self.parent_region) and self.access_rule(state): @@ -677,18 +685,19 @@ class Entrance(object): return str(self.__unicode__()) def __unicode__(self): - return '%s' % self.name + return '%s (Player %d)' % (self.name, self.player) class Dungeon(object): - def __init__(self, name, regions, big_key, small_keys, dungeon_items): + def __init__(self, name, regions, big_key, small_keys, dungeon_items, player): self.name = name self.regions = regions self.big_key = big_key self.small_keys = small_keys self.dungeon_items = dungeon_items self.bosses = dict() + self.player = player @property def boss(self): @@ -707,25 +716,26 @@ class Dungeon(object): return self.dungeon_items + self.keys def is_dungeon_item(self, item): - return item.name in [dungeon_item.name for dungeon_item in self.all_items] + return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items] def __str__(self): return str(self.__unicode__()) def __unicode__(self): - return '%s' % self.name + return '%s (Player %d)' % (self.name, self.player) class Boss(object): - def __init__(self, name, enemizer_name, defeat_rule): + def __init__(self, name, enemizer_name, defeat_rule, player): self.name = name self.enemizer_name = enemizer_name self.defeat_rule = defeat_rule + self.player = player def can_defeat(self, state): - return self.defeat_rule(state) + return self.defeat_rule(state, self.player) class Location(object): - def __init__(self, name='', address=None, crystal=False, hint_text=None, parent=None): + def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None): self.name = name self.parent_region = parent self.item = None @@ -739,6 +749,7 @@ class Location(object): self.always_allow = lambda item, state: False self.access_rule = lambda state: True self.item_rule = lambda item: True + self.player = player def can_fill(self, state, item, check_access=True): return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state))) @@ -752,12 +763,12 @@ class Location(object): return str(self.__unicode__()) def __unicode__(self): - return '%s' % self.name + return '%s (Player %d)' % (self.name, self.player) class Item(object): - def __init__(self, name='', advancement=False, priority=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None): + def __init__(self, name='', advancement=False, priority=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None, player=None): self.name = name self.advancement = advancement self.priority = priority @@ -771,6 +782,7 @@ class Item(object): self.hint_text = hint_text self.code = code self.location = None + self.player = player @property def key(self): @@ -792,7 +804,7 @@ class Item(object): return str(self.__unicode__()) def __unicode__(self): - return '%s' % self.name + return '%s (Player %d)' % (self.name, self.player) # have 6 address that need to be filled @@ -874,11 +886,14 @@ class Spoiler(object): self.shops = [] self.bosses = OrderedDict() - def set_entrance(self, entrance, exit, direction): - self.entrances[(entrance, direction)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)]) + def set_entrance(self, entrance, exit, direction, player): + self.entrances[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) def parse_data(self): - self.medallions = OrderedDict([('Misery Mire', self.world.required_medallions[0]), ('Turtle Rock', self.world.required_medallions[1])]) + self.medallions = OrderedDict() + for player in range(1, self.world.players + 1): + self.medallions['Misery Mire (Player %d)' % player] = self.world.required_medallions[player][0] + self.medallions['Turtle Rock (Player %d)' % player] = self.world.required_medallions[player][1] self.locations = OrderedDict() listed_locations = set() @@ -897,7 +912,7 @@ class Spoiler(object): for dungeon in self.world.dungeons: dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon] - self.locations[dungeon.name] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) + self.locations[str(dungeon)] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations]) listed_locations.update(dungeon_locations) other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations] @@ -905,10 +920,11 @@ class Spoiler(object): self.locations['Other Locations'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in other_locations]) listed_locations.update(other_locations) + self.shops = [] for shop in self.world.shops: if not shop.active: continue - shopdata = {'location': shop.region.name, + shopdata = {'location': str(shop.region), 'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop' } for index, item in enumerate(shop.inventory): @@ -917,22 +933,24 @@ class Spoiler(object): shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item'] self.shops.append(shopdata) - self.bosses["Eastern Palace"] = self.world.get_dungeon("Eastern Palace").boss.name - self.bosses["Desert Palace"] = self.world.get_dungeon("Desert Palace").boss.name - self.bosses["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera").boss.name - self.bosses["Hyrule Castle"] = "Agahnim" - self.bosses["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness").boss.name - self.bosses["Swamp Palace"] = self.world.get_dungeon("Swamp Palace").boss.name - self.bosses["Skull Woods"] = self.world.get_dungeon("Skull Woods").boss.name - self.bosses["Thieves Town"] = self.world.get_dungeon("Thieves Town").boss.name - self.bosses["Ice Palace"] = self.world.get_dungeon("Ice Palace").boss.name - self.bosses["Misery Mire"] = self.world.get_dungeon("Misery Mire").boss.name - self.bosses["Turtle Rock"] = self.world.get_dungeon("Turtle Rock").boss.name - self.bosses["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower').bosses['bottom'].name - self.bosses["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower').bosses['middle'].name - self.bosses["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower').bosses['top'].name - self.bosses["Ganons Tower"] = "Agahnim 2" - self.bosses["Ganon"] = "Ganon" + for player in range(1, self.world.players + 1): + self.bosses[str(player)] = OrderedDict() + self.bosses[str(player)]["Eastern Palace"] = self.world.get_dungeon("Eastern Palace", player).boss.name + self.bosses[str(player)]["Desert Palace"] = self.world.get_dungeon("Desert Palace", player).boss.name + self.bosses[str(player)]["Tower Of Hera"] = self.world.get_dungeon("Tower of Hera", player).boss.name + self.bosses[str(player)]["Hyrule Castle"] = "Agahnim" + self.bosses[str(player)]["Palace Of Darkness"] = self.world.get_dungeon("Palace of Darkness", player).boss.name + self.bosses[str(player)]["Swamp Palace"] = self.world.get_dungeon("Swamp Palace", player).boss.name + self.bosses[str(player)]["Skull Woods"] = self.world.get_dungeon("Skull Woods", player).boss.name + self.bosses[str(player)]["Thieves Town"] = self.world.get_dungeon("Thieves Town", player).boss.name + self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name + self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name + self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name + self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name + self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name + self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name + self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2" + self.bosses[str(player)]["Ganon"] = "Ganon" from Main import __version__ as ERVersion @@ -951,7 +969,8 @@ class Spoiler(object): 'quickswap': self.world.quickswap, 'fastmenu': self.world.fastmenu, 'disable_music': self.world.disable_music, - 'keysanity': self.world.keysanity} + 'keysanity': self.world.keysanity, + 'players': self.world.players} def to_json(self): self.parse_data() @@ -982,13 +1001,15 @@ class Spoiler(object): outfile.write('Maps and Compasses in Dungeons: %s\n' % ('Yes' if self.metadata['dungeonitems'] else 'No')) outfile.write('L\\R Quickswap enabled: %s\n' % ('Yes' if self.metadata['quickswap'] else 'No')) outfile.write('Menu speed: %s\n' % self.metadata['fastmenu']) - outfile.write('Keysanity enabled: %s' % ('Yes' if self.metadata['keysanity'] else 'No')) + outfile.write('Keysanity enabled: %s\n' % ('Yes' if self.metadata['keysanity'] else 'No')) + outfile.write('Players: %d' % self.metadata['players']) if self.entrances: outfile.write('\n\nEntrances:\n\n') - outfile.write('\n'.join(['%s %s %s' % (entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()])) - outfile.write('\n\nMedallions') - outfile.write('\n\nMisery Mire Medallion: %s' % self.medallions['Misery Mire']) - outfile.write('\nTurtle Rock Medallion: %s' % self.medallions['Turtle Rock']) + outfile.write('\n'.join(['Player %d: %s %s %s' % (entry['player'], entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()])) + outfile.write('\n\nMedallions\n') + for player in range(1, self.world.players + 1): + outfile.write('\nMisery Mire Medallion (Player %d): %s' % (player, self.medallions['Misery Mire (Player %d)' % player])) + outfile.write('\nTurtle Rock Medallion (Player %d): %s' % (player, self.medallions['Turtle Rock (Player %d)' % player])) outfile.write('\n\nLocations:\n\n') outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()])) outfile.write('\n\nShops:\n\n') diff --git a/Bosses.py b/Bosses.py index 20c7aabf..43072966 100644 --- a/Bosses.py +++ b/Bosses.py @@ -4,101 +4,101 @@ import random from BaseClasses import Boss from Fill import FillError -def BossFactory(boss): +def BossFactory(boss, player): if boss is None: return None if boss in boss_table: enemizer_name, defeat_rule = boss_table[boss] - return Boss(boss, enemizer_name, defeat_rule) + return Boss(boss, enemizer_name, defeat_rule, player) logging.getLogger('').error('Unknown Boss: %s', boss) return None -def ArmosKnightsDefeatRule(state): +def ArmosKnightsDefeatRule(state, player): # Magic amounts are probably a bit overkill return ( - state.has_blunt_weapon() or - (state.has('Cane of Somaria') and state.can_extend_magic(10)) or - (state.has('Cane of Byrna') and state.can_extend_magic(16)) or - (state.has('Ice Rod') and state.can_extend_magic(32)) or - (state.has('Fire Rod') and state.can_extend_magic(32)) or - state.has('Blue Boomerang') or - state.has('Red Boomerang')) + state.has_blunt_weapon(player) or + (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 10)) or + (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or + (state.has('Ice Rod', player) and state.can_extend_magic(player, 32)) or + (state.has('Fire Rod', player) and state.can_extend_magic(player, 32)) or + state.has('Blue Boomerang', player) or + state.has('Red Boomerang', player)) -def LanmolasDefeatRule(state): +def LanmolasDefeatRule(state, player): # TODO: Allow the canes here? return ( - state.has_blunt_weapon() or - state.has('Fire Rod') or - state.has('Ice Rod') or - state.can_shoot_arrows()) + state.has_blunt_weapon(player) or + state.has('Fire Rod', player) or + state.has('Ice Rod', player) or + state.can_shoot_arrows(player)) -def MoldormDefeatRule(state): - return state.has_blunt_weapon() +def MoldormDefeatRule(state, player): + return state.has_blunt_weapon(player) -def HelmasaurKingDefeatRule(state): - return state.has_blunt_weapon() or state.can_shoot_arrows() +def HelmasaurKingDefeatRule(state, player): + return state.has_blunt_weapon(player) or state.can_shoot_arrows(player) -def ArrghusDefeatRule(state): - if not state.has('Hookshot'): +def ArrghusDefeatRule(state, player): + if not state.has('Hookshot', player): return False # TODO: ideally we would have a check for bow and silvers, which combined with the # hookshot is enough. This is not coded yet because the silvers that only work in pyramid feature # makes this complicated - if state.has_blunt_weapon(): + if state.has_blunt_weapon(player): return True - return ((state.has('Fire Rod') and (state.can_shoot_arrows() or state.can_extend_magic(12))) or #assuming mostly gitting two puff with one shot - (state.has('Ice Rod') and (state.can_shoot_arrows() or state.can_extend_magic(16)))) + return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot + (state.has('Ice Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) -def MothulaDefeatRule(state): +def MothulaDefeatRule(state, player): return ( - state.has_blunt_weapon() or - (state.has('Fire Rod') and state.can_extend_magic(10)) or + state.has_blunt_weapon(player) or + (state.has('Fire Rod', player) and state.can_extend_magic(player, 10)) or # TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply # to non-vanilla locations, so are harder to test, so sticking with what VT has for now: - (state.has('Cane of Somaria') and state.can_extend_magic(16)) or - (state.has('Cane of Byrna') and state.can_extend_magic(16)) or - state.can_get_good_bee() + (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or + (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or + state.can_get_good_bee(player) ) -def BlindDefeatRule(state): - return state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna') +def BlindDefeatRule(state, player): + return state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) -def KholdstareDefeatRule(state): +def KholdstareDefeatRule(state, player): return ( ( - state.has('Fire Rod') or + state.has('Fire Rod', player) or ( - state.has('Bombos') and + state.has('Bombos', player) and # FIXME: the following only actually works for the vanilla location for swordless mode - (state.has_sword() or state.world.mode == 'swordless') + (state.has_sword(player) or state.world.mode == 'swordless') ) ) and ( - state.has_blunt_weapon() or - (state.has('Fire Rod') and state.can_extend_magic(20)) or + state.has_blunt_weapon(player) or + (state.has('Fire Rod', player) and state.can_extend_magic(player, 20)) or # FIXME: this actually only works for the vanilla location for swordless mode ( - state.has('Fire Rod') and - state.has('Bombos') and + state.has('Fire Rod', player) and + state.has('Bombos', player) and state.world.mode == 'swordless' and - state.can_extend_magic(16) + state.can_extend_magic(player, 16) ) ) ) -def VitreousDefeatRule(state): - return state.can_shoot_arrows() or state.has_blunt_weapon() +def VitreousDefeatRule(state, player): + return state.can_shoot_arrows(player) or state.has_blunt_weapon(player) -def TrinexxDefeatRule(state): - if not (state.has('Fire Rod') and state.has('Ice Rod')): +def TrinexxDefeatRule(state, player): + if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): return False - return state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32)) + return state.has('Hammer', player) or state.has_beam_sword(player) or (state.has_sword(player) and state.can_extend_magic(player, 32)) -def AgahnimDefeatRule(state): - return state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net') +def AgahnimDefeatRule(state, player): + return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player) boss_table = { 'Armos Knights': ('Armos', ArmosKnightsDefeatRule), @@ -137,7 +137,7 @@ def can_place_boss(world, boss, dungeon_name, level=None): return False return True -def place_bosses(world): +def place_bosses(world, player): if world.boss_shuffle == 'none': return # Most to least restrictive order @@ -162,7 +162,7 @@ def place_bosses(world): if world.boss_shuffle in ["basic", "normal"]: # temporary hack for swordless kholdstare: if world.mode == 'swordless': - world.get_dungeon('Ice Palace').boss = BossFactory('Kholdstare') + world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player) logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace') boss_locations.remove(['Ice Palace', None]) placeable_bosses.remove('Kholdstare') @@ -183,7 +183,7 @@ def place_bosses(world): bosses.remove(boss) logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) - world.get_dungeon(loc).bosses[level] = BossFactory(boss) + world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) elif world.boss_shuffle == "chaos": #all bosses chosen at random for [loc, level] in boss_locations: loc_text = loc + (' ('+level+')' if level else '') @@ -193,4 +193,4 @@ def place_bosses(world): raise FillError('Could not place boss for location %s' % loc_text) logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) - world.get_dungeon(loc).bosses[level] = BossFactory(boss) + world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) diff --git a/Dungeons.py b/Dungeons.py index a53e0b40..bed73730 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -6,44 +6,45 @@ from Fill import fill_restrictive from Items import ItemFactory -def create_dungeons(world): +def create_dungeons(world, player): def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): - dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items) - dungeon.boss = BossFactory(default_boss) + dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items, player) + dungeon.boss = BossFactory(default_boss, player) for region in dungeon.regions: - world.get_region(region).dungeon = dungeon + world.get_region(region, player).dungeon = dungeon return dungeon - ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'], None, [ItemFactory('Small Key (Escape)')], [ItemFactory('Map (Escape)')]) - EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'], ItemFactory('Big Key (Eastern Palace)'), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'])) - DP = make_dungeon('Desert Palace', 'Lanmolas', ['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)', 'Desert Palace East'], ItemFactory('Big Key (Desert Palace)'), [ItemFactory('Small Key (Desert Palace)')], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'])) - ToH = make_dungeon('Tower of Hera', 'Moldorm', ['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'], ItemFactory('Big Key (Tower of Hera)'), [ItemFactory('Small Key (Tower of Hera)')], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'])) - AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2), []) - PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', ['Palace of Darkness (Entrance)', 'Palace of Darkness (Center)', 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness (Bonk Section)', 'Palace of Darkness (North)', 'Palace of Darkness (Maze)', 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness (Final Section)'], ItemFactory('Big Key (Palace of Darkness)'), ItemFactory(['Small Key (Palace of Darkness)'] * 6), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'])) - TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], ItemFactory('Big Key (Thieves Town)'), [ItemFactory('Small Key (Thieves Town)')], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'])) - SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Second Section (Drop)', 'Skull Woods Final Section (Mothula)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'], ItemFactory('Big Key (Skull Woods)'), ItemFactory(['Small Key (Skull Woods)'] * 2), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'])) - SP = make_dungeon('Swamp Palace', 'Arrghus', ['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)', 'Swamp Palace (Center)', 'Swamp Palace (North)'], ItemFactory('Big Key (Swamp Palace)'), [ItemFactory('Small Key (Swamp Palace)')], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'])) - IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)'), ItemFactory(['Small Key (Ice Palace)'] * 2), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'])) - MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)'), ItemFactory(['Small Key (Misery Mire)'] * 3), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'])) - TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)'), ItemFactory(['Small Key (Turtle Rock)'] * 4), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'])) - GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)'), ItemFactory(['Small Key (Ganons Tower)'] * 4), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'])) + ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'], None, [ItemFactory('Small Key (Escape)', player)], [ItemFactory('Map (Escape)', player)]) + EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'], ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player)) + DP = make_dungeon('Desert Palace', 'Lanmolas', ['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)', 'Desert Palace East'], ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player)) + ToH = make_dungeon('Tower of Hera', 'Moldorm', ['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'], ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player)) + AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) + PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', ['Palace of Darkness (Entrance)', 'Palace of Darkness (Center)', 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness (Bonk Section)', 'Palace of Darkness (North)', 'Palace of Darkness (Maze)', 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness (Final Section)'], ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player)) + TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player)) + SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Second Section (Drop)', 'Skull Woods Final Section (Mothula)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'], ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 2, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player)) + SP = make_dungeon('Swamp Palace', 'Arrghus', ['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)', 'Swamp Palace (Center)', 'Swamp Palace (North)'], ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player)) + IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player)) + MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player)) + TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player)) + GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) - GT.bosses['bottom'] = BossFactory('Armos Knights') - GT.bosses['middle'] = BossFactory('Lanmolas') - GT.bosses['top'] = BossFactory('Moldorm') + GT.bosses['bottom'] = BossFactory('Armos Knights', player) + GT.bosses['middle'] = BossFactory('Lanmolas', player) + GT.bosses['top'] = BossFactory('Moldorm', player) - world.dungeons = [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT] + world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT] def fill_dungeons(world): freebes = ['Ganons Tower - Map Chest', 'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Big Key Chest', 'Turtle Rock - Big Key Chest'] all_state_base = world.get_all_state() - if world.retro: - world.push_item(world.get_location('Skull Woods - Pinball Room'), ItemFactory('Small Key (Universal)'), False) - else: - world.push_item(world.get_location('Skull Woods - Pinball Room'), ItemFactory('Small Key (Skull Woods)'), False) - world.get_location('Skull Woods - Pinball Room').event = True + for player in range(1, world.players + 1): + if world.retro: + world.push_item(world.get_location('Skull Woods - Pinball Room', player), ItemFactory('Small Key (Universal)', player), False) + else: + world.push_item(world.get_location('Skull Woods - Pinball Room', player), ItemFactory('Small Key (Skull Woods)', player), False) + world.get_location('Skull Woods - Pinball Room', player).event = True dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons] @@ -88,7 +89,7 @@ def fill_dungeons(world): small_keys.append(small_key) dungeons.append((dungeon_regions, big_key, small_keys, dungeon_items)) # infinite regression protection - if loopcnt < 30: + if loopcnt < (30 * world.players): break else: raise RuntimeError('No suitable location for %s' % small_key) @@ -114,13 +115,14 @@ def get_dungeon_item_pool(world): def fill_dungeons_restrictive(world, shuffled_locations): all_state_base = world.get_all_state() - skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room') - if world.retro: - world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Universal)'), False) - else: - world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Skull Woods)'), False) - skull_woods_big_chest.event = True - shuffled_locations.remove(skull_woods_big_chest) + for player in range(1, world.players + 1): + skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room', player) + if world.retro: + world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Universal)', player), False) + else: + world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Skull Woods)', player), False) + skull_woods_big_chest.event = True + shuffled_locations.remove(skull_woods_big_chest) if world.keysanity: #in keysanity dungeon items are distributed as part of the normal item pool diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index fed67800..55bf1d58 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -8,7 +8,7 @@ import sys from Gui import guiMain from Main import main -from Utils import is_bundled, close_console +from Utils import is_bundled, close_console, output_path class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -213,8 +213,15 @@ def start(): Output .json patch to stdout instead of a patched rom. Used for VT site integration, do not use otherwise. ''') + parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255)) + parser.add_argument('--skip_playthrough', action='store_true', default=False) + + parser.add_argument('--outputpath') args = parser.parse_args() + if args.outputpath and os.path.isdir(args.outputpath): + output_path.cached_path = args.outputpath + if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 73709adb..0265dae8 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -3,9 +3,9 @@ import random # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. -def link_entrances(world): - connect_two_way(world, 'Links House', 'Links House Exit') # unshuffled. For now - connect_exit(world, 'Chris Houlihan Room Exit', 'Links House') # should always match link's house, except for plandos +def link_entrances(world, player): + connect_two_way(world, 'Links House', 'Links House Exit', player) # unshuffled. For now + connect_exit(world, 'Chris Houlihan Room Exit', 'Links House', player) # should always match link's house, except for plandos Dungeon_Exits = Dungeon_Exits_Base.copy() Cave_Exits = Cave_Exits_Base.copy() @@ -16,24 +16,24 @@ def link_entrances(world): # setup mandatory connections for exitname, regionname in mandatory_connections: - connect_simple(world, exitname, regionname) + connect_simple(world, exitname, regionname, player) # if we do not shuffle, set default connections if world.shuffle == 'vanilla': for exitname, regionname in default_connections: - connect_simple(world, exitname, regionname) + connect_simple(world, exitname, regionname, player) for exitname, regionname in default_dungeon_connections: - connect_simple(world, exitname, regionname) + connect_simple(world, exitname, regionname, player) elif world.shuffle == 'dungeonssimple': for exitname, regionname in default_connections: - connect_simple(world, exitname, regionname) + connect_simple(world, exitname, regionname, player) simple_shuffle_dungeons(world) elif world.shuffle == 'dungeonsfull': for exitname, regionname in default_connections: - connect_simple(world, exitname, regionname) + connect_simple(world, exitname, regionname, player) - skull_woods_shuffle(world) + skull_woods_shuffle(world, player) dungeon_exits = list(Dungeon_Exits) lw_entrances = list(LW_Dungeon_Entrances) @@ -41,26 +41,26 @@ def link_entrances(world): if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) lw_entrances.append('Hyrule Castle Entrance (South)') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: dw_entrances.append('Ganons Tower') dungeon_exits.append('Ganons Tower Exit') if world.mode == 'standard': # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert - connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit)) + connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player) else: - connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit)) - connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit)) - connect_caves(world, lw_entrances, dw_entrances, dungeon_exits) + connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) + connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) + connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) elif world.shuffle == 'simple': - simple_shuffle_dungeons(world) + simple_shuffle_dungeons(world, player) old_man_entrances = list(Old_Man_Entrances) caves = list(Cave_Exits) @@ -79,8 +79,8 @@ def link_entrances(world): while two_door_caves: entrance1, entrance2 = two_door_caves.pop() exit1, exit2 = caves.pop() - connect_two_way(world, entrance1, exit1) - connect_two_way(world, entrance2, exit2) + connect_two_way(world, entrance1, exit1, player) + connect_two_way(world, entrance2, exit2, player) # now the remaining pairs two_door_caves = list(Two_Door_Caves) @@ -88,8 +88,8 @@ def link_entrances(world): while two_door_caves: entrance1, entrance2 = two_door_caves.pop() exit1, exit2 = caves.pop() - connect_two_way(world, entrance1, exit1) - connect_two_way(world, entrance2, exit2) + connect_two_way(world, entrance1, exit1, player) + connect_two_way(world, entrance2, exit2, player) # at this point only Light World death mountain entrances remain # place old man, has limited options @@ -100,38 +100,38 @@ def link_entrances(world): remaining_entrances.extend(old_man_entrances) random.shuffle(remaining_entrances) old_man_entrance = remaining_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)') - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)') + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) # add old man house to ensure it is always somewhere on light death mountain caves.extend(list(Old_Man_House)) caves.extend(list(three_exit_caves)) # connect rest - connect_caves(world, remaining_entrances, [], caves) + connect_caves(world, remaining_entrances, [], caves, player) # scramble holes - scramble_holes(world) + scramble_holes(world, player) # place blacksmith, has limited options random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) bomb_shop_doors.extend(blacksmith_doors) # place bomb shop, has limited options random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) single_doors.extend(bomb_shop_doors) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) # place remaining doors - connect_doors(world, single_doors, door_targets) + connect_doors(world, single_doors, door_targets, player) elif world.shuffle == 'restricted': - simple_shuffle_dungeons(world) + simple_shuffle_dungeons(world, player) lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) dw_entrances = list(DW_Entrances + DW_Single_Cave_Doors) @@ -144,17 +144,17 @@ def link_entrances(world): door_targets = list(Single_Cave_Targets) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) # in restricted, the only mandatory exits are in dark world - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits) + connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) # place old man, has limited options # exit has to come from specific set of doors, the entrance is free to move about old_man_entrances = [door for door in old_man_entrances if door in lw_entrances] random.shuffle(old_man_entrances) old_man_exit = old_man_entrances.pop() - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)') + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) lw_entrances.remove(old_man_exit) # place blacksmith, has limited options @@ -163,7 +163,7 @@ def link_entrances(world): blacksmith_doors = [door for door in blacksmith_doors if door in all_entrances] random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) if blacksmith_hut in lw_entrances: lw_entrances.remove(blacksmith_hut) if blacksmith_hut in dw_entrances: @@ -176,7 +176,7 @@ def link_entrances(world): bomb_shop_doors = [door for door in bomb_shop_doors if door in all_entrances] random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) if bomb_shop in lw_entrances: lw_entrances.remove(bomb_shop) if bomb_shop in dw_entrances: @@ -185,24 +185,24 @@ def link_entrances(world): # place the old man cave's entrance somewhere in the light world random.shuffle(lw_entrances) old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)') + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) # place Old Man House in Light World - connect_caves(world, lw_entrances, [], list(Old_Man_House)) #for multiple seeds + connect_caves(world, lw_entrances, [], list(Old_Man_House), player) #for multiple seeds # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves) + connect_caves(world, lw_entrances, dw_entrances, caves, player) # scramble holes - scramble_holes(world) + scramble_holes(world, player) doors = lw_entrances + dw_entrances # place remaining doors - connect_doors(world, doors, door_targets) + connect_doors(world, doors, door_targets, player) elif world.shuffle == 'restricted_legacy': - simple_shuffle_dungeons(world) + simple_shuffle_dungeons(world, player) lw_entrances = list(LW_Entrances) dw_entrances = list(DW_Entrances) @@ -216,7 +216,7 @@ def link_entrances(world): door_targets = list(Single_Cave_Targets) # only use two exit caves to do mandatory dw connections - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits) + connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) # add three exit doors to pool for remainder caves.extend(three_exit_caves) @@ -227,37 +227,37 @@ def link_entrances(world): lw_entrances.extend(old_man_entrances) random.shuffle(lw_entrances) old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)') - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)') + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) # place Old Man House in Light World - connect_caves(world, lw_entrances, [], Old_Man_House) + connect_caves(world, lw_entrances, [], Old_Man_House, player) # connect rest. There's 2 dw entrances remaining, so we will not run into parity issue placing caves - connect_caves(world, lw_entrances, dw_entrances, caves) + connect_caves(world, lw_entrances, dw_entrances, caves, player) # scramble holes - scramble_holes(world) + scramble_holes(world, player) # place blacksmith, has limited options random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) bomb_shop_doors.extend(blacksmith_doors) # place dam and pyramid fairy, have limited options random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) single_doors.extend(bomb_shop_doors) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) # place remaining doors - connect_doors(world, single_doors, door_targets) + connect_doors(world, single_doors, door_targets, player) elif world.shuffle == 'full': - skull_woods_shuffle(world) + skull_woods_shuffle(world, player) lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) @@ -271,17 +271,17 @@ def link_entrances(world): old_man_house = list(Old_Man_House) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) lw_entrances.append('Hyrule Castle Entrance (South)') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: dw_entrances.append('Ganons Tower') caves.append('Ganons Tower Exit') @@ -291,34 +291,34 @@ def link_entrances(world): #we also places the Old Man House at this time to make sure he can be connected to the desert one way if random.randint(0, 1) == 0: caves += old_man_house - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) try: caves.remove(old_man_house[0]) except ValueError: pass else: #if the cave wasn't placed we get here - connect_caves(world, lw_entrances, [], old_man_house) - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits) + connect_caves(world, lw_entrances, [], old_man_house, player) + connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) else: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits) + connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) caves += old_man_house - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) try: caves.remove(old_man_house[0]) except ValueError: pass else: #if the cave wasn't placed we get here - connect_caves(world, lw_entrances, [], old_man_house) + connect_caves(world, lw_entrances, [], old_man_house, player) if world.mode == 'standard': # rest of hyrule castle must be in light world - connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')]) + connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) # place old man, has limited options # exit has to come from specific set of doors, the entrance is free to move about old_man_entrances = [door for door in old_man_entrances if door in lw_entrances] random.shuffle(old_man_entrances) old_man_exit = old_man_entrances.pop() - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)') + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) lw_entrances.remove(old_man_exit) # place blacksmith, has limited options @@ -327,7 +327,7 @@ def link_entrances(world): blacksmith_doors = [door for door in blacksmith_doors if door in all_entrances] random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) if blacksmith_hut in lw_entrances: lw_entrances.remove(blacksmith_hut) if blacksmith_hut in dw_entrances: @@ -340,7 +340,7 @@ def link_entrances(world): bomb_shop_doors = [door for door in bomb_shop_doors if door in all_entrances] random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) if bomb_shop in lw_entrances: lw_entrances.remove(bomb_shop) if bomb_shop in dw_entrances: @@ -348,21 +348,21 @@ def link_entrances(world): # place the old man cave's entrance somewhere in the light world old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)') + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves) + connect_caves(world, lw_entrances, dw_entrances, caves, player) # scramble holes - scramble_holes(world) + scramble_holes(world, player) doors = lw_entrances + dw_entrances # place remaining doors - connect_doors(world, doors, door_targets) + connect_doors(world, doors, door_targets, player) elif world.shuffle == 'crossed': - skull_woods_shuffle(world) + skull_woods_shuffle(world, player) entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit) @@ -374,34 +374,34 @@ def link_entrances(world): door_targets = list(Single_Cave_Targets) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) entrances.append('Hyrule Castle Entrance (South)') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: entrances.append('Ganons Tower') caves.append('Ganons Tower Exit') #place must-exit caves - connect_mandatory_exits(world, entrances, caves, must_exits) + connect_mandatory_exits(world, entrances, caves, must_exits, player) if world.mode == 'standard': # rest of hyrule castle must be dealt with - connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')]) + connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) # place old man, has limited options # exit has to come from specific set of doors, the entrance is free to move about old_man_entrances = [door for door in old_man_entrances if door in entrances] random.shuffle(old_man_entrances) old_man_exit = old_man_entrances.pop() - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)') + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) entrances.remove(old_man_exit) # place blacksmith, has limited options @@ -409,7 +409,7 @@ def link_entrances(world): blacksmith_doors = [door for door in blacksmith_doors if door in entrances] random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) entrances.remove(blacksmith_hut) bomb_shop_doors.extend(blacksmith_doors) @@ -419,26 +419,26 @@ def link_entrances(world): bomb_shop_doors = [door for door in bomb_shop_doors if door in entrances] random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) entrances.remove(bomb_shop) # place the old man cave's entrance somewhere random.shuffle(entrances) old_man_entrance = entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)') + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) # now scramble the rest - connect_caves(world, entrances, [], caves) + connect_caves(world, entrances, [], caves, player) # scramble holes - scramble_holes(world) + scramble_holes(world, player) # place remaining doors - connect_doors(world, entrances, door_targets) + connect_doors(world, entrances, door_targets, player) elif world.shuffle == 'full_legacy': - skull_woods_shuffle(world) + skull_woods_shuffle(world, player) lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances) dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances) @@ -453,27 +453,27 @@ def link_entrances(world): if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) lw_entrances.append('Hyrule Castle Entrance (South)') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: dw_entrances.append('Ganons Tower') caves.append('Ganons Tower Exit') # we randomize which world requirements we fulfill first so we get better dungeon distribution if random.randint(0, 1) == 0: - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits) - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) + connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) else: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits) - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits) + connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) + connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) if world.mode == 'standard': # rest of hyrule castle must be in light world - connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')]) + connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) # place old man, has limited options # exit has to come from specific set of doors, the entrance is free to move about @@ -484,35 +484,35 @@ def link_entrances(world): random.shuffle(lw_entrances) old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)') - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)') + connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) + connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) # place Old Man House in Light World - connect_caves(world, lw_entrances, [], list(Old_Man_House)) #need this to avoid badness with multiple seeds + connect_caves(world, lw_entrances, [], list(Old_Man_House), player) #need this to avoid badness with multiple seeds # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves) + connect_caves(world, lw_entrances, dw_entrances, caves, player) # scramble holes - scramble_holes(world) + scramble_holes(world, player) # place blacksmith, has limited options random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) bomb_shop_doors.extend(blacksmith_doors) # place bomb shop, has limited options random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) single_doors.extend(bomb_shop_doors) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) # place remaining doors - connect_doors(world, single_doors, door_targets) + connect_doors(world, single_doors, door_targets, player) elif world.shuffle == 'madness_legacy': # here lie dragons, connections are no longer two way lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances) @@ -554,18 +554,18 @@ def link_entrances(world): if world.mode == 'standard': # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance') - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs') - connect_entrance(world, lw_doors.pop(), 'Hyrule Castle Secret Entrance Exit') + connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) + connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) + connect_entrance(world, lw_doors.pop(), 'Hyrule Castle Secret Entrance Exit', player) else: lw_hole_entrances.append('Hyrule Castle Secret Entrance Drop') hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) lw_entrances.append('Hyrule Castle Secret Entrance Stairs') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit') - connect_entrance(world, 'Pyramid Hole', 'Pyramid') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) + connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: dw_entrances.append('Ganons Tower') caves.append('Ganons Tower Exit') @@ -587,29 +587,29 @@ def link_entrances(world): sw_hole_pool = dw_hole_entrances mandatory_dark_world.append('Skull Woods First Section Exit') for target in ['Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']: - connect_entrance(world, sw_hole_pool.pop(), target) + connect_entrance(world, sw_hole_pool.pop(), target, player) # sanctuary has to be in light world - connect_entrance(world, lw_hole_entrances.pop(), 'Sewer Drop') + connect_entrance(world, lw_hole_entrances.pop(), 'Sewer Drop', player) mandatory_light_world.append('Sanctuary Exit') # fill up remaining holes for hole in dw_hole_entrances: exits, target = hole_targets.pop() mandatory_dark_world.append(exits) - connect_entrance(world, hole, target) + connect_entrance(world, hole, target, player) for hole in lw_hole_entrances: exits, target = hole_targets.pop() mandatory_light_world.append(exits) - connect_entrance(world, hole, target) + connect_entrance(world, hole, target, player) # hyrule castle handling if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') + connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) random.shuffle(lw_entrances) - connect_exit(world, 'Hyrule Castle Exit (South)', lw_entrances.pop()) + connect_exit(world, 'Hyrule Castle Exit (South)', lw_entrances.pop(), player) mandatory_light_world.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) else: lw_doors.append('Hyrule Castle Entrance (South)') @@ -648,8 +648,8 @@ def link_entrances(world): exit = cave[-1] cave = cave[:-1] - connect_exit(world, exit, entrance) - connect_entrance(world, worldoors.pop(), exit) + connect_exit(world, exit, entrance, player) + connect_entrance(world, worldoors.pop(), exit, player) # rest of cave now is forced to be in this world worldspecific.append(cave) @@ -672,8 +672,8 @@ def link_entrances(world): old_man_exit = old_man_entrances.pop() lw_entrances.remove(old_man_exit) - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit) - connect_entrance(world, lw_doors.pop(), 'Old Man Cave Exit (East)') + connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) + connect_entrance(world, lw_doors.pop(), 'Old Man Cave Exit (East)', player) mandatory_light_world.append('Old Man Cave Exit (West)') # we connect up the mandatory associations we have found @@ -682,18 +682,18 @@ def link_entrances(world): mandatory = (mandatory,) for exit in mandatory: # point out somewhere - connect_exit(world, exit, lw_entrances.pop()) + connect_exit(world, exit, lw_entrances.pop(), player) # point in from somewhere - connect_entrance(world, lw_doors.pop(), exit) + connect_entrance(world, lw_doors.pop(), exit, player) for mandatory in mandatory_dark_world: if not isinstance(mandatory, tuple): mandatory = (mandatory,) for exit in mandatory: # point out somewhere - connect_exit(world, exit, dw_entrances.pop()) + connect_exit(world, exit, dw_entrances.pop(), player) # point in from somewhere - connect_entrance(world, dw_doors.pop(), exit) + connect_entrance(world, dw_doors.pop(), exit, player) # handle remaining caves while caves: @@ -727,8 +727,8 @@ def link_entrances(world): target_entrances = dw_entrances for exit in cave: - connect_exit(world, exit, target_entrances.pop()) - connect_entrance(world, target_doors.pop(), exit) + connect_exit(world, exit, target_entrances.pop(), player) + connect_entrance(world, target_doors.pop(), exit, player) # handle simple doors @@ -740,20 +740,20 @@ def link_entrances(world): # place blacksmith, has limited options random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) bomb_shop_doors.extend(blacksmith_doors) # place dam and pyramid fairy, have limited options random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) single_doors.extend(bomb_shop_doors) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) # place remaining doors - connect_doors(world, single_doors, door_targets) + connect_doors(world, single_doors, door_targets, player) elif world.shuffle == 'insanity': # beware ye who enter here @@ -789,13 +789,13 @@ def link_entrances(world): 'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)'] # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) if world.mode == 'standard': # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance') - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs') - connect_entrance(world, doors.pop(), 'Hyrule Castle Secret Entrance Exit') + connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) + connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) + connect_entrance(world, doors.pop(), 'Hyrule Castle Secret Entrance Exit', player) else: hole_entrances.append('Hyrule Castle Secret Entrance Drop') hole_targets.append('Hyrule Castle Secret Entrance') @@ -803,9 +803,9 @@ def link_entrances(world): caves.append('Hyrule Castle Secret Entrance Exit') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit') - connect_entrance(world, 'Pyramid Hole', 'Pyramid') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) + connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: entrances.append('Ganons Tower') caves.extend(['Ganons Tower Exit', 'Pyramid Exit']) @@ -820,13 +820,13 @@ def link_entrances(world): # fill up holes for hole in hole_entrances: - connect_entrance(world, hole, hole_targets.pop()) + connect_entrance(world, hole, hole_targets.pop(), player) # hyrule castle handling if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') - connect_exit(world, 'Hyrule Castle Exit (South)', entrances.pop()) + connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_exit(world, 'Hyrule Castle Exit (South)', entrances.pop(), player) caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) else: doors.append('Hyrule Castle Entrance (South)') @@ -854,8 +854,8 @@ def link_entrances(world): exit = cave[-1] cave = cave[:-1] - connect_exit(world, exit, entrance) - connect_entrance(world, doors.pop(), exit) + connect_exit(world, exit, entrance, player) + connect_entrance(world, doors.pop(), exit, player) # rest of cave now is forced to be in this world caves.append(cave) @@ -870,22 +870,22 @@ def link_entrances(world): old_man_exit = old_man_entrances.pop() entrances.remove(old_man_exit) - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit) - connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)') + connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) + connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player) caves.append('Old Man Cave Exit (West)') # place blacksmith, has limited options blacksmith_doors = [door for door in blacksmith_doors if door in doors] random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) doors.remove(blacksmith_hut) # place dam and pyramid fairy, have limited options bomb_shop_doors = [door for door in bomb_shop_doors if door in doors] random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) doors.remove(bomb_shop) # handle remaining caves @@ -894,11 +894,11 @@ def link_entrances(world): cave = (cave,) for exit in cave: - connect_exit(world, exit, entrances.pop()) - connect_entrance(world, doors.pop(), exit) + connect_exit(world, exit, entrances.pop(), player) + connect_entrance(world, doors.pop(), exit, player) # place remaining doors - connect_doors(world, doors, door_targets) + connect_doors(world, doors, door_targets, player) elif world.shuffle == 'insanity_legacy': world.fix_fake_world = False # beware ye who enter here @@ -926,9 +926,9 @@ def link_entrances(world): if world.mode == 'standard': # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance') - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs') - connect_entrance(world, doors.pop(), 'Hyrule Castle Secret Entrance Exit') + connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) + connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) + connect_entrance(world, doors.pop(), 'Hyrule Castle Secret Entrance Exit', player) else: hole_entrances.append('Hyrule Castle Secret Entrance Drop') hole_targets.append('Hyrule Castle Secret Entrance') @@ -936,9 +936,9 @@ def link_entrances(world): caves.append('Hyrule Castle Secret Entrance Exit') if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit') - connect_entrance(world, 'Pyramid Hole', 'Pyramid') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) + connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: entrances.append('Ganons Tower') caves.extend(['Ganons Tower Exit', 'Pyramid Exit']) @@ -953,13 +953,13 @@ def link_entrances(world): # fill up holes for hole in hole_entrances: - connect_entrance(world, hole, hole_targets.pop()) + connect_entrance(world, hole, hole_targets.pop(), player) # hyrule castle handling if world.mode == 'standard': # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') - connect_exit(world, 'Hyrule Castle Exit (South)', entrances.pop()) + connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_exit(world, 'Hyrule Castle Exit (South)', entrances.pop(), player) caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) else: doors.append('Hyrule Castle Entrance (South)') @@ -987,8 +987,8 @@ def link_entrances(world): exit = cave[-1] cave = cave[:-1] - connect_exit(world, exit, entrance) - connect_entrance(world, doors.pop(), exit) + connect_exit(world, exit, entrance, player) + connect_entrance(world, doors.pop(), exit, player) # rest of cave now is forced to be in this world caves.append(cave) @@ -1003,8 +1003,8 @@ def link_entrances(world): old_man_exit = old_man_entrances.pop() entrances.remove(old_man_exit) - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit) - connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)') + connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) + connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player) caves.append('Old Man Cave Exit (West)') # handle remaining caves @@ -1013,8 +1013,8 @@ def link_entrances(world): cave = (cave,) for exit in cave: - connect_exit(world, exit, entrances.pop()) - connect_entrance(world, doors.pop(), exit) + connect_exit(world, exit, entrances.pop(), player) + connect_entrance(world, doors.pop(), exit, player) # handle simple doors @@ -1026,52 +1026,52 @@ def link_entrances(world): # place blacksmith, has limited options random.shuffle(blacksmith_doors) blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut') + connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) bomb_shop_doors.extend(blacksmith_doors) # place dam and pyramid fairy, have limited options random.shuffle(bomb_shop_doors) bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop') + connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) single_doors.extend(bomb_shop_doors) # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern']) + connect_doors(world, ['Tavern North'], ['Tavern'], player) # place remaining doors - connect_doors(world, single_doors, door_targets) + connect_doors(world, single_doors, door_targets, player) else: raise NotImplementedError('Shuffling not supported yet') # check for swamp palace fix - if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)': - world.swamp_patch_required = True + if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': + world.swamp_patch_required[player] = True # check for potion shop location - if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop': - world.powder_patch_required = True + if world.get_entrance('Potion Shop', player).connected_region.name != 'Potion Shop': + world.powder_patch_required[player] = True # check for ganon location - if world.get_entrance('Pyramid Hole').connected_region.name != 'Pyramid': - world.ganon_at_pyramid = False + if world.get_entrance('Pyramid Hole', player).connected_region.name != 'Pyramid': + world.ganon_at_pyramid[player] = False # check for Ganon's Tower location - if world.get_entrance('Ganons Tower').connected_region.name != 'Ganons Tower (Entrance)': - world.ganonstower_vanilla = False + if world.get_entrance('Ganons Tower', player).connected_region.name != 'Ganons Tower (Entrance)': + world.ganonstower_vanilla[player] = False -def connect_simple(world, exitname, regionname): - world.get_entrance(exitname).connect(world.get_region(regionname)) +def connect_simple(world, exitname, regionname, player): + world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) -def connect_entrance(world, entrancename, exitname): - entrance = world.get_entrance(entrancename) +def connect_entrance(world, entrancename, exitname, player): + entrance = world.get_entrance(entrancename, player) # check if we got an entrance or a region to connect to try: - region = world.get_region(exitname) + region = world.get_region(exitname, player) exit = None except RuntimeError: - exit = world.get_entrance(exitname) + exit = world.get_entrance(exitname, player) region = exit.parent_region # if this was already connected somewhere, remove the backreference @@ -1082,24 +1082,24 @@ def connect_entrance(world, entrancename, exitname): addresses = door_addresses[entrance.name][0] entrance.connect(region, addresses, target) - world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance') + world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player) -def connect_exit(world, exitname, entrancename): - entrance = world.get_entrance(entrancename) - exit = world.get_entrance(exitname) +def connect_exit(world, exitname, entrancename, player): + entrance = world.get_entrance(entrancename, player) + exit = world.get_entrance(exitname, player) # if this was already connected somewhere, remove the backreference if exit.connected_region is not None: exit.connected_region.entrances.remove(exit) exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) - world.spoiler.set_entrance(entrance.name, exit.name, 'exit') + world.spoiler.set_entrance(entrance.name, exit.name, 'exit', player) -def connect_two_way(world, entrancename, exitname): - entrance = world.get_entrance(entrancename) - exit = world.get_entrance(exitname) +def connect_two_way(world, entrancename, exitname, player): + entrance = world.get_entrance(entrancename, player) + exit = world.get_entrance(exitname, player) # if these were already connected somewhere, remove the backreference if entrance.connected_region is not None: @@ -1109,10 +1109,10 @@ def connect_two_way(world, entrancename, exitname): entrance.connect(exit.parent_region, door_addresses[entrance.name][0], exit_ids[exit.name][0]) exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) - world.spoiler.set_entrance(entrance.name, exit.name, 'both') + world.spoiler.set_entrance(entrance.name, exit.name, 'both', player) -def scramble_holes(world): +def scramble_holes(world, player): hole_entrances = [('Kakariko Well Cave', 'Kakariko Well Drop'), ('Bat Cave Cave', 'Bat Cave Drop'), ('North Fairy Cave', 'North Fairy Cave Drop'), @@ -1127,15 +1127,15 @@ def scramble_holes(world): ('Lumberjack Tree Exit', 'Lumberjack Tree (top)')] if not world.shuffle_ganon: - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit') - connect_entrance(world, 'Pyramid Hole', 'Pyramid') + connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: hole_targets.append(('Pyramid Exit', 'Pyramid')) if world.mode == 'standard': # cannot move uncle cave - connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit') - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance') + connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) + connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) else: hole_entrances.append(('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Drop')) hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) @@ -1146,30 +1146,30 @@ def scramble_holes(world): if world.shuffle_ganon: random.shuffle(hole_targets) exit, target = hole_targets.pop() - connect_two_way(world, 'Pyramid Entrance', exit) - connect_entrance(world, 'Pyramid Hole', target) + connect_two_way(world, 'Pyramid Entrance', exit, player) + connect_entrance(world, 'Pyramid Hole', target, player) if world.shuffle != 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) random.shuffle(hole_targets) for entrance, drop in hole_entrances: exit, target = hole_targets.pop() - connect_two_way(world, entrance, exit) - connect_entrance(world, drop, target) + connect_two_way(world, entrance, exit, player) + connect_entrance(world, drop, target, player) -def connect_random(world, exitlist, targetlist, two_way=False): +def connect_random(world, exitlist, targetlist, player, two_way=False): targetlist = list(targetlist) random.shuffle(targetlist) for exit, target in zip(exitlist, targetlist): if two_way: - connect_two_way(world, exit, target) + connect_two_way(world, exit, target, player) else: - connect_entrance(world, exit, target) + connect_entrance(world, exit, target, player) -def connect_mandatory_exits(world, entrances, caves, must_be_exits): +def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): """This works inplace""" random.shuffle(entrances) random.shuffle(caves) @@ -1187,7 +1187,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits): raise RuntimeError('No more caves left. Should not happen!') # all caves are sorted so that the last exit is always reachable - connect_two_way(world, exit, cave[-1]) + connect_two_way(world, exit, cave[-1], player) if len(cave) == 2: entrance = entrances.pop() # ToDo Better solution, this is a hot fix. Do not connect both sides of trock ledge only to each other @@ -1195,10 +1195,10 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits): new_entrance = entrances.pop() entrances.append(entrance) entrance = new_entrance - connect_two_way(world, entrance, cave[0]) + connect_two_way(world, entrance, cave[0], player) elif cave[-1] == 'Spectacle Rock Cave Exit': #Spectacle rock only has one exit for exit in cave[:-1]: - connect_two_way(world,entrances.pop(),exit) + connect_two_way(world,entrances.pop(),exit, player) else:#save for later so we can connect to multiple exits caves.append(cave[0:-1]) random.shuffle(caves) @@ -1207,11 +1207,11 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits): for cave in used_caves: if cave in caves: #check if we placed multiple entrances from this 3 or 4 exit for exit in cave: - connect_two_way(world, entrances.pop(), exit) + connect_two_way(world, entrances.pop(), exit, player) caves.remove(cave) -def connect_caves(world, lw_entrances, dw_entrances, caves): +def connect_caves(world, lw_entrances, dw_entrances, caves, player): """This works inplace""" random.shuffle(lw_entrances) random.shuffle(dw_entrances) @@ -1236,40 +1236,40 @@ def connect_caves(world, lw_entrances, dw_entrances, caves): target = lw_entrances if target is dw_entrances else dw_entrances for exit in cave: - connect_two_way(world, target.pop(), exit) + connect_two_way(world, target.pop(), exit, player) -def connect_doors(world, doors, targets): +def connect_doors(world, doors, targets, player): """This works inplace""" random.shuffle(doors) random.shuffle(targets) while doors: door = doors.pop() target = targets.pop() - connect_entrance(world, door, target) + connect_entrance(world, door, target, player) -def skull_woods_shuffle(world): +def skull_woods_shuffle(world, player): connect_random(world, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'], - ['Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)', 'Skull Woods Second Section (Drop)']) + ['Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)', 'Skull Woods Second Section (Drop)'], player) connect_random(world, ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'], - ['Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'], True) + ['Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'], player, True) -def simple_shuffle_dungeons(world): - skull_woods_shuffle(world) +def simple_shuffle_dungeons(world, player): + skull_woods_shuffle(world, player) dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace'] dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit'] if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit') + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: dungeon_entrances.append('Ganons Tower') dungeon_exits.append('Ganons Tower Exit') # shuffle up single entrance dungeons - connect_random(world, dungeon_entrances, dungeon_exits, True) + connect_random(world, dungeon_entrances, dungeon_exits, player, True) # mix up 4 door dungeons multi_dungeons = ['Desert', 'Turtle Rock'] @@ -1287,52 +1287,52 @@ def simple_shuffle_dungeons(world): # ToDo improve this? if hc_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)') - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)') - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)') - connect_two_way(world, 'Agahnims Tower', 'Agahnims Tower Exit') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Agahnims Tower', 'Agahnims Tower Exit', player) elif hc_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Hyrule Castle Exit (South)') - connect_two_way(world, 'Desert Palace Entrance (East)', 'Hyrule Castle Exit (East)') - connect_two_way(world, 'Desert Palace Entrance (West)', 'Hyrule Castle Exit (West)') - connect_two_way(world, 'Desert Palace Entrance (North)', 'Agahnims Tower Exit') + connect_two_way(world, 'Desert Palace Entrance (South)', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Agahnims Tower Exit', player) elif hc_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Hyrule Castle Exit (South)') - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Hyrule Castle Exit (East)') - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Hyrule Castle Exit (West)') - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Agahnims Tower Exit') + connect_two_way(world, 'Turtle Rock', 'Hyrule Castle Exit (South)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Hyrule Castle Exit (East)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Hyrule Castle Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Agahnims Tower Exit', player) if dp_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Desert Palace Exit (South)') - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Desert Palace Exit (East)') - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Desert Palace Exit (West)') - connect_two_way(world, 'Agahnims Tower', 'Desert Palace Exit (North)') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Agahnims Tower', 'Desert Palace Exit (North)', player) elif dp_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Desert Palace Exit (South)') - connect_two_way(world, 'Desert Palace Entrance (East)', 'Desert Palace Exit (East)') - connect_two_way(world, 'Desert Palace Entrance (West)', 'Desert Palace Exit (West)') - connect_two_way(world, 'Desert Palace Entrance (North)', 'Desert Palace Exit (North)') + connect_two_way(world, 'Desert Palace Entrance (South)', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Desert Palace Exit (North)', player) elif dp_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Desert Palace Exit (South)') - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Desert Palace Exit (East)') - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Desert Palace Exit (West)') - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Desert Palace Exit (North)') + connect_two_way(world, 'Turtle Rock', 'Desert Palace Exit (South)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Desert Palace Exit (East)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Desert Palace Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Desert Palace Exit (North)', player) if tr_target == 'Hyrule Castle': - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Turtle Rock Exit (Front)') - connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Turtle Rock Ledge Exit (East)') - connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Turtle Rock Ledge Exit (West)') - connect_two_way(world, 'Agahnims Tower', 'Turtle Rock Isolated Ledge Exit') + connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Turtle Rock Ledge Exit (East)', player) + connect_two_way(world, 'Hyrule Castle Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Agahnims Tower', 'Turtle Rock Isolated Ledge Exit', player) elif tr_target == 'Desert': - connect_two_way(world, 'Desert Palace Entrance (South)', 'Turtle Rock Exit (Front)') - connect_two_way(world, 'Desert Palace Entrance (North)', 'Turtle Rock Ledge Exit (East)') - connect_two_way(world, 'Desert Palace Entrance (West)', 'Turtle Rock Ledge Exit (West)') - connect_two_way(world, 'Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Exit') + connect_two_way(world, 'Desert Palace Entrance (South)', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Desert Palace Entrance (North)', 'Turtle Rock Ledge Exit (East)', player) + connect_two_way(world, 'Desert Palace Entrance (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Exit', player) elif tr_target == 'Turtle Rock': - connect_two_way(world, 'Turtle Rock', 'Turtle Rock Exit (Front)') - connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit') - connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)') - connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)') + connect_two_way(world, 'Turtle Rock', 'Turtle Rock Exit (Front)', player) + connect_two_way(world, 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit', player) + connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) + connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) def unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits): def shuffle_lists_in_list(ls): diff --git a/Fill.py b/Fill.py index c1becd44..86e0a5a7 100644 --- a/Fill.py +++ b/Fill.py @@ -1,6 +1,9 @@ import random import logging +from BaseClasses import CollectionState + + class FillError(RuntimeError): pass @@ -167,31 +170,41 @@ def fill_restrictive(world, base_state, locations, itempool): return new_state while itempool and locations: - item_to_place = itempool.pop() + items_to_place = [] + nextpool = [] + placing_players = set() + for item in reversed(itempool): + if item.player not in placing_players: + placing_players.add(item.player) + items_to_place.append(item) + else: + nextpool.insert(0, item) + itempool = nextpool + maximum_exploration_state = sweep_from_pool() perform_access_check = True if world.check_beatable_only: perform_access_check = not world.has_beaten_game(maximum_exploration_state) + for item_to_place in items_to_place: + spot_to_fill = None + for location in locations: + if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check): + spot_to_fill = location + break - spot_to_fill = None - for location in locations: - if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check): - spot_to_fill = location - break + if spot_to_fill is None: + # we filled all reachable spots. Maybe the game can be beaten anyway? + if world.can_beat_game(): + if not world.check_beatable_only: + logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place) + continue + raise FillError('No more spots to place %s' % item_to_place) - if spot_to_fill is None: - # we filled all reachable spots. Maybe the game can be beaten anyway? - if world.can_beat_game(): - if not world.check_beatable_only: - logging.getLogger('').warning('Not all items placed. Game beatable anyway.') - break - raise FillError('No more spots to place %s' % item_to_place) - - world.push_item(spot_to_fill, item_to_place, False) - locations.remove(spot_to_fill) - spot_to_fill.event = True + world.push_item(spot_to_fill, item_to_place, False) + locations.remove(spot_to_fill) + spot_to_fill.event = True def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=None): @@ -207,20 +220,25 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No restitempool = [item for item in world.itempool if not item.advancement and not item.priority] # fill in gtower locations with trash first - if world.ganonstower_vanilla: - gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name] - random.shuffle(gtower_locations) - trashcnt = 0 - while gtower_locations and restitempool and trashcnt < gftower_trash_count: - spot_to_fill = gtower_locations.pop() - item_to_place = restitempool.pop() - world.push_item(spot_to_fill, item_to_place, False) - fill_locations.remove(spot_to_fill) - trashcnt += 1 + for player in range(1, world.players + 1): + if world.ganonstower_vanilla[player]: + gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player] + random.shuffle(gtower_locations) + trashcnt = 0 + while gtower_locations and restitempool and trashcnt < gftower_trash_count: + spot_to_fill = gtower_locations.pop() + item_to_place = restitempool.pop() + world.push_item(spot_to_fill, item_to_place, False) + fill_locations.remove(spot_to_fill) + trashcnt += 1 random.shuffle(fill_locations) fill_locations.reverse() + # Make sure the escape small key is placed first in standard keysanity to prevent running out of spots + if world.keysanity and world.mode == 'standard': + progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0) + fill_restrictive(world, world.state, fill_locations, progitempool) random.shuffle(fill_locations) @@ -297,3 +315,93 @@ def flood_items(world): world.push_item(location, item_to_place, True) itempool.remove(item_to_place) break + +def balance_multiworld_progression(world): + state = CollectionState(world) + checked_locations = [] + unchecked_locations = world.get_locations().copy() + random.shuffle(unchecked_locations) + + reachable_locations_count = {} + for player in range(1, world.players + 1): + reachable_locations_count[player] = 0 + + def get_sphere_locations(sphere_state, locations): + if not world.keysanity: + sphere_state.sweep_for_events(key_only=True, locations=locations) + return [loc for loc in locations if sphere_state.can_reach(loc)] + + while True: + sphere_locations = get_sphere_locations(state, unchecked_locations) + for location in sphere_locations: + unchecked_locations.remove(location) + reachable_locations_count[location.player] += 1 + + if checked_locations: + average_reachable_locations = sum(reachable_locations_count.values()) / world.players + threshold = ((average_reachable_locations + max(reachable_locations_count.values())) / 2) * 0.8 #todo: probably needs some tweaking + + balancing_players = [player for player, reachables in reachable_locations_count.items() if reachables < threshold] + if balancing_players: + balancing_state = state.copy() + balancing_unchecked_locations = unchecked_locations.copy() + balancing_reachables = reachable_locations_count.copy() + balancing_sphere = sphere_locations.copy() + candidate_items = [] + while True: + for location in balancing_sphere: + if location.event: + balancing_state.collect(location.item, True, location) + if location.item.player in balancing_players: + candidate_items.append(location) + balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations) + for location in balancing_sphere: + balancing_unchecked_locations.remove(location) + balancing_reachables[location.player] += 1 + if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]): + break + + unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations] + items_to_replace = [] + for player in balancing_players: + locations_to_test = [l for l in unlocked_locations if l.player == player] + items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player] + while items_to_test: + testing = items_to_test.pop() + reducing_state = state.copy() + for location in [*[l for l in items_to_replace if l.item.player == player], *items_to_test]: + reducing_state.collect(location.item, True, location) + + reducing_state.sweep_for_events(locations=locations_to_test) + + if world.has_beaten_game(balancing_state): + if not world.has_beaten_game(reducing_state): + items_to_replace.append(testing) + else: + reduced_sphere = get_sphere_locations(reducing_state, locations_to_test) + if reachable_locations_count[player] + len(reduced_sphere) < threshold: + items_to_replace.append(testing) + + replaced_items = False + locations_for_replacing = [l for l in checked_locations if not l.event] + while locations_for_replacing and items_to_replace: + new_location = locations_for_replacing.pop() + old_location = items_to_replace.pop() + new_location.item, old_location.item = old_location.item, new_location.item + new_location.event = True + old_location.event = False + state.collect(new_location.item, True, new_location) + replaced_items = True + if replaced_items: + for location in get_sphere_locations(state, [l for l in unlocked_locations if l.player in balancing_players]): + unchecked_locations.remove(location) + reachable_locations_count[location.player] += 1 + sphere_locations.append(location) + + for location in sphere_locations: + if location.event: + state.collect(location.item, True, location) + checked_locations.extend(sphere_locations) + + if world.has_beaten_game(state): + break diff --git a/Gui.py b/Gui.py index 33d83c7f..50f60756 100755 --- a/Gui.py +++ b/Gui.py @@ -244,15 +244,20 @@ def guiMain(args=None): bottomFrame = Frame(randomizerWindow) + worldLabel = Label(bottomFrame, text='Worlds') + worldVar = StringVar() + worldSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=worldVar) + seedLabel = Label(bottomFrame, text='Seed #') seedVar = StringVar() - seedEntry = Entry(bottomFrame, textvariable=seedVar) + seedEntry = Entry(bottomFrame, width=15, textvariable=seedVar) countLabel = Label(bottomFrame, text='Count') countVar = StringVar() - countSpinbox = Spinbox(bottomFrame, from_=1, to=100, textvariable=countVar) + countSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=countVar) def generateRom(): guiargs = Namespace + guiargs.multi = int(worldVar.get()) guiargs.seed = int(seedVar.get()) if seedVar.get() else None guiargs.count = int(countVar.get()) if countVar.get() != '1' else None guiargs.mode = modeVar.get() @@ -290,6 +295,8 @@ def guiMain(args=None): guiargs.rom = romVar.get() guiargs.jsonout = None guiargs.sprite = sprite + guiargs.skip_playthrough = False + guiargs.outputpath = None try: if guiargs.count is not None: seed = guiargs.seed @@ -305,7 +312,9 @@ def guiMain(args=None): generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom) - seedLabel.pack(side=LEFT) + worldLabel.pack(side=LEFT) + worldSpinbox.pack(side=LEFT) + seedLabel.pack(side=LEFT, padx=(5, 0)) seedEntry.pack(side=LEFT) countLabel.pack(side=LEFT, padx=(5, 0)) countSpinbox.pack(side=LEFT) @@ -384,6 +393,7 @@ def guiMain(args=None): fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed') fastMenuLabel2.pack(side=LEFT) + heartbeepFrame2.pack(expand=True, anchor=E) heartcolorFrame2.pack(expand=True, anchor=E) fastMenuFrame2.pack(expand=True, anchor=E) diff --git a/ItemList.py b/ItemList.py index 8c7f3d71..97ab4198 100644 --- a/ItemList.py +++ b/ItemList.py @@ -207,7 +207,7 @@ difficulties = { ), } -def generate_itempool(world): +def generate_itempool(world, player): if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): raise NotImplementedError('Not supported yet') @@ -215,20 +215,20 @@ def generate_itempool(world): if world.timer in ['ohko', 'timed-ohko']: world.can_take_damage = False - world.push_item('Ganon', ItemFactory('Triforce'), False) - world.get_location('Ganon').event = True - world.push_item('Agahnim 1', ItemFactory('Beat Agahnim 1'), False) - world.get_location('Agahnim 1').event = True - world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False) - world.get_location('Agahnim 2').event = True - world.push_item('Dark Blacksmith Ruins', ItemFactory('Pick Up Purple Chest'), False) - world.get_location('Dark Blacksmith Ruins').event = True - world.push_item('Frog', ItemFactory('Get Frog'), False) - world.get_location('Frog').event = True - world.push_item('Missing Smith', ItemFactory('Return Smith'), False) - world.get_location('Missing Smith').event = True - world.push_item('Floodgate', ItemFactory('Open Floodgate'), False) - world.get_location('Floodgate').event = True + world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False) + world.get_location('Ganon', player).event = True + world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) + world.get_location('Agahnim 1', player).event = True + world.push_item(world.get_location('Agahnim 2', player), ItemFactory('Beat Agahnim 2', player), False) + world.get_location('Agahnim 2', player).event = True + world.push_item(world.get_location('Dark Blacksmith Ruins', player), ItemFactory('Pick Up Purple Chest', player), False) + world.get_location('Dark Blacksmith Ruins', player).event = True + world.push_item(world.get_location('Frog', player), ItemFactory('Get Frog', player), False) + world.get_location('Frog', player).event = True + world.push_item(world.get_location('Missing Smith', player), ItemFactory('Return Smith', player), False) + world.get_location('Missing Smith', player).event = True + world.push_item(world.get_location('Floodgate', player), ItemFactory('Open Floodgate', player), False) + world.get_location('Floodgate', player).event = True # set up item pool if world.custom: @@ -236,10 +236,10 @@ def generate_itempool(world): world.rupoor_cost = min(world.customitemarray[67], 9999) else: (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.retro) - world.itempool = ItemFactory(pool) + world.itempool += ItemFactory(pool, player) for (location, item) in placed_items: - world.push_item(location, ItemFactory(item), False) - world.get_location(location).event = True + world.push_item(world.get_location(location, player), ItemFactory(item, player), False) + world.get_location(location, player).event = True world.lamps_needed_for_dark_rooms = lamps_needed_for_dark_rooms if clock_mode is not None: world.clock_mode = clock_mode @@ -249,30 +249,30 @@ def generate_itempool(world): world.treasure_hunt_icon = treasure_hunt_icon if world.keysanity: - world.itempool.extend(get_dungeon_item_pool(world)) + world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player]) # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # rather than making all hearts/heart pieces progression items (which slows down generation considerably) # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) if world.difficulty in ['easy', 'normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0): - [item for item in world.itempool if item.name == 'Boss Heart Container'][0].advancement = True + [item for item in world.itempool if item.name == 'Boss Heart Container' and item.player == player][0].advancement = True elif world.difficulty in ['expert'] and not (world.custom and world.customitemarray[29] < 4): - adv_heart_pieces = [item for item in world.itempool if item.name == 'Piece of Heart'][0:4] + adv_heart_pieces = [item for item in world.itempool if item.name == 'Piece of Heart' and item.player == player][0:4] for hp in adv_heart_pieces: hp.advancement = True # shuffle medallions mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] - world.required_medallions = (mm_medallion, tr_medallion) + world.required_medallions[player] = (mm_medallion, tr_medallion) - place_bosses(world) - set_up_shops(world) + place_bosses(world, player) + set_up_shops(world, player) if world.retro: - set_up_take_anys(world) + set_up_take_anys(world, player) - create_dynamic_shop_locations(world) + create_dynamic_shop_locations(world, player) take_any_locations = [ 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', @@ -284,39 +284,39 @@ take_any_locations = [ 'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint'] -def set_up_take_anys(world): +def set_up_take_anys(world, player): regions = random.sample(take_any_locations, 5) - old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave') + old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player) world.regions.append(old_man_take_any) world.dynamic_regions.append(old_man_take_any) reg = regions.pop() - entrance = world.get_region(reg).entrances[0] - connect_entrance(world, entrance, old_man_take_any) + entrance = world.get_region(reg, player).entrances[0] + connect_entrance(world, entrance, old_man_take_any, player) entrance.target = 0x58 old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True) world.shops.append(old_man_take_any.shop) old_man_take_any.shop.active = True - swords = [item for item in world.itempool if item.type == 'Sword'] + swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player] if swords: sword = random.choice(swords) world.itempool.remove(sword) - world.itempool.append(ItemFactory('Rupees (20)')) + world.itempool.append(ItemFactory('Rupees (20)', player)) old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True) else: old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0) for num in range(4): - take_any = Region("Take-Any #{}".format(num+1), RegionType.Cave, 'a cave of choice') + take_any = Region("Take-Any #{}".format(num+1), RegionType.Cave, 'a cave of choice', player) world.regions.append(take_any) world.dynamic_regions.append(take_any) target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) reg = regions.pop() - entrance = world.get_region(reg).entrances[0] - connect_entrance(world, entrance, take_any) + entrance = world.get_region(reg, player).entrances[0] + connect_entrance(world, entrance, take_any, player) entrance.target = target take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True) world.shops.append(take_any.shop) @@ -326,50 +326,52 @@ def set_up_take_anys(world): world.intialize_regions() -def create_dynamic_shop_locations(world): +def create_dynamic_shop_locations(world, player): for shop in world.shops: - for i, item in enumerate(shop.inventory): - if item is None: - continue - if item['create_location']: - loc = Location("{} Item {}".format(shop.region.name, i+1), parent=shop.region) - shop.region.locations.append(loc) - world.dynamic_locations.append(loc) + if shop.region.player == player: + for i, item in enumerate(shop.inventory): + if item is None: + continue + if item['create_location']: + loc = Location(player, "{} Item {}".format(shop.region.name, i+1), parent=shop.region) + shop.region.locations.append(loc) + world.dynamic_locations.append(loc) - world.clear_location_cache() + world.clear_location_cache() - world.push_item(loc, ItemFactory(item['item']), False) - loc.event = True + world.push_item(loc, ItemFactory(item['item'], player), False) + loc.event = True def fill_prizes(world, attempts=15): - crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']) - crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'), - world.get_location('Thieves\' Town - Prize'), world.get_location('Skull Woods - Prize'), world.get_location('Swamp Palace - Prize'), world.get_location('Ice Palace - Prize'), - world.get_location('Misery Mire - Prize')] - placed_prizes = [loc.item.name for loc in crystal_locations if loc.item is not None] - unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes] - empty_crystal_locations = [loc for loc in crystal_locations if loc.item is None] + all_state = world.get_all_state(keys=True) + for player in range(1, world.players + 1): + crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player) + crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player), + world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player), + world.get_location('Misery Mire - Prize', player)] + placed_prizes = [loc.item.name for loc in crystal_locations if loc.item is not None] + unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes] + empty_crystal_locations = [loc for loc in crystal_locations if loc.item is None] - while attempts: - attempts -= 1 - try: - prizepool = list(unplaced_prizes) - prize_locs = list(empty_crystal_locations) - random.shuffle(prizepool) - random.shuffle(prize_locs) - fill_restrictive(world, world.get_all_state(keys=True), prize_locs, prizepool) - except FillError: - logging.getLogger('').info("Failed to place dungeon prizes. Will retry %s more times", attempts) - for location in empty_crystal_locations: - location.item = None - continue - break - else: - raise FillError('Unable to place dungeon prizes') + for attempt in range(attempts): + try: + prizepool = list(unplaced_prizes) + prize_locs = list(empty_crystal_locations) + random.shuffle(prizepool) + random.shuffle(prize_locs) + fill_restrictive(world, all_state, prize_locs, prizepool) + except FillError as e: + logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times" % (e, attempts)) + for location in empty_crystal_locations: + location.item = None + continue + break + else: + raise FillError('Unable to place dungeon prizes') -def set_up_shops(world): +def set_up_shops(world, player): # Changes to basic Shops # TODO: move hard+ mode changes for sheilds here, utilizing the new shops @@ -377,13 +379,13 @@ def set_up_shops(world): shop.active = True if world.retro: - rss = world.get_region('Red Shield Shop').shop + rss = world.get_region('Red Shield Shop', player).shop rss.active = True rss.add_inventory(2, 'Single Arrow', 80) # Randomized changes to Shops if world.retro: - for shop in random.sample([s for s in world.shops if s.replaceable], 5): + for shop in random.sample([s for s in world.shops if s.replaceable and s.region.player == player], 5): shop.active = True shop.add_inventory(0, 'Single Arrow', 80) shop.add_inventory(1, 'Small Key (Universal)', 100) diff --git a/Items.py b/Items.py index 2e0d8506..1bc94cc9 100644 --- a/Items.py +++ b/Items.py @@ -3,7 +3,7 @@ import logging from BaseClasses import Item -def ItemFactory(items): +def ItemFactory(items, player): ret = [] singleton = False if isinstance(items, str): @@ -12,7 +12,7 @@ def ItemFactory(items): for item in items: if item in item_table: advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text = item_table[item] - ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text)) + ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text, player)) else: logging.getLogger('').warning('Unknown Item: %s', item) return None diff --git a/Main.py b/Main.py index d8de72ba..413c1a0e 100644 --- a/Main.py +++ b/Main.py @@ -12,7 +12,7 @@ from EntranceShuffle import link_entrances from Rom import patch_rom, Sprite, LocalRom, JsonRom from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive -from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items +from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression from ItemList import generate_itempool, difficulties, fill_prizes from Utils import output_path @@ -40,7 +40,7 @@ def main(args, seed=None): start = time.clock() # initialize the world - world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) + world = World(args.multi, args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) logger = logging.getLogger('') if seed is None: random.seed(None) @@ -49,26 +49,32 @@ def main(args, seed=None): world.seed = int(seed) random.seed(world.seed) + world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} + logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed) world.difficulty_requirements = difficulties[world.difficulty] - create_regions(world) - - create_dungeons(world) + for player in range(1, world.players + 1): + create_regions(world, player) + create_dungeons(world, player) logger.info('Shuffling the World about.') - link_entrances(world) + for player in range(1, world.players + 1): + link_entrances(world, player) + mark_light_world_regions(world) logger.info('Generating Item Pool.') - generate_itempool(world) + for player in range(1, world.players + 1): + generate_itempool(world, player) logger.info('Calculating Access Rules.') - set_rules(world) + for player in range(1, world.players + 1): + set_rules(world, player) logger.info('Placing Dungeon Prizes.') @@ -102,9 +108,9 @@ def main(args, seed=None): elif args.algorithm == 'balanced': distribute_items_restrictive(world, gt_filler(world)) - logger.info('Calculating playthrough.') - - create_playthrough(world) + if world.players > 1: + logger.info('Balancing multiworld progression.') + balance_multiworld_progression(world) logger.info('Patching ROM.') @@ -118,20 +124,38 @@ def main(args, seed=None): outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed) + jsonout = {} if not args.suppress_rom: - if args.jsonout: - rom = JsonRom() + if world.players > 1: + raise NotImplementedError("Multiworld rom writes have not been implemented") else: - rom = LocalRom(args.rom) - patch_rom(world, rom, bytearray(logic_hash), args.heartbeep, args.heartcolor, sprite) - if args.jsonout: - print(json.dumps({'patch': rom.patches, 'spoiler': world.spoiler.to_json()})) - else: - rom.write_to_file(args.jsonout or output_path('%s.sfc' % outfilebase)) + player = 1 + + if args.jsonout: + rom = JsonRom() + else: + rom = LocalRom(args.rom) + patch_rom(world, player, rom, bytearray(logic_hash), args.heartbeep, args.heartcolor, sprite, player_names) + + if args.jsonout: + jsonout['patch'] = rom.patches + + else: + apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite, player_names) + rom.write_to_file(output_path('%s.sfc' % outfilebase)) if args.create_spoiler and not args.jsonout: world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) + if not args.skip_playthrough: + logger.info('Calculating playthrough.') + create_playthrough(world) + + if args.jsonout: + print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()})) + elif args.create_spoiler and not args.skip_playthrough: + world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) + logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.clock() - start) @@ -144,10 +168,12 @@ def gt_filler(world): def copy_world(world): # ToDo: Not good yet - ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) - ret.required_medallions = list(world.required_medallions) - ret.swamp_patch_required = world.swamp_patch_required - ret.ganon_at_pyramid = world.ganon_at_pyramid + ret = World(world.players, world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) + ret.required_medallions = world.required_medallions.copy() + ret.swamp_patch_required = world.swamp_patch_required.copy() + ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() + ret.powder_patch_required = world.powder_patch_required.copy() + ret.ganonstower_vanilla = world.ganonstower_vanilla.copy() ret.treasure_hunt_count = world.treasure_hunt_count ret.treasure_hunt_icon = world.treasure_hunt_icon ret.sewer_light_cone = world.sewer_light_cone @@ -162,53 +188,56 @@ def copy_world(world): ret.difficulty_requirements = world.difficulty_requirements ret.fix_fake_world = world.fix_fake_world ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms - create_regions(ret) - create_dungeons(ret) + + for player in range(1, world.players + 1): + create_regions(ret, player) + create_dungeons(ret, player) copy_dynamic_regions_and_locations(world, ret) # copy bosses for dungeon in world.dungeons: for level, boss in dungeon.bosses.items(): - ret.get_dungeon(dungeon.name).bosses[level] = boss + ret.get_dungeon(dungeon.name, dungeon.player).bosses[level] = boss for shop in world.shops: - copied_shop = ret.get_region(shop.region.name).shop + copied_shop = ret.get_region(shop.region.name, shop.region.player).shop copied_shop.active = shop.active copied_shop.inventory = copy.copy(shop.inventory) # connect copied world for region in world.regions: - copied_region = ret.get_region(region.name) + copied_region = ret.get_region(region.name, region.player) copied_region.is_light_world = region.is_light_world copied_region.is_dark_world = region.is_dark_world for entrance in region.entrances: - ret.get_entrance(entrance.name).connect(copied_region) + ret.get_entrance(entrance.name, entrance.player).connect(copied_region) # fill locations for location in world.get_locations(): if location.item is not None: - item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.type) - ret.get_location(location.name).item = item - item.location = ret.get_location(location.name) + item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.type, player = location.item.player) + ret.get_location(location.name, location.player).item = item + item.location = ret.get_location(location.name, location.player) if location.event: - ret.get_location(location.name).event = True + ret.get_location(location.name, location.player).event = True # copy remaining itempool. No item in itempool should have an assigned location for item in world.itempool: - ret.itempool.append(Item(item.name, item.advancement, item.priority, item.type)) + ret.itempool.append(Item(item.name, item.advancement, item.priority, item.type, player = item.player)) # copy progress items in state ret.state.prog_items = list(world.state.prog_items) ret.state.stale = True - set_rules(ret) + for player in range(1, world.players + 1): + set_rules(ret, player) return ret def copy_dynamic_regions_and_locations(world, ret): for region in world.dynamic_regions: - new_reg = Region(region.name, region.type, region.hint_text) + new_reg = Region(region.name, region.type, region.hint_text, region.player) ret.regions.append(new_reg) ret.dynamic_regions.append(new_reg) @@ -219,8 +248,8 @@ def copy_dynamic_regions_and_locations(world, ret): ret.shops.append(new_reg.shop) for location in world.dynamic_locations: - new_loc = Location(location.name, location.address, location.crystal, location.hint_text, location.parent_region) - new_reg = ret.get_region(location.parent_region.name) + new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, location.parent_region,) + new_reg = ret.get_region(location.parent_region.name, location.parent_region.player) new_reg.locations.append(new_loc) @@ -231,7 +260,8 @@ def create_playthrough(world): # in treasure hunt and pedestal goals, ganon is invincible if world.goal in ['pedestal', 'triforcehunt']: - world.get_location('Ganon').item = None + for player in range(1, world.players + 1): + world.get_location('Ganon', player).item = None # if we only check for beatable, we can do this sanity check first before writing down spheres if world.check_beatable_only and not world.can_beat_game(): @@ -264,7 +294,7 @@ def create_playthrough(world): logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations)) if not sphere: - logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates]) + logging.getLogger('').debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) if not world.check_beatable_only: raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.') else: @@ -275,11 +305,11 @@ def create_playthrough(world): to_delete = [] for location in sphere: # we remove the item at location and check if game is still beatable - logging.getLogger('').debug('Checking if %s is required to beat the game.', location.item.name) + logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player) old_item = location.item location.item = None state.remove(old_item) - ##if world.can_beat_game(state_cache[num]): + ##if world.can_beat_game(state_cache[num]): if world.can_beat_game(): to_delete.append(location) else: @@ -316,7 +346,7 @@ def create_playthrough(world): raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') # store the required locations for statistical analysis - old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere] + old_world.required_locations = [(location.name, location.player) for sphere in collection_spheres for location in sphere] def flist_to_iter(node): while node: @@ -331,9 +361,12 @@ def create_playthrough(world): pathpairs = zip_longest(pathsiter, pathsiter) return list(pathpairs) - old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere} - if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path): - old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop')) + old_world.spoiler.paths = dict() + for player in range(1, world.players + 1): + old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) + for _, path in dict(old_world.spoiler.paths).items(): + if any(exit == 'Pyramid Fairy' for (_, exit) in path): + old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) # we can finally output our playthrough old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) diff --git a/Plando.py b/Plando.py index f2f68873..0bb3dd1a 100755 --- a/Plando.py +++ b/Plando.py @@ -33,7 +33,7 @@ def main(args): start_time = time.clock() # initialize the world - world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None) + world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None, 'none', False) logger = logging.getLogger('') hasher = hashlib.md5() @@ -48,14 +48,14 @@ def main(args): world.difficulty_requirements = difficulties[world.difficulty] - create_regions(world) - create_dungeons(world) + create_regions(world, 1) + create_dungeons(world, 1) - link_entrances(world) + link_entrances(world, 1) logger.info('Calculating Access Rules.') - set_rules(world) + set_rules(world, 1) logger.info('Fill the world.') @@ -63,8 +63,8 @@ def main(args): fill_world(world, args.plando, text_patches) - if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)': - world.swamp_patch_required = True + if world.get_entrance('Dam', 1).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', 1).connected_region.name != 'Swamp Palace (Entrance)': + world.swamp_patch_required[1] = True logger.info('Calculating playthrough.') @@ -84,7 +84,7 @@ def main(args): sprite = None rom = LocalRom(args.rom) - patch_rom(world, rom, logic_hash, args.heartbeep, args.heartcolor, sprite) + patch_rom(world, 1, rom, logic_hash, args.heartbeep, args.heartcolor, sprite) for textname, texttype, text in text_patches: if texttype == 'text': @@ -174,33 +174,33 @@ def fill_world(world, plando, text_patches): continue locationstr, itemstr = line.split(':', 1) - location = world.get_location(locationstr.strip()) + location = world.get_location(locationstr.strip(), 1) if location is None: logger.warning('Unknown location: %s', locationstr) continue else: - item = ItemFactory(itemstr.strip()) + item = ItemFactory(itemstr.strip(), 1) if item is not None: world.push_item(location, item) if item.key: location.event = True elif '<=>' in line: entrance, exit = line.split('<=>', 1) - connect_two_way(world, entrance.strip(), exit.strip()) + connect_two_way(world, entrance.strip(), exit.strip(), 1) elif '=>' in line: entrance, exit = line.split('=>', 1) - connect_entrance(world, entrance.strip(), exit.strip()) + connect_entrance(world, entrance.strip(), exit.strip(), 1) elif '<=' in line: entrance, exit = line.split('<=', 1) - connect_exit(world, exit.strip(), entrance.strip()) + connect_exit(world, exit.strip(), entrance.strip(), 1) - world.required_medallions = (mm_medallion, tr_medallion) + world.required_medallions[1] = (mm_medallion, tr_medallion) # set up Agahnim Events - world.get_location('Agahnim 1').event = True - world.get_location('Agahnim 1').item = ItemFactory('Beat Agahnim 1') - world.get_location('Agahnim 2').event = True - world.get_location('Agahnim 2').item = ItemFactory('Beat Agahnim 2') + world.get_location('Agahnim 1', 1).event = True + world.get_location('Agahnim 1', 1).item = ItemFactory('Beat Agahnim 1', 1) + world.get_location('Agahnim 2', 1).event = True + world.get_location('Agahnim 2', 1).item = ItemFactory('Beat Agahnim 2', 1) def start(): diff --git a/Regions.py b/Regions.py index f95216a3..70ae3b0b 100644 --- a/Regions.py +++ b/Regions.py @@ -2,10 +2,10 @@ import collections from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType -def create_regions(world): +def create_regions(world, player): - world.regions = [ - create_lw_region('Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'], + world.regions += [ + create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'], ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam', 'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump', @@ -15,118 +15,118 @@ def create_regions(world): 'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)', 'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Waterfall of Wishing', 'Hyrule Castle Main Gate', 'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', 'Top of Pyramid']), - create_lw_region('Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']), - create_lw_region('Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']), - create_cave_region('Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top", + create_lw_region(player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']), + create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']), + create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top", "Blind\'s Hideout - Left", "Blind\'s Hideout - Right", "Blind\'s Hideout - Far Left", "Blind\'s Hideout - Far Right"]), - create_cave_region('Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']), - create_lw_region('Zoras River', ['King Zora', 'Zora\'s Ledge']), - create_cave_region('Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']), - create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']), - create_cave_region('Kings Grave', 'a cave with a chest', ['King\'s Tomb']), - create_cave_region('North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']), - create_cave_region('Dam', 'the dam', ['Floodgate', 'Floodgate Chest']), - create_cave_region('Links House', 'your house', ['Link\'s House'], ['Links House Exit']), - create_cave_region('Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']), - create_cave_region('Tavern', 'the tavern', ['Kakariko Tavern']), - create_cave_region('Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']), - create_cave_region('Snitch Lady (East)', 'a boring house'), - create_cave_region('Snitch Lady (West)', 'a boring house'), - create_cave_region('Bush Covered House', 'the grass man'), - create_cave_region('Tavern (Front)', 'the tavern'), - create_cave_region('Light World Bomb Hut', 'a restock room'), - create_cave_region('Kakariko Shop', 'a common shop'), - create_cave_region('Fortune Teller (Light)', 'a fortune teller'), - create_cave_region('Lake Hylia Fortune Teller', 'a fortune teller'), - create_cave_region('Lumberjack House', 'a boring house'), - create_cave_region('Bonk Fairy (Light)', 'a fairy fountain'), - create_cave_region('Bonk Fairy (Dark)', 'a fairy fountain'), - create_cave_region('Lake Hylia Healer Fairy', 'a fairy fountain'), - create_cave_region('Swamp Healer Fairy', 'a fairy fountain'), - create_cave_region('Desert Healer Fairy', 'a fairy fountain'), - create_cave_region('Dark Lake Hylia Healer Fairy', 'a fairy fountain'), - create_cave_region('Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'), - create_cave_region('Dark Desert Healer Fairy', 'a fairy fountain'), - create_cave_region('Dark Death Mountain Healer Fairy', 'a fairy fountain'), - create_cave_region('Chicken House', 'a house with a chest', ['Chicken House']), - create_cave_region('Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']), - create_cave_region('Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']), - create_cave_region('Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle', + create_cave_region(player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']), + create_lw_region(player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']), + create_cave_region(player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']), + create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']), + create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']), + create_cave_region(player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']), + create_cave_region(player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']), + create_cave_region(player, 'Links House', 'your house', ['Link\'s House'], ['Links House Exit']), + create_cave_region(player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']), + create_cave_region(player, 'Tavern', 'the tavern', ['Kakariko Tavern']), + create_cave_region(player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']), + create_cave_region(player, 'Snitch Lady (East)', 'a boring house'), + create_cave_region(player, 'Snitch Lady (West)', 'a boring house'), + create_cave_region(player, 'Bush Covered House', 'the grass man'), + create_cave_region(player, 'Tavern (Front)', 'the tavern'), + create_cave_region(player, 'Light World Bomb Hut', 'a restock room'), + create_cave_region(player, 'Kakariko Shop', 'a common shop'), + create_cave_region(player, 'Fortune Teller (Light)', 'a fortune teller'), + create_cave_region(player, 'Lake Hylia Fortune Teller', 'a fortune teller'), + create_cave_region(player, 'Lumberjack House', 'a boring house'), + create_cave_region(player, 'Bonk Fairy (Light)', 'a fairy fountain'), + create_cave_region(player, 'Bonk Fairy (Dark)', 'a fairy fountain'), + create_cave_region(player, 'Lake Hylia Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Swamp Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Desert Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Desert Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'), + create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']), + create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']), + create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']), + create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']), - create_cave_region('Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']), - create_cave_region('Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']), - create_lw_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']), - create_cave_region('Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']), - create_cave_region('Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']), - create_cave_region('Sick Kids House', 'the sick kid', ['Sick Kid']), - create_lw_region('Hobo Bridge', ['Hobo']), - create_cave_region('Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']), - create_cave_region('Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']), - create_cave_region('Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']), - create_cave_region('Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']), - create_lw_region('Cave 45 Ledge', None, ['Cave 45']), - create_cave_region('Cave 45', 'a cave with an item', ['Cave 45']), - create_lw_region('Graveyard Ledge', None, ['Graveyard Cave']), - create_cave_region('Graveyard Cave', 'a cave with an item', ['Graveyard Cave']), - create_cave_region('Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']), - create_cave_region('Long Fairy Cave', 'a fairy fountain'), - create_cave_region('Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', + create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']), + create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']), + create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']), + create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']), + create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']), + create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']), + create_lw_region(player, 'Hobo Bridge', ['Hobo']), + create_cave_region(player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']), + create_cave_region(player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']), + create_cave_region(player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']), + create_cave_region(player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']), + create_lw_region(player, 'Cave 45 Ledge', None, ['Cave 45']), + create_cave_region(player, 'Cave 45', 'a cave with an item', ['Cave 45']), + create_lw_region(player, 'Graveyard Ledge', None, ['Graveyard Cave']), + create_cave_region(player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']), + create_cave_region(player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']), + create_cave_region(player, 'Long Fairy Cave', 'a fairy fountain'), + create_cave_region(player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']), - create_cave_region('Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']), - create_cave_region('Good Bee Cave', 'a cold bee'), - create_cave_region('20 Rupee Cave', 'a cave with some cash'), - create_cave_region('Cave Shop (Lake Hylia)', 'a common shop'), - create_cave_region('Cave Shop (Dark Death Mountain)', 'a common shop'), - create_cave_region('Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']), - create_cave_region('Library', 'the library', ['Library']), - create_cave_region('Kakariko Gamble Game', 'a game of chance'), - create_cave_region('Potion Shop', 'the potion shop', ['Potion Shop']), - create_lw_region('Lake Hylia Island', ['Lake Hylia Island']), - create_cave_region('Capacity Upgrade', 'the queen of fairies'), - create_cave_region('Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']), - create_lw_region('Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)']), - create_cave_region('50 Rupee Cave', 'a cave with some cash'), - create_lw_region('Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']), - create_lw_region('Desert Ledge (Northeast)', None, ['Checkerboard Cave']), - create_lw_region('Desert Palace Stairs', None, ['Desert Palace Entrance (South)']), - create_lw_region('Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']), - create_lw_region('Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']), - create_dungeon_region('Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'], + create_cave_region(player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']), + create_cave_region(player, 'Good Bee Cave', 'a cold bee'), + create_cave_region(player, '20 Rupee Cave', 'a cave with some cash'), + create_cave_region(player, 'Cave Shop (Lake Hylia)', 'a common shop'), + create_cave_region(player, 'Cave Shop (Dark Death Mountain)', 'a common shop'), + create_cave_region(player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']), + create_cave_region(player, 'Library', 'the library', ['Library']), + create_cave_region(player, 'Kakariko Gamble Game', 'a game of chance'), + create_cave_region(player, 'Potion Shop', 'the potion shop', ['Potion Shop']), + create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island']), + create_cave_region(player, 'Capacity Upgrade', 'the queen of fairies'), + create_cave_region(player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']), + create_lw_region(player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)']), + create_cave_region(player, '50 Rupee Cave', 'a cave with some cash'), + create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']), + create_lw_region(player, 'Desert Ledge (Northeast)', None, ['Checkerboard Cave']), + create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)']), + create_lw_region(player, 'Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']), + create_lw_region(player, 'Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']), + create_dungeon_region(player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'], ['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']), - create_dungeon_region('Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']), - create_dungeon_region('Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']), - create_dungeon_region('Desert Palace North', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']), - create_dungeon_region('Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest', + create_dungeon_region(player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']), + create_dungeon_region(player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']), + create_dungeon_region(player, 'Desert Palace North', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']), + create_dungeon_region(player, 'Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest', 'Eastern Palace - Big Key Chest', 'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Palace Exit']), - create_lw_region('Master Sword Meadow', ['Master Sword Pedestal']), - create_cave_region('Lost Woods Gamble', 'a game of chance'), - create_lw_region('Hyrule Castle Courtyard', None, ['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Entrance (South)']), - create_lw_region('Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Ledge Courtyard Drop']), - create_dungeon_region('Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest'], + create_lw_region(player, 'Master Sword Meadow', ['Master Sword Pedestal']), + create_cave_region(player, 'Lost Woods Gamble', 'a game of chance'), + create_lw_region(player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Entrance (South)']), + create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Ledge Courtyard Drop']), + create_dungeon_region(player, 'Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest'], ['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']), - create_dungeon_region('Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks - create_dungeon_region('Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross'], ['Sewers Door']), - create_dungeon_region('Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', + create_dungeon_region(player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks + create_dungeon_region(player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross'], ['Sewers Door']), + create_dungeon_region(player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', 'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']), - create_dungeon_region('Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']), - create_dungeon_region('Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'], ['Agahnim 1', 'Agahnims Tower Exit']), - create_dungeon_region('Agahnim 1', 'Castle Tower', ['Agahnim 1'], None), - create_cave_region('Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']), - create_cave_region('Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']), - create_cave_region('Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']), - create_lw_region('Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']), - create_cave_region('Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), - create_lw_region('Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']), - create_cave_region('Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), - create_cave_region('Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), - create_cave_region('Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), - create_lw_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']), - create_cave_region('Hookshot Fairy', 'fairies deep in a cave'), - create_cave_region('Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']), - create_cave_region('Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left', + create_dungeon_region(player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']), + create_dungeon_region(player, 'Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'], ['Agahnim 1', 'Agahnims Tower Exit']), + create_dungeon_region(player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None), + create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']), + create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']), + create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']), + create_lw_region(player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']), + create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), + create_lw_region(player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']), + create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']), + create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']), + create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), + create_lw_region(player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']), + create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'), + create_cave_region(player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']), + create_cave_region(player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', @@ -134,174 +134,174 @@ def create_regions(world): 'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right'], ['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']), - create_cave_region('Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']), - create_cave_region('Light World Death Mountain Shop', 'a common shop'), - create_lw_region('East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']), - create_lw_region('Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']), - create_cave_region('Spiral Cave (Top)', 'a connector', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']), - create_cave_region('Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']), - create_lw_region('Fairy Ascension Plateau', None, ['Fairy Ascension Drop', 'Fairy Ascension Cave (Bottom)']), - create_cave_region('Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']), - create_cave_region('Fairy Ascension Cave (Drop)', 'a connector', None, ['Fairy Ascension Cave Pots']), - create_cave_region('Fairy Ascension Cave (Top)', 'a connector', None, ['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']), - create_lw_region('Fairy Ascension Ledge', None, ['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)']), - create_lw_region('Death Mountain (Top)', ['Ether Tablet'], ['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop']), - create_lw_region('Spectacle Rock', ['Spectacle Rock'], ['Spectacle Rock Drop']), - create_dungeon_region('Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']), - create_dungeon_region('Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']), - create_dungeon_region('Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']), + create_cave_region(player, 'Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']), + create_cave_region(player, 'Light World Death Mountain Shop', 'a common shop'), + create_lw_region(player, 'East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']), + create_lw_region(player, 'Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']), + create_cave_region(player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']), + create_cave_region(player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']), + create_lw_region(player, 'Fairy Ascension Plateau', None, ['Fairy Ascension Drop', 'Fairy Ascension Cave (Bottom)']), + create_cave_region(player, 'Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']), + create_cave_region(player, 'Fairy Ascension Cave (Drop)', 'a connector', None, ['Fairy Ascension Cave Pots']), + create_cave_region(player, 'Fairy Ascension Cave (Top)', 'a connector', None, ['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']), + create_lw_region(player, 'Fairy Ascension Ledge', None, ['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)']), + create_lw_region(player, 'Death Mountain (Top)', ['Ether Tablet'], ['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop']), + create_lw_region(player, 'Spectacle Rock', ['Spectacle Rock'], ['Spectacle Rock Drop']), + create_dungeon_region(player, 'Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']), + create_dungeon_region(player, 'Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']), + create_dungeon_region(player, 'Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']), - create_dw_region('East Dark World', ['Pyramid'], ['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)', 'Dark Lake Hylia Teleporter', + create_dw_region(player, 'East Dark World', ['Pyramid'], ['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)', 'Dark Lake Hylia Teleporter', 'Hyrule Castle Ledge Mirror Spot', 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Pyramid Hole', 'Northeast Dark World Broken Bridge Pass']), - create_dw_region('Northeast Dark World', ['Catfish'], ['West Dark World Gap', 'Dark World Potion Shop', 'East Dark World Broken Bridge Pass']), - create_cave_region('Palace of Darkness Hint', 'a storyteller'), - create_cave_region('East Dark World Hint', 'a storyteller'), - create_dw_region('South Dark World', ['Stumpy', 'Digging Game', 'Bombos Tablet'], ['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock', 'Maze Race Mirror Spot', + create_dw_region(player, 'Northeast Dark World', ['Catfish'], ['West Dark World Gap', 'Dark World Potion Shop', 'East Dark World Broken Bridge Pass']), + create_cave_region(player, 'Palace of Darkness Hint', 'a storyteller'), + create_cave_region(player, 'East Dark World Hint', 'a storyteller'), + create_dw_region(player, 'South Dark World', ['Stumpy', 'Digging Game', 'Bombos Tablet'], ['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock', 'Maze Race Mirror Spot', 'Cave 45 Mirror Spot', 'East Dark World Bridge', 'Big Bomb Shop', 'Archery Game', 'Bonk Fairy (Dark)', 'Dark Lake Hylia Shop']), - create_cave_region('Big Bomb Shop', 'the bomb shop'), - create_cave_region('Archery Game', 'a game of skill'), - create_dw_region('Dark Lake Hylia', None, ['Lake Hylia Island Mirror Spot', 'East Dark World Pier', 'Dark Lake Hylia Ledge']), - create_dw_region('Dark Lake Hylia Central Island', None, ['Ice Palace', 'Lake Hylia Central Island Mirror Spot']), - create_dw_region('Dark Lake Hylia Ledge', None, ['Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave']), - create_cave_region('Dark Lake Hylia Ledge Hint', 'a storyteller'), - create_cave_region('Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'), - create_cave_region('Hype Cave', 'a bounty of five items', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', + create_cave_region(player, 'Big Bomb Shop', 'the bomb shop'), + create_cave_region(player, 'Archery Game', 'a game of skill'), + create_dw_region(player, 'Dark Lake Hylia', None, ['Lake Hylia Island Mirror Spot', 'East Dark World Pier', 'Dark Lake Hylia Ledge']), + create_dw_region(player, 'Dark Lake Hylia Central Island', None, ['Ice Palace', 'Lake Hylia Central Island Mirror Spot']), + create_dw_region(player, 'Dark Lake Hylia Ledge', None, ['Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave']), + create_cave_region(player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'), + create_cave_region(player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'), + create_cave_region(player, 'Hype Cave', 'a bounty of five items', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom', 'Hype Cave - Generous Guy']), - create_dw_region('West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock', + create_dw_region(player, 'West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock', 'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']), - create_dw_region('Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']), - create_dw_region('Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']), - create_dw_region('Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']), - create_cave_region('Fortune Teller (Dark)', 'a fortune teller'), - create_cave_region('Village of Outcasts Shop', 'a common shop'), - create_cave_region('Dark Lake Hylia Shop', 'a common shop'), - create_cave_region('Dark World Lumberjack Shop', 'a common shop'), - create_cave_region('Dark World Potion Shop', 'a common shop'), - create_cave_region('Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']), - create_cave_region('Pyramid Fairy', 'a cave with two chests', ['Pyramid Fairy - Left', 'Pyramid Fairy - Right']), - create_cave_region('Brewery', 'a house with a chest', ['Brewery']), - create_cave_region('C-Shaped House', 'a house with a chest', ['C-Shaped House']), - create_cave_region('Chest Game', 'a game of 16 chests', ['Chest Game']), - create_cave_region('Red Shield Shop', 'the rare shop'), - create_cave_region('Dark Sanctuary Hint', 'a storyteller'), - create_cave_region('Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), - create_dw_region('Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Ledge Drop', 'Bumper Cave (Top)', 'Bumper Cave Ledge Mirror Spot']), - create_dw_region('Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', + create_dw_region(player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']), + create_dw_region(player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']), + create_dw_region(player, 'Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']), + create_cave_region(player, 'Fortune Teller (Dark)', 'a fortune teller'), + create_cave_region(player, 'Village of Outcasts Shop', 'a common shop'), + create_cave_region(player, 'Dark Lake Hylia Shop', 'a common shop'), + create_cave_region(player, 'Dark World Lumberjack Shop', 'a common shop'), + create_cave_region(player, 'Dark World Potion Shop', 'a common shop'), + create_cave_region(player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']), + create_cave_region(player, 'Pyramid Fairy', 'a cave with two chests', ['Pyramid Fairy - Left', 'Pyramid Fairy - Right']), + create_cave_region(player, 'Brewery', 'a house with a chest', ['Brewery']), + create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']), + create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']), + create_cave_region(player, 'Red Shield Shop', 'the rare shop'), + create_cave_region(player, 'Dark Sanctuary Hint', 'a storyteller'), + create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), + create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Ledge Drop', 'Bumper Cave (Top)', 'Bumper Cave Ledge Mirror Spot']), + create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']), - create_dw_region('Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section']), - create_dw_region('Dark Desert', None, ['Misery Mire', 'Mire Shed', 'Desert Ledge (Northeast) Mirror Spot', 'Desert Ledge Mirror Spot', 'Desert Palace Stairs Mirror Spot', + create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section']), + create_dw_region(player, 'Dark Desert', None, ['Misery Mire', 'Mire Shed', 'Desert Ledge (Northeast) Mirror Spot', 'Desert Ledge Mirror Spot', 'Desert Palace Stairs Mirror Spot', 'Desert Palace Entrance (North) Mirror Spot', 'Dark Desert Hint', 'Dark Desert Fairy']), - create_cave_region('Mire Shed', 'a cave with two chests', ['Mire Shed - Left', 'Mire Shed - Right']), - create_cave_region('Dark Desert Hint', 'a storyteller'), - create_dw_region('Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']), - create_dw_region('Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Superbunny Cave (Top)', + create_cave_region(player, 'Mire Shed', 'a cave with two chests', ['Mire Shed - Left', 'Mire Shed - Right']), + create_cave_region(player, 'Dark Desert Hint', 'a storyteller'), + create_dw_region(player, 'Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']), + create_dw_region(player, 'Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Superbunny Cave (Top)', 'Hookshot Cave', 'East Death Mountain (Top) Mirror Spot', 'Turtle Rock']), - create_dw_region('Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']), - create_dw_region('Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']), - create_dw_region('Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Fairy Ascension Mirror Spot']), - create_cave_region('Superbunny Cave', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], + create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']), + create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']), + create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Fairy Ascension Mirror Spot']), + create_cave_region(player, 'Superbunny Cave', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)', 'Superbunny Cave Exit (Bottom)']), - create_cave_region('Spike Cave', 'Spike Cave', ['Spike Cave']), - create_cave_region('Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], + create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), + create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), - create_dw_region('Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']), - create_lw_region('Death Mountain Floating Island (Light World)', ['Floating Island']), - create_dw_region('Turtle Rock (Top)', None, ['Turtle Rock Drop']), - create_lw_region('Mimic Cave Ledge', None, ['Mimic Cave']), - create_cave_region('Mimic Cave', 'Mimic Cave', ['Mimic Cave']), + create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']), + create_lw_region(player, 'Death Mountain Floating Island (Light World)', ['Floating Island']), + create_dw_region(player, 'Turtle Rock (Top)', None, ['Turtle Rock Drop']), + create_lw_region(player, 'Mimic Cave Ledge', None, ['Mimic Cave']), + create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']), - create_dungeon_region('Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']), - create_dungeon_region('Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']), - create_dungeon_region('Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest'], ['Swamp Palace (Center)']), - create_dungeon_region('Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', + create_dungeon_region(player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']), + create_dungeon_region(player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']), + create_dungeon_region(player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest'], ['Swamp Palace (Center)']), + create_dungeon_region(player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest'], ['Swamp Palace (North)']), - create_dungeon_region('Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', + create_dungeon_region(player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', 'Swamp Palace - Waterfall Room', 'Swamp Palace - Boss', 'Swamp Palace - Prize']), - create_dungeon_region('Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest', + create_dungeon_region(player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest', 'Thieves\' Town - Map Chest', 'Thieves\' Town - Compass Chest', 'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']), - create_dungeon_region('Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic', + create_dungeon_region(player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell'], ['Blind Fight']), - create_dungeon_region('Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']), - create_dungeon_region('Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']), - create_dungeon_region('Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']), - create_dungeon_region('Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']), - create_dungeon_region('Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']), - create_dungeon_region('Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']), - create_dungeon_region('Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), - create_dungeon_region('Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), - create_dungeon_region('Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), - create_dungeon_region('Ice Palace (Entrance)', 'Ice Palace', None, ['Ice Palace Entrance Room', 'Ice Palace Exit']), - create_dungeon_region('Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest', + create_dungeon_region(player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']), + create_dungeon_region(player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']), + create_dungeon_region(player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']), + create_dungeon_region(player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']), + create_dungeon_region(player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']), + create_dungeon_region(player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']), + create_dungeon_region(player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), + create_dungeon_region(player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), + create_dungeon_region(player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), + create_dungeon_region(player, 'Ice Palace (Entrance)', 'Ice Palace', None, ['Ice Palace Entrance Room', 'Ice Palace Exit']), + create_dungeon_region(player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest', 'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']), - create_dungeon_region('Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']), - create_dungeon_region('Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']), - create_dungeon_region('Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']), - create_dungeon_region('Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']), - create_dungeon_region('Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby', + create_dungeon_region(player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']), + create_dungeon_region(player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']), + create_dungeon_region(player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']), + create_dungeon_region(player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']), + create_dungeon_region(player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'], ['Misery Mire (West)', 'Misery Mire Big Key Door']), - create_dungeon_region('Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']), - create_dungeon_region('Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']), - create_dungeon_region('Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']), - create_dungeon_region('Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']), - create_dungeon_region('Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left', + create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']), + create_dungeon_region(player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']), + create_dungeon_region(player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']), + create_dungeon_region(player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']), + create_dungeon_region(player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right'], ['Turtle Rock Pokey Room', 'Turtle Rock Entrance Gap Reverse']), - create_dungeon_region('Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']), - create_dungeon_region('Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']), - create_dungeon_region('Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']), - create_dungeon_region('Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']), - create_dungeon_region('Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']), - create_dungeon_region('Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', + create_dungeon_region(player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']), + create_dungeon_region(player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']), + create_dungeon_region(player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']), + create_dungeon_region(player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']), + create_dungeon_region(player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']), + create_dungeon_region(player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'], ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']), - create_dungeon_region('Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']), - create_dungeon_region('Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']), - create_dungeon_region('Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'], + create_dungeon_region(player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']), + create_dungeon_region(player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']), + create_dungeon_region(player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'], ['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']), - create_dungeon_region('Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']), - create_dungeon_region('Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']), - create_dungeon_region('Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'], + create_dungeon_region(player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']), + create_dungeon_region(player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']), + create_dungeon_region(player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'], ['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']), - create_dungeon_region('Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']), - create_dungeon_region('Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']), - create_dungeon_region('Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']), - create_dungeon_region('Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right'], + create_dungeon_region(player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']), + create_dungeon_region(player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']), + create_dungeon_region(player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']), + create_dungeon_region(player, 'Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right'], ['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door', 'Ganons Tower Exit']), - create_dungeon_region('Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']), - create_dungeon_region('Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', + create_dungeon_region(player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']), + create_dungeon_region(player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'], ['Ganons Tower (Bottom) (East)']), - create_dungeon_region('Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', + create_dungeon_region(player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'], ['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']), - create_dungeon_region('Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']), - create_dungeon_region('Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']), - create_dungeon_region('Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', + create_dungeon_region(player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']), + create_dungeon_region(player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']), + create_dungeon_region(player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'], ['Ganons Tower (Bottom) (West)']), - create_dungeon_region('Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', + create_dungeon_region(player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']), - create_dungeon_region('Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']), - create_dungeon_region('Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right', + create_dungeon_region(player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']), + create_dungeon_region(player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right', 'Ganons Tower - Pre-Moldorm Chest'], ['Ganons Tower Moldorm Door']), - create_dungeon_region('Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']), - create_dungeon_region('Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None), - create_cave_region('Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']), - create_cave_region('Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']), - create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']) + create_dungeon_region(player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']), + create_dungeon_region(player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None), + create_cave_region(player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']), + create_cave_region(player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']), + create_dw_region(player, 'Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']) ] for region_name, (room_id, shopkeeper, replaceable) in shop_table.items(): - region = world.get_region(region_name) + region = world.get_region(region_name, player) shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable) region.shop = shop world.shops.append(shop) for index, (item, price) in enumerate(default_shop_contents[region_name]): shop.add_inventory(index, item, price) - region = world.get_region('Capacity Upgrade') + region = world.get_region('Capacity Upgrade', player) shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True) region.shop = shop world.shops.append(shop) @@ -309,30 +309,30 @@ def create_regions(world): shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7) world.intialize_regions() -def create_lw_region(name, locations=None, exits=None): - return _create_region(name, RegionType.LightWorld, 'Light World', locations, exits) +def create_lw_region(player, name, locations=None, exits=None): + return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits) -def create_dw_region(name, locations=None, exits=None): - return _create_region(name, RegionType.DarkWorld, 'Dark World', locations, exits) +def create_dw_region(player, name, locations=None, exits=None): + return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits) -def create_cave_region(name, hint='Hyrule', locations=None, exits=None): - return _create_region(name, RegionType.Cave, hint, locations, exits) +def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None): + return _create_region(player, name, RegionType.Cave, hint, locations, exits) -def create_dungeon_region(name, hint='Hyrule', locations=None, exits=None): - return _create_region(name, RegionType.Dungeon, hint, locations, exits) +def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None): + return _create_region(player, name, RegionType.Dungeon, hint, locations, exits) -def _create_region(name, type, hint='Hyrule', locations=None, exits=None): - ret = Region(name, type, hint) +def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None): + ret = Region(name, type, hint, player) if locations is None: locations = [] if exits is None: exits = [] for exit in exits: - ret.exits.append(Entrance(exit, ret)) + ret.exits.append(Entrance(player, exit, ret)) for location in locations: address, crystal, hint_text = location_table[location] - ret.locations.append(Location(location, address, crystal, hint_text, ret)) + ret.locations.append(Location(player, location, address, crystal, hint_text, ret)) return ret def mark_light_world_regions(world): @@ -398,221 +398,221 @@ default_shop_contents = { } location_table = {'Mushroom': (0x180013, False, 'in the woods'), - 'Bottle Merchant': (0x2EB18, False, 'with a merchant'), - 'Flute Spot': (0x18014A, False, 'underground'), + 'Bottle Merchant': (0x2eb18, False, 'with a merchant'), + 'Flute Spot': (0x18014a, False, 'underground'), 'Sunken Treasure': (0x180145, False, 'underwater'), - 'Purple Chest': (0x33D68, False, 'from a box'), - 'Blind\'s Hideout - Top': (0xEB0F, False, 'in a basement'), - 'Blind\'s Hideout - Left': (0xEB12, False, 'in a basement'), - 'Blind\'s Hideout - Right': (0xEB15, False, 'in a basement'), - 'Blind\'s Hideout - Far Left': (0xEB18, False, 'in a basement'), - 'Blind\'s Hideout - Far Right': (0xEB1B, False, 'in a basement'), - 'Link\'s Uncle': (0x2DF45, False, 'with your uncle'), - 'Secret Passage': (0xE971, False, 'near your uncle'), - 'King Zora': (0xEE1C3, False, 'at a high price'), - 'Zora\'s Ledge': (0x180149, False, 'near Zora'), - 'Waterfall Fairy - Left': (0xE9B0, False, 'near a fairy'), - 'Waterfall Fairy - Right': (0xE9D1, False, 'near a fairy'), - 'King\'s Tomb': (0xE97A, False, 'alone in a cave'), - 'Floodgate Chest': (0xE98C, False, 'in the dam'), - 'Link\'s House': (0xE9BC, False, 'in your home'), - 'Kakariko Tavern': (0xE9CE, False, 'in the bar'), - 'Chicken House': (0xE9E9, False, 'near poultry'), - 'Aginah\'s Cave': (0xE9F2, False, 'with Aginah'), - 'Sahasrahla\'s Hut - Left': (0xEA82, False, 'near the elder'), - 'Sahasrahla\'s Hut - Middle': (0xEA85, False, 'near the elder'), - 'Sahasrahla\'s Hut - Right': (0xEA88, False, 'near the elder'), - 'Sahasrahla': (0x2F1FC, False, 'with the elder'), - 'Kakariko Well - Top': (0xEA8E, False, 'in a well'), - 'Kakariko Well - Left': (0xEA91, False, 'in a well'), - 'Kakariko Well - Middle': (0xEA94, False, 'in a well'), - 'Kakariko Well - Right': (0xEA97, False, 'in a well'), - 'Kakariko Well - Bottom': (0xEA9A, False, 'in a well'), - 'Blacksmith': (0x18002A, False, 'with the smith'), + 'Purple Chest': (0x33d68, False, 'from a box'), + "Blind's Hideout - Top": (0xeb0f, False, 'in a basement'), + "Blind's Hideout - Left": (0xeb12, False, 'in a basement'), + "Blind's Hideout - Right": (0xeb15, False, 'in a basement'), + "Blind's Hideout - Far Left": (0xeb18, False, 'in a basement'), + "Blind's Hideout - Far Right": (0xeb1b, False, 'in a basement'), + "Link's Uncle": (0x2df45, False, 'with your uncle'), + 'Secret Passage': (0xe971, False, 'near your uncle'), + 'King Zora': (0xee1c3, False, 'at a high price'), + "Zora's Ledge": (0x180149, False, 'near Zora'), + 'Waterfall Fairy - Left': (0xe9b0, False, 'near a fairy'), + 'Waterfall Fairy - Right': (0xe9d1, False, 'near a fairy'), + "King's Tomb": (0xe97a, False, 'alone in a cave'), + 'Floodgate Chest': (0xe98c, False, 'in the dam'), + "Link's House": (0xe9bc, False, 'in your home'), + 'Kakariko Tavern': (0xe9ce, False, 'in the bar'), + 'Chicken House': (0xe9e9, False, 'near poultry'), + "Aginah's Cave": (0xe9f2, False, 'with Aginah'), + "Sahasrahla's Hut - Left": (0xea82, False, 'near the elder'), + "Sahasrahla's Hut - Middle": (0xea85, False, 'near the elder'), + "Sahasrahla's Hut - Right": (0xea88, False, 'near the elder'), + 'Sahasrahla': (0x2f1fc, False, 'with the elder'), + 'Kakariko Well - Top': (0xea8e, False, 'in a well'), + 'Kakariko Well - Left': (0xea91, False, 'in a well'), + 'Kakariko Well - Middle': (0xea94, False, 'in a well'), + 'Kakariko Well - Right': (0xea97, False, 'in a well'), + 'Kakariko Well - Bottom': (0xea9a, False, 'in a well'), + 'Blacksmith': (0x18002a, False, 'with the smith'), 'Magic Bat': (0x180015, False, 'with the bat'), - 'Sick Kid': (0x339CF, False, 'with the sick'), - 'Hobo': (0x33E7D, False, 'with the hobo'), + 'Sick Kid': (0x339cf, False, 'with the sick'), + 'Hobo': (0x33e7d, False, 'with the hobo'), 'Lost Woods Hideout': (0x180000, False, 'near a thief'), 'Lumberjack Tree': (0x180001, False, 'in a hole'), 'Cave 45': (0x180003, False, 'alone in a cave'), 'Graveyard Cave': (0x180004, False, 'alone in a cave'), 'Checkerboard Cave': (0x180005, False, 'alone in a cave'), - 'Mini Moldorm Cave - Far Left': (0xEB42, False, 'near Moldorms'), - 'Mini Moldorm Cave - Left': (0xEB45, False, 'near Moldorms'), - 'Mini Moldorm Cave - Right': (0xEB48, False, 'near Moldorms'), - 'Mini Moldorm Cave - Far Right': (0xEB4B, False, 'near Moldorms'), + 'Mini Moldorm Cave - Far Left': (0xeb42, False, 'near Moldorms'), + 'Mini Moldorm Cave - Left': (0xeb45, False, 'near Moldorms'), + 'Mini Moldorm Cave - Right': (0xeb48, False, 'near Moldorms'), + 'Mini Moldorm Cave - Far Right': (0xeb4b, False, 'near Moldorms'), 'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'), - 'Ice Rod Cave': (0xEB4E, False, 'in a frozen cave'), - 'Bonk Rock Cave': (0xEB3F, False, 'alone in a cave'), + 'Ice Rod Cave': (0xeb4e, False, 'in a frozen cave'), + 'Bonk Rock Cave': (0xeb3f, False, 'alone in a cave'), 'Library': (0x180012, False, 'near books'), 'Potion Shop': (0x180014, False, 'near potions'), 'Lake Hylia Island': (0x180144, False, 'on an island'), 'Maze Race': (0x180142, False, 'at the race'), 'Desert Ledge': (0x180143, False, 'in the desert'), - 'Desert Palace - Big Chest': (0xE98F, False, 'in Desert Palace'), + 'Desert Palace - Big Chest': (0xe98f, False, 'in Desert Palace'), 'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'), - 'Desert Palace - Map Chest': (0xE9B6, False, 'in Desert Palace'), - 'Desert Palace - Compass Chest': (0xE9CB, False, 'in Desert Palace'), - 'Desert Palace - Big Key Chest': (0xE9C2, False, 'in Desert Palace'), + 'Desert Palace - Map Chest': (0xe9b6, False, 'in Desert Palace'), + 'Desert Palace - Compass Chest': (0xe9cb, False, 'in Desert Palace'), + 'Desert Palace - Big Key Chest': (0xe9c2, False, 'in Desert Palace'), 'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'), - 'Eastern Palace - Compass Chest': (0xE977, False, 'in Eastern Palace'), - 'Eastern Palace - Big Chest': (0xE97D, False, 'in Eastern Palace'), - 'Eastern Palace - Cannonball Chest': (0xE9B3, False, 'in Eastern Palace'), - 'Eastern Palace - Big Key Chest': (0xE9B9, False, 'in Eastern Palace'), - 'Eastern Palace - Map Chest': (0xE9F5, False, 'in Eastern Palace'), + 'Eastern Palace - Compass Chest': (0xe977, False, 'in Eastern Palace'), + 'Eastern Palace - Big Chest': (0xe97d, False, 'in Eastern Palace'), + 'Eastern Palace - Cannonball Chest': (0xe9b3, False, 'in Eastern Palace'), + 'Eastern Palace - Big Key Chest': (0xe9b9, False, 'in Eastern Palace'), + 'Eastern Palace - Map Chest': (0xe9f5, False, 'in Eastern Palace'), 'Eastern Palace - Boss': (0x180150, False, 'with the Armos'), - 'Master Sword Pedestal': (0x289B0, False, 'at the pedestal'), - 'Hyrule Castle - Boomerang Chest': (0xE974, False, 'in Hyrule Castle'), - 'Hyrule Castle - Map Chest': (0xEB0C, False, 'in Hyrule Castle'), - 'Hyrule Castle - Zelda\'s Chest': (0xEB09, False, 'in Hyrule Castle'), - 'Sewers - Dark Cross': (0xE96E, False, 'in the sewers'), - 'Sewers - Secret Room - Left': (0xEB5D, False, 'in the sewers'), - 'Sewers - Secret Room - Middle': (0xEB60, False, 'in the sewers'), - 'Sewers - Secret Room - Right': (0xEB63, False, 'in the sewers'), - 'Sanctuary': (0xEA79, False, 'in Sanctuary'), - 'Castle Tower - Room 03': (0xEAB5, False, 'in Castle Tower'), - 'Castle Tower - Dark Maze': (0xEAB2, False, 'in Castle Tower'), - 'Old Man': (0xF69FA, False, 'with the old man'), + 'Master Sword Pedestal': (0x289b0, False, 'at the pedestal'), + 'Hyrule Castle - Boomerang Chest': (0xe974, False, 'in Hyrule Castle'), + 'Hyrule Castle - Map Chest': (0xeb0c, False, 'in Hyrule Castle'), + "Hyrule Castle - Zelda's Chest": (0xeb09, False, 'in Hyrule Castle'), + 'Sewers - Dark Cross': (0xe96e, False, 'in the sewers'), + 'Sewers - Secret Room - Left': (0xeb5d, False, 'in the sewers'), + 'Sewers - Secret Room - Middle': (0xeb60, False, 'in the sewers'), + 'Sewers - Secret Room - Right': (0xeb63, False, 'in the sewers'), + 'Sanctuary': (0xea79, False, 'in Sanctuary'), + 'Castle Tower - Room 03': (0xeab5, False, 'in Castle Tower'), + 'Castle Tower - Dark Maze': (0xeab2, False, 'in Castle Tower'), + 'Old Man': (0xf69fa, False, 'with the old man'), 'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'), - 'Paradox Cave Lower - Far Left': (0xEB2A, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Left': (0xEB2D, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Right': (0xEB30, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Far Right': (0xEB33, False, 'in a cave with seven chests'), - 'Paradox Cave Lower - Middle': (0xEB36, False, 'in a cave with seven chests'), - 'Paradox Cave Upper - Left': (0xEB39, False, 'in a cave with seven chests'), - 'Paradox Cave Upper - Right': (0xEB3C, False, 'in a cave with seven chests'), - 'Spiral Cave': (0xE9BF, False, 'in spiral cave'), + 'Paradox Cave Lower - Far Left': (0xeb2a, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Left': (0xeb2d, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Right': (0xeb30, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Far Right': (0xeb33, False, 'in a cave with seven chests'), + 'Paradox Cave Lower - Middle': (0xeb36, False, 'in a cave with seven chests'), + 'Paradox Cave Upper - Left': (0xeb39, False, 'in a cave with seven chests'), + 'Paradox Cave Upper - Right': (0xeb3c, False, 'in a cave with seven chests'), + 'Spiral Cave': (0xe9bf, False, 'in spiral cave'), 'Ether Tablet': (0x180016, False, 'at a monolith'), 'Spectacle Rock': (0x180140, False, 'atop a rock'), 'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'), - 'Tower of Hera - Map Chest': (0xE9AD, False, 'in Tower of Hera'), - 'Tower of Hera - Big Key Chest': (0xE9E6, False, 'in Tower of Hera'), - 'Tower of Hera - Compass Chest': (0xE9FB, False, 'in Tower of Hera'), - 'Tower of Hera - Big Chest': (0xE9F8, False, 'in Tower of Hera'), + 'Tower of Hera - Map Chest': (0xe9ad, False, 'in Tower of Hera'), + 'Tower of Hera - Big Key Chest': (0xe9e6, False, 'in Tower of Hera'), + 'Tower of Hera - Compass Chest': (0xe9fb, False, 'in Tower of Hera'), + 'Tower of Hera - Big Chest': (0xe9f8, False, 'in Tower of Hera'), 'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'), 'Pyramid': (0x180147, False, 'on the pyramid'), - 'Catfish': (0xEE185, False, 'with a catfish'), - 'Stumpy': (0x330C7, False, 'with tree boy'), + 'Catfish': (0xee185, False, 'with a catfish'), + 'Stumpy': (0x330c7, False, 'with tree boy'), 'Digging Game': (0x180148, False, 'underground'), 'Bombos Tablet': (0x180017, False, 'at a monolith'), - 'Hype Cave - Top': (0xEB1E, False, 'near a bat-like man'), - 'Hype Cave - Middle Right': (0xEB21, False, 'near a bat-like man'), - 'Hype Cave - Middle Left': (0xEB24, False, 'near a bat-like man'), - 'Hype Cave - Bottom': (0xEB27, False, 'near a bat-like man'), + 'Hype Cave - Top': (0xeb1e, False, 'near a bat-like man'), + 'Hype Cave - Middle Right': (0xeb21, False, 'near a bat-like man'), + 'Hype Cave - Middle Left': (0xeb24, False, 'near a bat-like man'), + 'Hype Cave - Bottom': (0xeb27, False, 'near a bat-like man'), 'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'), 'Peg Cave': (0x180006, False, 'alone in a cave'), - 'Pyramid Fairy - Left': (0xE980, False, 'near a fairy'), - 'Pyramid Fairy - Right': (0xE983, False, 'near a fairy'), - 'Brewery': (0xE9EC, False, 'alone in a home'), - 'C-Shaped House': (0xE9EF, False, 'alone in a home'), - 'Chest Game': (0xEDA8, False, 'as a prize'), + 'Pyramid Fairy - Left': (0xe980, False, 'near a fairy'), + 'Pyramid Fairy - Right': (0xe983, False, 'near a fairy'), + 'Brewery': (0xe9ec, False, 'alone in a home'), + 'C-Shaped House': (0xe9ef, False, 'alone in a home'), + 'Chest Game': (0xeda8, False, 'as a prize'), 'Bumper Cave Ledge': (0x180146, False, 'on a ledge'), - 'Mire Shed - Left': (0xEA73, False, 'near sparks'), - 'Mire Shed - Right': (0xEA76, False, 'near sparks'), - 'Superbunny Cave - Top': (0xEA7C, False, 'in a connection'), - 'Superbunny Cave - Bottom': (0xEA7F, False, 'in a connection'), - 'Spike Cave': (0xEA8B, False, 'beyond spikes'), - 'Hookshot Cave - Top Right': (0xEB51, False, 'across pits'), - 'Hookshot Cave - Top Left': (0xEB54, False, 'across pits'), - 'Hookshot Cave - Bottom Right': (0xEB5A, False, 'across pits'), - 'Hookshot Cave - Bottom Left': (0xEB57, False, 'across pits'), + 'Mire Shed - Left': (0xea73, False, 'near sparks'), + 'Mire Shed - Right': (0xea76, False, 'near sparks'), + 'Superbunny Cave - Top': (0xea7c, False, 'in a connection'), + 'Superbunny Cave - Bottom': (0xea7f, False, 'in a connection'), + 'Spike Cave': (0xea8b, False, 'beyond spikes'), + 'Hookshot Cave - Top Right': (0xeb51, False, 'across pits'), + 'Hookshot Cave - Top Left': (0xeb54, False, 'across pits'), + 'Hookshot Cave - Bottom Right': (0xeb5a, False, 'across pits'), + 'Hookshot Cave - Bottom Left': (0xeb57, False, 'across pits'), 'Floating Island': (0x180141, False, 'on an island'), - 'Mimic Cave': (0xE9C5, False, 'in a cave of mimicry'), - 'Swamp Palace - Entrance': (0xEA9D, False, 'in Swamp Palace'), - 'Swamp Palace - Map Chest': (0xE986, False, 'in Swamp Palace'), - 'Swamp Palace - Big Chest': (0xE989, False, 'in Swamp Palace'), - 'Swamp Palace - Compass Chest': (0xEAA0, False, 'in Swamp Palace'), - 'Swamp Palace - Big Key Chest': (0xEAA6, False, 'in Swamp Palace'), - 'Swamp Palace - West Chest': (0xEAA3, False, 'in Swamp Palace'), - 'Swamp Palace - Flooded Room - Left': (0xEAA9, False, 'in Swamp Palace'), - 'Swamp Palace - Flooded Room - Right': (0xEAAC, False, 'in Swamp Palace'), - 'Swamp Palace - Waterfall Room': (0xEAAF, False, 'in Swamp Palace'), + 'Mimic Cave': (0xe9c5, False, 'in a cave of mimicry'), + 'Swamp Palace - Entrance': (0xea9d, False, 'in Swamp Palace'), + 'Swamp Palace - Map Chest': (0xe986, False, 'in Swamp Palace'), + 'Swamp Palace - Big Chest': (0xe989, False, 'in Swamp Palace'), + 'Swamp Palace - Compass Chest': (0xeaa0, False, 'in Swamp Palace'), + 'Swamp Palace - Big Key Chest': (0xeaa6, False, 'in Swamp Palace'), + 'Swamp Palace - West Chest': (0xeaa3, False, 'in Swamp Palace'), + 'Swamp Palace - Flooded Room - Left': (0xeaa9, False, 'in Swamp Palace'), + 'Swamp Palace - Flooded Room - Right': (0xeaac, False, 'in Swamp Palace'), + 'Swamp Palace - Waterfall Room': (0xeaaf, False, 'in Swamp Palace'), 'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'), - 'Thieves\' Town - Big Key Chest': (0xEA04, False, 'in Thieves\' Town'), - 'Thieves\' Town - Map Chest': (0xEA01, False, 'in Thieves\' Town'), - 'Thieves\' Town - Compass Chest': (0xEA07, False, 'in Thieves\' Town'), - 'Thieves\' Town - Ambush Chest': (0xEA0A, False, 'in Thieves\' Town'), - 'Thieves\' Town - Attic': (0xEA0D, False, 'in Thieves\' Town'), - 'Thieves\' Town - Big Chest': (0xEA10, False, 'in Thieves\' Town'), - 'Thieves\' Town - Blind\'s Cell': (0xEA13, False, 'in Thieves\' Town'), - 'Thieves\' Town - Boss': (0x180156, False, 'with Blind'), - 'Skull Woods - Compass Chest': (0xE992, False, 'in Skull Woods'), - 'Skull Woods - Map Chest': (0xE99B, False, 'in Skull Woods'), - 'Skull Woods - Big Chest': (0xE998, False, 'in Skull Woods'), - 'Skull Woods - Pot Prison': (0xE9A1, False, 'in Skull Woods'), - 'Skull Woods - Pinball Room': (0xE9C8, False, 'in Skull Woods'), - 'Skull Woods - Big Key Chest': (0xE99E, False, 'in Skull Woods'), - 'Skull Woods - Bridge Room': (0xE9FE, False, 'near Mothula'), + "Thieves' Town - Big Key Chest": (0xea04, False, "in Thieves' Town"), + "Thieves' Town - Map Chest": (0xea01, False, "in Thieves' Town"), + "Thieves' Town - Compass Chest": (0xea07, False, "in Thieves' Town"), + "Thieves' Town - Ambush Chest": (0xea0a, False, "in Thieves' Town"), + "Thieves' Town - Attic": (0xea0d, False, "in Thieves' Town"), + "Thieves' Town - Big Chest": (0xea10, False, "in Thieves' Town"), + "Thieves' Town - Blind's Cell": (0xea13, False, "in Thieves' Town"), + "Thieves' Town - Boss": (0x180156, False, 'with Blind'), + 'Skull Woods - Compass Chest': (0xe992, False, 'in Skull Woods'), + 'Skull Woods - Map Chest': (0xe99b, False, 'in Skull Woods'), + 'Skull Woods - Big Chest': (0xe998, False, 'in Skull Woods'), + 'Skull Woods - Pot Prison': (0xe9a1, False, 'in Skull Woods'), + 'Skull Woods - Pinball Room': (0xe9c8, False, 'in Skull Woods'), + 'Skull Woods - Big Key Chest': (0xe99e, False, 'in Skull Woods'), + 'Skull Woods - Bridge Room': (0xe9fe, False, 'near Mothula'), 'Skull Woods - Boss': (0x180155, False, 'with Mothula'), - 'Ice Palace - Compass Chest': (0xE9D4, False, 'in Ice Palace'), - 'Ice Palace - Freezor Chest': (0xE995, False, 'in Ice Palace'), - 'Ice Palace - Big Chest': (0xE9AA, False, 'in Ice Palace'), - 'Ice Palace - Iced T Room': (0xE9E3, False, 'in Ice Palace'), - 'Ice Palace - Spike Room': (0xE9E0, False, 'in Ice Palace'), - 'Ice Palace - Big Key Chest': (0xE9A4, False, 'in Ice Palace'), - 'Ice Palace - Map Chest': (0xE9DD, False, 'in Ice Palace'), + 'Ice Palace - Compass Chest': (0xe9d4, False, 'in Ice Palace'), + 'Ice Palace - Freezor Chest': (0xe995, False, 'in Ice Palace'), + 'Ice Palace - Big Chest': (0xe9aa, False, 'in Ice Palace'), + 'Ice Palace - Iced T Room': (0xe9e3, False, 'in Ice Palace'), + 'Ice Palace - Spike Room': (0xe9e0, False, 'in Ice Palace'), + 'Ice Palace - Big Key Chest': (0xe9a4, False, 'in Ice Palace'), + 'Ice Palace - Map Chest': (0xe9dd, False, 'in Ice Palace'), 'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'), - 'Misery Mire - Big Chest': (0xEA67, False, 'in Misery Mire'), - 'Misery Mire - Map Chest': (0xEA6A, False, 'in Misery Mire'), - 'Misery Mire - Main Lobby': (0xEA5E, False, 'in Misery Mire'), - 'Misery Mire - Bridge Chest': (0xEA61, False, 'in Misery Mire'), - 'Misery Mire - Spike Chest': (0xE9DA, False, 'in Misery Mire'), - 'Misery Mire - Compass Chest': (0xEA64, False, 'in Misery Mire'), - 'Misery Mire - Big Key Chest': (0xEA6D, False, 'in Misery Mire'), + 'Misery Mire - Big Chest': (0xea67, False, 'in Misery Mire'), + 'Misery Mire - Map Chest': (0xea6a, False, 'in Misery Mire'), + 'Misery Mire - Main Lobby': (0xea5e, False, 'in Misery Mire'), + 'Misery Mire - Bridge Chest': (0xea61, False, 'in Misery Mire'), + 'Misery Mire - Spike Chest': (0xe9da, False, 'in Misery Mire'), + 'Misery Mire - Compass Chest': (0xea64, False, 'in Misery Mire'), + 'Misery Mire - Big Key Chest': (0xea6d, False, 'in Misery Mire'), 'Misery Mire - Boss': (0x180158, False, 'with Vitreous'), - 'Turtle Rock - Compass Chest': (0xEA22, False, 'in Turtle Rock'), - 'Turtle Rock - Roller Room - Left': (0xEA1C, False, 'in Turtle Rock'), - 'Turtle Rock - Roller Room - Right': (0xEA1F, False, 'in Turtle Rock'), - 'Turtle Rock - Chain Chomps': (0xEA16, False, 'in Turtle Rock'), - 'Turtle Rock - Big Key Chest': (0xEA25, False, 'in Turtle Rock'), - 'Turtle Rock - Big Chest': (0xEA19, False, 'in Turtle Rock'), - 'Turtle Rock - Crystaroller Room': (0xEA34, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Bottom Left': (0xEA31, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Bottom Right': (0xEA2E, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Top Left': (0xEA2B, False, 'in Turtle Rock'), - 'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'), + 'Turtle Rock - Compass Chest': (0xea22, False, 'in Turtle Rock'), + 'Turtle Rock - Roller Room - Left': (0xea1c, False, 'in Turtle Rock'), + 'Turtle Rock - Roller Room - Right': (0xea1f, False, 'in Turtle Rock'), + 'Turtle Rock - Chain Chomps': (0xea16, False, 'in Turtle Rock'), + 'Turtle Rock - Big Key Chest': (0xea25, False, 'in Turtle Rock'), + 'Turtle Rock - Big Chest': (0xea19, False, 'in Turtle Rock'), + 'Turtle Rock - Crystaroller Room': (0xea34, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Top Left': (0xea2b, False, 'in Turtle Rock'), + 'Turtle Rock - Eye Bridge - Top Right': (0xea28, False, 'in Turtle Rock'), 'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'), - 'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'), - 'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'), - 'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'), - 'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'), - 'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'), - 'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'), - 'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Basement - Right': (0xEA4F, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Maze - Top': (0xEA55, False, 'in Palace of Darkness'), - 'Palace of Darkness - Dark Maze - Bottom': (0xEA58, False, 'in Palace of Darkness'), - 'Palace of Darkness - Big Chest': (0xEA40, False, 'in Palace of Darkness'), - 'Palace of Darkness - Harmless Hellway': (0xEA46, False, 'in Palace of Darkness'), + 'Palace of Darkness - Shooter Room': (0xea5b, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Bridge': (0xea3d, False, 'in Palace of Darkness'), + 'Palace of Darkness - Stalfos Basement': (0xea49, False, 'in Palace of Darkness'), + 'Palace of Darkness - Big Key Chest': (0xea37, False, 'in Palace of Darkness'), + 'Palace of Darkness - The Arena - Ledge': (0xea3a, False, 'in Palace of Darkness'), + 'Palace of Darkness - Map Chest': (0xea52, False, 'in Palace of Darkness'), + 'Palace of Darkness - Compass Chest': (0xea43, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Basement - Left': (0xea4c, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Basement - Right': (0xea4f, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Maze - Top': (0xea55, False, 'in Palace of Darkness'), + 'Palace of Darkness - Dark Maze - Bottom': (0xea58, False, 'in Palace of Darkness'), + 'Palace of Darkness - Big Chest': (0xea40, False, 'in Palace of Darkness'), + 'Palace of Darkness - Harmless Hellway': (0xea46, False, 'in Palace of Darkness'), 'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'), - 'Ganons Tower - Bob\'s Torch': (0x180161, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Hope Room - Left': (0xEAD9, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Hope Room - Right': (0xEADC, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Tile Room': (0xEAE2, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Compass Room - Top Left': (0xEAE5, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Compass Room - Top Right': (0xEAE8, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Compass Room - Bottom Left': (0xEAEB, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Compass Room - Bottom Right': (0xEAEE, False, 'in Ganon\'s Tower'), - 'Ganons Tower - DMs Room - Top Left': (0xEAB8, False, 'in Ganon\'s Tower'), - 'Ganons Tower - DMs Room - Top Right': (0xEABB, False, 'in Ganon\'s Tower'), - 'Ganons Tower - DMs Room - Bottom Left': (0xEABE, False, 'in Ganon\'s Tower'), - 'Ganons Tower - DMs Room - Bottom Right': (0xEAC1, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Map Chest': (0xEAD3, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Firesnake Room': (0xEAD0, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Randomizer Room - Top Left': (0xEAC4, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Randomizer Room - Top Right': (0xEAC7, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Randomizer Room - Bottom Left': (0xEACA, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Randomizer Room - Bottom Right': (0xEACD, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Bob\'s Chest': (0xEADF, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Big Chest': (0xEAD6, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Big Key Room - Left': (0xEAF4, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Big Key Room - Right': (0xEAF7, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Big Key Chest': (0xEAF1, False, 'in Ganon\'s Tower'), - 'Ganons Tower - Mini Helmasaur Room - Left': (0xEAFD, False, 'atop Ganon\'s Tower'), - 'Ganons Tower - Mini Helmasaur Room - Right': (0xEB00, False, 'atop Ganon\'s Tower'), - 'Ganons Tower - Pre-Moldorm Chest': (0xEB03, False, 'atop Ganon\'s Tower'), - 'Ganons Tower - Validation Chest': (0xEB06, False, 'atop Ganon\'s Tower'), + "Ganons Tower - Bob's Torch": (0x180161, False, "in Ganon's Tower"), + 'Ganons Tower - Hope Room - Left': (0xead9, False, "in Ganon's Tower"), + 'Ganons Tower - Hope Room - Right': (0xeadc, False, "in Ganon's Tower"), + 'Ganons Tower - Tile Room': (0xeae2, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Top Left': (0xeae5, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Top Right': (0xeae8, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, False, "in Ganon's Tower"), + 'Ganons Tower - Compass Room - Bottom Right': (0xeaee, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Top Left': (0xeab8, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Top Right': (0xeabb, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Bottom Left': (0xeabe, False, "in Ganon's Tower"), + 'Ganons Tower - DMs Room - Bottom Right': (0xeac1, False, "in Ganon's Tower"), + 'Ganons Tower - Map Chest': (0xead3, False, "in Ganon's Tower"), + 'Ganons Tower - Firesnake Room': (0xead0, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Top Left': (0xeac4, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Top Right': (0xeac7, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, False, "in Ganon's Tower"), + 'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, False, "in Ganon's Tower"), + "Ganons Tower - Bob's Chest": (0xeadf, False, "in Ganon's Tower"), + 'Ganons Tower - Big Chest': (0xead6, False, "in Ganon's Tower"), + 'Ganons Tower - Big Key Room - Left': (0xeaf4, False, "in Ganon's Tower"), + 'Ganons Tower - Big Key Room - Right': (0xeaf7, False, "in Ganon's Tower"), + 'Ganons Tower - Big Key Chest': (0xeaf1, False, "in Ganon's Tower"), + 'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, False, "atop Ganon's Tower"), + 'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, False, "atop Ganon's Tower"), + 'Ganons Tower - Pre-Moldorm Chest': (0xeb03, False, "atop Ganon's Tower"), + 'Ganons Tower - Validation Chest': (0xeb06, False, "atop Ganon's Tower"), 'Ganon': (None, False, 'from me'), 'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'), 'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'), diff --git a/Rom.py b/Rom.py index 9f21eb0d..c439f07d 100644 --- a/Rom.py +++ b/Rom.py @@ -6,13 +6,13 @@ import os import struct import random -from BaseClasses import ShopType +from BaseClasses import ShopType, Region, Location, Item from Dungeons import dungeon_music_addresses from Text import MultiByteTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Utils import local_path, int16_as_bytes, int32_as_bytes -from Items import ItemFactory +from Items import ItemFactory, item_table JAP10HASH = '03a63945398191337e896e5771f77173' @@ -22,6 +22,7 @@ RANDOMIZERBASEHASH = 'cb560220b7b1b8202e92381aee19cd36' class JsonRom(object): def __init__(self): + self.name = None self.patches = {} def write_byte(self, address, value): @@ -52,6 +53,7 @@ class JsonRom(object): class LocalRom(object): def __init__(self, file, patch=True): + self.name = None with open(file, 'rb') as stream: self.buffer = read_rom(stream) if patch: @@ -273,15 +275,18 @@ class Sprite(object): # split into palettes of 15 colors return array_chunk(palette_as_colors, 15) -def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): +def patch_rom(world, player, rom, hashtable, beep='normal', color='red', sprite=None): + random.seed(world.rom_seeds[player]) # patch items for location in world.get_locations(): - itemid = location.item.code if location.item is not None else 0x5A - - if itemid is None or location.address is None: + if location.player != player: + continue + + itemid = location.item.code if location.item is not None else 0x5A + + if location.address is None: continue - locationaddress = location.address if not location.crystal: # Keys in their native dungeon should use the orignal item code for keys if location.parent_region.dungeon: @@ -291,10 +296,10 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): itemid = 0x32 if location.item.type == "SmallKey": itemid = 0x24 - rom.write_byte(locationaddress, itemid) + rom.write_byte(location.address, itemid) else: # crystals - for address, value in zip(locationaddress, itemid): + for address, value in zip(location.address, itemid): rom.write_byte(address, value) # patch music @@ -312,7 +317,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): # patch entrance/exits/holes for region in world.regions: for exit in region.exits: - if exit.target is not None: + if exit.target is not None and exit.player == player: if isinstance(exit.addresses, tuple): offset = exit.target room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = exit.addresses @@ -360,25 +365,25 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): # patch door table rom.write_byte(0xDBB73 + exit.addresses, exit.target) - write_custom_shops(rom, world) + write_custom_shops(rom, world, player) # patch medallion requirements - if world.required_medallions[0] == 'Bombos': + if world.required_medallions[player][0] == 'Bombos': rom.write_byte(0x180022, 0x00) # requirement rom.write_byte(0x4FF2, 0x31) # sprite rom.write_byte(0x50D1, 0x80) rom.write_byte(0x51B0, 0x00) - elif world.required_medallions[0] == 'Quake': + elif world.required_medallions[player][0] == 'Quake': rom.write_byte(0x180022, 0x02) # requirement rom.write_byte(0x4FF2, 0x31) # sprite rom.write_byte(0x50D1, 0x88) rom.write_byte(0x51B0, 0x00) - if world.required_medallions[1] == 'Bombos': + if world.required_medallions[player][1] == 'Bombos': rom.write_byte(0x180023, 0x00) # requirement rom.write_byte(0x5020, 0x31) # sprite rom.write_byte(0x50FF, 0x90) rom.write_byte(0x51DE, 0x00) - elif world.required_medallions[1] == 'Ether': + elif world.required_medallions[player][1] == 'Ether': rom.write_byte(0x180023, 0x01) # requirement rom.write_byte(0x5020, 0x31) # sprite rom.write_byte(0x50FF, 0x98) @@ -408,8 +413,8 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) GREEN_TWENTY_RUPEES = 0x47 - TRIFORCE_PIECE = ItemFactory('Triforce Piece').code - GREEN_CLOCK = ItemFactory('Green Clock').code + TRIFORCE_PIECE = ItemFactory('Triforce Piece', player).code + GREEN_CLOCK = ItemFactory('Green Clock', player).code rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on # handle difficulty @@ -713,7 +718,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): # assorted fixes rom.write_byte(0x1800A2, 0x01) # remain in real dark world when dying in dark word dungion before killing aga1 rom.write_byte(0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence. - rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid else 0x00) # Enable respawning on pyramid after ganon death + rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death rom.write_byte(0x180173, 0x01) # Bob is enabled rom.write_byte(0x180168, 0x08) # Spike Cave Damage rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) #Set spike cave and MM spike room Cape usage @@ -801,18 +806,19 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) # patch swamp: Need to enable permanent drain of water as dam or swamp were moved - rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required else 0x00) + rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00) - # powder patch: remove the need to leave the scrren after powder, since it causes problems for potion shop at race game + # powder patch: remove the need to leave the screen after powder, since it causes problems for potion shop at race game # temporarally we are just nopping out this check we will conver this to a rom fix soon. - rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) + rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required[player] else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) # allow smith into multi-entrance caves in appropriate shuffles if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']: rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item - if world.get_location('Tower of Hera - Basement Cage').item is not None and world.get_location('Tower of Hera - Basement Cage').item.name == 'Small Key (Tower of Hera)': + hera_basement = world.get_location('Tower of Hera - Basement Cage', player) + if hera_basement.item is not None and hera_basement.item.name == 'Small Key (Tower of Hera)' and hera_basement.item.player == player: rom.write_byte(0x4E3BB, 0xE4) else: rom.write_byte(0x4E3BB, 0xEB) @@ -827,11 +833,13 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): rom.write_byte(0xFED31, 0x2A) # preopen bombable exit rom.write_byte(0xFEE41, 0x2A) # preopen bombable exit - write_strings(rom, world) + write_strings(rom, world, player) # set rom name # 21 bytes - rom.write_bytes(0x7FC0, bytearray('ER_062_%09d\0' % world.seed, 'utf8') + world.option_identifier.to_bytes(4, 'big')) + rom.name = bytearray('ER062%09d' % world.seed, 'utf8') + world.option_identifier(7, player).to_bytes(7, 'big') + assert(len(rom.name) == 21) + rom.write_bytes(0x7FC0, rom.name) # Write title screen Code hashint = int(rom.get_hash(), 16) @@ -848,8 +856,8 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): return rom -def write_custom_shops(rom, world): - shops = [shop for shop in world.shops if shop.replaceable and shop.active] +def write_custom_shops(rom, world, player): + shops = [shop for shop in world.shops if shop.replaceable and shop.active and shop.region.player == player] shop_data = bytearray() items_data = bytearray() @@ -870,7 +878,7 @@ def write_custom_shops(rom, world): for item in shop.inventory: if item is None: break - item_data = [shop_id, ItemFactory(item['item']).code] + int16_as_bytes(item['price']) + [item['max'], ItemFactory(item['replacement']).code if item['replacement'] else 0xFF] + int16_as_bytes(item['replacement_price']) + item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes(item['replacement_price']) items_data.extend(item_data) rom.write_bytes(0x184800, shop_data) @@ -1013,7 +1021,7 @@ def write_string_to_rom(rom, target, string): rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes)) -def write_strings(rom, world): +def write_strings(rom, world, player): tt = TextTable() tt.removeUnwantedText() @@ -1022,6 +1030,17 @@ def write_strings(rom, world): tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' + def hint_text(dest, ped_hint=False): + hint = dest.hint_text if not ped_hint else dest.pedestal_hint_text + if dest.player != player: + if ped_hint: + hint += " for p%d!" % dest.player + elif type(dest) in [Region, Location]: + hint += " in p%d's world" % dest.player + elif type(dest) is Item: + hint += " for p%d" % dest.player + return hint + # For hints, first we write hints about entrances, some from the inconvenient list others from all reasonable entrances. if world.hints: tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!' @@ -1031,16 +1050,17 @@ def write_strings(rom, world): entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'}) hint_locations = HintLocations.copy() random.shuffle(hint_locations) - all_entrances = world.get_entrances() + all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player] random.shuffle(all_entrances) - hint_count = 4 + hint_count = 4 if world.shuffle != 'vanilla' else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: - this_hint = entrances_to_hint[entrance.name] + ' leads to ' + entrance.connected_region.hint_text + '.' - tt[hint_locations.pop(0)] = this_hint - entrances_to_hint.pop(entrance.name) - hint_count -= 1 - if hint_count < 1: + if hint_count > 0: + this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' + tt[hint_locations.pop(0)] = this_hint + entrances_to_hint.pop(entrance.name) + hint_count -= 1 + else: break entrances_to_hint.update(OtherEntrances) @@ -1048,57 +1068,58 @@ def write_strings(rom, world): entrances_to_hint.update(InsanityEntrances) if world.shuffle_ganon: entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) - hint_count = 4 + hint_count = 4 if world.shuffle != 'vanilla' else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: - this_hint = entrances_to_hint[entrance.name] + ' leads to ' + entrance.connected_region.hint_text + '.' - tt[hint_locations.pop(0)] = this_hint - entrances_to_hint.pop(entrance.name) - hint_count -= 1 - if hint_count < 1: + if hint_count > 0: + this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' + tt[hint_locations.pop(0)] = this_hint + entrances_to_hint.pop(entrance.name) + hint_count -= 1 + else: break # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() random.shuffle(locations_to_hint) - hint_count = 3 - del locations_to_hint[hint_count:] + hint_count = 3 if world.shuffle != 'vanilla' else 4 + del locations_to_hint[hint_count:] for location in locations_to_hint: if location == 'Swamp Left': if random.randint(0, 1) == 0: - first_item = world.get_location('Swamp Palace - West Chest').item.hint_text - second_item = world.get_location('Swamp Palace - Big Key Chest').item.hint_text + first_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item) + second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item) else: - second_item = world.get_location('Swamp Palace - West Chest').item.hint_text - first_item = world.get_location('Swamp Palace - Big Key Chest').item.hint_text + second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item) + first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item) this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.') tt[hint_locations.pop(0)] = this_hint elif location == 'Mire Left': if random.randint(0, 1) == 0: - first_item = world.get_location('Misery Mire - Compass Chest').item.hint_text - second_item = world.get_location('Misery Mire - Big Key Chest').item.hint_text + first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item) + second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item) else: - second_item = world.get_location('Misery Mire - Compass Chest').item.hint_text - first_item = world.get_location('Misery Mire - Big Key Chest').item.hint_text + second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item) + first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item) this_hint = ('The westmost chests in Misery Mire contain ' + first_item + ' and ' + second_item + '.') tt[hint_locations.pop(0)] = this_hint elif location == 'Tower of Hera - Big Key Chest': - this_hint = 'Waiting in the Tower of Hera basement leads to ' + world.get_location(location).item.hint_text + '.' + this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text(world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Ganons Tower - Big Chest': - this_hint = 'The big chest in Ganon\'s Tower contains ' + world.get_location(location).item.hint_text + '.' + this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text(world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Thieves\' Town - Big Chest': - this_hint = 'The big chest in Thieves\' Town contains ' + world.get_location(location).item.hint_text + '.' + this_hint = 'The big chest in Thieves\' Town contains ' + hint_text(world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Ice Palace - Big Chest': - this_hint = 'The big chest in Ice Palace contains ' + world.get_location(location).item.hint_text + '.' + this_hint = 'The big chest in Ice Palace contains ' + hint_text(world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint elif location == 'Eastern Palace - Big Key Chest': - this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + world.get_location(location).item.hint_text + '.' + this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text(world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint else: - this_hint = location + ' leads to ' + world.get_location(location).item.hint_text + '.' + this_hint = location + ' leads to ' + hint_text(world.get_location(location, player).item) + '.' tt[hint_locations.pop(0)] = this_hint # Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well. @@ -1106,17 +1127,15 @@ def write_strings(rom, world): if world.keysanity: items_to_hint.extend(KeysanityItems) random.shuffle(items_to_hint) - hint_count = 5 - while(hint_count > 0): + hint_count = 5 if world.shuffle != 'vanilla' else 7 + while hint_count > 0: this_item = items_to_hint.pop(0) - this_location = world.find_items(this_item) + this_location = world.find_items(this_item, player) random.shuffle(this_location) if this_location: - this_hint = this_location[0].item.hint_text + ' can be found ' + this_location[0].hint_text + '.' + this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.' tt[hint_locations.pop(0)] = this_hint hint_count -= 1 - else: - continue # All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice. junk_hints = junk_texts.copy() @@ -1125,16 +1144,16 @@ def write_strings(rom, world): tt[location] = junk_hints.pop(0) # We still need the older hints of course. Those are done here. - silverarrows = world.find_items('Silver Arrows') + silverarrows = world.find_items('Silver Arrows', player) random.shuffle(silverarrows) - silverarrow_hint = (' %s?' % silverarrows[0].hint_text.replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' + silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' tt['ganon_phase_3'] = 'Did you find the silver arrows%s' % silverarrow_hint - crystal5 = world.find_items('Crystal 5')[0] - crystal6 = world.find_items('Crystal 6')[0] + crystal5 = world.find_items('Crystal 5', player)[0] + crystal6 = world.find_items('Crystal 6', player)[0] tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5.hint_text, crystal6.hint_text) - greenpendant = world.find_items('Green Pendant')[0] + greenpendant = world.find_items('Green Pendant', player)[0] tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text tt['uncle_leaving_text'] = Uncle_texts[random.randint(0, len(Uncle_texts) - 1)] @@ -1154,32 +1173,32 @@ def write_strings(rom, world): tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)] - pedestalitem = world.get_location('Master Sword Pedestal').item - pedestal_text = 'Some Hot Air' if pedestalitem is None else pedestalitem.pedestal_hint_text if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' + pedestalitem = world.get_location('Master Sword Pedestal', player).item + pedestal_text = 'Some Hot Air' if pedestalitem is None else hint_text(pedestalitem, True) if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' tt['mastersword_pedestal_translated'] = pedestal_text pedestal_credit_text = 'and the Hot Air' if pedestalitem is None else pedestalitem.pedestal_credit_text if pedestalitem.pedestal_credit_text is not None else 'and the Unknown Item' - etheritem = world.get_location('Ether Tablet').item - ether_text = 'Some Hot Air' if etheritem is None else etheritem.pedestal_hint_text if etheritem.pedestal_hint_text is not None else 'Unknown Item' + etheritem = world.get_location('Ether Tablet', player).item + ether_text = 'Some Hot Air' if etheritem is None else hint_text(etheritem, True) if etheritem.pedestal_hint_text is not None else 'Unknown Item' tt['tablet_ether_book'] = ether_text - bombositem = world.get_location('Bombos Tablet').item - bombos_text = 'Some Hot Air' if bombositem is None else bombositem.pedestal_hint_text if bombositem.pedestal_hint_text is not None else 'Unknown Item' + bombositem = world.get_location('Bombos Tablet', player).item + bombos_text = 'Some Hot Air' if bombositem is None else hint_text(bombositem, True) if bombositem.pedestal_hint_text is not None else 'Unknown Item' tt['tablet_bombos_book'] = bombos_text rom.write_bytes(0xE0000, tt.getBytes()) credits = Credits() - sickkiditem = world.get_location('Sick Kid').item + sickkiditem = world.get_location('Sick Kid', player).item sickkiditem_text = random.choice(SickKid_texts) if sickkiditem is None or sickkiditem.sickkid_credit_text is None else sickkiditem.sickkid_credit_text - zoraitem = world.get_location('King Zora').item + zoraitem = world.get_location('King Zora', player).item zoraitem_text = random.choice(Zora_texts) if zoraitem is None or zoraitem.zora_credit_text is None else zoraitem.zora_credit_text - magicshopitem = world.get_location('Potion Shop').item + magicshopitem = world.get_location('Potion Shop', player).item magicshopitem_text = random.choice(MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text - fluteboyitem = world.get_location('Flute Spot').item + fluteboyitem = world.get_location('Flute Spot', player).item fluteboyitem_text = random.choice(FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts)) diff --git a/Rules.py b/Rules.py index 93df6081..bf6f0a50 100644 --- a/Rules.py +++ b/Rules.py @@ -2,29 +2,29 @@ import collections import logging -def set_rules(world): +def set_rules(world, player): if world.logic == 'nologic': logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!') - world.get_region('Links House').can_reach = lambda state: True - world.get_region('Sanctuary').can_reach = lambda state: True - old_rule = world.get_region('Old Man House').can_reach - world.get_region('Old Man House').can_reach = lambda state: state.can_reach('Old Man', 'Location') or old_rule(state) + world.get_region('Links House', player).can_reach = lambda state: True + world.get_region('Sanctuary', player).can_reach = lambda state: True + old_rule = world.get_region('Old Man House', player).can_reach + world.get_region('Old Man House', player).can_reach = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) return - global_rules(world) + global_rules(world, player) if world.mode == 'open': - open_rules(world) + open_rules(world, player) elif world.mode == 'standard': - standard_rules(world) + standard_rules(world, player) elif world.mode == 'swordless': - swordless_rules(world) + swordless_rules(world, player) else: raise NotImplementedError('Not implemented yet') if world.logic == 'noglitches': - no_glitches_rules(world) + no_glitches_rules(world, player) elif world.logic == 'minorglitches': logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') else: @@ -32,18 +32,18 @@ def set_rules(world): if world.goal == 'dungeons': # require all dungeons to beat ganon - add_rule(world.get_location('Ganon'), lambda state: state.can_reach('Master Sword Pedestal', 'Location') and state.has('Beat Agahnim 1') and state.has('Beat Agahnim 2')) + add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player)) elif world.goal == 'ganon': # require aga2 to beat ganon - add_rule(world.get_location('Ganon'), lambda state: state.has('Beat Agahnim 2')) + add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) - set_big_bomb_rules(world) + set_big_bomb_rules(world, player) # if swamp and dam have not been moved we require mirror for swamp palace - if not world.swamp_patch_required: - add_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has_Mirror()) + if not world.swamp_patch_required[player]: + add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has_Mirror(player)) - set_bunny_rules(world) + set_bunny_rules(world, player) def set_rule(spot, rule): @@ -64,375 +64,380 @@ def add_rule(spot, rule, combine='and'): spot.access_rule = lambda state: rule(state) and old_rule(state) -def add_lamp_requirement(spot): - add_rule(spot, lambda state: state.has('Lamp', state.world.lamps_needed_for_dark_rooms)) +def add_lamp_requirement(spot, player): + add_rule(spot, lambda state: state.has('Lamp', player, state.world.lamps_needed_for_dark_rooms)) -def forbid_item(location, item): +def forbid_item(location, item, player): old_rule = location.item_rule - location.item_rule = lambda i: i.name != item and old_rule(i) + location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i) -def item_in_locations(state, item, locations): +def item_in_locations(state, item, player, locations): for location in locations: - if item_name(state, location) == item: + if item_name(state, location[0], location[1]) == (item, player): return True return False -def item_name(state, location): - location = state.world.get_location(location) +def item_name(state, location, player): + location = state.world.get_location(location, player) if location.item is None: return None - return location.item.name + return (location.item.name, location.item.player) -def global_rules(world): +def global_rules(world, player): + if world.goal == 'triforcehunt': + for location in world.get_locations(): + if location.player != player: + forbid_item(location, 'Triforce Piece', player) + # ganon can only carry triforce - world.get_location('Ganon').item_rule = lambda item: item.name == 'Triforce' + world.get_location('Ganon', player).item_rule = lambda item: item.name == 'Triforce' and item.player == player # these are default save&quit points and always accessible - world.get_region('Links House').can_reach = lambda state: True - world.get_region('Sanctuary').can_reach = lambda state: True + world.get_region('Links House', player).can_reach = lambda state: True + world.get_region('Sanctuary', player).can_reach = lambda state: True # we can s&q to the old man house after we rescue him. This may be somewhere completely different if caves are shuffled! - old_rule = world.get_region('Old Man House').can_reach - world.get_region('Old Man House').can_reach = lambda state: state.can_reach('Old Man', 'Location') or old_rule(state) + old_rule = world.get_region('Old Man House', player).can_reach + world.get_region('Old Man House', player).can_reach = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) # overworld requirements - set_rule(world.get_entrance('Kings Grave'), lambda state: state.has_Boots()) - set_rule(world.get_entrance('Kings Grave Outer Rocks'), lambda state: state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Kings Grave Inner Rocks'), lambda state: state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Kings Grave Mirror Spot'), lambda state: state.has_Pearl() and state.has_Mirror()) + set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Kings Grave Inner Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Kings Grave Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) # Caution: If king's grave is releaxed at all to account for reaching it via a two way cave's exit in insanity mode, then the bomb shop logic will need to be updated (that would involve create a small ledge-like Region for it) - set_rule(world.get_entrance('Bonk Fairy (Light)'), lambda state: state.has_Boots()) - set_rule(world.get_location('Sunken Treasure'), lambda state: state.can_reach('Dam')) - set_rule(world.get_entrance('Bat Cave Drop Ledge'), lambda state: state.has('Hammer')) - set_rule(world.get_entrance('Lumberjack Tree Tree'), lambda state: state.has_Boots() and state.has('Beat Agahnim 1')) - set_rule(world.get_entrance('Bonk Rock Cave'), lambda state: state.has_Boots()) - set_rule(world.get_entrance('Desert Palace Stairs'), lambda state: state.has('Book of Mudora')) - set_rule(world.get_entrance('Sanctuary Grave'), lambda state: state.can_lift_rocks()) - set_rule(world.get_entrance('20 Rupee Cave'), lambda state: state.can_lift_rocks()) - set_rule(world.get_entrance('50 Rupee Cave'), lambda state: state.can_lift_rocks()) - set_rule(world.get_entrance('Death Mountain Entrance Rock'), lambda state: state.can_lift_rocks()) - set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Flute Spot 1'), lambda state: state.has('Ocarina')) - set_rule(world.get_entrance('Lake Hylia Central Island Teleporter'), lambda state: state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Dark Desert Teleporter'), lambda state: state.has('Ocarina') and state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('East Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer - set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer - set_rule(world.get_entrance('Kakariko Teleporter'), lambda state: ((state.has('Hammer') and state.can_lift_rocks()) or state.can_lift_heavy_rocks()) and state.has_Pearl()) # bunny cannot lift bushes - set_rule(world.get_location('Flute Spot'), lambda state: state.has('Shovel')) - set_rule(world.get_location('Dark Blacksmith Ruins'), lambda state: state.has('Return Smith')) - set_rule(world.get_location('Purple Chest'), lambda state: state.has('Pick Up Purple Chest')) # Can S&Q with chest + set_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has_Boots(player)) + set_rule(world.get_location('Sunken Treasure', player), lambda state: state.can_reach('Dam', 'Region', player)) + set_rule(world.get_entrance('Bat Cave Drop Ledge', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Lumberjack Tree Tree', player), lambda state: state.has_Boots(player) and state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('Bonk Rock Cave', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Desert Palace Stairs', player), lambda state: state.has('Book of Mudora', player)) + set_rule(world.get_entrance('Sanctuary Grave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('20 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Death Mountain Entrance Rock', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.has('Ocarina', player)) + set_rule(world.get_entrance('Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.has('Ocarina', player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer + set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes + set_rule(world.get_location('Flute Spot', player), lambda state: state.has('Shovel', player)) + set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) + set_rule(world.get_location('Purple Chest', player), lambda state: state.has('Pick Up Purple Chest', player)) # Can S&Q with chest - set_rule(world.get_location('Zora\'s Ledge'), lambda state: state.has('Flippers')) - set_rule(world.get_entrance('Waterfall of Wishing'), lambda state: state.has('Flippers')) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo - set_rule(world.get_location('Frog'), lambda state: state.can_lift_heavy_rocks()) # will get automatic moon pearl requirement - set_rule(world.get_location('Missing Smith'), lambda state: state.has('Get Frog')) # Can S&Q with smith - set_rule(world.get_location('Blacksmith'), lambda state: state.has('Return Smith')) - set_rule(world.get_location('Magic Bat'), lambda state: state.has('Magic Powder')) - set_rule(world.get_location('Sick Kid'), lambda state: state.has_bottle()) - set_rule(world.get_location('Library'), lambda state: state.has_Boots()) - set_rule(world.get_location('Potion Shop'), lambda state: state.has('Mushroom')) - set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks'), lambda state: state.can_lift_rocks()) - set_rule(world.get_entrance('Desert Ledge Return Rocks'), lambda state: state.can_lift_rocks()) # should we decide to place something that is not a dungeon end up there at some point - set_rule(world.get_entrance('Checkerboard Cave'), lambda state: state.can_lift_rocks()) - set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant')) - set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant')) - set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle - set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has_key('Small Key (Agahnims Tower)', 2)) - set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1')) - set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has_key('Small Key (Agahnims Tower)')) - set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1')) - set_rule(world.get_entrance('Old Man Cave Exit (West)'), lambda state: False) # drop cannot be climbed up - set_rule(world.get_entrance('Broken Bridge (West)'), lambda state: state.has('Hookshot')) - set_rule(world.get_entrance('Broken Bridge (East)'), lambda state: state.has('Hookshot')) - set_rule(world.get_entrance('East Death Mountain Teleporter'), lambda state: state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Fairy Ascension Rocks'), lambda state: state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Paradox Cave Push Block Reverse'), lambda state: state.has('Mirror')) # can erase block - set_rule(world.get_entrance('Death Mountain (Top)'), lambda state: state.has('Hammer')) - set_rule(world.get_entrance('Turtle Rock Teleporter'), lambda state: state.can_lift_heavy_rocks() and state.has('Hammer')) - set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has_beam_sword()) - set_rule(world.get_entrance('East Death Mountain (Top)'), lambda state: state.has('Hammer')) + set_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Flippers', player)) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo + set_rule(world.get_location('Frog', player), lambda state: state.can_lift_heavy_rocks(player)) # will get automatic moon pearl requirement + set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player)) # Can S&Q with smith + set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player)) + set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player)) + set_rule(world.get_location('Sick Kid', player), lambda state: state.has_bottle(player)) + set_rule(world.get_location('Library', player), lambda state: state.has_Boots(player)) + set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player)) + set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Desert Ledge Return Rocks', player), lambda state: state.can_lift_rocks(player)) # should we decide to place something that is not a dungeon end up there at some point + set_rule(world.get_entrance('Checkerboard Cave', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) + set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle + set_rule(world.get_entrance('Agahnim 1', player), lambda state: state.has_sword(player) and state.has_key('Small Key (Agahnims Tower)', player, 2)) + set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) + set_rule(world.get_location('Castle Tower - Dark Maze', player), lambda state: state.has_key('Small Key (Agahnims Tower)', player)) + set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has('Beat Agahnim 1', player)) + set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up + set_rule(world.get_entrance('Broken Bridge (West)', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Broken Bridge (East)', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('East Death Mountain Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Fairy Ascension Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has('Mirror', player)) # can erase block + set_rule(world.get_entrance('Death Mountain (Top)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Turtle Rock Teleporter', player), lambda state: state.can_lift_heavy_rocks(player) and state.has('Hammer', player)) + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player)) + set_rule(world.get_entrance('East Death Mountain (Top)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_location('Catfish'), lambda state: state.can_lift_rocks()) - set_rule(world.get_entrance('Northeast Dark World Broken Bridge Pass'), lambda state: state.has_Pearl() and (state.can_lift_rocks() or state.has('Hammer') or state.has('Flippers'))) - set_rule(world.get_entrance('East Dark World Broken Bridge Pass'), lambda state: state.has_Pearl() and (state.can_lift_rocks() or state.has('Hammer'))) - set_rule(world.get_entrance('South Dark World Bridge'), lambda state: state.has('Hammer') and state.has_Pearl()) - set_rule(world.get_entrance('Bonk Fairy (Dark)'), lambda state: state.has_Pearl() and state.has_Boots()) - set_rule(world.get_entrance('West Dark World Gap'), lambda state: state.has_Pearl() and state.has('Hookshot')) - set_rule(world.get_entrance('Palace of Darkness'), lambda state: state.has_Pearl()) # kiki needs pearl - set_rule(world.get_entrance('Hyrule Castle Ledge Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Hyrule Castle Main Gate'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Dark Lake Hylia Drop (East)'), lambda state: (state.has_Pearl() and state.has('Flippers') or state.has_Mirror())) # Overworld Bunny Revival - set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has_beam_sword() and state.has_Mirror()) - set_rule(world.get_entrance('Dark Lake Hylia Drop (South)'), lambda state: state.has_Pearl() and state.has('Flippers')) # ToDo any fake flipper set up? - set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy'), lambda state: state.has_Pearl()) # bomb required - set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave'), lambda state: state.can_lift_rocks() and state.has_Pearl()) - set_rule(world.get_entrance('Dark Lake Hylia Teleporter'), lambda state: state.has_Pearl() and (state.has('Hammer') or state.can_lift_rocks())) # Fake Flippers - set_rule(world.get_entrance('Village of Outcasts Heavy Rock'), lambda state: state.has_Pearl() and state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Hype Cave'), lambda state: state.has_Pearl()) # bomb required - set_rule(world.get_entrance('Brewery'), lambda state: state.has_Pearl()) # bomb required - set_rule(world.get_entrance('Thieves Town'), lambda state: state.has_Pearl()) # bunny cannot pull - set_rule(world.get_entrance('Skull Woods First Section Hole (North)'), lambda state: state.has_Pearl()) # bunny cannot lift bush - set_rule(world.get_entrance('Skull Woods Second Section Hole'), lambda state: state.has_Pearl()) # bunny cannot lift bush - set_rule(world.get_entrance('Maze Race Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Cave 45 Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('East Dark World Bridge'), lambda state: state.has_Pearl() and state.has('Hammer')) - set_rule(world.get_entrance('Lake Hylia Island Mirror Spot'), lambda state: state.has_Pearl() and state.has_Mirror() and state.has('Flippers')) - set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('East Dark World River Pier'), lambda state: state.has_Pearl() and state.has('Flippers')) # ToDo any fake flipper set up? - set_rule(world.get_entrance('Graveyard Ledge Mirror Spot'), lambda state: state.has_Pearl() and state.has_Mirror()) - set_rule(world.get_entrance('Bumper Cave Entrance Rock'), lambda state: state.has_Pearl() and state.can_lift_rocks()) - set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Bat Cave Drop Ledge Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Dark World Hammer Peg Cave'), lambda state: state.has_Pearl() and state.has('Hammer')) - set_rule(world.get_entrance('Village of Outcasts Eastern Rocks'), lambda state: state.has_Pearl() and state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Peg Area Rocks'), lambda state: state.has_Pearl() and state.can_lift_heavy_rocks()) - set_rule(world.get_entrance('Village of Outcasts Pegs'), lambda state: state.has_Pearl() and state.has('Hammer')) - set_rule(world.get_entrance('Grassy Lawn Pegs'), lambda state: state.has_Pearl() and state.has('Hammer')) - set_rule(world.get_entrance('Bumper Cave Exit (Top)'), lambda state: state.has('Cape')) - set_rule(world.get_entrance('Bumper Cave Exit (Bottom)'), lambda state: state.has('Cape') or state.has('Hookshot')) + set_rule(world.get_location('Catfish', player), lambda state: state.can_lift_rocks(player)) + set_rule(world.get_entrance('Northeast Dark World Broken Bridge Pass', player), lambda state: state.has_Pearl(player) and (state.can_lift_rocks(player) or state.has('Hammer', player) or state.has('Flippers', player))) + set_rule(world.get_entrance('East Dark World Broken Bridge Pass', player), lambda state: state.has_Pearl(player) and (state.can_lift_rocks(player) or state.has('Hammer', player))) + set_rule(world.get_entrance('South Dark World Bridge', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Bonk Fairy (Dark)', player), lambda state: state.has_Pearl(player) and state.has_Boots(player)) + set_rule(world.get_entrance('West Dark World Gap', player), lambda state: state.has_Pearl(player) and state.has('Hookshot', player)) + set_rule(world.get_entrance('Palace of Darkness', player), lambda state: state.has_Pearl(player)) # kiki needs pearl + set_rule(world.get_entrance('Hyrule Castle Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Hyrule Castle Main Gate', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: (state.has_Pearl(player) and state.has('Flippers', player) or state.has_Mirror(player))) # Overworld Bunny Revival + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has_beam_sword(player) and state.has_Mirror(player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (South)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # ToDo any fake flipper set up? + set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has_Pearl(player)) # bomb required + set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) + set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) # Fake Flippers + set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.has_Pearl(player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has_Pearl(player)) # bomb required + set_rule(world.get_entrance('Brewery', player), lambda state: state.has_Pearl(player)) # bomb required + set_rule(world.get_entrance('Thieves Town', player), lambda state: state.has_Pearl(player)) # bunny cannot pull + set_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), lambda state: state.has_Pearl(player)) # bunny cannot lift bush + set_rule(world.get_entrance('Skull Woods Second Section Hole', player), lambda state: state.has_Pearl(player)) # bunny cannot lift bush + set_rule(world.get_entrance('Maze Race Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Cave 45 Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('East Dark World Bridge', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) + set_rule(world.get_entrance('Lake Hylia Island Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Lake Hylia Central Island Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('East Dark World River Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # ToDo any fake flipper set up? + set_rule(world.get_entrance('Graveyard Ledge Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) + set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.has_Pearl(player) and state.can_lift_rocks(player)) + set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Bat Cave Drop Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Dark World Hammer Peg Cave', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) + set_rule(world.get_entrance('Village of Outcasts Eastern Rocks', player), lambda state: state.has_Pearl(player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Peg Area Rocks', player), lambda state: state.has_Pearl(player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Village of Outcasts Pegs', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) + set_rule(world.get_entrance('Grassy Lawn Pegs', player), lambda state: state.has_Pearl(player) and state.has('Hammer', player)) + set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player)) + set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) - set_rule(world.get_entrance('Skull Woods Final Section'), lambda state: state.has('Fire Rod') and state.has_Pearl()) # bunny cannot use fire rod - set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_sword() and state.has_misery_mire_medallion()) # sword required to cast magic (!) - set_rule(world.get_entrance('Desert Ledge (Northeast) Mirror Spot'), lambda state: state.has_Mirror()) + set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player) and state.has_Pearl(player)) # bunny cannot use fire rod + set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!) + set_rule(world.get_entrance('Desert Ledge (Northeast) Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Desert Ledge Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Desert Palace Stairs Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Desert Palace Entrance (North) Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Spectacle Rock Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Hookshot Cave'), lambda state: state.can_lift_rocks() and state.has_Pearl()) + set_rule(world.get_entrance('Desert Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Desert Palace Stairs Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Desert Palace Entrance (North) Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Spectacle Rock Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Hookshot Cave', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) - set_rule(world.get_entrance('East Death Mountain (Top) Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Mimic Cave Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Spiral Cave Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Fairy Ascension Mirror Spot'), lambda state: state.has_Mirror() and state.has_Pearl()) # need to lift flowers - set_rule(world.get_entrance('Isolated Ledge Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling + set_rule(world.get_entrance('East Death Mountain (Top) Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Mimic Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Spiral Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Fairy Ascension Mirror Spot', player), lambda state: state.has_Mirror(player) and state.has_Pearl(player)) # need to lift flowers + set_rule(world.get_entrance('Isolated Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling - set_rule(world.get_location('Spike Cave'), lambda state: - state.has('Hammer') and state.can_lift_rocks() and - ((state.has('Cape') and state.can_extend_magic(16, True)) or - (state.has('Cane of Byrna') and - (state.can_extend_magic(12, True) or - (state.world.can_take_damage and (state.has_Boots() or state.has_hearts(4)))))) + set_rule(world.get_location('Spike Cave', player), lambda state: + state.has('Hammer', player) and state.can_lift_rocks(player) and + ((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or + (state.has('Cane of Byrna', player) and + (state.can_extend_magic(player, 12, True) or + (state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4)))))) ) - set_rule(world.get_location('Hookshot Cave - Top Right'), lambda state: state.has('Hookshot')) - set_rule(world.get_location('Hookshot Cave - Top Left'), lambda state: state.has('Hookshot')) - set_rule(world.get_location('Hookshot Cave - Bottom Right'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots')) - set_rule(world.get_location('Hookshot Cave - Bottom Left'), lambda state: state.has('Hookshot')) - set_rule(world.get_entrance('Floating Island Mirror Spot'), lambda state: state.has_Mirror()) - set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword required to cast magic (!) - set_rule(world.get_location('Mimic Cave'), lambda state: state.has('Hammer')) + set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_location('Hookshot Cave - Top Left', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player)) + set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) + set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Sewers Door'), lambda state: state.has_key('Small Key (Escape)')) - set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has_key('Small Key (Escape)')) + set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player)) + set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player)) - set_rule(world.get_location('Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)')) - set_rule(world.get_location('Eastern Palace - Boss'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)') and world.get_location('Eastern Palace - Boss').parent_region.dungeon.boss.can_defeat(state)) - set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)') and world.get_location('Eastern Palace - Prize').parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) + set_rule(world.get_location('Eastern Palace - Boss', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Eastern Palace - Prize', player), lambda state: state.can_shoot_arrows(player) and state.has('Big Key (Eastern Palace)', player) and world.get_location('Eastern Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) for location in ['Eastern Palace - Boss', 'Eastern Palace - Big Chest']: - forbid_item(world.get_location(location), 'Big Key (Eastern Palace)') + forbid_item(world.get_location(location, player), 'Big Key (Eastern Palace)', player) - set_rule(world.get_location('Desert Palace - Big Chest'), lambda state: state.has('Big Key (Desert Palace)')) - set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots()) - set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has_key('Small Key (Desert Palace)')) - set_rule(world.get_location('Desert Palace - Prize'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and world.get_location('Desert Palace - Prize').parent_region.dungeon.boss.can_defeat(state)) - set_rule(world.get_location('Desert Palace - Boss'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and world.get_location('Desert Palace - Boss').parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) + set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state.has_key('Small Key (Desert Palace)', player)) + set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and world.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state.has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and world.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) for location in ['Desert Palace - Boss', 'Desert Palace - Big Chest']: - forbid_item(world.get_location(location), 'Big Key (Desert Palace)') + forbid_item(world.get_location(location, player), 'Big Key (Desert Palace)', player) for location in ['Desert Palace - Boss', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']: - forbid_item(world.get_location(location), 'Small Key (Desert Palace)') + forbid_item(world.get_location(location, player), 'Small Key (Desert Palace)', player) - set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has_key('Small Key (Tower of Hera)') or item_name(state, 'Tower of Hera - Big Key Chest') == 'Small Key (Tower of Hera)') - set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)')) - set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)')) - set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source()) - set_always_allow(world.get_location('Tower of Hera - Big Key Chest'), lambda state, item: item.name == 'Small Key (Tower of Hera)') - set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize')) + set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state.has_key('Small Key (Tower of Hera)', player) or item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player)) + set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player)) + set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) + set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player)) + set_always_allow(world.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) + set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player)) for location in ['Tower of Hera - Boss', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']: - forbid_item(world.get_location(location), 'Big Key (Tower of Hera)') + forbid_item(world.get_location(location, player), 'Big Key (Tower of Hera)', player) # for location in ['Tower of Hera - Big Key Chest']: -# forbid_item(world.get_location(location), 'Small Key (Tower of Hera)') +# forbid_item(world.get_location(location, player), 'Small Key (Tower of Hera)', player) - set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.has('Open Floodgate')) - add_rule(world.get_location('Sunken Treasure'), lambda state: state.has('Open Floodgate')) + set_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) + add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) - set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has_key('Small Key (Swamp Palace)')) - set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer')) - set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)') - set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)') - set_rule(world.get_entrance('Swamp Palace (North)'), lambda state: state.has('Hookshot')) - set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize')) + set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state.has_key('Small Key (Swamp Palace)', player)) + set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player) or item_name(state, 'Swamp Palace - Big Chest', player) == ('Big Key (Swamp Palace)', player)) + set_always_allow(world.get_location('Swamp Palace - Big Chest', player), lambda state, item: item.name == 'Big Key (Swamp Palace)' and item.player == player) + set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player)) + set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', player)) for location in ['Swamp Palace - Entrance']: - forbid_item(world.get_location(location), 'Big Key (Swamp Palace)') + forbid_item(world.get_location(location, player), 'Big Key (Swamp Palace)', player) - set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)')) - set_rule(world.get_entrance('Blind Fight'), lambda state: state.has_key('Small Key (Thieves Town)')) - set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Prize')) - set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has_key('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer')) - set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)' and state.has('Hammer')) - set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has_key('Small Key (Thieves Town)')) + set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) + set_rule(world.get_entrance('Blind Fight', player), lambda state: state.has_key('Small Key (Thieves Town)', player)) + set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Prize', player)) + set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has_key('Small Key (Thieves Town)', player) or item_name(state, 'Thieves\' Town - Big Chest', player) == ('Small Key (Thieves Town)', player)) and state.has('Hammer', player)) + set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player and state.has('Hammer', player)) + set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state.has_key('Small Key (Thieves Town)', player)) for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: - forbid_item(world.get_location(location), 'Big Key (Thieves Town)') + forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player) for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Boss']: - forbid_item(world.get_location(location), 'Small Key (Thieves Town)') + forbid_item(world.get_location(location, player), 'Small Key (Thieves Town)', player) - set_rule(world.get_entrance('Skull Woods First Section South Door'), lambda state: state.has_key('Small Key (Skull Woods)')) - set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has_key('Small Key (Skull Woods)')) - set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has_key('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section - set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has_key('Small Key (Skull Woods)', 2)) - set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(state, 'Skull Woods - Big Chest') == 'Big Key (Skull Woods)') - set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)') - set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain - set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize')) + set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player)) + set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player)) + set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section + set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) + set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) or item_name(state, 'Skull Woods - Big Chest', player) == ('Big Key (Skull Woods)', player)) + set_always_allow(world.get_location('Skull Woods - Big Chest', player), lambda state, item: item.name == 'Big Key (Skull Woods)' and item.player == player) + set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player) and state.has_sword(player)) # sword required for curtain + set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) for location in ['Skull Woods - Boss']: - forbid_item(world.get_location(location), 'Small Key (Skull Woods)') + forbid_item(world.get_location(location, player), 'Small Key (Skull Woods)', player) - set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword())) - set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)')) - set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has_key('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has_key('Small Key (Ice Palace)', 1)))) + set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or (state.has('Bombos', player) and state.has_sword(player))) + set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) + set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state.has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state.has_key('Small Key (Ice Palace)', player, 1)))) # TODO: investigate change from VT. Changed to hookshot or 2 keys (no checking for big key in specific chests) - set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has_key('Small Key (Ice Palace)'))) and (state.world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna'))) - set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) - set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize')) + set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or (item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state.has_key('Small Key (Ice Palace)', player))) and (state.world.can_take_damage or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player))) + set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) + set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player)) for location in ['Ice Palace - Big Chest', 'Ice Palace - Boss']: - forbid_item(world.get_location(location), 'Big Key (Ice Palace)') + forbid_item(world.get_location(location, player), 'Big Key (Ice Palace)', player) - set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.can_shoot_arrows())) # need to defeat wizzrobes, bombs don't work ... - set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)')) - set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape')) - set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)')) + set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has_Boots(player) or state.has('Hookshot', player)) and (state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player))) # need to defeat wizzrobes, bombs don't work ... + set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player)) + set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) + set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player)) # you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ... # big key gives backdoor access to that from the teleporter in the north west - set_rule(world.get_location('Misery Mire - Map Chest'), lambda state: state.has_key('Small Key (Misery Mire)', 1) or state.has('Big Key (Misery Mire)')) + set_rule(world.get_location('Misery Mire - Map Chest', player), lambda state: state.has_key('Small Key (Misery Mire)', player, 1) or state.has('Big Key (Misery Mire)', player)) # in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key... - set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has_key('Small Key (Misery Mire)', 2) or state.has_key('Big Key (Misery Mire)')) + set_rule(world.get_location('Misery Mire - Main Lobby', player), lambda state: state.has_key('Small Key (Misery Mire)', player, 2) or state.has_key('Big Key (Misery Mire)', player)) # we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet - set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has_key('Small Key (Misery Mire)', 2) if ((item_name(state, 'Misery Mire - Compass Chest') in ['Big Key (Misery Mire)']) or - (item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has_key('Small Key (Misery Mire)', 3)) - set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source()) - set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source()) - set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria')) - set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize')) + set_rule(world.get_entrance('Misery Mire (West)', player), lambda state: state.has_key('Small Key (Misery Mire)', player, 2) if ((item_name(state, 'Misery Mire - Compass Chest', player) in [('Big Key (Misery Mire)', player)]) or + (item_name(state, 'Misery Mire - Big Key Chest', player) in [('Big Key (Misery Mire)', player)])) else state.has_key('Small Key (Misery Mire)', player, 3)) + set_rule(world.get_location('Misery Mire - Compass Chest', player), lambda state: state.has_fire_source(player)) + set_rule(world.get_location('Misery Mire - Big Key Chest', player), lambda state: state.has_fire_source(player)) + set_rule(world.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player)) + set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Misery Mire - Prize', player)) for location in ['Misery Mire - Big Chest', 'Misery Mire - Boss']: - forbid_item(world.get_location(location), 'Big Key (Misery Mire)') + forbid_item(world.get_location(location, player), 'Big Key (Misery Mire)', player) - set_rule(world.get_entrance('Turtle Rock Entrance Gap'), lambda state: state.has('Cane of Somaria')) - set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse'), lambda state: state.has('Cane of Somaria')) - set_rule(world.get_location('Turtle Rock - Compass Chest'), lambda state: state.has('Cane of Somaria')) # We could get here from the middle section without Cane as we don't cross the entrance gap! - set_rule(world.get_location('Turtle Rock - Roller Room - Left'), lambda state: state.has('Cane of Somaria') and state.has('Fire Rod')) - set_rule(world.get_location('Turtle Rock - Roller Room - Right'), lambda state: state.has('Cane of Somaria') and state.has('Fire Rod')) - set_rule(world.get_location('Turtle Rock - Big Chest'), lambda state: state.has('Big Key (Turtle Rock)') and (state.has('Cane of Somaria') or state.has('Hookshot'))) - set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)'), lambda state: state.has('Cane of Somaria') or state.has('Hookshot')) - set_rule(world.get_entrance('Turtle Rock Big Key Door'), lambda state: state.has('Big Key (Turtle Rock)')) - set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria')) - set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria')) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) - set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria')) - set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize')) + set_rule(world.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player)) # We could get here from the middle section without Cane as we don't cross the entrance gap! + set_rule(world.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + set_rule(world.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) + set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player))) + set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) + set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player)) + set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 4) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) + set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize', player)) - set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.can_shoot_arrows()) - set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer')) - set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has_key('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area - set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.can_shoot_arrows() and state.has('Hammer')) - set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has_key('Small Key (Palace of Darkness)', 4)) - set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) + set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: state.can_shoot_arrows(player)) + set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player)) + set_rule(world.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area + set_rule(world.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and state.can_shoot_arrows(player) and state.has('Hammer', player)) + set_rule(world.get_entrance('Palace of Darkness (North)', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 4)) + set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player)) - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Big Key Chest') in ['Small Key (Palace of Darkness)'] and state.has_key('Small Key (Palace of Darkness)', 3))) - set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has_key('Small Key (Palace of Darkness)', 5)) + set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 3))) + set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5)) - set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway') in ['Small Key (Palace of Darkness)'] and state.has_key('Small Key (Palace of Darkness)', 4))) - set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has_key('Small Key (Palace of Darkness)', 5)) - set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6)) - set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss')) - set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize')) + set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 4))) + set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5)) + set_rule(world.get_entrance('Palace of Darkness Maze Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6)) + set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss', player)) + set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player)) # these key rules are conservative, you might be able to get away with more lenient rules randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'] - set_rule(world.get_location('Ganons Tower - Bob\'s Torch'), lambda state: state.has_Boots()) - set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria')) - set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer')) + set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has_Boots(player)) + set_rule(world.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player)) - set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 4) or (item_name(state, 'Ganons Tower - Map Chest') in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has_key('Small Key (Ganons Tower)', 3))) - set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has_key('Small Key (Ganons Tower)', 3)) + set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4) or (item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player), ('Small Key (Ganons Tower)', player)] and state.has_key('Small Key (Ganons Tower)', player, 3))) + set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state.has_key('Small Key (Ganons Tower)', player, 3)) # It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements. # However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos. - set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 2)) + set_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 2)) # It is possible to need more than 3 keys .... - set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 3)) + set_rule(world.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3)) #The actual requirements for these rooms to avoid key-lock - set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has_key('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has_key('Small Key (Ganons Tower)', 2))) + set_rule(world.get_location('Ganons Tower - Firesnake Room', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state.has_key('Small Key (Ganons Tower)', player, 2))) for location in randomizer_room_chests: - set_rule(world.get_location(location), lambda state: state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has_key('Small Key (Ganons Tower)', 3))) + set_rule(world.get_location(location, player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state.has_key('Small Key (Ganons Tower)', player, 3))) # Once again it is possible to need more than 3 keys... - set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 3) and state.has('Fire Rod')) + set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3) and state.has('Fire Rod', player)) # Actual requirements for location in compass_room_chests: - set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has_key('Small Key (Ganons Tower)', 3)))) + set_rule(world.get_location(location, player), lambda state: state.has('Fire Rod', player) and (state.has_key('Small Key (Ganons Tower)', player, 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state.has_key('Small Key (Ganons Tower)', player, 3)))) - set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)')) + set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player)) - set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: world.get_location('Ganons Tower - Big Key Room - Left').parent_region.dungeon.bosses['bottom'].can_defeat(state)) - set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: world.get_location('Ganons Tower - Big Key Chest').parent_region.dungeon.bosses['bottom'].can_defeat(state)) - set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: world.get_location('Ganons Tower - Big Key Room - Right').parent_region.dungeon.bosses['bottom'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player), lambda state: world.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Big Key Chest', player), lambda state: world.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player), lambda state: world.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) - set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.can_shoot_arrows()) - set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source() and world.get_entrance('Ganons Tower Torch Rooms').parent_region.dungeon.bosses['middle'].can_defeat(state)) - set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has_key('Small Key (Ganons Tower)', 3)) - set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 4)) - set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot') and world.get_entrance('Ganons Tower Moldorm Gap').parent_region.dungeon.bosses['top'].can_defeat(state)) - set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2')) - set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2')) + set_rule(world.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player) and state.can_shoot_arrows(player)) + set_rule(world.get_entrance('Ganons Tower Torch Rooms', player), lambda state: state.has_fire_source(player) and world.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 3)) + set_rule(world.get_entrance('Ganons Tower Moldorm Door', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4)) + set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state)) + set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) + set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player)) for location in ['Ganons Tower - Big Chest', 'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right', 'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']: - forbid_item(world.get_location(location), 'Big Key (Ganons Tower)') + forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player) - set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2') - and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7') - and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.can_shoot_arrows()) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times - set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop + set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has('Crystal 1', player) and state.has('Crystal 2', player) + and state.has('Crystal 3', player) and state.has('Crystal 4', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player) and state.has('Crystal 7', player) + and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times + set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop - set_rule(world.get_entrance('Ganons Tower'), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. + set_rule(world.get_entrance('Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. - set_trock_key_rules(world) + set_trock_key_rules(world, player) - set_rule(world.get_entrance('Ganons Tower'), lambda state: state.has('Crystal 1') and state.has('Crystal 2') and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')) + set_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has('Crystal 1', player) and state.has('Crystal 2', player) and state.has('Crystal 3', player) and state.has('Crystal 4', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player) and state.has('Crystal 7', player)) -def no_glitches_rules(world): - set_rule(world.get_entrance('Zoras River'), lambda state: state.has('Flippers') or state.can_lift_rocks()) - set_rule(world.get_entrance('Lake Hylia Central Island Pier'), lambda state: state.has('Flippers')) # can be fake flippered to - set_rule(world.get_entrance('Hobo Bridge'), lambda state: state.has('Flippers')) - set_rule(world.get_entrance('Dark Lake Hylia Drop (East)'), lambda state: state.has_Pearl() and state.has('Flippers')) - set_rule(world.get_entrance('Dark Lake Hylia Teleporter'), lambda state: state.has_Pearl() and state.has('Flippers') and (state.has('Hammer') or state.can_lift_rocks())) - set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop'), lambda state: state.has_Pearl() and state.has('Flippers')) - add_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hookshot') or state.has_Boots()) - add_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has('Hookshot')) +def no_glitches_rules(world, player): + set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) + set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to + set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) + add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) + add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player)) DMs_room_chests = ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'] for location in DMs_room_chests: - add_rule(world.get_location(location), lambda state: state.has('Hookshot')) - set_rule(world.get_entrance('Paradox Cave Push Block Reverse'), lambda state: False) # no glitches does not require block override - set_rule(world.get_entrance('Paradox Cave Bomb Jump'), lambda state: False) - set_rule(world.get_entrance('Skull Woods First Section Bomb Jump'), lambda state: False) + add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override + set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) + set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False) # Light cones in standard depend on which world we actually are in, not which one the location would normally be # We add Lamp requirements only to those locations which lie in the dark world (or everything if open @@ -447,11 +452,11 @@ def no_glitches_rules(world): def add_conditional_lamp(spot, region, spottype='Location'): if spottype == 'Location': - spot = world.get_location(spot) + spot = world.get_location(spot, player) else: - spot = world.get_entrance(spot) - if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region))) or (not world.light_world_light_cone and not check_is_dark_world(world.get_region(region))): - add_lamp_requirement(spot) + spot = world.get_entrance(spot, player) + if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region, player))) or (not world.light_world_light_cone and not check_is_dark_world(world.get_region(region, player))): + add_lamp_requirement(spot, player) add_conditional_lamp('Misery Mire (Vitreous)', 'Misery Mire (Entrance)', 'Entrance') add_conditional_lamp('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Entrance)', 'Entrance') @@ -473,79 +478,79 @@ def no_glitches_rules(world): add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location') if not world.sewer_light_cone: - add_lamp_requirement(world.get_location('Sewers - Dark Cross')) - add_lamp_requirement(world.get_entrance('Sewers Back Door')) - add_lamp_requirement(world.get_entrance('Throne Room')) + add_lamp_requirement(world.get_location('Sewers - Dark Cross', player), player) + add_lamp_requirement(world.get_entrance('Sewers Back Door', player), player) + add_lamp_requirement(world.get_entrance('Throne Room', player), player) -def open_rules(world): +def open_rules(world, player): # softlock protection as you can reach the sewers small key door with a guard drop key - forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)') - forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)') + forbid_item(world.get_location('Hyrule Castle - Boomerang Chest', player), 'Small Key (Escape)', player) + forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest', player), 'Small Key (Escape)', player) - set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has_key('Small Key (Escape)')) - set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has_key('Small Key (Escape)')) + set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player)) + set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_key('Small Key (Escape)', player)) -def swordless_rules(world): +def swordless_rules(world, player): # for the time being swordless mode just inherits all fixes from open mode. # should there ever be fixes that apply to open mode but not swordless, this # can be revisited. - open_rules(world) + open_rules(world, player) - set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle - set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or state.has('Fire Rod') or state.can_shoot_arrows() or state.has('Cane of Somaria')) and state.has_key('Small Key (Agahnims Tower)', 2)) - set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer')) - set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror()) - set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!) - set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword not required to use medallion for opening in swordless (!) - set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod')) # no curtain - set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) #in swordless mode bombos pads are present in the relevant parts of ice palace - set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.can_shoot_arrows() and state.has('Crystal 1') and state.has('Crystal 2') - and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')) - set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop + set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle + set_rule(world.get_entrance('Agahnim 1', player), lambda state: (state.has('Hammer', player) or state.has('Fire Rod', player) or state.can_shoot_arrows(player) or state.has('Cane of Somaria', player)) and state.has_key('Small Key (Agahnims Tower)', player, 2)) + set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) + set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player) and state.has_Mirror(player)) + set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) + set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) + set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain + set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace + set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has('Crystal 1', player) and state.has('Crystal 2', player) + and state.has('Crystal 3', player) and state.has('Crystal 4', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player) and state.has('Crystal 7', player)) + set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop -def standard_rules(world): - for loc in ['Sanctuary','Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', +def standard_rules(world, player): + for loc in ['Sanctuary', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', 'Sewers - Secret Room - Right']: - add_rule(world.get_location(loc), lambda state: state.can_kill_most_things() and state.has_key('Small Key (Escape)')) + add_rule(world.get_location(loc, player), lambda state: state.can_kill_most_things(player) and state.has_key('Small Key (Escape)', player)) # easiest way to enforce key placement not relevant for open - set_rule(world.get_location('Sewers - Dark Cross'), lambda state: state.can_kill_most_things()) + set_rule(world.get_location('Sewers - Dark Cross', player), lambda state: state.can_kill_most_things(player)) - set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.can_kill_most_things()) - set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.can_kill_most_things()) + set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.can_kill_most_things(player)) + set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.can_kill_most_things(player)) -def set_trock_key_rules(world): +def set_trock_key_rules(world, player): all_state = world.get_all_state(True) # First set all relevant locked doors to impassible. for entrance in ['Turtle Rock Dark Room Staircase', 'Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock Pokey Room']: - set_rule(world.get_entrance(entrance), lambda state: False) + set_rule(world.get_entrance(entrance, player), lambda state: False) # Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon. - can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)')) if world.can_access_trock_eyebridge is None else world.can_access_trock_eyebridge + can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge is None else world.can_access_trock_eyebridge world.can_access_trock_eyebridge = can_reach_back - can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)')) if world.can_access_trock_front is None else world.can_access_trock_front + can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) if world.can_access_trock_front is None else world.can_access_trock_front world.can_access_trock_front = can_reach_front - can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)')) if world.can_access_trock_big_chest is None else world.can_access_trock_big_chest + can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)) if world.can_access_trock_big_chest is None else world.can_access_trock_big_chest world.can_access_trock_big_chest = can_reach_big_chest - can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)')) if world.can_access_trock_middle is None else world.can_access_trock_middle + can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player)) if world.can_access_trock_middle is None else world.can_access_trock_middle world.can_access_trock_middle = can_reach_middle # No matter what, the key requirement for going from the middle to the bottom should be three keys. - set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has_key('Small Key (Turtle Rock)', 3)) + set_rule(world.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 3)) # The following represent the most common and most restrictive key rules. These are overwritten later as needed. - set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4)) - set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4)) - set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has_key('Small Key (Turtle Rock)', 4)) + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 4)) + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 4)) + set_rule(world.get_entrance('Turtle Rock Pokey Room', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 4)) # No matter what, the Big Key cannot be in the Big Chest or held by Trinexx. non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Boss'] @@ -553,45 +558,45 @@ def set_trock_key_rules(world): def tr_big_key_chest_keys_needed(state): # This function handles the key requirements for the TR Big Chest in the situations it having the Big Key should logically require 2 keys, small key # should logically require no keys, and anything else should logically require 4 keys. - item = item_name(state, 'Turtle Rock - Big Key Chest') - if item in ['Small Key (Turtle Rock)']: + item = item_name(state, 'Turtle Rock - Big Key Chest', player) + if item in [('Small Key (Turtle Rock)', player)]: return 0 - if item in ['Big Key (Turtle Rock)']: + if item in [('Big Key (Turtle Rock)', player)]: return 2 return 4 # Now we need to set rules based on which entrances we have access to. The most important point is whether we have back access. If we have back access, we # might open all the locked doors in any order so we need maximally restrictive rules. if can_reach_back: - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: (state.has_key('Small Key (Turtle Rock)', 4) or item_name(state, 'Turtle Rock - Big Key Chest') == 'Small Key (Turtle Rock)')) - set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)') + set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player))) + set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) elif can_reach_front and can_reach_middle: - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has_key('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state))) - set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)') + set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state))) + set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'] elif can_reach_front: - set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has_key('Small Key (Turtle Rock)', 2)) - set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has_key('Small Key (Turtle Rock)', 1)) - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has_key('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state))) - set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)' and state.has_key('Small Key (Turtle Rock)', 2)) + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2)) + set_rule(world.get_entrance('Turtle Rock Pokey Room', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 1)) + set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state))) + set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player and state.has_key('Small Key (Turtle Rock)', player, 2)) non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'] elif can_reach_big_chest: - set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has_key('Small Key (Turtle Rock)', 2) if item_in_locations(state, 'Big Key (Turtle Rock)', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right']) else state.has_key('Small Key (Turtle Rock)', 4)) - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: (state.has_key('Small Key (Turtle Rock)', 4) or item_name(state, 'Turtle Rock - Big Key Chest') == 'Small Key (Turtle Rock)')) - set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)') + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4)) + set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player))) + set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'] if not world.keysanity: non_big_key_locations += ['Turtle Rock - Big Key Chest'] else: - set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has_key('Small Key (Turtle Rock)', 2) if item_in_locations(state, 'Big Key (Turtle Rock)', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right']) else state.has_key('Small Key (Turtle Rock)', 4)) - set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: (state.has_key('Small Key (Turtle Rock)', 4) or item_name(state, 'Turtle Rock - Big Key Chest') == 'Small Key (Turtle Rock)')) - set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)') + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4)) + set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player))) + set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'] @@ -600,16 +605,16 @@ def set_trock_key_rules(world): # set big key restrictions for location in non_big_key_locations: - forbid_item(world.get_location(location), 'Big Key (Turtle Rock)') + forbid_item(world.get_location(location, player), 'Big Key (Turtle Rock)', player) # small key restriction for location in ['Turtle Rock - Boss']: - forbid_item(world.get_location(location), 'Small Key (Turtle Rock)') + forbid_item(world.get_location(location, player), 'Small Key (Turtle Rock)', player) -def set_big_bomb_rules(world): +def set_big_bomb_rules(world, player): # this is a mess - bombshop_entrance = world.get_region('Big Bomb Shop').entrances[0] + bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0] Normal_LW_entrances = ['Blinds Hideout', 'Bonk Fairy (Light)', 'Lake Hylia Fairy', @@ -723,23 +728,23 @@ def set_big_bomb_rules(world): Desert_mirrorable_ledge_entrances = ['Desert Palace Entrance (West)', 'Desert Palace Entrance (North)', 'Desert Palace Entrance (South)', - 'Checkerboard Cave',] + 'Checkerboard Cave'] - set_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.can_reach('East Dark World', 'Region') and state.can_reach('Big Bomb Shop', 'Region') and state.has('Crystal 5') and state.has('Crystal 6')) + set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player)) #crossing peg bridge starting from the southern dark world def cross_peg_bridge(state): - return state.has('Hammer') and state.has_Pearl() + return state.has('Hammer', player) and state.has_Pearl(player) # returning via the eastern and southern teleporters needs the same items, so we use the southern teleporter for out routing. # crossing preg bridge already requires hammer so we just add the gloves to the requirement def southern_teleporter(state): - return state.can_lift_rocks() and cross_peg_bridge(state) + return state.can_lift_rocks(player) and cross_peg_bridge(state) # the basic routes assume you can reach eastern light world with the bomb. # you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp def basic_routes(state): - return southern_teleporter(state) or state.can_reach('Top of Pyramid', 'Entrance') + return southern_teleporter(state) or state.can_reach('Top of Pyramid', 'Entrance', player) # Key for below abbreviations: # P = pearl @@ -752,90 +757,90 @@ def set_big_bomb_rules(world): #1. basic routes #2. Can reach Eastern dark world some other way, mirror, get bomb, return to mirror spot, walk to pyramid: Needs mirror # -> M or BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: basic_routes(state) or state.has_Mirror()) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: basic_routes(state) or state.has_Mirror(player)) elif bombshop_entrance.name in LW_walkable_entrances: #1. Mirror then basic routes # -> M and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has_Mirror() and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and basic_routes(state)) elif bombshop_entrance.name in Northern_DW_entrances: #1. Mirror and basic routes #2. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (M and BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.can_lift_heavy_rocks() and cross_peg_bridge(state)) or (state.has_Mirror() and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (state.has_Mirror(player) and basic_routes(state))) elif bombshop_entrance.name == 'Bumper Cave (Bottom)': #1. Mirror and Lift rock and basic_routes #2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) #3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (((G or Flute) and M) and BR)) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.can_lift_heavy_rocks() and cross_peg_bridge(state)) or (((state.can_lift_rocks() or state.has('Ocarina')) and state.has_Mirror()) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.has('Ocarina', player)) and state.has_Mirror(player)) and basic_routes(state))) elif bombshop_entrance.name in Southern_DW_entrances: #1. Mirror and enter via gate: Need mirror and Aga1 #2. cross peg bridge: Need hammer and moon pearl # -> CPB or (M and A) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: cross_peg_bridge(state) or (state.has_Mirror() and state.can_reach('Top of Pyramid', 'Entrance'))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.can_reach('Top of Pyramid', 'Entrance', player))) elif bombshop_entrance.name in Isolated_DW_entrances: # 1. mirror then flute then basic routes # -> M and Flute and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has_Mirror() and state.has('Ocarina') and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.has('Ocarina', player) and basic_routes(state)) elif bombshop_entrance.name in Isolated_LW_entrances: # 1. flute then basic routes # Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations. # -> Flute and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has('Ocarina') and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and basic_routes(state)) elif bombshop_entrance.name in West_LW_DM_entrances: # 1. flute then basic routes or mirror # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly. # -> Flute and (M or BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has('Ocarina') and (state.has_Mirror() or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and (state.has_Mirror(player) or basic_routes(state))) elif bombshop_entrance.name in East_LW_DM_entrances: # 1. flute then basic routes or mirror and hookshot # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot # -> Flute and ((M and Hookshot) or BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has('Ocarina') and ((state.has_Mirror() and state.has('Hookshot')) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': # Same as East_LW_DM_entrances except navigation without BR requires Mitts # -> Flute and ((M and Hookshot and Mitts) or BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has('Ocarina') and ((state.has_Mirror() and state.has('Hookshot') and state.can_lift_heavy_rocks()) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) elif bombshop_entrance.name in Castle_ledge_entrances: # 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror # 2. flute then basic routes # -> M or (Flute and BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has_Mirror() or (state.has('Ocarina') and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.has('Ocarina', player) and basic_routes(state))) elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances: # Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and: # 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes # 2. flute then basic routes # -> (Mire access and M) or Flute) and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: ((state.can_reach('Dark Desert', 'Region') and state.has_Mirror()) or state.has('Ocarina')) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.has('Ocarina', player)) and basic_routes(state)) elif bombshop_entrance.name == 'Old Man Cave (West)': # 1. Lift rock then basic_routes # 2. flute then basic_routes # -> (Flute or G) and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.has('Ocarina') or state.can_lift_rocks()) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Ocarina', player) or state.can_lift_rocks(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Graveyard Cave': # 1. flute then basic routes # 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge # -> (Flute or (M and P and West Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.has('Ocarina') or (state.can_reach('West Dark World', 'Region') and state.has_Pearl() and state.has_Mirror())) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Ocarina', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name in Mirror_from_SDW_entrances: # 1. flute then basic routes # 2. (has South dark world access) use existing mirror spot, mirror again off ledge # -> (Flute or (M and South Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.has('Ocarina') or (state.can_reach('South Dark World', 'Region') and state.has_Mirror())) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Ocarina', player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name == 'Dark World Potion Shop': # 1. walk down by lifting rock: needs gloves and pearl` # 2. walk down by hammering peg: needs hammer and pearl # 3. mirror and basic routes # -> (P and (H or Gloves)) or (M and BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.has_Pearl() and (state.has('Hammer') or state.can_lift_rocks())) or (state.has_Mirror() and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has_Pearl(player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) or (state.has_Mirror(player) and basic_routes(state))) elif bombshop_entrance.name == 'Kings Grave': # same as the Normal_LW_entrances case except that the pre-existing mirror is only possible if you have mitts # (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot) # to account for insanity, must consider a way to escape without a cave for basic_routes # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) - add_rule(world.get_entrance('Pyramid Fairy'), lambda state: (state.can_lift_heavy_rocks() and state.has_Mirror()) or ((state.can_lift_heavy_rocks() or state.has('Ocarina') or (state.can_reach('West Dark World', 'Region') and state.has_Pearl() and state.has_Mirror())) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.has('Ocarina', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) -def set_bunny_rules(world): +def set_bunny_rules(world, player): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. @@ -853,12 +858,12 @@ def set_bunny_rules(world): def get_rule_to_add(region): if not region.is_light_world: - return lambda state: state.has_Pearl() + return lambda state: state.has_Pearl(player) # in this case we are mixed region. # we collect possible options. # The base option is having the moon pearl - possible_options = [lambda state: state.has_Pearl()] + possible_options = [lambda state: state.has_Pearl(player)] # We will search entrances recursively until we find # one that leads to an exclusively light world region @@ -885,7 +890,7 @@ def set_bunny_rules(world): return options_to_access_rule(possible_options) # Add requirements for bunny-impassible caves if they occur in the dark world - for region in [world.get_region(name) for name in bunny_impassable_caves]: + for region in [world.get_region(name, player) for name in bunny_impassable_caves]: if not region.is_dark_world: continue @@ -893,13 +898,13 @@ def set_bunny_rules(world): for exit in region.exits: add_rule(exit, rule) - paradox_shop = world.get_region('Light World Death Mountain Shop') + paradox_shop = world.get_region('Light World Death Mountain Shop', player) if paradox_shop.is_dark_world: add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop)) # Add requirements for all locations that are actually in the dark world, except those available to the bunny for location in world.get_locations(): - if location.parent_region.is_dark_world: + if location.player == player and location.parent_region.is_dark_world: if location.name in bunny_accessible_locations: continue