Merge pull request #7 from KevinCathcart/Dev

Kevin's latest updates
This commit is contained in:
AmazingAmpharos 2018-01-05 16:04:06 -06:00 committed by GitHub
commit 971c7b9365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 177 additions and 69 deletions

View File

@ -1,6 +1,7 @@
import copy import copy
import logging import logging
import json import json
from itertools import zip_longest
from collections import OrderedDict from collections import OrderedDict
@ -53,6 +54,7 @@ class World(object):
self.disable_music = disable_music self.disable_music = disable_music
self.keysanity = keysanity self.keysanity = keysanity
self.can_take_damage = True self.can_take_damage = True
self.difficulty_requirements = None
self.spoiler = Spoiler(self) self.spoiler = Spoiler(self)
def intialize_regions(self): def intialize_regions(self):
@ -105,13 +107,13 @@ class World(object):
if 'Sword' in item.name: if 'Sword' in item.name:
if ret.has('Golden Sword'): if ret.has('Golden Sword'):
pass pass
elif ret.has('Tempered Sword'): elif ret.has('Tempered Sword') and self.difficulty_requirements.progressive_sword_limit >= 4:
ret.prog_items.append('Golden Sword') ret.prog_items.append('Golden Sword')
elif ret.has('Master Sword'): elif ret.has('Master Sword') and self.difficulty_requirements.progressive_sword_limit >= 3:
ret.prog_items.append('Tempered Sword') ret.prog_items.append('Tempered Sword')
elif ret.has('Fighter Sword'): elif ret.has('Fighter Sword') and self.difficulty_requirements.progressive_sword_limit >= 2:
ret.prog_items.append('Master Sword') ret.prog_items.append('Master Sword')
else: elif self.difficulty_requirements.progressive_sword_limit >= 1:
ret.prog_items.append('Fighter Sword') ret.prog_items.append('Fighter Sword')
elif 'Glove' in item.name: elif 'Glove' in item.name:
if ret.has('Titans Mitts'): if ret.has('Titans Mitts'):
@ -123,13 +125,15 @@ class World(object):
elif 'Shield' in item.name: elif 'Shield' in item.name:
if ret.has('Mirror Shield'): if ret.has('Mirror Shield'):
pass pass
elif ret.has('Red Shield'): elif ret.has('Red Shield') and self.difficulty_requirements.progressive_shield_limit >= 3:
ret.prog_items.append('Mirror Shield') ret.prog_items.append('Mirror Shield')
elif ret.has('Blue Shield'): elif ret.has('Blue Shield') and self.difficulty_requirements.progressive_shield_limit >= 2:
ret.prog_items.append('Red Shield') ret.prog_items.append('Red Shield')
else: elif self.difficulty_requirements.progressive_shield_limit >= 1:
ret.prog_items.append('Blue Shield') ret.prog_items.append('Blue Shield')
elif item.name.startswith('Bottle'):
if ret.bottle_count() < self.difficulty_requirements.progressive_bottle_limit:
ret.prog_items.append(item.name)
elif item.advancement or item.key: elif item.advancement or item.key:
ret.prog_items.append(item.name) ret.prog_items.append(item.name)
@ -159,7 +163,7 @@ class World(object):
location.item = item location.item = item
item.location = location item.location = location
if collect: if collect:
self.state.collect(item, location.event) self.state.collect(item, location.event, location)
logging.getLogger('').debug('Placed %s at %s', item, location) logging.getLogger('').debug('Placed %s at %s', item, location)
else: else:
@ -208,13 +212,17 @@ class World(object):
return False return False
def can_beat_game(self, starting_state=None): def can_beat_game(self, starting_state=None):
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event)]
if starting_state: if starting_state:
state = starting_state.copy() state = starting_state.copy()
else: else:
state = CollectionState(self) state = CollectionState(self)
treasure_pieces_collected = 0
if self.has_beaten_game(state):
return True
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
treasure_pieces_collected = state.item_count('Triforce Piece') + state.item_count('Power Star')
while prog_locations: while prog_locations:
sphere = [] sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
@ -234,7 +242,7 @@ class World(object):
for location in sphere: for location in sphere:
prog_locations.remove(location) prog_locations.remove(location)
state.collect(location.item, True) state.collect(location.item, True, location)
return False return False
@ -265,6 +273,9 @@ class CollectionState(object):
self.entrance_cache = {} self.entrance_cache = {}
self.recursion_count = 0 self.recursion_count = 0
self.events = [] self.events = []
self.path = {}
self.locations_checked = set()
def clear_cached_unreachable(self): def clear_cached_unreachable(self):
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items # we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
@ -279,6 +290,8 @@ class CollectionState(object):
ret.location_cache = copy.copy(self.location_cache) ret.location_cache = copy.copy(self.location_cache)
ret.entrance_cache = copy.copy(self.entrance_cache) ret.entrance_cache = copy.copy(self.entrance_cache)
ret.events = copy.copy(self.events) ret.events = copy.copy(self.events)
ret.path = copy.copy(self.path)
ret.locations_checked = copy.copy(self.locations_checked)
return ret return ret
def can_reach(self, spot, resolution_hint=None): def can_reach(self, spot, resolution_hint=None):
@ -334,7 +347,7 @@ class CollectionState(object):
for event in reachable_events: for event in reachable_events:
if event.name not in self.events: if event.name not in self.events:
self.events.append(event.name) self.events.append(event.name)
self.collect(event.item, True) self.collect(event.item, True, event)
new_locations = len(reachable_events) > checked_locations new_locations = len(reachable_events) > checked_locations
checked_locations = len(reachable_events) checked_locations = len(reachable_events)
@ -350,7 +363,10 @@ class CollectionState(object):
return self.has('Power Glove') or self.has('Titans Mitts') return self.has('Power Glove') or self.has('Titans Mitts')
def has_bottle(self): def has_bottle(self):
return self.has('Bottle') or self.has('Bottle (Red Potion)') or self.has('Bottle (Green Potion)') or self.has('Bottle (Blue Potion)') or self.has('Bottle (Fairy)') or self.has('Bottle (Bee)') or self.has('Bottle (Good Bee)') return self.bottle_count() > 0
def bottle_count(self):
return len([pritem for pritem in self.prog_items if pritem.startswith('Bottle')])
def can_lift_heavy_rocks(self): def can_lift_heavy_rocks(self):
return self.has('Titans Mitts') return self.has('Titans Mitts')
@ -393,22 +409,24 @@ class CollectionState(object):
def has_turtle_rock_medallion(self): def has_turtle_rock_medallion(self):
return self.has(self.world.required_medallions[1]) return self.has(self.world.required_medallions[1])
def collect(self, item, event=False): def collect(self, item, event=False, location=None):
if location:
self.locations_checked.add(location)
changed = False changed = False
if item.name.startswith('Progressive '): if item.name.startswith('Progressive '):
if 'Sword' in item.name: if 'Sword' in item.name:
if self.has('Golden Sword'): if self.has('Golden Sword'):
pass pass
elif self.has('Tempered Sword'): elif self.has('Tempered Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 4:
self.prog_items.append('Golden Sword') self.prog_items.append('Golden Sword')
changed = True changed = True
elif self.has('Master Sword'): elif self.has('Master Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 3:
self.prog_items.append('Tempered Sword') self.prog_items.append('Tempered Sword')
changed = True changed = True
elif self.has('Fighter Sword'): elif self.has('Fighter Sword') and self.world.difficulty_requirements.progressive_sword_limit >= 2:
self.prog_items.append('Master Sword') self.prog_items.append('Master Sword')
changed = True changed = True
else: elif self.world.difficulty_requirements.progressive_sword_limit >= 1:
self.prog_items.append('Fighter Sword') self.prog_items.append('Fighter Sword')
changed = True changed = True
elif 'Glove' in item.name: elif 'Glove' in item.name:
@ -423,16 +441,19 @@ class CollectionState(object):
elif 'Shield' in item.name: elif 'Shield' in item.name:
if self.has('Mirror Shield'): if self.has('Mirror Shield'):
pass pass
elif self.has('Red Shield'): elif self.has('Red Shield') and self.world.difficulty_requirements.progressive_shield_limit >= 3:
self.prog_items.append('Mirror Shield') self.prog_items.append('Mirror Shield')
changed = True changed = True
elif self.has('Blue Shield'): elif self.has('Blue Shield') and self.world.difficulty_requirements.progressive_shield_limit >= 2:
self.prog_items.append('Red Shield') self.prog_items.append('Red Shield')
changed = True changed = True
else: elif self.world.difficulty_requirements.progressive_shield_limit >= 1:
self.prog_items.append('Blue Shield') self.prog_items.append('Blue Shield')
changed = True changed = True
elif item.name.startswith('Bottle'):
if self.bottle_count() < self.world.difficulty_requirements.progressive_bottle_limit:
self.prog_items.append(item.name)
changed = True
elif event or item.advancement: elif event or item.advancement:
self.prog_items.append(item.name) self.prog_items.append(item.name)
changed = True changed = True
@ -504,6 +525,8 @@ class Region(object):
def can_reach(self, state): def can_reach(self, state):
for entrance in self.entrances: for entrance in self.entrances:
if state.can_reach(entrance): if state.can_reach(entrance):
if not self in state.path:
state.path[self] = (self.name, state.path.get(entrance, None))
return True return True
return False return False
@ -537,6 +560,8 @@ class Entrance(object):
def can_reach(self, state): def can_reach(self, state):
if self.access_rule(state) and state.can_reach(self.parent_region): if self.access_rule(state) and state.can_reach(self.parent_region):
if not self in state.path:
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
return True return True
return False return False
@ -666,6 +691,7 @@ class Spoiler(object):
self.medallions = {} self.medallions = {}
self.playthrough = {} self.playthrough = {}
self.locations = {} self.locations = {}
self.paths = {}
self.metadata = {} self.metadata = {}
def set_entrance(self, entrance, exit, direction): def set_entrance(self, entrance, exit, direction):
@ -699,6 +725,7 @@ class Spoiler(object):
out.update(self.locations) out.update(self.locations)
out['medallions'] = self.medallions out['medallions'] = self.medallions
out['playthrough'] = self.playthrough out['playthrough'] = self.playthrough
out['paths'] = self.paths
out['meta'] = self.metadata out['meta'] = self.metadata
return json.dumps(out) return json.dumps(out)
@ -726,3 +753,18 @@ class Spoiler(object):
outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()])) outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()]))
outfile.write('\n\nPlaythrough:\n\n') outfile.write('\n\nPlaythrough:\n\n')
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()])) outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
outfile.write('\n\nPaths:\n\n')
path_listings = []
for location, paths in self.paths.items():
path_lines = []
pathsiter = iter(paths)
pathpairs = zip_longest(pathsiter, pathsiter)
for region, exit in pathpairs:
if exit is not None:
path_lines.append("{} -> {}".format(region, exit))
else:
path_lines.append(region)
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
outfile.write('\n'.join(path_listings))

