Make keys, pendants, crystals and agahnim fights "events", which we sweep for everytime we have new unlock potential. Simplifies searching for items significantly.

This commit is contained in:
LLCoolDave 2017-06-17 14:40:37 +02:00
parent 1fe6774ba0
commit a73dba985f
7 changed files with 152 additions and 112 deletions

View File

@ -76,6 +76,39 @@ class World(object):
return r_location
raise RuntimeError('No such location %s' % location)
def get_all_state(self):
ret = CollectionState(self)
def soft_collect(item):
if item.name.startswith('Progressive '):
if 'Sword' in item.name:
if ret.has('Golden Sword'):
pass
elif ret.has('Tempered Sword'):
ret.prog_items.append('Golden Sword')
elif ret.has('Master Sword'):
ret.prog_items.append('Tempered Sword')
elif ret.has('Fighter Sword'):
ret.prog_items.append('Master Sword')
else:
ret.prog_items.append('Fighter Sword')
elif 'Glove' in item.name:
if ret.has('Titans Mitts'):
pass
elif ret.has('Power Glove'):
ret.prog_items.append('Titans Mitts')
else:
ret.prog_items.append('Power Glove')
elif item.advancement:
ret.prog_items.append(item.name)
for location in self.get_filled_locations():
soft_collect(location.item)
for item in self.itempool:
soft_collect(item)
return ret
def find_items(self, item):
return [location for location in self.get_locations() if location.item is not None and location.item.name == item]
@ -87,7 +120,7 @@ class World(object):
location.item = item
item.location = location
if collect:
self.state.collect(item)
self.state.collect(item, location.event)
logging.getLogger('').debug('Placed %s at %s' % (item, location))
else:
@ -103,6 +136,9 @@ class World(object):
def get_unfilled_locations(self):
return [location for location in self.get_locations() if location.item is None]
def get_filled_locations(self):
return [location for location in self.get_locations() if location.item is not None]
def get_reachable_locations(self, state=None):
if state is None:
state = self.state
@ -116,7 +152,7 @@ class World(object):
def unlocks_new_location(self, item):
temp_state = self.state.copy()
temp_state._clear_cache()
temp_state.collect(item)
temp_state.collect(item, True)
for location in self.get_unfilled_locations():
if temp_state.can_reach(location) and not self.state.can_reach(location):
@ -125,7 +161,7 @@ class World(object):
return False
def can_beat_game(self):
prog_locations = [location for location in self.get_locations() if location.item is not None and location.item.advancement]
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event)]
state = CollectionState(self)
treasure_pieces_collected = 0
@ -148,7 +184,7 @@ class World(object):
for location in sphere:
prog_locations.remove(location)
state.collect(location.item)
state.collect(location.item, True)
return False
@ -166,14 +202,14 @@ class World(object):
class CollectionState(object):
def __init__(self, parent, has_everything=False):
def __init__(self, parent):
self.prog_items = []
self.world = parent
self.has_everything = has_everything
self.region_cache = {}
self.location_cache = {}
self.entrance_cache = {}
self.recursion_count = 0
self.events = []
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
@ -182,11 +218,12 @@ class CollectionState(object):
self.entrance_cache = {k: v for k, v in self.entrance_cache.items() if v}
def copy(self):
ret = CollectionState(self.world, self.has_everything)
ret = CollectionState(self.world)
ret.prog_items = copy.copy(self.prog_items)
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.events = copy.copy(self.events)
return ret
def can_reach(self, spot, resolution_hint=None):
@ -233,45 +270,24 @@ class CollectionState(object):
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?
return self._can_reach_n(self.world.find_items(item), 1)
return self.can_reach(cached)
def sweep_for_events(self, key_only=False):
# this may need improvement
new_locations = True
checked_locations = 0
while new_locations:
reachable_events = [location for location in self.world.get_filled_locations() if location.event and (not key_only or location.item.key) and self.can_reach(location)]
for event in reachable_events:
if event.name not in self.events:
self.events.append(event.name)
self.collect(event.item, True)
new_locations = len(reachable_events) > checked_locations
checked_locations = len(reachable_events)
return self._can_reach_n(self.world.find_items(item), count)
def _can_reach_n(self, candidates, count):
maxfail = len(candidates) - count
fail = 0
success = 0
for candidate in candidates:
if self.can_reach(candidate):
success += 1
else:
fail += 1
if fail > maxfail:
return False
if success >= count:
return True
return False
def has(self, item):
if self.has_everything:
return True
else:
def has(self, item, count=1):
if count == 1:
return item in self.prog_items
else:
return len([pritem for pritem in self.prog_items if pritem == item]) >= count
def can_lift_rocks(self):
return self.has('Power Glove') or self.has('Titans Mitts')
@ -306,7 +322,7 @@ class CollectionState(object):
def has_turtle_rock_medallion(self):
return self.has(self.world.required_medallions[1])
def collect(self, item):
def collect(self, item, event=False):
changed = False
if item.name.startswith('Progressive '):
if 'Sword' in item.name:
@ -334,12 +350,15 @@ class CollectionState(object):
self.prog_items.append('Power Glove')
changed = True
elif item.advancement:
elif event or item.advancement:
self.prog_items.append(item.name)
changed = True
if changed:
self._clear_cache()
if not event:
self.sweep_for_events()
self._clear_cache()
def remove(self, item):
if item.advancement:
@ -456,6 +475,7 @@ class Location(object):
self.hint_text = hint_text if hint_text is not None else 'Hyrule'
self.recursion_count = 0
self.staleness_count = 0
self.event = False
def access_rule(self, state):
return True

