Fixed remaining key logic, correctly implemented sewers. Added support for other game modes, create playthrough.

This commit is contained in:
LLCoolDave 2017-05-16 21:23:47 +02:00
parent 57483052e4
commit f374c637c3
5 changed files with 313 additions and 61 deletions

View File

@ -1,9 +1,15 @@
import copy
import logging
class World(object):
def __init__(self):
def __init__(self, shuffle, logic, mode, difficulty, goal):
self.shuffle = shuffle
self.logic = logic
self.mode = mode
self.difficulty = difficulty
self.goal = goal
self.regions = []
self.itempool = []
self.state = CollectionState(self)
@ -66,7 +72,7 @@ class World(object):
if collect:
self.state.collect(item)
print('Placed %s at %s' % (item, location))
logging.getLogger('').debug('Placed %s at %s' % (item, location))
else:
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
@ -96,11 +102,34 @@ class World(object):
temp_state.collect(item)
return len(self.get_placeable_locations()) < len(self.get_placeable_locations(temp_state))
def can_beat_game(self):
prog_locations = [location for location in self.get_locations() if location.item is not None and location.item.advancement]
state = CollectionState(self)
while prog_locations:
sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
for location in prog_locations:
if state.can_reach(location):
if location.item.name == 'Triforce':
return True
sphere.append(location)
if not sphere:
# ran out of places and did not find triforce yet, quit
return False
for location in sphere:
prog_locations.remove(location)
state.collect(location.item)
return False
class CollectionState(object):
def __init__(self, parent, has_everything=False):
self.prog_items = set()
self.prog_items = []
self.world = parent
self.has_everything = has_everything
self.changed = False
@ -233,32 +262,68 @@ class CollectionState(object):
if self.has('Golden Sword'):
return
elif self.has('Tempered Sword'):
self.prog_items.add('Golden Sword')
self.prog_items.append('Golden Sword')
self.changed = True
elif self.has('Master Sword'):
self.prog_items.add('Tempered Sword')
self.prog_items.append('Tempered Sword')
self.changed = True
elif self.has('Fighter Sword'):
self.prog_items.add('Master Sword')
self.prog_items.append('Master Sword')
self.changed = True
else:
self.prog_items.add('Fighter Sword')
self.prog_items.append('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.prog_items.append('Titans Mitts')
self.changed = True
else:
self.prog_items.add('Power Glove')
self.prog_items.append('Power Glove')
self.changed = True
return
if item.advancement:
self.prog_items.add(item.name)
self.prog_items.append(item.name)
self.changed = True
def remove(self, item):
if item.advancement:
to_remove = item.name
if to_remove.startswith('Progressive '):
if 'Sword' in to_remove:
if self.has('Golden Sword'):
to_remove = 'Golden Sword'
elif self.has('Tempered Sword'):
to_remove = 'Tempered Sword'
elif self.has('Master Sword'):
to_remove = 'Master Sword'
elif self.has('Fighter Sword'):
to_remove = 'Fighter Sword'
else:
to_remove = None
elif 'Glove' in item.name:
if self.has('Titans Mitts'):
to_remove = 'Titans Mitts'
elif self.has('Power Glove'):
to_remove = 'Power Glove'
else:
to_remove = None
if to_remove is not None:
try:
self.prog_items.remove(to_remove)
except IndexError:
return
# invalidate caches, nothing can be trusted anymore now
self.region_cache = {}
self.location_cache = {}
self.entrance_cache = {}
self.recursion_cache = []
self.changed = False
def __getattr__(self, item):
if item.startswith('can_reach_'):
return self.can_reach(item[10])
@ -340,10 +405,7 @@ class Location(object):
return True
def item_rule(self, item):
if item.name != 'Triforce':
return True
else:
return False
return True
def can_reach(self, state):
if self.parent_region:
@ -372,4 +434,3 @@ class Item(object):
def __unicode__(self):
return '%s' % self.name

View File

@ -1,10 +1,10 @@
def link_entrances(world, shuffle):
def link_entrances(world):
# setup mandatory connections
for exitname, regionname in mandatory_connections:
connect(world, exitname, regionname)
# if we do not shuffle, set default connections
if shuffle=='Default':
if world.shuffle == 'Default':
for exitname, regionname in default_connections:
connect(world, exitname, regionname)
return
@ -27,6 +27,11 @@ mandatory_connections = [('Zoras River', 'Zoras River'),
('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'),
('Desert Palace Stairs', 'Desert Palace Stairs'),
('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'),
('Throne Room', 'Sewers (Dark)'),
('Sewers Door', 'Sewers'),
('Sanctuary Push Door', 'Sanctuary'),
('Sewer Drop', 'Sewers'),
('Sewers Back Door', 'Sewers (Dark)'),
('Aghanim 1', 'Aghanim 1'),
('Flute Spot 1', 'Death Mountain'),
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
@ -174,8 +179,9 @@ default_connections = [("Thiefs Hut", "Thiefs Hut"),
('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'),
('Sanctuary', 'Sanctuary'),
('Sanctuary Grave', 'Sewer Drop'),
('Sanctuary Exit', 'Light World'),
('Old Man Cave (West)', 'Old Man Cave'),
('Old Man Cave (East)', 'Old Man Cave'),

193
Main.py
View File

@ -1,4 +1,4 @@
from BaseClasses import World
from BaseClasses import World, CollectionState
from Regions import create_regions
from EntranceShuffle import link_entrances
from Rules import set_rules
@ -7,19 +7,21 @@ from Items import *
import random
import cProfile
import time
import logging
def main(seed=None, shuffle='Default', logic='no-glitches', mode='standard', difficulty='normal', goal='defeat ganon'):
# initialize the world
world = World()
world = World(shuffle, logic, mode, difficulty, goal)
create_regions(world)
random.seed(seed)
link_entrances(world, shuffle)
set_rules(world, logic, mode)
generate_itempool(world, difficulty, goal)
link_entrances(world)
set_rules(world)
generate_itempool(world)
distribute_items(world)
# flood_items(world) # different algo, biased towards early game progress items
return world
@ -56,6 +58,10 @@ def distribute_items(world):
if candidate_item_to_place is not None:
item_to_place = candidate_item_to_place
else:
# we placed all available progress items. Maybe the game can be beaten anyway?
if world.can_beat_game():
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
break
raise RuntimeError('No more progress items left to place.')
spot_to_fill = None
@ -65,21 +71,80 @@ def distribute_items(world):
break
if spot_to_fill is None:
# we filled all reachable spots. Maybe the game can be beaten anyway?
if world.can_beat_game():
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
break
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))
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % (itempool, fill_locations))
def generate_itempool(world, difficulty, goal):
if difficulty != 'normal' or goal != 'defeat ganon':
def flood_items(world):
# get items to distribute
random.shuffle(world.itempool)
itempool = world.itempool
progress_done = False
# fill world from top of itempool while we can
while not progress_done:
location_list = world.get_unfilled_locations()
random.shuffle(location_list)
spot_to_fill = None
for location in location_list:
if world.state.can_reach(location):
spot_to_fill = location
break
if spot_to_fill:
item = itempool.pop(0)
world.push_item(spot_to_fill, item, True)
continue
# ran out of spots, check if we need to step in and correct things
if len(world.get_reachable_locations()) == len(world.get_locations()):
progress_done = True
continue
# need to place a progress item instead of an already placed item, find candidate
item_to_place = None
candidate_item_to_place = None
for item in itempool:
if item.advancement:
candidate_item_to_place = item
if world.unlocks_new_location(item):
item_to_place = item
break
# 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 item_to_place is None:
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.')
# find item to replace with progress item
location_list = world.get_reachable_locations()
random.shuffle(location_list)
for location in location_list:
if location.item is not None and not location.item.advancement and not location.item.key and 'Map' not in location.item.name and 'Compass' not in location.item.name:
# safe to replace
replace_item = location.item
replace_item.location = None
itempool.append(replace_item)
world.push_item(location, item_to_place, True)
itempool.remove(item_to_place)
break
def generate_itempool(world):
if world.difficulty != 'normal' or world.goal not in ['defeat ganon', 'pedestal', 'all dungeons'] or world.mode not in ['open', 'standard']:
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
@ -134,6 +199,22 @@ def generate_itempool(world, difficulty, goal):
Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20()
]
if world.mode == 'standard':
world.push_item('Uncle', ProgressiveSword())
else:
world.itempool.append(ProgressiveSword())
if world.goal == 'pedestal':
world.push_item('Altar', Triforce())
items = list(world.itempool)
random.shuffle(items)
for item in items:
if not item.advancement:
# save to remove
world.itempool.remove(item)
break
# ToDo what to do if EVERYTHING is a progress item?
if random.randint(0, 3) == 0:
world.itempool.append(QuarterMagic())
else:
@ -156,15 +237,95 @@ def generate_itempool(world, difficulty, goal):
# push dungeon items
fill_dungeons(world)
#profiler = cProfile.Profile()
#profiler.enable()
def copy_world(world):
# ToDo: Not good yet
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.goal)
ret.required_medallions = list(world.required_medallions)
create_regions(ret)
set_rules(ret)
# connect copied world
for region in world.regions:
for entrance in region.entrances:
ret.get_entrance(entrance.name).connect(ret.get_region(region.name))
# fill locations
for location in world.get_locations():
if location.item is not None:
item = Item(location.item.name, location.item.advancement, location.item.key)
ret.get_location(location.name).item = item
item.location = ret.get_location(location.name)
# copy remaining itempool. No item in itempool should have an assigned location
for item in world.itempool:
ret.itempool.append(Item(item.name, item.advancement, item.key))
# copy progress items in state
ret.state.prog_items = list(world.state.prog_items)
return ret
def create_playthrough(world):
# create a copy as we will modify it
world = copy_world(world)
# get locations containing progress items
prog_locations = [location for location in world.get_locations() if location.item is not None and location.item.advancement]
collection_spheres = []
state = CollectionState(world)
sphere_candidates = list(prog_locations)
while sphere_candidates:
sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
for location in sphere_candidates:
if state.can_reach(location):
sphere.append(location)
for location in sphere:
sphere_candidates.remove(location)
state.collect(location.item)
collection_spheres.append(sphere)
# in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it
for sphere in reversed(collection_spheres):
to_delete = []
for location in sphere:
# we remove the item at location and check if game is still beatable
old_item = location.item
location.item = None
state.remove(old_item)
world._item_cache = {} # need to invalidate
if world.can_beat_game():
to_delete.append(location)
else:
# still required, got to keep it around
location.item = old_item
# cull entries in spheres for spoiler walkthrough at end
for location in to_delete:
sphere.remove(location)
# we are now down to just the required progress items in collection_spheres in a minimum number of spheres. As a cleanup, we right trim empty spheres (can happen if we have multiple triforces)
collection_spheres = [sphere for sphere in collection_spheres if sphere]
# we can finally output our playthrough
return ''.join(['%s: {\n%s}\n' % (i + 1, ''.join([' %s: %s\n' % (location, location.item) for location in sphere])) for i, sphere in enumerate(collection_spheres)])
profiler = cProfile.Profile()
profiler.enable()
tally = {}
iterations = 300
iterations = 10
start = time.clock()
for i in range(iterations):
print('Seed %s\n\n' % i)
w = main()
w = main(mode='open')
print(create_playthrough(w))
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))
@ -183,6 +344,6 @@ 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()
profiler.disable()
profiler.print_stats()