30
Gui.py
View File

@ -106,13 +106,13 @@ def guiMain(args=None):
nonlocal sprite nonlocal sprite
if sprite_param is None or not sprite_param.valid: if sprite_param is None or not sprite_param.valid:
sprite = None sprite = None
spriteNameVar.set('(default)') spriteNameVar.set('(unchanged)')
else: else:
sprite = sprite_param sprite = sprite_param
spriteNameVar.set(sprite.name) spriteNameVar.set(sprite.name)
set_sprite(None) set_sprite(None)
spriteNameVar.set('(default)') spriteNameVar.set('(unchanged)')
spriteEntry = Label(spriteDialogFrame, textvariable=spriteNameVar) spriteEntry = Label(spriteDialogFrame, textvariable=spriteNameVar)
def SpriteSelect(): def SpriteSelect():
@ -310,7 +310,10 @@ def guiMain(args=None):
baseSpriteLabel2 = Label(spriteDialogFrame2, text='Link Sprite') baseSpriteLabel2 = Label(spriteDialogFrame2, text='Link Sprite')
spriteEntry2 = Label(spriteDialogFrame2, textvariable=spriteNameVar) spriteEntry2 = Label(spriteDialogFrame2, textvariable=spriteNameVar)
spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelect) def SpriteSelectAdjuster():
SpriteSelector(mainWindow, set_sprite, adjuster=True)
spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelectAdjuster)
baseSpriteLabel2.pack(side=LEFT) baseSpriteLabel2.pack(side=LEFT)
spriteEntry2.pack(side=LEFT) spriteEntry2.pack(side=LEFT)
@ -389,12 +392,13 @@ def guiMain(args=None):
mainWindow.mainloop() mainWindow.mainloop()
class SpriteSelector(object): class SpriteSelector(object):
def __init__(self, parent, callback): def __init__(self, parent, callback, adjuster=False):
if is_bundled(): if is_bundled():
self.deploy_icons() self.deploy_icons()
self.parent = parent self.parent = parent
self.window = Toplevel(parent) self.window = Toplevel(parent)
self.callback = callback self.callback = callback
self.adjuster = adjuster
self.window.wm_title("TAKE ANY ONE YOU WANT") self.window.wm_title("TAKE ANY ONE YOU WANT")
self.window['padx'] = 5 self.window['padx'] = 5
@ -412,7 +416,7 @@ class SpriteSelector(object):
title_link.pack(side=LEFT) title_link.pack(side=LEFT)
title_link.bind("<Button-1>", open_unofficial_sprite_dir) title_link.bind("<Button-1>", open_unofficial_sprite_dir)
self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official Sprites not found. Click "Update Official Sprites" to download them.') self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official sprites not found. Click "Update official sprites" to download them.')
self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.') self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.')
frame = Frame(self.window) frame = Frame(self.window)
@ -421,11 +425,15 @@ class SpriteSelector(object):
button = Button(frame, text="Browse for file...", command=self.browse_for_sprite) button = Button(frame, text="Browse for file...", command=self.browse_for_sprite)
button.pack(side=RIGHT, padx=(5, 0)) button.pack(side=RIGHT, padx=(5, 0))
button = Button(frame, text="Update Official Sprites", command=self.update_official_sprites) button = Button(frame, text="Update official sprites", command=self.update_official_sprites)
button.pack(side=RIGHT, padx=(5, 0)) button.pack(side=RIGHT, padx=(5, 0))
button = Button(frame, text="Use Default Sprite", command=self.use_default_sprite) button = Button(frame, text="Use default Link sprite", command=self.use_default_link_sprite)
button.pack(side=LEFT) button.pack(side=LEFT, padx=(0, 5))
if adjuster:
button = Button(frame, text="Use current sprite from rom", command=self.use_default_sprite)
button.pack(side=LEFT, padx=(0, 5))
set_icon(self.window) set_icon(self.window)
self.window.focus() self.window.focus()
@ -465,7 +473,7 @@ class SpriteSelector(object):
messagebox.showinfo("Sprite Updater", resultmessage) messagebox.showinfo("Sprite Updater", resultmessage)
else: else:
messagebox.showerror("Sprite Updater", resultmessage) messagebox.showerror("Sprite Updater", resultmessage)
SpriteSelector(self.parent, self.callback) SpriteSelector(self.parent, self.callback, self.adjuster)
try: try:
task.update_status("Downloading official sprites list") task.update_status("Downloading official sprites list")
@ -536,6 +544,10 @@ class SpriteSelector(object):
self.callback(None) self.callback(None)
self.window.destroy() self.window.destroy()
def use_default_link_sprite(self):
self.callback(Sprite.default_link_sprite())
self.window.destroy()
def select_sprite(self, spritename): def select_sprite(self, spritename):
self.callback(spritename) self.callback(spritename)
self.window.destroy() self.window.destroy()