View File

@ -20,15 +20,18 @@ def fill_dungeons(world):
freebes = ['[dungeon-A2-1F] Ganons Tower - Map Room', '[dungeon-D1-1F] Dark Palace - Spike Statue Room', '[dungeon-D1-1F] Dark Palace - Big Key Room']
all_state_base = world.get_all_state()
# this key is in a fixed location (for now)
world.push_item(world.get_location('[dungeon-D3-B1] Skull Woods - South of Big Chest'), ItemFactory('Small Key (Skull Woods)'), False)
world.get_location('[dungeon-D3-B1] Skull Woods - South of Big Chest').event = True
for dungeon_regions, big_key, small_keys, dungeon_items in [TR, ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, 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)
all_state = all_state_base.copy()
# first place big key
if big_key is not None:
@ -42,11 +45,13 @@ def fill_dungeons(world):
raise RuntimeError('No suitable location for %s' % big_key)
world.push_item(bk_location, big_key, False)
bk_location.event = True
dungeon_locations.remove(bk_location)
all_state._clear_cache()
# next place small keys
for small_key in small_keys:
all_state.sweep_for_events()
sk_location = None
for location in dungeon_locations:
if location.name in freebes or location.can_reach(all_state):
@ -57,6 +62,7 @@ def fill_dungeons(world):
raise RuntimeError('No suitable location for %s' % small_key)
world.push_item(sk_location, small_key, False)
sk_location.event = True
dungeon_locations.remove(sk_location)
all_state._clear_cache()

View File

@ -155,4 +155,6 @@ item_table = {'Bow': (True, False, False, False, 0x0B, 'You have\nchosen the\nar
'Big Key (Ganons Tower)': (False, False, True, False, 0x32, None, None, None, None, None, None),
'Compass (Ganons Tower)': (False, True, False, False, 0x25, None, None, None, None, None, None),
'Map (Ganons Tower)': (False, True, False, False, 0x33, None, None, None, None, None, None),
'Nothing': (False, False, False, False, 0x5A, 'Some Hot Air', None, None, None, None, None)}
'Nothing': (False, False, False, False, 0x5A, 'Some Hot Air', None, None, None, None, None),
'Beat Agahnim 1': (True, False, False, False, None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, False, False, None, None, None, None, None, None, None)}

11
Main.py
View File

@ -309,6 +309,10 @@ def generate_itempool(world):
raise NotImplementedError('Not supported yet')
world.push_item('Ganon', ItemFactory('Triforce'), False)
world.push_item('Agahnim 1', ItemFactory('Beat Agahnim 1'), False)
world.get_location('Agahnim 1').event = True
world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False)
world.get_location('Agahnim 2').event = True
# set up item pool
if world.difficulty in ['timed', 'timed-countdown']:
@ -380,6 +384,7 @@ def generate_itempool(world):
random.shuffle(crystals)
for location, crystal in zip(crystal_locations, crystals):
world.push_item(location, crystal, False)
location.event = True
# shuffle medallions
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
@ -418,6 +423,8 @@ def copy_world(world):
item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.key)
ret.get_location(location.name).item = item
item.location = ret.get_location(location.name)
if location.event:
ret.get_location(location.name).event = True
# copy remaining itempool. No item in itempool should have an assigned location
for item in world.itempool:
@ -454,7 +461,9 @@ def create_playthrough(world):
for location in sphere:
sphere_candidates.remove(location)
state.collect(location.item)
state.collect(location.item, True)
state.sweep_for_events(key_only=True)
collection_spheres.append(sphere)