View File

@ -56,10 +56,13 @@ def create_regions(world):
'[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('Hyrule Castle', ['[dungeon-C-B1] Hyrule Castle - Boomerang Room', '[dungeon-C-B1] Hyrule Castle - Map Room', '[dungeon-C-B1] Hyrule Castle - Next To Zelda'],
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']),
create_region('Sewer Drop', None, ['Sewer Drop']), # This exists only to be referenced for access checks
create_region('Sewers (Dark)', ['[dungeon-C-B1] Escape - First B1 Room'], ['Sewers Door']),
create_region('Sewers', ['[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]'], ['Sanctuary Push Door', 'Sewers Back Door']),
create_region('Sanctuary', ['[dungeon-C-1F] Sanctuary'], ['Sanctuary Exit']),
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']),

View File

@ -1,11 +1,22 @@
def set_rules(world, logic, mode):
def set_rules(world):
global_rules(world)
if logic == 'no-glitches' and mode in ['open', 'standard']:
no_glitches_rules(world, mode)
if world.logic == 'no-glitches':
no_glitches_rules(world)
else:
raise NotImplementedError('Not implemented yet')
if world.mode == 'open':
open_rules(world)
elif world.mode == 'standard':
standard_rules(world)
else:
raise NotImplementedError('Not implemented yet')
if world.goal == 'all dungeons':
# require altar for ganon to enforce getting everything
add_rule(world.get_location('Ganon'), lambda state: state.can_reach('Altar', 'Location'))
def set_rule(spot, rule):
spot.access_rule = rule
@ -111,9 +122,8 @@ 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_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_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_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)'))
@ -151,24 +161,25 @@ def global_rules(world):
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_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)'))
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')))
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
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_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos'))
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 (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))
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
(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'))
for location in ['[dungeon-D5-B5] Ice Palace - Big Chest']:
for location in ['[dungeon-D5-B5] Ice Palace - Big Chest', 'Kholdstare - Heart Container']:
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'))
@ -201,7 +212,6 @@ def global_rules(world):
'[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
@ -238,8 +248,7 @@ def global_rules(world):
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
def no_glitches_rules(world):
set_rule(world.get_entrance('Zoras River'), lambda state: state.has('Flippers') or state.can_lift_rocks())
set_rule(world.get_entrance('Hobo Bridge'), lambda state: state.has('Flippers'))
add_rule(world.get_entrance('Ice Palace'), lambda state: state.has_Pearl() and state.has('Flippers'))
@ -253,7 +262,7 @@ def no_glitches_rules(world, mode):
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':
if world.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'))
@ -261,8 +270,20 @@ def no_glitches_rules(world, mode):
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)'))
def open_rules(world):
pass
def standard_rules(world):
# easiest way to enforce key placement not relevant for open
forbid_item(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), 'Small Key (Escape)')
forbid_item(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), 'Small Key (Escape)')
forbid_item(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), 'Small Key (Escape)')
forbid_item(world.get_location('[dungeon-C-1F] Sanctuary'), 'Small Key (Escape)')
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), lambda state: state.can_reach('Sewer Drop'))
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), lambda state: state.can_reach('Sewer Drop'))
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), lambda state: state.can_reach('Sewer Drop'))
add_rule(world.get_location('[dungeon-C-B1] Escape - First B1 Room'), lambda state: state.can_reach('Sewer Drop') or (state.world.get_location('[dungeon-C-B1] Escape - First B1 Room').item is not None and state.world.get_location('[dungeon-C-B1] Escape - First B1 Room').item.name in ['Small Key (Escape)'])) # you could skip this chest and be unable to go back until you can drop into escape