View File

@ -62,7 +62,8 @@ Difficulty = namedtuple('Difficulty',
'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
'progressivesword', 'basicsword', 'timedohko', 'timedother', 'progressivesword', 'basicsword', 'timedohko', 'timedother',
'triforcehunt', 'triforce_pieces_required', 'conditional_extras', 'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
'extras']) 'extras', 'progressive_sword_limit', 'progressive_shield_limit',
'progressive_armor_limit', 'progressive_bottle_limit'])
total_items_to_place = 153 total_items_to_place = 153
@ -77,7 +78,7 @@ def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
def no_conditonal_extras(*_args): def no_conditonal_extras(*_args):
return [] return []
# pylint: disable=
difficulties = { difficulties = {
'normal': Difficulty( 'normal': Difficulty(
baseitems = normalbaseitems, baseitems = normalbaseitems,
@ -96,7 +97,11 @@ difficulties = {
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
conditional_extras = no_conditonal_extras, conditional_extras = no_conditonal_extras,
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra] extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
progressive_sword_limit = 4,
progressive_shield_limit = 3,
progressive_armor_limit = 2,
progressive_bottle_limit = 4,
), ),
'easy': Difficulty( 'easy': Difficulty(
baseitems = easybaseitems, baseitems = easybaseitems,
@ -116,6 +121,10 @@ difficulties = {
triforce_pieces_required = 10, triforce_pieces_required = 10,
conditional_extras = easy_conditional_extras, conditional_extras = easy_conditional_extras,
extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra], extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
progressive_sword_limit = 4,
progressive_shield_limit = 3,
progressive_armor_limit = 2,
progressive_bottle_limit = 4,
), ),
'hard': Difficulty( 'hard': Difficulty(
baseitems = hardbaseitems, baseitems = hardbaseitems,
@ -135,6 +144,10 @@ difficulties = {
triforce_pieces_required = 30, triforce_pieces_required = 30,
conditional_extras = no_conditonal_extras, conditional_extras = no_conditonal_extras,
extras = [hardfirst20extra, hardsecond20extra, hardthird20extra, hardfinal20extra], extras = [hardfirst20extra, hardsecond20extra, hardthird20extra, hardfinal20extra],
progressive_sword_limit = 3,
progressive_shield_limit = 2,
progressive_armor_limit = 1,
progressive_bottle_limit = 2,
), ),
'expert': Difficulty( 'expert': Difficulty(
baseitems = expertbaseitems, baseitems = expertbaseitems,
@ -154,6 +167,10 @@ difficulties = {
triforce_pieces_required = 40, triforce_pieces_required = 40,
conditional_extras = no_conditonal_extras, conditional_extras = no_conditonal_extras,
extras = [expertfirst15extra, expertsecond25extra, expertthird15extra, expertfinal25extra], extras = [expertfirst15extra, expertsecond25extra, expertthird15extra, expertfinal25extra],
progressive_sword_limit = 2,
progressive_shield_limit = 0,
progressive_armor_limit = 0,
progressive_bottle_limit = 1,
), ),
'insane': Difficulty( 'insane': Difficulty(
baseitems = insanebaseitems, baseitems = insanebaseitems,
@ -173,6 +190,10 @@ difficulties = {
triforce_pieces_required = 50, triforce_pieces_required = 50,
conditional_extras = no_conditonal_extras, conditional_extras = no_conditonal_extras,
extras = [insanefirst15extra, insanesecond25extra, insanethird10extra, insanefourth15extra, insanefinal25extra], extras = [insanefirst15extra, insanesecond25extra, insanethird10extra, insanefourth15extra, insanefinal25extra],
progressive_sword_limit = 2,
progressive_shield_limit = 0,
progressive_armor_limit = 0,
progressive_bottle_limit = 1,
), ),
} }

23
Main.py
View File

@ -11,7 +11,7 @@ from Rom import patch_rom, Sprite, LocalRom, JsonRom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items
from ItemList import generate_itempool from ItemList import generate_itempool, difficulties
from Utils import output_path from Utils import output_path
__version__ = '0.5.1-dev' __version__ = '0.5.1-dev'
@ -41,6 +41,8 @@ def main(args, seed=None):
logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed) logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed)
world.difficulty_requirements = difficulties[world.difficulty]
create_regions(world) create_regions(world)
create_dungeons(world) create_dungeons(world)
@ -134,6 +136,7 @@ def copy_world(world):
ret.seed = world.seed ret.seed = world.seed
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge
ret.can_take_damage = world.can_take_damage ret.can_take_damage = world.can_take_damage
ret.difficulty_requirements = world.difficulty_requirements
create_regions(ret) create_regions(ret)
create_dungeons(ret) create_dungeons(ret)
@ -179,7 +182,7 @@ def create_playthrough(world):
# get locations containing progress items # get locations containing progress items
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement] prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
state_cache = [None]
collection_spheres = [] collection_spheres = []
state = CollectionState(world) state = CollectionState(world)
sphere_candidates = list(prog_locations) sphere_candidates = list(prog_locations)
@ -196,12 +199,13 @@ def create_playthrough(world):
for location in sphere: for location in sphere:
sphere_candidates.remove(location) sphere_candidates.remove(location)
state.collect(location.item, True) state.collect(location.item, True, location)
collection_spheres.append(sphere) collection_spheres.append(sphere)
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations)) state_cache.append(state.copy())
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
if not sphere: if not sphere:
logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates]) logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
if not world.check_beatable_only: if not world.check_beatable_only:
@ -210,7 +214,7 @@ def create_playthrough(world):
break break
# 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 # 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): for num, sphere in reversed(list(enumerate(collection_spheres))):
to_delete = [] to_delete = []
for location in sphere: for location in sphere:
# we remove the item at location and check if game is still beatable # we remove the item at location and check if game is still beatable
@ -218,7 +222,7 @@ def create_playthrough(world):
old_item = location.item old_item = location.item
location.item = None location.item = None
state.remove(old_item) state.remove(old_item)
if world.can_beat_game(): if world.can_beat_game(state_cache[num]):
to_delete.append(location) to_delete.append(location)
else: else:
# still required, got to keep it around # still required, got to keep it around
@ -234,5 +238,12 @@ def create_playthrough(world):
# store the required locations for statistical analysis # store the required locations for statistical analysis
old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere] old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere]
def flist_to_iter(node):
while node:
value, node = node
yield value
old_world.spoiler.paths = {location.name : list(reversed(list(map(str, flist_to_iter(state.path.get(location.parent_region, (location.parent_region, None))))))) for sphere in collection_spheres for location in sphere}
# we can finally output our playthrough # we can finally output our playthrough
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])