View File

@ -13,7 +13,7 @@ def create_regions(world):
'Sanctuary', 'Sanctuary Grave', 'Old Man Cave (West)', 'Flute Spot 1', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter',
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Waterfall of Wishing',
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Swamp Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game']),
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Swamp Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', 'Top of Pyramid']),
create_region('Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']),
create_region('Thiefs Hut', ["[cave-022-B1] Thiefs hut [top chest]",
"[cave-022-B1] Thiefs hut [top left chest]",
@ -96,7 +96,7 @@ def create_regions(world):
'[dungeon-C-B1] Escape - Final Basement Room [right chest]'], ['Sanctuary Push Door', 'Sewers Back Door']),
create_region('Sanctuary', ['[dungeon-C-1F] Sanctuary'], ['Sanctuary Exit']),
create_region('Agahnims Tower', ['[dungeon-A1-2F] Hyrule Castle Tower - 2 Knife Guys Room', '[dungeon-A1-3F] Hyrule Castle Tower - Maze Room'], ['Agahnim 1', 'Agahnims Tower Exit']),
create_region('Agahnim 1', None, ['Top of Pyramid']),
create_region('Agahnim 1', ['Agahnim 1'], None),
create_region('Old Man Cave', ['Old Mountain Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
create_region('Old Man House', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
create_region('Old Man House Back', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
@ -132,7 +132,7 @@ def create_regions(world):
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 Pay Kiki', 'Dark Lake Hylia Drop (East)', 'Dark Lake Hylia Teleporter',
'Hyrule Castle Ledge Mirror Spot', 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Dark World Potion Shop']),
'Hyrule Castle Ledge Mirror Spot', 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Dark World Potion Shop', 'Pyramid Hole']),
create_region('Palace of Darkness Kiki Door', None, ['Palace of Darkness', 'Palace of Darkness Kiki Door Reverse']),
create_region('Palace of Darkness Hint'),
create_region('East Dark World Hint'),
@ -262,7 +262,7 @@ def create_regions(world):
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('Agahnim 2', ['[dungeon-A2-6F] Ganons Tower - Moldorm Room'], ['Pyramid Hole']),
create_region('Agahnim 2', ['[dungeon-A2-6F] Ganons Tower - Moldorm Room', 'Agahnim 2'], None),
create_region('Pyramid', ['Ganon'], ['Ganon Drop']),
create_region('Bottom of Pyramid', None, ['Pyramid Exit']),
create_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop'])
@ -499,6 +499,8 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
'[dungeon-A2-6F] Ganons Tower - Room before Moldorm': (0xEB03, False, 'atop My Tower'),
'[dungeon-A2-6F] Ganons Tower - Moldorm Room': (0xEB06, False, 'atop My Tower'),
'Ganon': (None, False, 'from me'),
'Agahnim 1': (None, False, 'from my wizardry from'),
'Agahnim 2': (None, False, 'from my wizardry from'),
'Armos - Pendant': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
'Lanmolas - Pendant': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
'Moldorm - Pendant': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),

7
Rom.py
View File

@ -14,12 +14,11 @@ RANDOMIZERBASEHASH = 'd5b61947feef1972e0f546ba43180e62'
def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None):
# patch items
for location in world.get_locations():
if location.name == 'Ganon':
# cannot shuffle this yet
continue
itemid = location.item.code if location.item is not None else 0x5A
if itemid is None or location.address is None:
continue
locationaddress = location.address
if not location.crystal:
# regular items

112
Rules.py
View File

@ -69,7 +69,7 @@ def global_rules(world):
set_rule(world.get_entrance('Bonk Fairy (Light)'), lambda state: state.has_Boots())
set_rule(world.get_location('Piece of Heart (Dam)'), lambda state: state.can_reach('Dam'))
set_rule(world.get_entrance('Bat Cave Drop Ledge'), lambda state: state.has('Hammer'))
set_rule(world.get_entrance('Lumberjack Tree Tree'), lambda state: state.has_Boots() and state.can_reach('Top of Pyramid', 'Entrance'))
set_rule(world.get_entrance('Lumberjack Tree Tree'), lambda state: state.has_Boots() and state.has('Beat Agahnim 1'))
set_rule(world.get_entrance('Bonk Rock Cave'), lambda state: state.has_Boots())
set_rule(world.get_entrance('Desert Palace Stairs'), lambda state: state.has('Book of Mudora'))
set_rule(world.get_entrance('Sanctuary Grave'), lambda state: state.can_lift_rocks())
@ -95,10 +95,11 @@ def global_rules(world):
set_rule(world.get_entrance('Desert Palace Entrance (North) Rocks'), lambda state: state.can_lift_rocks())
set_rule(world.get_entrance('Desert Ledge Return Rocks'), lambda state: state.can_lift_rocks()) # should we decide to place something that is not a dungeon end up there at some point
set_rule(world.get_entrance('Checkerboard Cave'), lambda state: state.can_lift_rocks())
set_rule(world.get_location('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_location('Altar'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant'))
set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant'))
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.can_reach('Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword())
set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1'))
set_rule(world.get_entrance('Old Man Cave Exit (West)'), lambda state: False) # drop cannott be climbed up
set_rule(world.get_entrance('Broken Bridge (West)'), lambda state: state.has('Hookshot'))
set_rule(world.get_entrance('Broken Bridge (East)'), lambda state: state.has('Hookshot'))
@ -160,7 +161,7 @@ def global_rules(world):
set_rule(world.get_entrance('Spike Cave'), lambda state: state.has_Pearl())
set_rule(world.get_entrance('Dark Death Mountain Fairy'), 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('Ganons Tower'), lambda state: state.has('Crystal 1') and state.has('Crystal 2') and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7'))
set_rule(world.get_entrance('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())
@ -180,28 +181,28 @@ def global_rules(world):
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_entrance('Sewers Door'), lambda state: state.can_collect('Small Key (Escape)'))
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.can_collect('Small Key (Escape)'))
set_rule(world.get_entrance('Sewers Door'), lambda state: state.has('Small Key (Escape)'))
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has('Small Key (Escape)'))
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)'))
set_rule(world.get_location('[dungeon-L1-1F] Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)'))
set_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)'))
set_rule(world.get_location('Armos - Pendant'), lambda state: state.has('Bow') and state.has('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 - Big Chest'), lambda state: state.has('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
set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has('Small Key (Desert Palace)'))
set_rule(world.get_location('Lanmolas - Pendant'), lambda state: state.has('Small Key (Desert Palace)') and state.has('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
set_rule(world.get_location('Lanmolas - Heart Container'), lambda state: state.has('Small Key (Desert Palace)') and state.has('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_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)'))
set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)'))
set_rule(world.get_location('[dungeon-L3-1F] Tower of Hera - Big Chest'), lambda state: state.has('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())
@ -209,31 +210,31 @@ def global_rules(world):
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.can_reach('Dam'))
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 Small Key Door'), lambda state: state.has('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_location('[dungeon-D2-B1] Swamp Palace - Big Chest'), lambda state: state.has('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.can_collect('Small Key (Thieves Town)') and (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)'))
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)'))
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (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.has('Small Key (Thieves Town)') and state.has('Hammer'))
set_rule(world.get_location('[dungeon-D4-1F] Thieves Town - Room above Boss'), lambda state: state.has('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_sword()) # sword required for curtain
set_rule(world.get_location('[dungeon-D3-B1] Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)'))
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain
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') and state.has_sword()))
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') and state.can_collect('Big Key (Ice Palace)') and (state.can_collect('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.can_collect('Small Key (Ice Palace)', 1))))
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot') or (state.can_collect('Small Key (Ice Palace)', 1) and ((state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item is not None and state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item.name in ['Big Key (Ice Palace)']) or
set_rule(world.get_location('[dungeon-D5-B5] Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)'))
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has('Small Key (Ice Palace)', 1))))
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot') or (state.has('Small Key (Ice Palace)', 1) and ((state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item is not None and state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item.name in ['Big Key (Ice Palace)']) or
(state.world.get_location('[dungeon-D5-B1] Ice Palace - Big Key Room').item is not None and state.world.get_location('[dungeon-D5-B1] Ice Palace - Big Key Room').item.name in ['Big Key (Ice Palace)']) or
(state.world.get_location('[dungeon-D5-B2] Ice Palace - Map Room').item is not None and state.world.get_location('[dungeon-D5-B2] Ice Palace - Map Room').item.name in ['Big Key (Ice Palace)'])))) # if you do ipbj and waste SKs in the basement, you have to BJ over the hookshot room to fix your mess potentially. This seems fair
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
@ -241,13 +242,13 @@ def global_rules(world):
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')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ...
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)'))
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)'))
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Hub Room'), lambda state: state.can_collect('Small Key (Misery Mire)', 1) or state.can_collect('Big Key (Misery Mire)'))
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Map Room'), lambda state: state.can_collect('Small Key (Misery Mire)', 1) or state.can_collect('Big Key (Misery Mire)'))
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Hub Room'), lambda state: state.has('Small Key (Misery Mire)', 1) or state.has('Big Key (Misery Mire)'))
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Map Room'), lambda state: state.has('Small Key (Misery Mire)', 1) or state.has('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 (West)'), lambda state: state.has('Small Key (Misery Mire)', 3) if state.can_reach('Misery Mire (Final Area)') else state.has('Small Key (Misery Mire)', 2))
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Compass Room'), lambda state: state.has_fire_source())
set_rule(world.get_location('[dungeon-D6-B1] Misery Mire - Big Key Room'), lambda state: state.has_fire_source())
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon()))
@ -260,16 +261,16 @@ def global_rules(world):
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 Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 3) if state.can_reach('Turtle Rock (Dark Room) (North)', 'Entrance') else state.has('Small Key (Turtle Rock)', 2) if state.can_reach('Turtle Rock (Eye Bridge)') else state.has('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.has('Small Key (Turtle Rock)', 4)) # Just to be save
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 4) if state.can_reach('Turtle Rock (Dark Room) (North)', 'Entrance') else state.has('Small Key (Turtle Rock)', 3) if state.can_reach('Turtle Rock (Eye Bridge)') else state.has('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.has('Big Key (Turtle Rock)') and (state.has('Cane of Somaria') or state.has('Hookshot')))
set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)'), lambda state: state.has('Cane of Somaria') or state.has('Hookshot'))
set_rule(world.get_entrance('Turtle Rock Big Key Door'), lambda state: state.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 Big Key Door'), lambda state: state.has('Big Key (Turtle Rock)'))
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has('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
set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('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]',
@ -278,13 +279,13 @@ def global_rules(world):
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)'))
set_rule(world.get_entrance('Dark Palace Bridge Room'), lambda state: state.has('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.has('Small Key (Palace of Darkness)', 6) and state.has('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.has('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.has('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.has('Small Key (Palace of Darkness)', 4))
set_rule(world.get_entrance('Dark Palace Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6))
set_rule(world.get_location('[dungeon-D1-1F] Dark Palace - Big Chest'), lambda state: state.has('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)')
@ -292,19 +293,20 @@ def global_rules(world):
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_entrance('Ganons Tower (Map Room)'), lambda state: state.has('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.has('Small Key (Ganons Tower)', 2))
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3))
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has('Small Key (Ganons Tower)', 3)) # possibly too pessimistic
set_rule(world.get_location('[dungeon-A2-1F] Ganons Tower - Big Chest'), lambda state: state.has('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)') and state.has('Bow'))
set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.has('Bow'))
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 Door'), lambda state: state.has('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
set_rule(world.get_location('Agahnim 2'), lambda state: state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net'))
set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2'))
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)')
@ -453,7 +455,7 @@ def set_big_bomb_rules(world):
'Mimic Cave Mirror Spot']
Isolated_LW_entrances =['Capacity Upgrade',
'Hookshot Fairy']
set_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has_Pearl() and state.can_reach('Big Bomb Shop', 'Region') and state.can_collect('Crystal 5') and state.can_collect('Crystal 6'))
set_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.has_Pearl() and state.can_reach('Big Bomb Shop', 'Region') and state.has('Crystal 5') and state.has('Crystal 6'))
if bombshop_entrance.name in Normal_LW_entrances:
add_rule(world.get_entrance('Pyramid Fairy'), lambda state: state.can_reach('Top of Pyramid', 'Entrance') or (state.has('Hammer') and state.can_lift_rocks()) or state.has_Mirror())
elif bombshop_entrance.name in LW_walkable_entrances: