From abcacd36eb9062a3381b8eca99cee8663e00534a Mon Sep 17 00:00:00 2001 From: LLCoolDave Date: Mon, 15 May 2017 20:28:04 +0200 Subject: [PATCH] First Iteration (can handle no glitches open/standard logic), WIP --- BaseClasses.py | 375 ++++++++++++++++++++++++++++ Dungeons.py | 69 ++++++ EntranceShuffle.py | 240 ++++++++++++++++++ Items.py | 601 +++++++++++++++++++++++++++++++++++++++++++++ Main.py | 188 ++++++++++++++ Regions.py | 230 +++++++++++++++++ Rules.py | 268 ++++++++++++++++++++ 7 files changed, 1971 insertions(+) create mode 100644 BaseClasses.py create mode 100644 Dungeons.py create mode 100644 EntranceShuffle.py create mode 100644 Items.py create mode 100644 Main.py create mode 100644 Regions.py create mode 100644 Rules.py diff --git a/BaseClasses.py b/BaseClasses.py new file mode 100644 index 00000000..5d4a7c25 --- /dev/null +++ b/BaseClasses.py @@ -0,0 +1,375 @@ +import copy + + +class World(object): + + def __init__(self): + self.regions = [] + self.itempool = [] + self.state = CollectionState(self) + self.required_medallions = ['Ether', 'Quake'] + self._cached_locations = None + self._entrance_cache = {} + self._region_cache = {} + self._entrance_cache = {} + self._location_cache = {} + self._item_cache = {} + + def get_region(self, regionname): + if isinstance(regionname, Region): + return regionname + try: + return self._region_cache[regionname] + except KeyError: + for region in self.regions: + if region.name == regionname: + self._region_cache[regionname] = region + return region + raise RuntimeError('No such region %s' % regionname) + + def get_entrance(self, entrance): + if isinstance(entrance, Entrance): + return entrance + try: + return self._entrance_cache[entrance] + except KeyError: + for region in self.regions: + for exit in region.exits: + if exit.name == entrance: + self._entrance_cache[entrance] = exit + return exit + raise RuntimeError('No such entrance %s' % entrance) + + def get_location(self, location): + if isinstance(location, Location): + return location + try: + return self._location_cache[location] + except KeyError: + for region in self.regions: + for r_location in region.locations: + if r_location.name == location: + self._location_cache[location] = r_location + return r_location + raise RuntimeError('No such entrance %s' % location) + + 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 push_item(self, location, item, collect=True): + if not isinstance(location, Location): + location = self.get_location(location) + + if location.item_rule(item): + location.item = item + item.location = location + if collect: + self.state.collect(item) + + print('Placed %s at %s' % (item, location)) + else: + raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) + + def get_locations(self): + if self._cached_locations is None: + self._cached_locations = [] + for region in self.regions: + self._cached_locations.extend(region.locations) + return self._cached_locations + + def get_unfilled_locations(self): + return [location for location in self.get_locations() if location.item is None] + + def get_reachable_locations(self, state=None): + if state is None: + state = self.state + return [location for location in self.get_locations() if state.can_reach(location)] + + def get_placeable_locations(self, state=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)] + + def unlocks_new_location(self, item): + temp_state = self.state.copy() + temp_state._clear_cache() + temp_state.collect(item) + return len(self.get_placeable_locations()) < len(self.get_placeable_locations(temp_state)) + + +class CollectionState(object): + + def __init__(self, parent, has_everything=False): + self.prog_items = set() + self.world = parent + self.has_everything = has_everything + self.changed = False + self.region_cache = {} + self.location_cache = {} + self.entrance_cache = {} + # use to avoid cycle dependencies during resolution + self.recursion_cache = [] + + def _clear_cache(self): + # we only need to invalidate results which were False, places we could reach before we can still reach after adding more items + self.region_cache = {k: v for k, v in self.region_cache.items() if v} + self.location_cache = {k: v for k, v in self.location_cache.items() if v} + self.entrance_cache = {k: v for k, v in self.entrance_cache.items() if v} + self.recursion_cache = [] + self.changed = False + + def copy(self): + ret = CollectionState(self.world, self.has_everything) + ret.prog_items = copy.copy(self.prog_items) + ret.changed = self.changed + ret.region_cache = copy.copy(self.region_cache) + ret.location_cache = copy.copy(self.location_cache) + ret.entrance_cache = copy.copy(self.entrance_cache) + ret.recursion_cache = copy.copy(self.recursion_cache) + return ret + + def can_reach(self, spot, resolution_hint=None): + if self.changed: + self._clear_cache() + + if spot in self.recursion_cache: + return False + + if isinstance(spot, Region): + correct_cache = self.region_cache + elif isinstance(spot, Location): + correct_cache = self.location_cache + elif isinstance(spot, Entrance): + correct_cache = self.entrance_cache + else: + # try to resolve a name + if resolution_hint == 'Location': + spot = self.world.get_location(spot) + correct_cache = self.location_cache + elif resolution_hint == 'Entrance': + spot = self.world.get_entrance(spot) + correct_cache = self.entrance_cache + else: + # default to Region + spot = self.world.get_region(spot) + correct_cache = self.region_cache + + if spot not in correct_cache: + # for the purpose of evaluating results, recursion is resolved by always denying recursive access (as that ia what we are trying to figure out right now in the first place + self.recursion_cache.append(spot) + can_reach = spot.can_reach(self) + self.recursion_cache.pop() + + # we only store qualified false results (i.e. ones not inside a hypothetical) + if not can_reach: + if not self.recursion_cache: + correct_cache[spot] = can_reach + else: + correct_cache[spot] = can_reach + return can_reach + return correct_cache[spot] + + def can_collect(self, item, count=None): + if isinstance(item, Item): + item = item.name + if count is None: + cached = self.world._item_cache.get(item, None) + if cached is None: + candidates = self.world.find_items(item) + if not candidates: + return False + elif len(candidates) == 1: + cached = candidates[0] + self.world._item_cache[item] = cached + else: + # this should probably not happen, wonky item distribution? + len([location for location in candidates if self.can_reach(location)]) >= 1 + return self.can_reach(cached) + + return len([location for location in self.world.find_items(item) if self.can_reach(location)]) >= count + + def has(self, item): + if self.has_everything: + return True + else: + return item in self.prog_items + + def can_lift_rocks(self): + return self.has('Power Glove') or self.has('Titans Mitts') + + def can_lift_heavy_rocks(self): + return self.has('Titans Mitts') + + 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_beam_sword(self): + return self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword') + + def has_blunt_weapon(self): + return self.has_sword() or self.has('Hammer') + + def has_Mirror(self): + return self.has('Magic Mirror') + + def has_Boots(self): + return self.has('Pegasus Boots') + + def has_Pearl(self): + return self.has('Moon Pearl') + + def has_fire_source(self): + return self.has('Fire Rod') or self.has('Lamp') + + def has_misery_mire_medallion(self): + return self.has(self.world.required_medallions[0]) + + def has_turtle_rock_medallion(self): + return self.has(self.world.required_medallions[1]) + + def collect(self, item): + if item.name.startswith('Progressive '): + if 'Sword' in item.name: + if self.has('Golden Sword'): + return + elif self.has('Tempered Sword'): + self.prog_items.add('Golden Sword') + self.changed = True + elif self.has('Master Sword'): + self.prog_items.add('Tempered Sword') + self.changed = True + elif self.has('Fighter Sword'): + self.prog_items.add('Master Sword') + self.changed = True + else: + self.prog_items.add('Fighter Sword') + self.changed = True + elif 'Glove' in item.name: + if self.has('Titans Mitts'): + return + elif self.has('Power Glove'): + self.prog_items.add('Titans Mitts') + self.changed = True + else: + self.prog_items.add('Power Glove') + self.changed = True + return + + if item.advancement: + self.prog_items.add(item.name) + self.changed = True + + def __getattr__(self, item): + if item.startswith('can_reach_'): + return self.can_reach(item[10]) + elif item.startswith('has_'): + return self.has(item[4]) + + raise RuntimeError('Cannot parse %s.' % item) + + +class Region(object): + + def __init__(self, name): + self.name = name + self.entrances = [] + self.exits = [] + self.locations = [] + + def can_reach(self, state): + for entrance in self.entrances: + if state.can_reach(entrance): + return True + return False + + def add_locations(self, *locations): + for location in locations: + self.locations.append(Location(location, self)) + + def add_exits(self, *exits): + for exit in exits: + self.exits.append(Entrance(exit, self)) + + def __str__(self): + return str(self.__unicode__()) + + def __unicode__(self): + return '%s' % self.name + + +class Entrance(object): + + def __init__(self, name='', parent=None): + self.name = name + self.parent_region = parent + self.connected_region = None + + def access_rule(self, state): + return True + + def can_reach(self, state): + if self.parent_region: + if not state.can_reach(self.parent_region): + return False + + return self.access_rule(state) + + def connect(self, region): + self.connected_region = region + region.entrances.append(self) + + def __str__(self): + return str(self.__unicode__()) + + def __unicode__(self): + return '%s' % self.name + + +class Location(object): + + def __init__(self, name='', parent=None, access=None, item_rule=None): + self.name = name + self.parent_region = parent + self.item = None + if access is not None: + self.access_rule = access + if item_rule is not None: + self.item_rule = item_rule + + def access_rule(self, state): + return True + + def item_rule(self, item): + if item.name != 'Triforce': + return True + else: + return False + + def can_reach(self, state): + if self.parent_region: + if not state.can_reach(self.parent_region): + return False + + return self.access_rule(state) + + def __str__(self): + return str(self.__unicode__()) + + def __unicode__(self): + return '%s' % self.name + + +class Item(object): + + def __init__(self, name='', advancement=False, key=False): + self.name = name + self.advancement = advancement + self.key = key + self.location = None + + def __str__(self): + return str(self.__unicode__()) + + def __unicode__(self): + return '%s' % self.name + diff --git a/Dungeons.py b/Dungeons.py new file mode 100644 index 00000000..da8b145d --- /dev/null +++ b/Dungeons.py @@ -0,0 +1,69 @@ +from Items import * +import random +from BaseClasses import CollectionState + + +def fill_dungeons(world): + ES = (['Hyrule Castle'], None, [ESSmallKey()], [ESMap()]) + EP = (['Eastern Palace'], EPBigKey(), [], [EPMap(), EPCompass()]) + DP = (['Desert Palace Main', 'Desert Palace East', 'Desert Palace North'], DPBigKey(), [DPSmallKey()], [DPCompass(), DPMap()]) + ToH = (['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'], THBigKey(), [THSmallKey()], [THCompass(), THMap()]) + AT = (['Aghanims Tower', 'Aghanim 1'], None, [ATSmallKey(), ATSmallKey()], []) + PoD = (['Dark Palace (Entrance)', 'Dark Palace (Center)', 'Dark Palace (Big Key Chest)', 'Dark Palace (Bonk Section)', 'Dark Palace (North)', 'Dark Palace (Maze)', 'Dark Palace (Spike Statue Room)', 'Dark Palace (Final Section)'], PDBigKey(), [PDSmallKey(), PDSmallKey(), PDSmallKey(), PDSmallKey(), PDSmallKey(), PDSmallKey()], [PDCompass(), PDMap()]) + TT = (['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], TTBigKey(), [TTSmallKey()], [TTCompass(), TTMap()]) + SW = (['Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Final Section (Entrance)', 'Skull Woods Final Section (Mothula)'], SWBigKey(), [SWSmallKey(), SWSmallKey()], [SWCompass(), SWMap()]) + SP = (['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)', 'Swamp Palace (Center)', 'Swamp Palace (North)'], SPBigKey(), [SPSmallKey()], [SPMap(), SPCompass()]) + IP = (['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], IPBigKey(), [IPSmallKey(), IPSmallKey()], [IPMap(), IPCompass()]) + MM = (['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], MMBigKey(), [MMSmallKey(), MMSmallKey(), MMSmallKey()], [MMCompass(), MMMap()]) + TR = (['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Roller Switch Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], TRBigKey(), [TRSmallKey(), TRSmallKey(), TRSmallKey(), TRSmallKey()], [TRMap(), TRCompass()]) + GT = (['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)', 'Aghanim 2'], GTBigKey(), [GTSmallKey(), GTSmallKey(), GTSmallKey(), GTSmallKey()], [GTMap(), GTCompass()]) + + freebes = ['[dungeon-A2-1F] Ganons Tower - Map Room', '[dungeon-D1-1F] Dark Palace - Spike Statue Room', '[dungeon-D1-1F] Dark Palace - Big Key Room'] + + # this key is in a fixed location (for now) + world.push_item(world.get_location('[dungeon - D3 - B1] Skull Woods - South of Big Chest'), SWSmallKey(), False) + + for dungeon_regions, big_key, small_keys, dungeon_items in [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]: + # this is what we need to fill + dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions] + random.shuffle(dungeon_locations) + + all_state = CollectionState(world, True) + + # first place big key + if big_key is not None: + bk_location = None + for location in dungeon_locations: + if location.item_rule(big_key): + bk_location = location + break + + if bk_location is None: + raise RuntimeError('No suitable location for %s' % big_key) + + world.push_item(bk_location, big_key, False) + dungeon_locations.remove(bk_location) + all_state._clear_cache() + + # next place small keys + for small_key in small_keys: + sk_location = None + for location in dungeon_locations: + if location.name in freebes or location.can_reach(all_state): + sk_location = location + break + + if sk_location is None: + raise RuntimeError('No suitable location for %s' % small_key) + + world.push_item(sk_location, small_key, False) + dungeon_locations.remove(sk_location) + all_state._clear_cache() + + # next place dungeon items + if True: # optional in future + for dungeon_item in dungeon_items: + di_location = dungeon_locations.pop() + world.push_item(di_location, dungeon_item, False) + + world.state._clear_cache() diff --git a/EntranceShuffle.py b/EntranceShuffle.py new file mode 100644 index 00000000..ea788a5d --- /dev/null +++ b/EntranceShuffle.py @@ -0,0 +1,240 @@ +def link_entrances(world, shuffle): + # setup mandatory connections + for exitname, regionname in mandatory_connections: + connect(world, exitname, regionname) + + # if we do not shuffle, set default connections + if shuffle=='Default': + for exitname, regionname in default_connections: + connect(world, exitname, regionname) + return + + raise NotImplementedError('Shuffling not supported yet') + + +def connect(world, exitname, regionname): + world.get_entrance(exitname).connect(world.get_region(regionname)) + +# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions +mandatory_connections = [('Zoras River', 'Zoras River'), + ('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'), + ('Dam', 'Dam'), + ('Hobo Bridge', 'Hobo Bridge'), + ('Desert Palace East Wing', 'Desert Palace East'), + ('Bat Cave Drop Ledge', 'Bat Cave Drop Ledge'), + ('Bat Cave Door', 'Bat Cave (left)'), + ('Thieves Forest Hideout (top to bottom)', 'Thieves Forest Hideout (bottom)'), + ('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'), + ('Desert Palace Stairs', 'Desert Palace Stairs'), + ('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'), + ('Aghanim 1', 'Aghanim 1'), + ('Flute Spot 1', 'Death Mountain'), + ('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'), + ('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'), + ('Broken Bridge (West)', 'East Death Mountain (Bottom)'), + ('Broken Bridge (East)', 'Death Mountain'), + ('East Death Mountain Drop', 'East Death Mountain (Bottom)'), + ('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'), + ('East Death Mountain (Top)', 'East Death Mountain (Top)'), + ('Death Mountain (Top)', 'Death Mountain (Top)'), + ('Death Mountain Drop', 'Death Mountain'), + ('Spectacle Rock Drop', 'Death Mountain (Top)'), + + ('Top of Pyramid', 'East Dark World'), + ('Dark Lake Hylia Drop (East)', 'Dark Lake Hylia'), + ('Dark Lake Hylia Drop (South)', 'Dark Lake Hylia'), + ('East Dark World Pier', 'East Dark World'), + ('Lake Hylia Island Mirror Spot', 'Lake Hylia Island'), + ('Hyrule Castle Ledge Mirror Spot', 'Hyrule Castle Ledge'), + ('South Dark World Bridge', 'South Dark World'), + ('Big Bomb Shop', 'Big Bomb Shop'), + ('East Dark World Bridge', 'East Dark World'), + ('Maze Race Mirror Spot', 'Maze Race Ledge'), + ('Village of Outcasts Heavy Rock', 'West Dark World'), + ('Village of Outcasts Drop', 'South Dark World'), + ('Bat Cave Drop Ledge Mirror Spot', 'Bat Cave Drop Ledge'), + ('East Dark World River Pier', 'East Dark World'), + ('West Dark World Gap', 'West Dark World'), + ('Bumper Cave Ledge Drop', 'West Dark World'), + ('Skull Woods Forest', 'Skull Woods Forest'), + ('Desert Ledge Mirror Spot', 'Desert Ledge'), + ('Desert Ledge (West) Mirror Spot', 'Desert Ledge (West)'), + ('Desert Palace Entrance (North) Mirror Spot', 'Desert Palace Entrance (North) Spot'), + ('Dark Desert Teleporter', 'Dark Desert'), + ('Desert Palace Stairs Mirror Spot', 'Desert Palace Stairs'), + ('East Hyrule Teleporter', 'East Dark World'), + ('South Hyrule Teleporter', 'South Dark World'), + ('Kakariko Teleporter', 'West Dark World'), + ('Death Mountain Teleporter', 'Dark Death Mountain (West Bottom)'), + ('Spectacle Rock Mirror Spot', 'Spectacle Rock'), + ('Dark Death Mountain Drop (East)', 'Dark Death Mountain (East Bottom)'), + ('Dark Death Mountain Drop (West)', 'Dark Death Mountain (West Bottom)'), + ('East Death Mountain (Top) Mirror Spot', 'East Death Mountain (Top)'), + ('Turtle Rock Teleporter', 'Turtle Rock (Top)'), + ('Turtle Rock Drop', 'Dark Death Mountain (Top)'), + ('Floating Island Drop', 'Dark Death Mountain (Top)'), + ('East Death Mountain Teleporter', 'Dark Death Mountain (East Bottom)'), + ('Isolated Ledge Mirror Spot', 'East Death Mountain (Bottom)'), + + ('Swamp Palace', 'Swamp Palace (Entrance)'), + ('Swamp Palace Moat', 'Swamp Palace (First Room)'), + ('Swamp Palace Small Key Door', 'Swamp Palace (Starting Area)'), + ('Swamp Palace (Center)', 'Swamp Palace (Center)'), + ('Swamp Palace (North)', 'Swamp Palace (North)'), + ('Thieves Town Big Key Door', 'Thieves Town (Deep)'), + ('Blind Fight', 'Blind Fight'), + ('Ice Palace Entrance Room', 'Ice Palace (Main)'), + ('Ice Palace (East)', 'Ice Palace (East)'), + ('Ice Palace (East Top)', 'Ice Palace (East Top)'), + ('Ice Palace (Kholdstare)', 'Ice Palace (Kholdstare)'), + ('Misery Mire Entrance Gap', 'Misery Mire (Main)'), + ('Misery Mire (West)', 'Misery Mire (West)'), + ('Misery Mire Big Key Door', 'Misery Mire (Final Area)'), + ('Misery Mire (Vitreous)', 'Misery Mire (Vitreous)'), + ('Turtle Rock Entrance Gap', 'Turtle Rock (First Section)'), + ('Turtle Rock Pokey Room', 'Turtle Rock (Chain Chomp Room)'), + ('Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Second Section)'), + ('Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock (First Section)'), + ('Turtle Rock Chain Chomp Staircase', 'Turtle Rock (Chain Chomp Room)'), + ('Turtle Rock (Big Chest) (North)', 'Turtle Rock (Second Section)'), + ('Turtle Rock Big Key Door', 'Turtle Rock (Roller Switch Room)'), + ('Turtle Rock Dark Room Staircase', 'Turtle Rock (Dark Room)'), + ('Turtle Rock (Dark Room) (North)', 'Turtle Rock (Roller Switch Room)'), + ('Turtle Rock (Dark Room) (South)', 'Turtle Rock (Eye Bridge)'), + ('Turtle Rock Dark Room (South)', 'Turtle Rock (Dark Room)'), + ('Turtle Rock (Trinexx)', 'Turtle Rock (Trinexx)'), + ('Dark Palace Bridge Room', 'Dark Palace (Center)'), + ('Dark Palace Bonk Wall', 'Dark Palace (Bonk Section)'), + ('Dark Palace Big Key Chest Staircase', 'Dark Palace (Big Key Chest)'), + ('Dark Palace (North)', 'Dark Palace (North)'), + ('Dark Palace Big Key Door', 'Dark Palace (Final Section)'), + ('Dark Palace Hammer Peg Drop', 'Dark Palace (Center)'), + ('Dark Palace Spike Statue Room Door', 'Dark Palace (Spike Statue Room)'), + ('Dark Palace Maze Door', 'Dark Palace (Maze)'), + ('Ganons Tower', 'Ganons Tower (Entrance)'), + ('Ganons Tower (Tile Room)', 'Ganons Tower (Tile Room)'), + ('Ganons Tower (Tile Room) Key Door', 'Ganons Tower (Compass Room)'), + ('Ganons Tower (Bottom) (East)', 'Ganons Tower (Bottom)'), + ('Ganons Tower (Hookshot Room)', 'Ganons Tower (Hookshot Room)'), + ('Ganons Tower (Map Room)', 'Ganons Tower (Map Room)'), + ('Ganons Tower (Double Switch Room)', 'Ganons Tower (Firesnake Room)'), + ('Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)'), + ('Ganons Tower (Bottom) (West)', 'Ganons Tower (Bottom)'), + ('Ganons Tower Big Key Door', 'Ganons Tower (Top)'), + ('Ganons Tower Torch Rooms', 'Ganons Tower (Before Moldorm)'), + ('Ganons Tower Moldorm Door', 'Ganons Tower (Moldorm)'), + ('Ganons Tower Moldorm Gap', 'Aghanim 2'), + ('Pyramid Hole', 'Pyramid') + ] + +# non-shuffled entrance links +default_connections = [("Thiefs Hut", "Thiefs Hut"), + ("Hyrule Castle Secret Entrance Drop", "Hyrule Castle Secret Entrance"), + ("Hyrule Castle Secret Entrance Stairs", "Hyrule Castle Secret Entrance"), + ('Kings Grave', 'Kings Grave'), + ('Links House', 'Links House'), + ('Tavern North', 'Tavern'), + ('Chicken House', 'Chicken House'), + ('Aginahs Cave', 'Aginahs Cave'), + ('Sahasrahlas Hut', 'Sahasrahlas Hut'), + ('Kakariko Well Drop', 'Kakariko Well (top)'), + ('Kakariko Well Cave', 'Kakariko Well (bottom)'), + ('Blacksmiths Hut', 'Blacksmiths Hut'), + ('Bat Cave Drop', 'Bat Cave (right)'), + ('Bat Cave Cave', 'Bat Cave (left)'), + ('Sick Kids House', 'Sick Kids House'), + ('Thieves Forest Hideout Drop', 'Thieves Forest Hideout (top)'), + ('Thieves Forest Hideout Stump', 'Thieves Forest Hideout (bottom)'), + ('Lumberjack Tree Tree', 'Lumberjack Tree (top)'), + ('Lumberjack Tree Cave', 'Lumberjack Tree (bottom)'), + ('Cave South of Haunted Grove', 'Cave South of Haunted Grove'), + ('Graveyard Cave', 'Graveyard Cave'), + ('Desert Cave', 'Desert Cave'), + ('Lake Hylia Cave', 'Lake Hylia Cave'), + ('Ice Cave', 'Ice Cave'), + ('Bonk Rock Cave', 'Bonk Rock Cave'), + ('Library', 'Library'), + ('Witch Hut', 'Witch Hut'), + ('Two Brothers House', 'Two Brothers House'), + ('Two Brothers House (left)', 'Maze Race Ledge'), + ('Master Sword Meadow', 'Master Sword Meadow'), + + ('Desert Palace Entrance (South)', 'Desert Palace Main'), + ('Desert Palace Entrance (West)', 'Desert Palace Main'), + ('Desert Palace Entrance (North)', 'Desert Palace North'), + ('Desert Palace Exit (South)', 'Light World'), + ('Desert Palace Exit (West)', 'Desert Ledge'), + ('Desert Palace Exit (East)', 'Light World'), + ('Eastern Palace', 'Eastern Palace'), + + ('Hyrule Castle Entrance (South)', 'Hyrule Castle'), + ('Hyrule Castle Entrance (West)', 'Hyrule Castle'), + ('Hyrule Castle Entrance (East)', 'Hyrule Castle'), + ('Hyrule Castle Exit (South)', 'Light World'), + ('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'), + ('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'), + ('Aghanims Tower', 'Aghanims Tower'), + ('Sanctuary', 'Hyrule Castle'), # this set of two exits can be randomized together! + ('Sanctuary Grave', 'Hyrule Castle'), + + ('Old Man Cave (West)', 'Old Man Cave'), + ('Old Man Cave (East)', 'Old Man Cave'), + ('Old Man Cave Exit', 'Death Mountain'), + ('Death Mountain Return Cave', 'Death Mountain Return Cave'), + ('Death Mountain Return Cave Exit (West)', 'Light World'), + ('Death Mountain Return Cave Exit (East)', 'Death Mountain'), + ('Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Peak)'), + ('Spectacle Rock Cave', 'Spectacle Rock Cave (Top)'), + ('Spectacle Rock Cave Exit', 'Death Mountain'), + ('Death Mountain Climb', 'Death Mountain Climb Cave'), + ('Death Mountain Climb (Top)', 'Death Mountain Climb Cave'), + ('Death Mountain Climb Exit (Bottom)', 'East Death Mountain (Bottom)'), + ('Death Mountain Climb Exit (Top)', 'East Death Mountain (Top)'), + ('Spiral Cave', 'Spiral Cave (Top)'), + ('Spiral Cave Exit', 'East Death Mountain (Bottom)'), + ('Tower of Hera', 'Tower of Hera (Bottom)'), + ('Tower of Hera Small Key Door', 'Tower of Hera (Basement)'), + ('Tower of Hera Big Key Door', 'Tower of Hera (Top)'), + + ('Pyramid Fairy', 'Pyramid Fairy'), + ('Dark Swamp Cave', 'Dark Swamp Cave'), + ('Doorless Hut', 'Doorless Hut'), + ('C-Shaped House', 'C-Shaped House'), + ('Chest Game', 'Chest Game'), + ('Dark World Hammer Peg Cave', 'Dark World Hammer Peg Cave'), + ('Bumper Cave', 'Bumper Cave'), + ('Bumper Cave (Top)', 'Bumper Cave Ledge'), + ('Bumper Cave (Bottom)', 'West Dark World'), + ('Dark Desert Cave', 'Dark Desert Cave'), + ('Spike Cave', 'Spike Cave'), + ('Hookshot Cave', 'Hookshot Cave'), + ('Dark Death Mountain Climb (Top)', 'Dark Death Mountain Climb'), + ('Dark Death Mountain Climb (Bottom)', 'Dark Death Mountain Climb'), + ('Dark Death Mountain Climb Exit (Top)', 'Dark Death Mountain (Top)'), + ('Dark Death Mountain Climb Exit (Bottom)', 'Dark Death Mountain (East Bottom)'), + ('Hookshot Cave Exit (South)', 'Dark Death Mountain (Top)'), + ('Hookshot Cave Exit (North)', 'Death Mountain Floating Island'), + ('Mimic Cave Mirror Spot', 'Mimic Cave'), + + ('Thieves Town', 'Thieves Town (Entrance)'), + ('Skull Woods First Section Hole (East)', 'Skull Woods First Section'), + ('Skull Woods First Section Hole (West)', 'Skull Woods First Section'), + ('Skull Woods First Section Hole (North)', 'Skull Woods First Section'), + ('Skull Woods First Section Door', 'Skull Woods First Section'), + ('Skull Woods First Section Exit', 'Skull Woods Forest'), + ('Skull Woods Second Section Hole', 'Skull Woods Second Section'), + ('Skull Woods Second Section Door (East)', 'Skull Woods Second Section'), + ('Skull Woods Second Section Door (West)', 'Skull Woods Second Section'), + ('Skull Woods Second Section Exit (East)', 'Skull Woods Forest'), + ('Skull Woods Second Section Exit (West)', 'Skull Woods Forest'), + ('Skull Woods Final Section', 'Skull Woods Final Section (Entrance)'), + ('Skull Woods Torch Room', 'Skull Woods Final Section (Mothula)'), + ('Ice Palace', 'Ice Palace (Entrance)'), + ('Misery Mire', 'Misery Mire (Entrance)'), + ('Turtle Rock', 'Turtle Rock (Entrance)'), + ('Turtle Rock Ledge Exit (West)', 'Dark Death Mountain Ledge'), + ('Dark Death Mountain Ledge (West)', 'Turtle Rock (Second Section)'), + ('Dark Death Mountain Ledge (East)', 'Turtle Rock (Big Chest)'), + ('Turtle Rock Isolated Ledge Exit', 'Dark Death Mountain Isolated Ledge'), + ('Palace of Darkness', 'Dark Palace (Entrance)') + ] diff --git a/Items.py b/Items.py new file mode 100644 index 00000000..04ae8ebe --- /dev/null +++ b/Items.py @@ -0,0 +1,601 @@ +from BaseClasses import Item + + +class Bow(Item): + + def __init__(self): + super(Bow, self).__init__('Bow', True) + + +class Book(Item): + + def __init__(self): + super(Book, self).__init__('Book of Mudora', True) + + +class Hammer(Item): + + def __init__(self): + super(Hammer, self).__init__('Hammer', True) + + +class Hookshot(Item): + + def __init__(self): + super(Hookshot, self).__init__('Hookshot', True) + + +class Mirror(Item): + + def __init__(self): + super(Mirror, self).__init__('Magic Mirror', True) + + +class Ocarina(Item): + def __init__(self): + super(Ocarina, self).__init__('Ocarina', True) + + +class Boots(Item): + def __init__(self): + super(Boots, self).__init__('Pegasus Boots', True) + + +class Glove(Item): + def __init__(self): + super(Glove, self).__init__('Power Glove', True) + + +class Cape(Item): + def __init__(self): + super(Cape, self).__init__('Cape', True) + + +class Mushroom(Item): + def __init__(self): + super(Mushroom, self).__init__('Mushroom', True) + + +class Shovel(Item): + def __init__(self): + super(Shovel, self).__init__('Shovel', True) + + +class Lamp(Item): + def __init__(self): + super(Lamp, self).__init__('Lamp', True) + + +class Powder(Item): + def __init__(self): + super(Powder, self).__init__('Magic Powder', True) + + +class Pearl(Item): + def __init__(self): + super(Pearl, self).__init__('Moon Pearl', True) + + +class Somaria(Item): + def __init__(self): + super(Somaria, self).__init__('Cane of Somaria', True) + + +class FireRod(Item): + def __init__(self): + super(FireRod, self).__init__('Fire Rod', True) + + +class Flippers(Item): + def __init__(self): + super(Flippers, self).__init__('Flippers', True) + + +class IceRod(Item): + def __init__(self): + super(IceRod, self).__init__('Ice Rod', True) + + +class Mitts(Item): + def __init__(self): + super(Mitts, self).__init__("Titans Mitts", True) + + +class Ether(Item): + def __init__(self): + super(Ether, self).__init__('Ether', True) + + +class Bombos(Item): + def __init__(self): + super(Bombos, self).__init__('Bombos', True) + + +class Quake(Item): + def __init__(self): + super(Quake, self).__init__('Quake', True) + + +class Bottle(Item): + def __init__(self): + super(Bottle, self).__init__('Bottle', True) + + +class MasterSword(Item): + def __init__(self): + super(MasterSword, self).__init__('Master Sword', True) + + +class TemperedSword(Item): + def __init__(self): + super(TemperedSword, self).__init__('Tempered Sword', True) + + +class FighterSword(Item): + def __init__(self): + super(FighterSword, self).__init__('Fighter Sword', True) + + +class GoldenSword(Item): + def __init__(self): + super(GoldenSword, self).__init__('GoldenSword', True) + + +class ProgressiveSword(Item): + def __init__(self): + super(ProgressiveSword, self).__init__('Progressive Sword', True) + + +class ProgressiveGlove(Item): + def __init__(self): + super(ProgressiveGlove, self).__init__('Progressive Glove', True) + + +class SilverArrows(Item): + def __init__(self): + super(SilverArrows, self).__init__('Silver Arrows', True) + + +class GreenPendant(Item): + def __init__(self): + super(GreenPendant, self).__init__('Green Pendant', True) + + +class RedPendant(Item): + def __init__(self): + super(RedPendant, self).__init__('Red Pendant', True) + + +class BluePendant(Item): + def __init__(self): + super(BluePendant, self).__init__('Blue Pendant', True) + + +class Triforce(Item): + def __init__(self): + super(Triforce, self).__init__('Triforce', True) + + +class Crystal1(Item): + def __init__(self): + super(Crystal1, self).__init__('Crystal 1', True) + + +class Crystal2(Item): + def __init__(self): + super(Crystal2, self).__init__('Crystal 2', True) + + +class Crystal3(Item): + def __init__(self): + super(Crystal3, self).__init__('Crystal 3', True) + + +class Crystal4(Item): + def __init__(self): + super(Crystal4, self).__init__('Crystal 4', True) + + +class Crystal5(Item): + def __init__(self): + super(Crystal5, self).__init__('Crystal 5', True) + + +class Crystal6(Item): + def __init__(self): + super(Crystal6, self).__init__('Crystal 6', True) + + +class Crystal7(Item): + def __init__(self): + super(Crystal7, self).__init__('Crystal 7', True) + + +class SingleArrow(Item): + def __init__(self): + super(SingleArrow, self).__init__('Single Arrow', False) + + +class Arrows10(Item): + def __init__(self): + super(Arrows10, self).__init__('Arrows (10)', False) + + +class ArrowUpgrade10(Item): + def __init__(self): + super(ArrowUpgrade10, self).__init__('Arrow Upgrade (+10)', False) + + +class ArrowUpgrade5(Item): + def __init__(self): + super(ArrowUpgrade5, self).__init__('Arrow Upgrade (+10)', False) + + +class SingleBomb(Item): + def __init__(self): + super(SingleBomb, self).__init__('Single Bomb', False) + + +class Bombs3(Item): + def __init__(self): + super(Bombs3, self).__init__('Bombs (3)', False) + + +class BombUpgrade10(Item): + def __init__(self): + super(BombUpgrade10, self).__init__('Bomb Upgrade (+10)', False) + + +class BombUpgrade5(Item): + def __init__(self): + super(BombUpgrade5, self).__init__('Bomb Upgrade (+5)', False) + + +class BlueMail(Item): + def __init__(self): + super(BlueMail, self).__init__('Blue Mail', False) + + +class RedMail(Item): + def __init__(self): + super(RedMail, self).__init__('Red Mail', False) + + +class ProgressiveArmor(Item): + def __init__(self): + super(ProgressiveArmor, self).__init__('Progressive Armor', False) + + +class BlueBoomerang(Item): + def __init__(self): + super(BlueBoomerang, self).__init__('Blue Boomerang', False) + + +class RedBoomerang(Item): + def __init__(self): + super(RedBoomerang, self).__init__('Red Boomerang', False) + + +class BlueShield(Item): + def __init__(self): + super(BlueShield, self).__init__('Blue Shield', False) + + +class RedShield(Item): + def __init__(self): + super(RedShield, self).__init__('Red Shield', False) + + +class MirrorShield(Item): + def __init__(self): + super(MirrorShield, self).__init__('Mirror Shield', False) + + +class ProgressiveShield(Item): + def __init__(self): + super(ProgressiveShield, self).__init__('Progressive Shield', False) + + +class Net(Item): + def __init__(self): + super(Net, self).__init__('Bug Catching Net', False) + + +class Byrna(Item): + def __init__(self): + super(Byrna, self).__init__('Cane of Byrna', False) + + +class HeartContainer(Item): + def __init__(self): + super(HeartContainer, self).__init__('Boss Heart Container', False) + + +class SancHeart(Item): + def __init__(self): + super(SancHeart, self).__init__('Sanctuary Heart Container', False) + + +class PieceOfHeart(Item): + def __init__(self): + super(PieceOfHeart, self).__init__('Piece of Heart', False) + + +class Rupee(Item): + def __init__(self): + super(Rupee, self).__init__('Rupee (1)', False) + + +class Rupees5(Item): + def __init__(self): + super(Rupees5, self).__init__('Rupees (5)', False) + + +class Rupees20(Item): + def __init__(self): + super(Rupees20, self).__init__('Rupees (20)', False) + + +class Rupees50(Item): + def __init__(self): + super(Rupees50, self).__init__('Rupees (50)', False) + + +class Rupees100(Item): + def __init__(self): + super(Rupees100, self).__init__('Rupees (100)', False) + + +class Rupees300(Item): + def __init__(self): + super(Rupees300, self).__init__('Rupees (300)', False) + + +class HalfMagic(Item): + def __init__(self): + super(HalfMagic, self).__init__('Magic Upgrade (1/2)', True) # can be required to beat mothula in an open seed in very very rare circumstance + + +class QuarterMagic(Item): + def __init__(self): + super(QuarterMagic, self).__init__('Magic Upgrade (1/4)', True) # can be required to beat mothula in an open seed in very very rare circumstance + + +class EPSmallKey(Item): + def __init__(self): + super(EPSmallKey, self).__init__('Small Key (Eastern Palace)', False, True) + + +class EPBigKey(Item): + def __init__(self): + super(EPBigKey, self).__init__('Big Key (Eastern Palace)', False, True) + + +class EPCompass(Item): + def __init__(self): + super(EPCompass, self).__init__('Compass (Eastern Palace)', False) + + +class EPMap(Item): + def __init__(self): + super(EPMap, self).__init__('Map (Eastern Palace)', False) + + +class DPSmallKey(Item): + def __init__(self): + super(DPSmallKey, self).__init__('Small Key (Desert Palace)', False, True) + + +class DPBigKey(Item): + def __init__(self): + super(DPBigKey, self).__init__('Big Key (Desert Palace)', False, True) + + +class DPCompass(Item): + def __init__(self): + super(DPCompass, self).__init__('Compass (Desert Palace)', False) + + +class DPMap(Item): + def __init__(self): + super(DPMap, self).__init__('Map (Desert Palace)', False) + + +class THSmallKey(Item): + def __init__(self): + super(THSmallKey, self).__init__('Small Key (Tower of Hera)', False, True) + + +class THBigKey(Item): + def __init__(self): + super(THBigKey, self).__init__('Big Key (Tower of Hera)', False, True) + + +class THCompass(Item): + def __init__(self): + super(THCompass, self).__init__('Compass (Tower of Hera)', False) + + +class THMap(Item): + def __init__(self): + super(THMap, self).__init__('Map (Tower of Hera)', False) + + +class ESSmallKey(Item): + def __init__(self): + super(ESSmallKey, self).__init__('Small Key (Escape)', False, True) + + +class ESBigKey(Item): + def __init__(self): + super(ESBigKey, self).__init__('Big Key (Escape)', False, True) + + +class ESMap(Item): + def __init__(self): + super(ESMap, self).__init__('Map (Escape)', False) + + +class ATSmallKey(Item): + def __init__(self): + super(ATSmallKey, self).__init__('Small Key (Aghanims Tower)', False, True) + + +class PDSmallKey(Item): + def __init__(self): + super(PDSmallKey, self).__init__('Small Key (Palace of Darkness)', False, True) + + +class PDBigKey(Item): + def __init__(self): + super(PDBigKey, self).__init__('Big Key (Palace of Darkness)', False, True) + + +class PDCompass(Item): + def __init__(self): + super(PDCompass, self).__init__('Compass (Palace of Darkness)', False) + + +class PDMap(Item): + def __init__(self): + super(PDMap, self).__init__('Map (Palace of Darkness)', False) + + +class TTSmallKey(Item): + def __init__(self): + super(TTSmallKey, self).__init__('Small Key (Thieves Town)', False, True) + + +class TTBigKey(Item): + def __init__(self): + super(TTBigKey, self).__init__('Big Key (Thieves Town)', False, True) + + +class TTCompass(Item): + def __init__(self): + super(TTCompass, self).__init__('Compass (Thieves Town)', False) + + +class TTMap(Item): + def __init__(self): + super(TTMap, self).__init__('Map (Thieves Town)', False) + + +class SWSmallKey(Item): + def __init__(self): + super(SWSmallKey, self).__init__('Small Key (Skull Woods)', False, True) + + +class SWBigKey(Item): + def __init__(self): + super(SWBigKey, self).__init__('Big Key (Skull Woods)', False, True) + + +class SWCompass(Item): + def __init__(self): + super(SWCompass, self).__init__('Compass (Skull Woods)', False) + + +class SWMap(Item): + def __init__(self): + super(SWMap, self).__init__('Map (Skull Woods)', False) + + +class SPSmallKey(Item): + def __init__(self): + super(SPSmallKey, self).__init__('Small Key (Swamp Palace)', False, True) + + +class SPBigKey(Item): + def __init__(self): + super(SPBigKey, self).__init__('Big Key (Swamp Palace)', False, True) + + +class SPCompass(Item): + def __init__(self): + super(SPCompass, self).__init__('Compass (Swamp Palace)', False) + + +class SPMap(Item): + def __init__(self): + super(SPMap, self).__init__('Map (Swamp Palace)', False) + + +class IPSmallKey(Item): + def __init__(self): + super(IPSmallKey, self).__init__('Small Key (Ice Palace)', False, True) + + +class IPBigKey(Item): + def __init__(self): + super(IPBigKey, self).__init__('Big Key (Ice Palace)', False, True) + + +class IPCompass(Item): + def __init__(self): + super(IPCompass, self).__init__('Compass (Ice Palace)', False) + + +class IPMap(Item): + def __init__(self): + super(IPMap, self).__init__('Map (Ice Palace)', False) + + +class MMSmallKey(Item): + def __init__(self): + super(MMSmallKey, self).__init__('Small Key (Misery Mire)', False, True) + + +class MMBigKey(Item): + def __init__(self): + super(MMBigKey, self).__init__('Big Key (Misery Mire)', False, True) + + +class MMCompass(Item): + def __init__(self): + super(MMCompass, self).__init__('Compass (Misery Mire)', False) + + +class MMMap(Item): + def __init__(self): + super(MMMap, self).__init__('Map (Misery Mire)', False) + + +class TRSmallKey(Item): + def __init__(self): + super(TRSmallKey, self).__init__('Small Key (Turtle Rock)', False, True) + + +class TRBigKey(Item): + def __init__(self): + super(TRBigKey, self).__init__('Big Key (Turtle Rock)', False, True) + + +class TRCompass(Item): + def __init__(self): + super(TRCompass, self).__init__('Compass (Turtle Rock)', False) + + +class TRMap(Item): + def __init__(self): + super(TRMap, self).__init__('Map (Turtle Rock)', False) + + +class GTSmallKey(Item): + def __init__(self): + super(GTSmallKey, self).__init__('Small Key (Ganons Tower)', False, True) + + +class GTBigKey(Item): + def __init__(self): + super(GTBigKey, self).__init__('Big Key (Ganons Tower)', False, True) + + +class GTCompass(Item): + def __init__(self): + super(GTCompass, self).__init__('Compass (Ganons Tower)', False) + + +class GTMap(Item): + def __init__(self): + super(GTMap, self).__init__('Map (Ganons Tower)', False) \ No newline at end of file diff --git a/Main.py b/Main.py new file mode 100644 index 00000000..d45b3826 --- /dev/null +++ b/Main.py @@ -0,0 +1,188 @@ +from BaseClasses import World +from Regions import create_regions +from EntranceShuffle import link_entrances +from Rules import set_rules +from Dungeons import fill_dungeons +from Items import * +import random +import cProfile +import time + + +def main(seed=None, shuffle='Default', logic='no-glitches', mode='standard', difficulty='normal', goal='defeat ganon'): + # initialize the world + world = World() + create_regions(world) + + random.seed(seed) + + link_entrances(world, shuffle) + set_rules(world, logic, mode) + generate_itempool(world, difficulty, goal) + distribute_items(world) + return world + + +def distribute_items(world): + # get list of locations to fill in + fill_locations = world.get_unfilled_locations() + random.shuffle(fill_locations) + + # get items to distribute + random.shuffle(world.itempool) + itempool = world.itempool + + progress_done = False + + while itempool and fill_locations: + candidate_item_to_place = None + item_to_place = None + for item in itempool: + if progress_done: + item_to_place = item + break + if item.advancement: + candidate_item_to_place = item + if world.unlocks_new_location(item): + item_to_place = item + break + + if item_to_place is None: + # check if we can reach all locations and that is why we find no new locations to place + if len(world.get_reachable_locations()) == len(world.get_locations()): + progress_done = True + continue + # we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying + if candidate_item_to_place is not None: + item_to_place = candidate_item_to_place + else: + raise RuntimeError('No more progress items left to place.') + + spot_to_fill = None + for location in fill_locations: + if world.state.can_reach(location) and location.item_rule(item_to_place): + spot_to_fill = location + break + + if spot_to_fill is None: + raise RuntimeError('No more spots to place %s' % item_to_place) + + world.push_item(spot_to_fill, item_to_place, True) + itempool.remove(item_to_place) + fill_locations.remove(spot_to_fill) + + print('Unplaced items: %s - Unfilled Locations: %s' % (itempool, fill_locations)) + + +def generate_itempool(world, difficulty, goal): + if difficulty != 'normal' or goal != 'defeat ganon': + raise NotImplementedError('Not supported yet') + + # Push the two fixed items + world.push_item('Uncle', ProgressiveSword()) + world.push_item('Ganon', Triforce(), False) + + # set up item pool + world.itempool = [ + ArrowUpgrade5(), ArrowUpgrade5(), ArrowUpgrade5(), ArrowUpgrade5(), ArrowUpgrade5(), ArrowUpgrade5(), + ArrowUpgrade10(), + SingleArrow(), + ProgressiveArmor(), ProgressiveArmor(), + BombUpgrade5(), BombUpgrade5(), BombUpgrade5(), BombUpgrade5(), BombUpgrade5(), BombUpgrade5(), + BombUpgrade10(), + Bombos(), + Book(), + BlueBoomerang(), + Bottle(), Bottle(), Bottle(), Bottle(), + Bow(), + Net(), + Byrna(), + Somaria(), + Ether(), + Rupees50(), Rupees50(), Rupees50(), Rupees50(), Rupees50(), Rupees50(), Rupees50(), + ProgressiveShield(), ProgressiveShield(), ProgressiveShield(), + ProgressiveSword(), ProgressiveSword(), ProgressiveSword(), + FireRod(), + Rupees5(), Rupees5(), Rupees5(), Rupees5(), + Flippers(), + Ocarina(), + Hammer(), + SancHeart(), + HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), HeartContainer(), + Hookshot(), + IceRod(), + Lamp(), + Cape(), + Mirror(), + Powder(), + RedBoomerang(), + Pearl(), + Mushroom(), + Rupees100(), + Rupee(), Rupee(), + Boots(), + PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), + PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), PieceOfHeart(), + ProgressiveGlove(), ProgressiveGlove(), + Quake(), + Shovel(), + SilverArrows(), + Arrows10(), Arrows10(), Arrows10(), Arrows10(), + Bombs3(), Bombs3(), Bombs3(), Bombs3(), Bombs3(), Bombs3(), Bombs3(), Bombs3(), Bombs3(), Bombs3(), + Rupees300(), Rupees300(), Rupees300(), Rupees300(), + Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), + Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20() + ] + + if random.randint(0, 3) == 0: + world.itempool.append(QuarterMagic()) + else: + world.itempool.append(HalfMagic()) + + # distribute crystals + crystals = [GreenPendant(), RedPendant(), BluePendant(), Crystal1(), Crystal2(), Crystal3(), Crystal4(), Crystal5(), Crystal6(), Crystal7()] + crystal_locations = [world.get_location('Armos - Pendant'), world.get_location('Lanmolas - Pendant'), world.get_location('Moldorm - Pendant'), world.get_location('Helmasaur - Crystal'), + world.get_location('Blind - Crystal'), world.get_location('Mothula - Crystal'), world.get_location('Arrghus - Crystal'), world.get_location('Kholdstare - Crystal'), + world.get_location('Vitreous - Crystal'), world.get_location('Trinexx - Crystal')] + random.shuffle(crystals) + for location, crystal in zip(crystal_locations, crystals): + world.push_item(location, crystal, False) + + # 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) + + # push dungeon items + fill_dungeons(world) + +#profiler = cProfile.Profile() + +#profiler.enable() +tally = {} +iterations = 300 +start = time.clock() +for i in range(iterations): + print('Seed %s\n\n' % i) + w = main() + for location in w.get_locations(): + if location.item is not None: + old_sk, old_bk, old_prog = tally.get(location.name, (0, 0, 0)) + if location.item.advancement: + old_prog += 1 + elif 'Small Key' in location.item.name: + old_sk += 1 + elif 'Big Key' in location.item.name: + old_bk += 1 + tally[location.name] = (old_sk, old_bk, old_prog) + +diff = time.clock() - start +print('Duration: %s, Average: %s' % (diff, diff/float(iterations))) + +print('\n\n\n') + +for location, stats in tally.items(): + print('%s, %s, %s, %s, %s, %s, %s, %s' % (location, stats[0], stats[0]/float(iterations), stats[1], stats[1]/float(iterations), stats[2], stats[2]/float(iterations), 0 if iterations - stats[0] - stats[1] == 0 else stats[2]/float(iterations - stats[0] - stats[1]))) +#profiler.disable() + +#profiler.print_stats() diff --git a/Regions.py b/Regions.py new file mode 100644 index 00000000..e021e3ae --- /dev/null +++ b/Regions.py @@ -0,0 +1,230 @@ +from BaseClasses import Region + + +def create_regions(world): + + world.regions = [ + LightWorld(), + create_region('Thiefs Hut', ["[cave-022-B1] Thiefs hut [top chest]", + "[cave-022-B1] Thiefs hut [top left chest]", + "[cave-022-B1] Thiefs hut [top right chest]", + "[cave-022-B1] Thiefs hut [bottom left chest]", + "[cave-022-B1] Thiefs hut [bottom right chest]"]), + create_region('Hyrule Castle Secret Entrance', ['Uncle', '[cave-034] Hyrule Castle Secret Entrance']), + create_region('Zoras River', ['King Zora', 'Piece of Heart (Zoras River)']), + create_region('Kings Grave', ['[cave-018] Graveyard - top right grave']), + create_region('Dam', ['[cave-047] Dam']), + create_region('Links House', ['[cave-040] Links House']), + create_region('Tavern', ['[cave-031] Tavern']), + create_region('Chicken House', ['[cave-026] Chicken House']), + create_region('Aginahs Cave', ['[cave-044] Aginahs Cave']), + create_region('Sahasrahlas Hut', ['[cave-035] Sahasrahlas Hut [left chest]', '[cave-035] Sahasrahlas Hut [center chest]', '[cave-035] Sahasrahlas Hut [right chest]', 'Sahasrahla']), + create_region('Kakariko Well (top)', ['[cave-021] Kakariko Well [top chest]', '[cave-021] Kakariko Well [left chest row of 3]', '[cave-021] Kakariko Well [center chest row of 3]', + '[cave-021] Kakariko Well [right chest row of 3]', '[cave-021] Kakariko Well [bottom chest]'], ['Kakariko Well (top to bottom)']), + create_region('Kakariko Well (bottom)'), + create_region('Blacksmiths Hut', ['Blacksmiths']), + create_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']), + create_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']), + create_region('Bat Cave (left)'), + create_region('Sick Kids House', ['Sick Kid']), + create_region('Hobo Bridge', ['Hobo']), + create_region('Thieves Forest Hideout (top)', ['Piece of Heart (Thieves Forest Hideout)'], ['Thieves Forest Hideout (top to bottom)']), + create_region('Thieves Forest Hideout (bottom)'), + create_region('Lumberjack Tree (top)', ['Piece of Heart (Lumberjack Tree)'], ['Lumberjack Tree (top to bottom)']), + create_region('Lumberjack Tree (bottom)'), + create_region('Cave South of Haunted Grove', ['Piece of Heart (Cave South of Haunted Grove)']), + create_region('Graveyard Cave', ['Piece of Heart (Graveyard Cave)']), + create_region('Desert Cave', ['Piece of Heart (Desert Cave)']), + create_region('Lake Hylia Cave', ['[cave-050] Lake Hylia Cave [bottom left chest]', '[cave-050] Lake Hylia Cave [top left chest]', '[cave-050] Lake Hylia Cave [top right chest]', + '[cave-050] Lake Hylia Cave [bottom right chest]', '[cave-050] Lake Hylia Cave [generous guy]']), + create_region('Ice Cave', ['[cave-051] Ice Cave']), + create_region('Bonk Rock Cave', ['[cave-016] Bonk Rock Cave']), + create_region('Library', ['Library']), + create_region('Witch Hut', ['Witch']), + create_region('Lake Hylia Island', ['Piece of Heart (Lake Hylia)']), + create_region('Two Brothers House', None, ['Two Brothers House (left)']), + create_region('Maze Race Ledge', ['Piece of Heart (Maze Race)']), + create_region('Desert Ledge', ['Piece of Heart (Desert - west side)'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']), + create_region('Desert Ledge (West)', None, ['Desert Cave']), + create_region('Desert Palace Stairs', None, ['Desert Palace Entrance (South)']), + create_region('Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)']), + create_region('Desert Palace Main', ['[dungeon-L2-B1] Desert Palace - Big Chest', '[dungeon-L2-B1] Desert Palace - Torch', '[dungeon-L2-B1] Desert Palace - Map Room'], + ['Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']), + create_region('Desert Palace East', ['[dungeon-L2-B1] Desert Palace - Compass Room', '[dungeon-L2-B1] Desert Palace - Big Key Room']), + create_region('Desert Palace North', ['Lanmolas - Heart Container', 'Lanmolas - Pendant']), + create_region('Eastern Palace', ['[dungeon-L1-1F] Eastern Palace - Compass Room', '[dungeon-L1-1F] Eastern Palace - Big Chest', '[dungeon-L1-1F] Eastern Palace - Big Ball Room', + '[dungeon-L1-1F] Eastern Palace - Big Key Room', '[dungeon-L1-1F] Eastern Palace - Map Room', 'Armos - Heart Container', 'Armos - Pendant']), + create_region('Master Sword Meadow', ['Altar']), + create_region('Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Aghanims Tower']), + create_region('Hyrule Castle', ['[dungeon-C-B1] Hyrule Castle - Boomerang Room', '[dungeon-C-B1] Hyrule Castle - Map Room', '[dungeon-C-B1] Hyrule Castle - Next To Zelda', + '[dungeon-C-B1] Escape - First B1 Room', '[dungeon-C-B1] Escape - Final Basement Room [left chest]', '[dungeon-C-B1] Escape - Final Basement Room [middle chest]', + '[dungeon-C-B1] Escape - Final Basement Room [right chest]', '[dungeon-C-1F] Sanctuary'], + ['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)']), + create_region('Aghanims Tower', ['[dungeon-A1-2F] Hyrule Castle Tower - 2 Knife Guys Room', '[dungeon-A1-3F] Hyrule Castle Tower - Maze Room'], ['Aghanim 1']), + create_region('Aghanim 1', None, ['Top of Pyramid']), + create_region('Old Man Cave', ['Old Mountain Man'], ['Old Man Cave Exit']), + create_region('Death Mountain', None, ['Old Man Cave (East)', 'Death Mountain Return Cave', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Broken Bridge (West)', 'Death Mountain Teleporter']), + create_region('Death Mountain Return Cave', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']), + create_region('Spectacle Rock Cave (Top)', ['Piece of Heart (Spectacle Rock Cave)'], ['Spectacle Rock Cave Drop']), + create_region('Spectacle Rock Cave (Bottom)', None, ['Spectacle Rock Cave Exit']), + create_region('Spectacle Rock Cave (Peak)', None, ['Spectacle Rock Cave Peak Drop']), + create_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Death Mountain Climb', 'East Death Mountain Teleporter']), + create_region('Death Mountain Climb Cave', ['[cave-009-1F] Death Mountain - right cave [top left chest]', + '[cave-009-1F] Death Mountain - right cave [top left middle chest]', + '[cave-009-1F] Death Mountain - right cave [top right middle chest]', + '[cave-009-1F] Death Mountain - right cave [top right chest]', + '[cave-009-1F] Death Mountain - right cave [bottom chest]', + '[cave-009-B1] Death Mountain - right cave [left chest]', + '[cave-009-B1] Death Mountain - right cave [right chest]'], + ['Death Mountain Climb Exit (Bottom)', 'Death Mountain Climb Exit (Top)']), + create_region('East Death Mountain (Top)', None, ['Death Mountain Climb (Top)', 'Death Mountain (Top)', 'Spiral Cave', 'East Death Mountain Drop', 'Turtle Rock Teleporter']), + create_region('Spiral Cave (Top)', ['[cave-012-1F] Death Mountain - left cave]'], ['Spiral Cave (top to bottom)']), + create_region('Spiral Cave (Bottom)', None, ['Spiral Cave Exit']), + create_region('Death Mountain (Top)', ['Ether Tablet'], ['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop']), + create_region('Spectacle Rock', ['Piece of Heart (Spectacle Rock)'], ['Spectacle Rock Drop']), + create_region('Tower of Hera (Bottom)', ['[dungeon-L3-1F] Tower of Hera - Freestanding Key', '[dungeon-L3-1F] Tower of Hera - Entrance'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door']), + create_region('Tower of Hera (Basement)', ['[dungeon-L3-1F] Tower of Hera - Basement']), + create_region('Tower of Hera (Top)', ['[dungeon-L3-1F] Tower of Hera - 4F [small chest]', '[dungeon-L3-1F] Tower of Hera - Big Chest', 'Moldorm - Heart Container', 'Moldorm - Pendant']), + + create_region('East Dark World', ['Piece of Heart (Pyramid)', 'Catfish'], ['Pyramid Fairy', 'South Dark World Bridge', 'West Dark World Gap', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)', + 'Hyrule Castle Ledge Mirror Spot']), + create_region('South Dark World', ['Flute Boy', 'Piece of Heart (Digging Game)', 'Bombos Tablet'], ['Dark Lake Hylia Drop (South)', 'Dark Swamp Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock', + 'Maze Race Mirror Spot', 'Cave South of Haunted Grove', 'East Dark World Bridge', 'Big Bomb Shop']), + create_region('Big Bomb Shop'), + create_region('Dark Lake Hylia', None, ['Lake Hylia Island Mirror Spot', 'East Dark World Pier']), + create_region('Dark Swamp Cave', ['[cave-073] Cave Northeast of Swamp Palace [top chest]', '[cave-073] Cave Northeast of Swamp Palace [top middle chest]', '[cave-073] Cave Northeast of Swamp Palace [bottom middle chest]', + '[cave-073] Cave Northeast of Swamp Palace [bottom chest]', '[cave-073] Cave Northeast of Swamp Palace [generous guy]']), + create_region('West Dark World', None, ['Village of Outcasts Drop', 'East Dark World River Pier', 'Doorless Hut', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Cave', 'Bumper Cave', 'Skull Woods Forest', + 'Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave']), + create_region('Dark World Hammer Peg Cave', ['Piece of Heart (Dark World Blacksmith Pegs)']), + create_region('Pyramid Fairy', ['Pyramid Fairy [left chest]', 'Pyramid Fairy [right chest]']), + create_region('Doorless Hut', ['[cave-063] Doorless Hut']), + create_region('C-Shaped House', ['[cave-062] C-Shaped House']), + create_region('Chest Game', ['Piece of Heart (Treasure Chest Game)']), + create_region('Bumper Cave', None, ['Bumper Cave (Bottom)', 'Bumper Cave (Top)']), + create_region('Bumper Cave Ledge', ['Piece of Heart (Bumper Cave)'], ['Bumper Cave Ledge Drop']), + create_region('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 Hole', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section']), + create_region('Dark Desert', None, ['Misery Mire', 'Dark Desert Cave', 'Desert Ledge (West) Mirror Spot', 'Desert Ledge Mirror Spot', 'Desert Palace Stairs Mirror Spot', 'Desert Palace Entrance (North) Mirror Spot']), + create_region('Dark Desert Cave', ['[cave-071] Misery Mire West Area [left chest]', '[cave-071] Misery Mire West Area [right chest]']), + create_region('Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot']), + create_region('Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Dark Death Mountain Climb (Top)', 'Hookshot Cave', + 'East Death Mountain (Top) Mirror Spot']), + create_region('Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot']), + create_region('Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot']), + create_region('Dark Death Mountain (East Bottom)', None, ['Dark Death Mountain Climb (Bottom)']), + create_region('Dark Death Mountain Climb', ['[cave-057-1F] Dark World Death Mountain Climb [top chest]', '[cave-057-1F] Dark World Death Mountain Climb [bottom chest]'], + ['Dark Death Mountain Climb Exit (Top)', 'Dark Death Mountain Climb Exit (Bottom)']), + create_region('Spike Cave', ['[cave-055] Spike Cave']), + create_region('Hookshot Cave', ['[cave-056] Hookshot Cave [top right chest]', '[cave-056] Hookshot Cave [top left chest]', '[cave-056] Hookshot Cave [bottom right chest]', '[cave-056] Hookshot Cave [bottom left chest]'], + ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), + create_region('Death Mountain Floating Island', ['Piece of Heart (Death Mountain - Floating Island)'], ['Floating Island Drop']), + create_region('Turtle Rock (Top)', None, ['Turtle Rock', 'Turtle Rock Drop']), + create_region('Mimic Cave', ['[cave-013] Mimic Cave']), + + create_region('Swamp Palace (Entrance)', None, ['Swamp Palace Moat']), + create_region('Swamp Palace (First Room)', ['[dungeon-D2-1F] Swamp Palace - First Room'], ['Swamp Palace Small Key Door']), + create_region('Swamp Palace (Starting Area)', ['[dungeon-D2-1F] Swamp Palace - Map Room'], ['Swamp Palace (Center)']), + create_region('Swamp Palace (Center)', ['[dungeon-D2-B1] Swamp Palace - Big Chest', '[dungeon-D2-B1] Swamp Palace - South of Hookshot Room', + '[dungeon-D2-B1] Swamp Palace - Big Key Chest', '[dungeon-D2-B1] Swamp Palace - Compass Chest'], ['Swamp Palace (North)']), + create_region('Swamp Palace (North)', ['[dungeon-D2-B2] Swamp Palace - Flooded Room [left chest]', '[dungeon-D2-B2] Swamp Palace - Flooded Room [right chest]', + '[dungeon-D2-B2] Swamp Palace - Waterfall Room', 'Arrghus - Heart Container', 'Arrghus - Crystal']), + create_region('Thieves Town (Entrance)', ['[dungeon-D4-B1] Thieves Town - Bottom Left of Huge Room [bottom right chest]', + '[dungeon-D4-B1] Thieves Town - Bottom Left of Huge Room [top left chest]', + '[dungeon-D4-B1] Thieves Town - Bottom Right of Huge Room', + '[dungeon-D4-B1] Thieves Town - Top Left of Huge Room'], ['Thieves Town Big Key Door']), + create_region('Thieves Town (Deep)', ['[dungeon-D4-1F] Thieves Town - Room above Boss', + '[dungeon-D4-B2] Thieves Town - Big Chest', + '[dungeon-D4-B2] Thieves Town - Chest next to Blind'], ['Blind Fight']), + create_region('Blind Fight', ['Blind - Heart Container', 'Blind - Crystal']), + create_region('Skull Woods First Section', ['[dungeon-D3-B1] Skull Woods - Compass Room', '[dungeon-D3-B1] Skull Woods - East of Big Chest', + '[dungeon-D3-B1] Skull Woods - Big Chest', '[dungeon-D3-B1] Skull Woods - Map Room', + '[dungeon - D3 - B1] Skull Woods - South of Big Chest'], ['Skull Woods First Section Exit']), + create_region('Skull Woods Second Section', ['[dungeon-D3-B1] Skull Woods - Big Key Room'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), + create_region('Skull Woods Final Section (Entrance)', ['[dungeon-D3-B1] Skull Woods - Final Section Entrance'], ['Skull Woods Torch Room']), + create_region('Skull Woods Final Section (Mothula)', ['Mothula - Heart Container', 'Mothula - Crystal']), + create_region('Ice Palace (Entrance)', None, ['Ice Palace Entrance Room']), + create_region('Ice Palace (Main)', ['[dungeon-D5-B1] Ice Palace - Compass Room', '[dungeon-D5-B4] Ice Palace - Above Big Chest', + '[dungeon-D5-B5] Ice Palace - Big Chest', '[dungeon-D5-B5] Ice Palace - Jellyfish Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']), + create_region('Ice Palace (East)', ['[dungeon-D5-B3] Ice Palace - Spike Room'], ['Ice Palace (East Top)']), + create_region('Ice Palace (East Top)', ['[dungeon-D5-B1] Ice Palace - Big Key Room', '[dungeon-D5-B2] Ice Palace - Map Room']), + create_region('Ice Palace (Kholdstare)', ['Kholdstare - Heart Container', 'Kholdstare - Crystal']), + create_region('Misery Mire (Entrance)', None, ['Misery Mire Entrance Gap']), + create_region('Misery Mire (Main)', ['[dungeon-D6-B1] Misery Mire - Big Chest', '[dungeon-D6-B1] Misery Mire - Map Room', '[dungeon-D6-B1] Misery Mire - Hub Room', + '[dungeon-D6-B1] Misery Mire - End of Bridge', '[dungeon-D6-B1] Misery Mire - Spike Room'], ['Misery Mire (West)', 'Misery Mire Big Key Door']), + create_region('Misery Mire (West)', ['[dungeon-D6-B1] Misery Mire - Compass Room', '[dungeon-D6-B1] Misery Mire - Big Key Room']), + create_region('Misery Mire (Final Area)', None, ['Misery Mire (Vitreous)']), + create_region('Misery Mire (Vitreous)', ['Vitreous - Heart Container', 'Vitreous - Crystal']), + create_region('Turtle Rock (Entrance)', None, ['Turtle Rock Entrance Gap']), + create_region('Turtle Rock (First Section)', ['[dungeon-D7-1F] Turtle Rock - Compass Room', '[dungeon-D7-1F] Turtle Rock - Map Room [left chest]', + '[dungeon-D7-1F] Turtle Rock - Map Room [right chest]'], ['Turtle Rock Pokey Room']), + create_region('Turtle Rock (Chain Chomp Room)', ['[dungeon-D7-1F] Turtle Rock - Chain Chomp Room'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']), + create_region('Turtle Rock (Second Section)', ['[dungeon-D7-B1] Turtle Rock - Big Key Room'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']), + create_region('Turtle Rock (Big Chest)', ['[dungeon-D7-B1] Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)']), + create_region('Turtle Rock (Roller Switch Room)', ['[dungeon-D7-B1] Turtle Rock - Roller Switch Room'], ['Turtle Rock Dark Room Staircase']), + create_region('Turtle Rock (Dark Room)', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']), + create_region('Turtle Rock (Eye Bridge)', ['[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom left chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]', + '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]'], + ['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']), + create_region('Turtle Rock (Trinexx)', ['Trinexx - Heart Container', 'Trinexx - Crystal']), + create_region('Dark Palace (Entrance)', ['[dungeon-D1-B1] Dark Palace - Shooter Room'], ['Dark Palace Bridge Room', 'Dark Palace Bonk Wall']), + create_region('Dark Palace (Center)', ['[dungeon-D1-1F] Dark Palace - Jump Room [left chest]', '[dungeon-D1-B1] Dark Palace - Turtle Stalfos Room'], + ['Dark Palace Big Key Chest Staircase', 'Dark Palace (North)', 'Dark Palace Big Key Door']), + create_region('Dark Palace (Big Key Chest)', ['[dungeon-D1-1F] Dark Palace - Big Key Room']), + create_region('Dark Palace (Bonk Section)', ['[dungeon-D1-1F] Dark Palace - Jump Room [right chest]', '[dungeon-D1-1F] Dark Palace - Statue Push Room'], ['Dark Palace Hammer Peg Drop']), + create_region('Dark Palace (North)', ['[dungeon-D1-1F] Dark Palace - Compass Room', '[dungeon-D1-B1] Dark Palace - Dark Room [left chest]', '[dungeon-D1-B1] Dark Palace - Dark Room [right chest]'], + ['Dark Palace Spike Statue Room Door', 'Dark Palace Maze Door']), + create_region('Dark Palace (Maze)', ['[dungeon-D1-1F] Dark Palace - Maze Room [top chest]', '[dungeon-D1-1F] Dark Palace - Maze Room [bottom chest]', '[dungeon-D1-1F] Dark Palace - Big Chest']), + create_region('Dark Palace (Spike Statue Room)', ['[dungeon-D1-1F] Dark Palace - Spike Statue Room']), + create_region('Dark Palace (Final Section)', ['Helmasaur - Heart Container', 'Helmasaur - Crystal']), + create_region('Ganons Tower (Entrance)', ['[dungeon-A2-1F] Ganons Tower - Torch', '[dungeon-A2-1F] Ganons Tower - Right Staircase [left chest]', '[dungeon-A2-1F] Ganons Tower - Right Staircase [right chest]'], + ['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door']), + create_region('Ganons Tower (Tile Room)', ['[dungeon-A2-1F] Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']), + create_region('Ganons Tower (Compass Room)', ['[dungeon-A2-1F] Ganons Tower - Compass Room [top left chest]', '[dungeon-A2-1F] Ganons Tower - Compass Room [top right chest]', + '[dungeon-A2-1F] Ganons Tower - Compass Room [bottom left chest]', '[dungeon-A2-1F] Ganons Tower - Compass Room [bottom right chest]'], + ['Ganons Tower (Bottom) (East)']), + create_region('Ganons Tower (Hookshot Room)', ['[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [top left chest]', '[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [top right chest]', + '[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [bottom left chest]', '[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [bottom right chest]'], + ['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']), + create_region('Ganons Tower (Map Room)', ['[dungeon-A2-1F] Ganons Tower - Map Room']), + create_region('Ganons Tower (Firesnake Room)', ['[dungeon-A2-1F] Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']), + create_region('Ganons Tower (Teleport Room)', ['[dungeon-A2-1F] Ganons Tower - Teleport Room [top left chest]', '[dungeon-A2-1F] Ganons Tower - Teleport Room [top right chest]', + '[dungeon-A2-1F] Ganons Tower - Teleport Room [bottom left chest]', '[dungeon-A2-1F] Ganons Tower - Teleport Room [bottom right chest]'], + ['Ganons Tower (Bottom) (West)']), + create_region('Ganons Tower (Bottom)', ['[dungeon-A2-1F] Ganons Tower - above Armos', '[dungeon-A2-1F] Ganons Tower - Big Chest', '[dungeon-A2-B1] Ganons Tower - Armos Room [left chest]', + '[dungeon-A2-B1] Ganons Tower - Armos Room [right chest]', '[dungeon-A2-B1] Ganons Tower - Armos Room [bottom chest]']), + create_region('Ganons Tower (Top)', None, ['Ganons Tower Torch Rooms']), + create_region('Ganons Tower (Before Moldorm)', ['[dungeon-A2-6F] Ganons Tower - Mini Helmasaur Room [left chest]', '[dungeon-A2-6F] Ganons Tower - Mini Helmasaur Room [right chest]', + '[dungeon-A2-6F] Ganons Tower - Room before Moldorm'], ['Ganons Tower Moldorm Door']), + create_region('Ganons Tower (Moldorm)', None, ['Ganons Tower Moldorm Gap']), + create_region('Aghanim 2', ['[dungeon-A2-6F] Ganons Tower - Moldorm Room'], ['Pyramid Hole']), + create_region('Pyramid', ['Ganon']) + ] + + +def create_region(name, locations=None, exits=None): + ret = Region(name) + if locations is None: + locations = [] + if exits is None: + exits = [] + ret.add_exits(*exits) + ret.add_locations(*locations) + return ret + + +class LightWorld(Region): + + def __init__(self): + super(LightWorld, self).__init__('Light World') + self.add_exits("Thiefs Hut", "Hyrule Castle Secret Entrance Drop", "Hyrule Castle Secret Entrance Stairs", 'Zoras River', 'Kings Grave', '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', 'Thieves Forest Hideout Drop', 'Thieves Forest Hideout Stump', + 'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Lake Hylia Cave', 'Ice Cave', + 'Bonk Rock Cave', 'Library', 'Witch Hut', 'Two Brothers House', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow', 'Hyrule Castle Entrance (South)', + 'Sanctuary', 'Sanctuary Grave', 'Old Man Cave (West)', 'Flute Spot 1', 'Ice Palace', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter') + self.add_locations('Mushroom', 'Bottle Vendor', 'Haunted Grove', 'Piece of Heart (Dam)', 'Purple Chest') + + def can_reach(self, state): + # this is our starting region + return True diff --git a/Rules.py b/Rules.py new file mode 100644 index 00000000..71fac8ae --- /dev/null +++ b/Rules.py @@ -0,0 +1,268 @@ +def set_rules(world, logic, mode): + global_rules(world) + + if logic == 'no-glitches' and mode in ['open', 'standard']: + no_glitches_rules(world, mode) + else: + raise NotImplementedError('Not implemented yet') + + +def set_rule(spot, rule): + spot.access_rule = rule + + +def add_rule(spot, rule, combine='and'): + old_rule = spot.access_rule + if combine == 'or': + spot.access_rule = lambda state: rule(state) or old_rule(state) + else: + spot.access_rule = lambda state: rule(state) and old_rule(state) + + +def forbid_item(location, item): + old_rule = location.item_rule + location.item_rule = lambda i: i.name != item and old_rule(i) + + +def global_rules(world): + # ganon can only carry triforce + world.get_location('Ganon').item_rule = lambda item: item.name == 'Triforce' + + # overworld requirements + set_rule(world.get_entrance('Kings Grave'), lambda state: state.has_Boots() and (state.can_lift_heavy_rocks() or (state.has_Mirror() and state.can_reach('West Dark World')))) + 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.can_reach('Top of Pyramid', 'Entrance')) + 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('Old Man Cave (West)'), lambda state: state.can_lift_rocks()) + set_rule(world.get_entrance('Flute Spot 1'), lambda state: state.has('Ocarina')) + set_rule(world.get_entrance('Ice Palace'), 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()) + set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) + 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()) + set_rule(world.get_location('Haunted Grove'), lambda state: state.has('Shovel')) + set_rule(world.get_location('Purple Chest'), lambda state: state.can_reach('Blacksmiths', 'Location')) + + set_rule(world.get_location('Piece of Heart (Zoras River)'), lambda state: state.has('Flippers')) + set_rule(world.get_location('Blacksmiths'), lambda state: state.can_lift_heavy_rocks() and state.has_Mirror() and state.can_reach('West Dark World')) + 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('Witch'), 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 Cave'), lambda state: state.can_lift_rocks()) + set_rule(world.get_location('Altar'), lambda state: state.can_collect('Red Pendant') and state.can_collect('Blue Pendant') and state.can_collect('Green Pendant')) + set_rule(world.get_location('Sahasrahla'), lambda state: state.can_collect('Green Pendant')) + set_rule(world.get_entrance('Aghanims Tower'), lambda state: state.has('Cape') or state.has_beam_sword()) + set_rule(world.get_entrance('Aghanim 1'), lambda state: state.has_blunt_weapon()) + 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('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('Catfish'), lambda state: state.has_Pearl() and state.can_lift_rocks()) + set_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.can_reach('Big Bomb Shop', 'Region') and state.has_Pearl() and state.can_collect('Crystal 5') and state.can_collect('Crystal 6') and + (state.can_reach('Top of Pyramid', 'Entrance') or state.can_reach('East Dark World Bridge', 'Entrance'))) + set_rule(world.get_entrance('South Dark World Bridge'), lambda state: state.has('Hammer') and state.has_Pearl()) + set_rule(world.get_entrance('West Dark World Gap'), lambda state: state.has_Pearl() and state.has('Hookshot') and (state.has('Flippers') or state.has('Hammer') or state.can_lift_rocks())) + set_rule(world.get_entrance('Palace of Darkness'), lambda state: state.has_Pearl()) # ToDo Not sure if required + set_rule(world.get_entrance('Hyrule Castle Ledge Mirror Spot'), 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('Flippers')) # ToDo any fake flipper set up? + set_rule(world.get_entrance('Village of Outcasts Heavy Rock'), lambda state: state.can_lift_heavy_rocks()) + set_rule(world.get_entrance('Maze Race Mirror Spot'), lambda state: state.has_Mirror()) + set_rule(world.get_entrance('Cave South of Haunted Grove'), lambda state: state.has_Mirror()) + set_rule(world.get_entrance('East Dark World Bridge'), lambda state: state.has('Hammer')) + set_rule(world.get_entrance('Lake Hylia Island Mirror Spot'), lambda state: state.has_Mirror() and state.has('Flippers')) + set_rule(world.get_entrance('East Dark World River Pier'), lambda state: state.has('Flippers')) # ToDo any fake flipper set up? + set_rule(world.get_entrance('Graveyard Cave'), lambda state: state.has_Mirror()) + set_rule(world.get_entrance('Bumper Cave'), lambda state: state.can_lift_rocks()) + set_rule(world.get_entrance('Bat Cave Drop Ledge Mirror Spot'), lambda state: state.can_lift_heavy_rocks() and state.has_Mirror()) + set_rule(world.get_entrance('Dark World Hammer Peg Cave'), lambda state: state.can_lift_heavy_rocks() and state.has('Hammer')) + set_rule(world.get_entrance('Bumper Cave (Top)'), lambda state: state.has('Cape')) + set_rule(world.get_entrance('Skull Woods Final Section'), lambda state: state.has('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 (West) Mirror Spot'), lambda state: state.has_Mirror()) + 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('Dark Desert Cave'), lambda state: state.has_Pearl()) # ToDo Bunny Revival can give access to this cave in super bunny state. Not sure how to deal with shuffled entrances, as much easier to block of cave entrances than individual shuffled chests + set_rule(world.get_entrance('Spike Cave'), lambda state: state.has_Pearl()) + set_rule(world.get_entrance('Spectacle Rock Mirror Spot'), lambda state: state.has_Mirror()) + set_rule(world.get_entrance('Ganons Tower'), lambda state: state.can_collect('Crystal 1') and state.can_collect('Crystal 2') and state.can_collect('Crystal 3') and state.can_collect('Crystal 4') and state.can_collect('Crystal 5') and state.can_collect('Crystal 6') and state.can_collect('Crystal 7')) + set_rule(world.get_entrance('Hookshot Cave'), lambda state: state.can_lift_rocks() and state.has_Pearl()) + 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('Isolated Ledge Mirror Spot'), lambda state: state.has_Mirror()) + set_rule(world.get_entrance('Dark Death Mountain Climb (Top)'), lambda state: state.has_Pearl()) # Chests inside could be collected with super bunny, but may be shuffled. rather limit access for now ToDo + set_rule(world.get_entrance('Dark Death Mountain Climb (Bottom)'), lambda state: state.has_Pearl()) + set_rule(world.get_location('[cave-055] Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks()) # damage should be survivable always somehow. MAY need more logic ToDo + set_rule(world.get_location('[cave-056] Hookshot Cave [top right chest]'), lambda state: state.has('Hookshot')) + set_rule(world.get_location('[cave-056] Hookshot Cave [top left chest]'), lambda state: state.has('Hookshot')) + set_rule(world.get_location('[cave-056] Hookshot Cave [bottom right chest]'), lambda state: state.has('Hookshot')) + set_rule(world.get_location('[cave-056] Hookshot Cave [bottom left chest]'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots')) + set_rule(world.get_location('Piece of Heart (Death Mountain - Floating Island)'), 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()) # sword required to cast magic (!) + set_rule(world.get_location('[cave-013] Mimic Cave'), lambda state: state.has('Hammer')) + + set_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), lambda state: state.can_lift_rocks()) # ToDo fix this up for shuffling, need access to drop into escape + set_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), lambda state: state.can_lift_rocks()) + set_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), lambda state: state.can_lift_rocks()) + + set_rule(world.get_location('[dungeon-L1-1F] Eastern Palace - Big Chest'), lambda state: state.can_collect('Big Key (Eastern Palace)')) + set_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Bow') and state.can_collect('Big Key (Eastern Palace)')) + set_rule(world.get_location('Armos - Pendant'), lambda state: state.has('Bow') and state.can_collect('Big Key (Eastern Palace)')) + for location in ['Armos - Heart Container', '[dungeon-L1-1F] Eastern Palace - Big Chest']: + forbid_item(world.get_location(location), 'Big Key (Eastern Palace)') + + set_rule(world.get_location('[dungeon-L2-B1] Desert Palace - Big Chest'), lambda state: state.can_collect('Big Key (Desert Palace)')) + set_rule(world.get_location('[dungeon-L2-B1] Desert Palace - Torch'), lambda state: state.has_Boots()) + set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.can_collect('Small Key (Desert Palace)')) + set_rule(world.get_location('Lanmolas - Pendant'), lambda state: state.can_collect('Small Key (Desert Palace)') and state.can_collect('Big Key (Desert Palace)') and state.has_fire_source() and + (state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow'))) + set_rule(world.get_location('Lanmolas - Heart Container'), lambda state: state.can_collect('Small Key (Desert Palace)') and state.can_collect('Big Key (Desert Palace)') and state.has_fire_source() and + (state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow'))) + for location in ['Lanmolas - Heart Container', '[dungeon-L2-B1] Desert Palace - Big Chest']: + forbid_item(world.get_location(location), 'Big Key (Desert Palace)') + + set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.can_collect('Small Key (Tower of Hera)')) + set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.can_collect('Big Key (Tower of Hera)')) + set_rule(world.get_location('[dungeon-L3-1F] Tower of Hera - Big Chest'), lambda state: state.can_collect('Big Key (Tower of Hera)')) + set_rule(world.get_location('[dungeon-L3-1F] Tower of Hera - Basement'), lambda state: state.has_fire_source()) + set_rule(world.get_location('Moldorm - Heart Container'), lambda state: state.has_blunt_weapon()) + set_rule(world.get_location('Moldorm - Pendant'), lambda state: state.has_blunt_weapon()) + for location in ['Moldorm - Heart Container', '[dungeon-L3-1F] Tower of Hera - Big Chest', '[dungeon-L3-1F] Tower of Hera - 4F [small chest]']: + forbid_item(world.get_location(location), 'Big Key (Tower of Hera)') + + set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.has_Pearl()) + set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.can_collect('Small Key (Swamp Palace)')) + set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer')) + set_rule(world.get_location('[dungeon-D2-B1] Swamp Palace - Big Chest'), lambda state: state.can_collect('Big Key (Swamp Palace)')) + set_rule(world.get_entrance('Swamp Palace (North)'), lambda state: state.has('Hookshot')) + set_rule(world.get_location('Arrghus - Heart Container'), lambda state: state.has_blunt_weapon()) + set_rule(world.get_location('Arrghus - Crystal'), lambda state: state.has_blunt_weapon()) + for location in ['[dungeon-D2-B1] Swamp Palace - Big Chest', '[dungeon-D2-1F] Swamp Palace - First Room']: + forbid_item(world.get_location(location), 'Big Key (Swamp Palace)') + + set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.can_collect('Big Key (Thieves Town)')) + set_rule(world.get_entrance('Blind Fight'), lambda state: state.has_blunt_weapon() or state.has('Cane of Somaria')) + set_rule(world.get_location('[dungeon-D4-B2] Thieves Town - Big Chest'), lambda state: state.can_collect('Small Key (Thieves Town)') and state.has('Hammer')) + set_rule(world.get_location('[dungeon-D4-1F] Thieves Town - Room above Boss'), lambda state: state.can_collect('Small Key (Thieves Town)')) + for location in ['[dungeon-D4-1F] Thieves Town - Room above Boss', '[dungeon-D4-B2] Thieves Town - Big Chest', '[dungeon-D4-B2] Thieves Town - Chest next to Blind', 'Blind - Heart Container']: + forbid_item(world.get_location(location), 'Big Key (Thieves Town)') + + set_rule(world.get_location('[dungeon-D3-B1] Skull Woods - Big Chest'), lambda state: state.can_collect('Big Key (Skull Woods)')) + set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.can_collect('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and + (state.has_blunt_weapon() or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) + for location in ['[dungeon-D3-B1] Skull Woods - Big Chest']: + forbid_item(world.get_location(location), 'Big Key (Skull Woods)') + + set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) # ToDo Rework key logic for Ice Palace for non-bombjump routes, will impose more restrictions + set_rule(world.get_location('[dungeon-D5-B5] Ice Palace - Big Chest'), lambda state: state.can_collect('Big Key (Ice Palace)')) + set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) + set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot')) # as one can be stupid and waste all keys but the guaranteed one on the East Wing, The Small Key Door access can never be required + set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) + for location in ['[dungeon-D5-B5] Ice Palace - Big Chest']: + forbid_item(world.get_location(location), 'Big Key (Ice Palace)') + + set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: state.has_Boots() or state.has('Hookshot')) + set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Big Chest'), lambda state: state.can_collect('Big Key (Misery Mire)')) + set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.can_collect('Big Key (Misery Mire)')) + # 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.can_collect('Small Key (Misery Mire)', 3) if state.can_reach('Misery Mire (Final Area)') else state.can_collect('Small Key (Misery Mire)', 2)) + set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon())) + for location in ['[dungeon-D6-B1] Misery Mire - Big Chest', 'Vitreous - Heart Container']: + forbid_item(world.get_location(location), 'Big Key (Misery Mire)') + + # This should be okay + set_rule(world.get_entrance('Turtle Rock Entrance Gap'), lambda state: state.has('Cane of Somaria')) + set_rule(world.get_location('[dungeon-D7-1F] Turtle Rock - Compass Room'), 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('[dungeon-D7-1F] Turtle Rock - Map Room [left chest]'), lambda state: state.has('Cane of Somaria') and state.has('Fire Rod')) + set_rule(world.get_location('[dungeon-D7-1F] Turtle Rock - Map Room [right chest]'), lambda state: state.has('Cane of Somaria') and state.has('Fire Rod')) + set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.can_collect('Small Key (Turtle Rock)', 3) if state.can_reach('Turtle Rock (Dark Room) (North)', 'Entrance') else state.can_collect('Small Key (Turtle Rock)', 2) if state.can_reach('Turtle Rock (Eye Bridge)') else state.can_collect('Small Key (Turtle Rock)', 1)) # May waste keys from back entrance if accessible + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.can_collect('Small Key (Turtle Rock)', 4)) # Just to be save + set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.can_collect('Small Key (Turtle Rock)', 4) if state.can_reach('Turtle Rock (Dark Room) (North)', 'Entrance') else state.can_collect('Small Key (Turtle Rock)', 3) if state.can_reach('Turtle Rock (Eye Bridge)') else state.can_collect('Small Key (Turtle Rock)', 2)) # May waste keys from back entrance if accessible + set_rule(world.get_location('[dungeon-D7-B1] Turtle Rock - Big Chest'), lambda state: state.can_collect('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.can_collect('Big Key (Turtle Rock)')) + set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.can_collect('Small Key (Turtle Rock)', 3)) + 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_entrance('Turtle Rock (Trinexx)'), lambda state: state.can_collect('Small Key (Turtle Rock)', 4) and state.can_collect('Big Key (Turtle Rock)') and + state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and + (state.has('Hammer') or state.has_beam_sword() or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) + for location in ['[dungeon-D7-B1] Turtle Rock - Big Chest', 'Trinexx - Heart Container', '[dungeon-D7-B1] Turtle Rock - Roller Switch Room', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom left chest]', + '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]']: # ToDo Big Key can be elsewhere if we have an entrance shuffle + forbid_item(world.get_location(location), 'Big Key (Turtle Rock)') + + # this key logic sucks ToDo + set_rule(world.get_entrance('Dark Palace Bonk Wall'), lambda state: state.has('Bow')) + set_rule(world.get_entrance('Dark Palace Hammer Peg Drop'), lambda state: state.has('Hammer')) + set_rule(world.get_entrance('Dark Palace Bridge Room'), lambda state: state.can_collect('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('Dark Palace Big Key Door'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 6) and state.can_collect('Big Key (Palace of Darkness)') and state.has('Bow') and state.has('Hammer')) + set_rule(world.get_entrance('Dark Palace Big Key Chest Staircase'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 6) or (state.world.get_location('[dungeon-D1-1F] Dark Palace - Big Key Room').item is not None and (state.world.get_location('[dungeon-D1-1F] Dark Palace - Big Key Room').item.name in ['Small Key (Palace of Darkness)']))) + set_rule(world.get_entrance('Dark Palace Spike Statue Room Door'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 6) or (state.world.get_location('[dungeon-D1-1F] Dark Palace - Spike Statue Room').item is not None and (state.world.get_location('[dungeon-D1-1F] Dark Palace - Spike Statue Room').item.name in ['Small Key (Palace of Darkness)']))) + set_rule(world.get_entrance('Dark Palace (North)'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 4)) + set_rule(world.get_entrance('Dark Palace Maze Door'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 6)) + set_rule(world.get_location('[dungeon-D1-1F] Dark Palace - Big Chest'), lambda state: state.can_collect('Big Key (Palace of Darkness)')) + for location in ['[dungeon-D1-1F] Dark Palace - Big Chest', 'Helmasaur - Heart Container']: + forbid_item(world.get_location(location), 'Big Key (Palace of Darkness)') + + # these key rules are conservative, you might be able to get away with more lenient rules + set_rule(world.get_location('[dungeon-A2-1F] Ganons Tower - 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_entrance('Ganons Tower (Map Room)'), lambda state: state.can_collect('Small Key (Ganons Tower)', 3) or (state.world.get_location('[dungeon-A2-1F] Ganons Tower - Map Room').item is not None and state.world.get_location('[dungeon-A2-1F] Ganons Tower - Map Room').item.name == 'Small Key (Ganons Tower)')) + set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.can_collect('Small Key (Ganons Tower)', 2)) + set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.can_collect('Small Key (Ganons Tower)', 3)) + set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.can_collect('Small Key (Ganons Tower)', 3)) # possibly too pessimistic + set_rule(world.get_location('[dungeon-A2-1F] Ganons Tower - Big Chest'), lambda state: state.can_collect('Big Key (Ganons Tower)')) + set_rule(world.get_location('[dungeon-A2-B1] Ganons Tower - Armos Room [left chest]'), lambda state: state.has('Bow') or state.has_blunt_weapon()) + set_rule(world.get_location('[dungeon-A2-B1] Ganons Tower - Armos Room [bottom chest]'), lambda state: state.has('Bow') or state.has_blunt_weapon()) + set_rule(world.get_location('[dungeon-A2-B1] Ganons Tower - Armos Room [right chest]'), lambda state: state.has('Bow') or state.has_blunt_weapon()) + set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.can_collect('Big Key (Ganons Tower)')) + set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source()) + set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.can_collect('Small Key (Ganons Tower)', 4)) + set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot')) + set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.can_reach('East Dark World') and (state.has_sword() or state.has('Bottle') or state.has('Bug Catching Net'))) # some obscure hammer on pyramid soft lock potential scenarios + for location in ['[dungeon-A2-1F] Ganons Tower - Big Chest', '[dungeon-A2-6F] Ganons Tower - Mini Helmasaur Room [left chest]', '[dungeon-A2-6F] Ganons Tower - Mini Helmasaur Room [right chest]', + '[dungeon-A2-6F] Ganons Tower - Room before Moldorm', '[dungeon-A2-6F] Ganons Tower - Moldorm Room']: + forbid_item(world.get_location(location), 'Big Key (Ganons Tower)') + + set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and (state.has('Tempered Sword') or state.has('Golden Sword') or state.has('Silver Arrows') or state.has('Lamp') or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) # need to light torch a sufficient amount of times + + +def no_glitches_rules(world, mode): + # overworld requirements + set_rule(world.get_entrance('Zoras River'), lambda state: state.has('Flippers') or state.can_lift_rocks()) + set_rule(world.get_entrance('Hobo Bridge'), lambda state: state.has('Flippers')) + add_rule(world.get_entrance('Ice Palace'), lambda state: state.has_Pearl() and state.has('Flippers')) + set_rule(world.get_entrance('Dark Lake Hylia Drop (East)'), lambda state: state.has_Pearl() and state.has('Flippers')) + add_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Lamp')) + add_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Lamp')) + add_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Lamp')) + add_rule(world.get_entrance('Dark Palace Big Key Door'), lambda state: state.has('Lamp')) + add_rule(world.get_entrance('Dark Palace Maze Door'), lambda state: state.has('Lamp')) + set_rule(world.get_location('[dungeon-D1-B1] Dark Palace - Dark Room [left chest]'), lambda state: state.has('Lamp')) + set_rule(world.get_location('[dungeon-D1-B1] Dark Palace - Dark Room [right chest]'), lambda state: state.has('Lamp')) + add_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hookshot')) + + if mode == 'open': + add_rule(world.get_entrance('Aghanim 1'), lambda state: state.has('Lamp')) + set_rule(world.get_location('Old Mountain Man'), lambda state: state.has('Lamp')) + set_rule(world.get_entrance('Old Man Cave Exit'), lambda state: state.has('Lamp')) + set_rule(world.get_location('[dungeon-L1-1F] Eastern Palace - Big Key Room'), lambda state: state.has('Lamp')) + add_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Lamp')) + add_rule(world.get_location('Armos - Pendant'), lambda state: state.has('Lamp')) + add_rule(world.get_location('[dungeon-C-B1] Escape - First B1 Room'), lambda state: state.has('Lamp')) + elif mode == 'standard': + add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), lambda state: state.can_collect('Small Key (Escape)')) + add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), lambda state: state.can_collect('Small Key (Escape)')) + add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), lambda state: state.can_collect('Small Key (Escape)')) + add_rule(world.get_location('[dungeon-C-1F] Sanctuary'), lambda state: state.can_collect('Small Key (Escape)'))