View File

@ -14,6 +14,7 @@ from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons from Dungeons import create_dungeons
from Items import ItemFactory from Items import ItemFactory
from ItemList import difficulties
from Main import create_playthrough from Main import create_playthrough
__version__ = '0.2-dev' __version__ = '0.2-dev'
@ -45,6 +46,8 @@ def main(args):
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando) logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando)
world.difficulty_requirements = difficulties[world.difficulty]
create_regions(world) create_regions(world)
create_dungeons(world) create_dungeons(world)

34
Rom.py
View File

@ -11,10 +11,11 @@ from Text import string_to_alttp_text, text_addresses, Credits
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import local_path from Utils import local_path
from Items import ItemFactory
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '1deebb05eccefd2ab68297c6e9c0d25f' RANDOMIZERBASEHASH = '00bf3203026a0d1ee0d988943518806b'
class JsonRom(object): class JsonRom(object):
@ -155,6 +156,10 @@ class Sprite(object):
else: else:
self.valid = False self.valid = False
@staticmethod
def default_link_sprite():
return Sprite(local_path('data/default.zspr'))
def decode8(self, pos): def decode8(self, pos):
arr = [[0 for _ in range(8)] for _ in range(8)] arr = [[0 for _ in range(8)] for _ in range(8)]
for y in range(8): for y in range(8):
@ -345,6 +350,10 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00) rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00)
rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00)
GREEN_TWENTY_RUPEES = 0x47
TRIFORCE_PIECE = ItemFactory('Triforce Piece').code
GREEN_CLOCK = ItemFactory('Green Clock').code
# handle difficulty # handle difficulty
if world.difficulty == 'hard': if world.difficulty == 'hard':
# Powdered Fairies Prize # Powdered Fairies Prize
@ -359,8 +368,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
rom.write_bytes(0x45C42, [0x08, 0x08, 0x08]) rom.write_bytes(0x45C42, [0x08, 0x08, 0x08])
#Disable catching fairies #Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
#Set overflow items for progressive equipment overflow_replacement = GREEN_TWENTY_RUPEES
rom.write_bytes(0x180090, [0x03, 0x47, 0x02, 0x47, 0x01, 0x47, 0x02, 0x47])
# Rupoor negative value # Rupoor negative value
rom.write_int16_to_rom(0x180036, 10) rom.write_int16_to_rom(0x180036, 10)
#Make Blue Shield more expensive #Make Blue Shield more expensive
@ -394,8 +402,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
rom.write_bytes(0x45C42, [0x08, 0x08, 0x08]) rom.write_bytes(0x45C42, [0x08, 0x08, 0x08])
#Disable catching fairies #Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
#Set overflow items for progressive equipment overflow_replacement = GREEN_TWENTY_RUPEES
rom.write_bytes(0x180090, [0x02, 0x47, 0x00, 0x47, 0x00, 0x47, 0x01, 0x47])
# Rupoor negative value # Rupoor negative value
rom.write_int16_to_rom(0x180036, 20) rom.write_int16_to_rom(0x180036, 20)
#Make Blue Shield more expensive #Make Blue Shield more expensive
@ -429,8 +436,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
rom.write_bytes(0x45C42, [0x08, 0x08, 0x08]) rom.write_bytes(0x45C42, [0x08, 0x08, 0x08])
#Disable catching fairies #Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
#Set overflow items for progressive equipment overflow_replacement = GREEN_TWENTY_RUPEES
rom.write_bytes(0x180090, [0x02, 0x47, 0x00, 0x47, 0x00, 0x47, 0x01, 0x47])
# Rupoor negative value # Rupoor negative value
rom.write_int16_to_rom(0x180036, 9999) rom.write_int16_to_rom(0x180036, 9999)
#Make Blue Shield more expensive #Make Blue Shield more expensive
@ -466,11 +472,19 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
rom.write_byte(0x34FD6, 0xF0) rom.write_byte(0x34FD6, 0xF0)
#Set overflow items for progressive equipment #Set overflow items for progressive equipment
if world.goal == 'triforcehunt': if world.goal == 'triforcehunt':
rom.write_bytes(0x180090, [0x04, 0x6C, 0x03, 0x6C, 0x02, 0x6C, 0x04, 0x6C]) overflow_replacement = TRIFORCE_PIECE
elif world.timer in ['timed', 'timed-countdown', 'timed-ohko']: elif world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
rom.write_bytes(0x180090, [0x04, 0x5D, 0x03, 0x5D, 0x02, 0x5D, 0x04, 0x5D]) overflow_replacement = GREEN_CLOCK
else: else:
rom.write_bytes(0x180090, [0x04, 0x47, 0x03, 0x47, 0x02, 0x47, 0x04, 0x47]) overflow_replacement = GREEN_TWENTY_RUPEES
difficulty = world.difficulty_requirements
#Set overflow items for progressive equipment
rom.write_bytes(0x180090,
[difficulty.progressive_sword_limit, overflow_replacement,
difficulty.progressive_shield_limit, overflow_replacement,
difficulty.progressive_armor_limit, overflow_replacement,
difficulty.progressive_bottle_limit, overflow_replacement])
# set up game internal RNG seed # set up game internal RNG seed
for i in range(1024): for i in range(1024):

