commit
971c7b9365
|
@ -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
30
Gui.py
|
@ -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()
|
||||||
|
|
27
ItemList.py
27
ItemList.py
|
@ -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
23
Main.py
|
@ -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)])
|
||||||
|
|
|
@ -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
34
Rom.py
|
@ -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):
|
||||||
|
|
37
Rules.py
37
Rules.py
|
@ -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
Binary file not shown.
Loading…
Reference in New Issue