View File

@ -187,7 +187,7 @@ def global_rules(world):
set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling
set_rule(world.get_location('Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and state.can_extend_magic()) set_rule(world.get_location('Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and state.can_extend_magic())
# TODO: Current-VT logic is: hammer and lift_rocks and ((cape and extend) or (byrna and (can-take-damage OR canextend))) # TODO: Current-VT logic is: hammer and lift_rocks and ((cape and extend) or (byrna and (can-take-damage OR canextend)))
# Is that really good enough? Can you really get through with byrna, single magic w/o refills and only 3 hearts? (answer: probnably but seems to requires tas-like timing.) # Is that really good enough? Can you really get through with byrna, single magic w/o refills and only 3 hearts? (answer: probably but seems to requires tas-like timing.)
set_rule(world.get_location('Hookshot Cave - Top Right'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Hookshot Cave - Top Right'), lambda state: state.has('Hookshot'))
set_rule(world.get_location('Hookshot Cave - Top Left'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Hookshot Cave - Top Left'), lambda state: state.has('Hookshot'))
set_rule(world.get_location('Hookshot Cave - Bottom Right'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots')) set_rule(world.get_location('Hookshot Cave - Bottom Right'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots'))
@ -264,7 +264,7 @@ def global_rules(world):
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_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword()))
set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)')) set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)'))
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('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 (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 (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)')) or state.has('Small Key (Ice Palace)', 2)) and (world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna'))) set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)'))) and (world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna')))
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']: for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
forbid_item(world.get_location(location), 'Big Key (Ice Palace)') forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
@ -298,7 +298,6 @@ def global_rules(world):
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 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) (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 (Dark Room) (South)'), lambda state: state.has('Cane of Somaria'))
# FIXME: should shield overflow count check to the progrssive logic stuff, so we don't get false mirror shields which would cause problems here
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
@ -312,17 +311,20 @@ def global_rules(world):
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer')) set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer'))
set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area set_rule(world.get_entrance('Palace of Darkness 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('Palace of Darkness 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('Palace of Darkness 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('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)']))
set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4)) set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4))
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
if world.keysanity: if world.keysanity:
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'])) set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)']))
# TODO: add an always_allow rule for this to permit key for a key
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)']))
# TODO: add an always_allow rule for this to permit key for a key
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6)) set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6))
else: else:
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'])) set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)']))
# TODO: add an always_allow rule for this to permit key for a key
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)']))
# TODO: add an always_allow rule for this to permit key for a key
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5)) set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5))
for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Helmasaur']: for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Helmasaur']:
@ -504,18 +506,21 @@ def set_trock_key_rules(world):
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3))
# the most complicated one # the most complicated one
# if we have back entrance access, we could waste all keys before touching this def tr_big_key_chest_keys_needed(state):
# if we don't, we have access to all chests by the time we can waste a key on trinexx door item = item_name(state.world.get_location('Turtle Rock - Big Key Chest'))
# in that case, if it contains the big key, we can also not waste a key on the roller switch door # handle key for a key situation in the usual way (by letting us logically open the door using the key locked inside it)
# however in keysanity being able to reach all other chests while only having three keys does not imply this contains if item in ['Small Key (Turtle Rock)']:
# a key, so we again need all four keys unless it contains the big key return 3
if can_reach_back: # if we lack backdoor access and cannot reach the back before opening this chest because it contains the big key
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Small Key (Turtle Rock)'])) # then that means there are two doors left that we cannot have spent a key on, (crystalroller and trinexx) so we only need
elif world.keysanity: # two keys
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Big Key (Turtle Rock)']) else state.has('Small Key (Turtle Rock)', 4) or (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Small Key (Turtle Rock)'])) if item in ['Big Key (Turtle Rock)'] and not can_reach_back:
else: return 2
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Big Key (Turtle Rock)']) else state.has('Small Key (Turtle Rock)', 3) or (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Small Key (Turtle Rock)'])) # otherwise we could potentially have opened every other door already, so we need all 4 keys.
# TODO add key-for-key logic to the above mess via always_allow rules. Ugh! return 4
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state)))
set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)' and state.has('Small Key (Turtle Rock)', 3))
# set big key restrictions # set big key restrictions
non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx'] non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx']

File diff suppressed because one or more lines are too long

BIN
data/default.zspr Normal file

Binary file not shown.