Various fixes and QoL changes

Dam softlock fix, fake world fix, TT basement keylock, adjustments for disable music, shuffle Ganon on by default, custom missing certain progression items will fail less often.
This commit is contained in:
AmazingAmpharos 2018-01-29 15:48:56 -06:00 committed by GitHub
commit ffe8df3304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 74 deletions

View File

@ -57,6 +57,7 @@ class World(object):
self.customitemarray = customitemarray self.customitemarray = customitemarray
self.can_take_damage = True self.can_take_damage = True
self.difficulty_requirements = None self.difficulty_requirements = None
self.fix_fake_world = True
self.spoiler = Spoiler(self) self.spoiler = Spoiler(self)
def intialize_regions(self): def intialize_regions(self):

View File

@ -174,10 +174,12 @@ def start():
ensure all locations are reachable. This only has an effect ensure all locations are reachable. This only has an effect
on the restrictive algorithm currently. on the restrictive algorithm currently.
''', action='store_true') ''', action='store_true')
parser.add_argument('--shuffleganon', help='''\ # included for backwards compatibility
If set, include the Pyramid Hole and Ganon's Tower in the parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=True)
entrance shuffle pool. parser.add_argument('--no-shuffleganon', help='''\
''', action='store_true') If set, the Pyramid Hole and Ganon's Tower are not
included entrance shuffle pool.
''', action='store_false', dest='shuffleganon')
parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'], parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'],
help='''\ help='''\
Select the rate at which the heart beep sound is played at Select the rate at which the heart beep sound is played at

View File

@ -499,6 +499,7 @@ def link_entrances(world):
connect_doors(world, single_doors, door_targets) connect_doors(world, single_doors, door_targets)
elif world.shuffle == 'insanity': elif world.shuffle == 'insanity':
world.fix_fake_world = False
# beware ye who enter here # beware ye who enter here
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)'] entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']

14
Fill.py
View File

@ -1,6 +1,8 @@
import random import random
import logging import logging
class FillError(RuntimeError):
pass
def distribute_items_cutoff(world, cutoffrate=0.33): def distribute_items_cutoff(world, cutoffrate=0.33):
# get list of locations to fill in # get list of locations to fill in
@ -53,7 +55,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
progress_done = True progress_done = True
continue continue
raise RuntimeError('No more progress items left to place.') raise FillError('No more progress items left to place.')
spot_to_fill = None spot_to_fill = None
for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations): for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations):
@ -66,7 +68,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
if world.can_beat_game(): if world.can_beat_game():
logging.getLogger('').warning('Not all items placed. Game beatable anyway.') logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
break break
raise RuntimeError('No more spots to place %s' % item_to_place) raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, True) world.push_item(spot_to_fill, item_to_place, True)
itempool.remove(item_to_place) itempool.remove(item_to_place)
@ -121,7 +123,7 @@ def distribute_items_staleness(world):
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
progress_done = True progress_done = True
continue continue
raise RuntimeError('No more progress items left to place.') raise FillError('No more progress items left to place.')
spot_to_fill = None spot_to_fill = None
for location in fill_locations: for location in fill_locations:
@ -147,7 +149,7 @@ def distribute_items_staleness(world):
if world.can_beat_game(): if world.can_beat_game():
logging.getLogger('').warning('Not all items placed. Game beatable anyway.') logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
break break
raise RuntimeError('No more spots to place %s' % item_to_place) raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, True) world.push_item(spot_to_fill, item_to_place, True)
itempool.remove(item_to_place) itempool.remove(item_to_place)
@ -185,7 +187,7 @@ def fill_restrictive(world, base_state, locations, itempool):
if not world.check_beatable_only: if not world.check_beatable_only:
logging.getLogger('').warning('Not all items placed. Game beatable anyway.') logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
break break
raise RuntimeError('No more spots to place %s' % item_to_place) raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, False) world.push_item(spot_to_fill, item_to_place, False)
locations.remove(spot_to_fill) locations.remove(spot_to_fill)
@ -281,7 +283,7 @@ def flood_items(world):
if candidate_item_to_place is not None: if candidate_item_to_place is not None:
item_to_place = candidate_item_to_place item_to_place = candidate_item_to_place
else: else:
raise RuntimeError('No more progress items left to place.') raise FillError('No more progress items left to place.')
# find item to replace with progress item # find item to replace with progress item
location_list = world.get_reachable_locations() location_list = world.get_reachable_locations()

1
Gui.py
View File

@ -69,6 +69,7 @@ def guiMain(args=None):
disableMusicVar = IntVar() disableMusicVar = IntVar()
disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar) disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar)
shuffleGanonVar = IntVar() shuffleGanonVar = IntVar()
shuffleGanonVar.set(1) #set default
shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar) shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar)
customVar = IntVar() customVar = IntVar()
customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar) customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar)

View File

@ -1,8 +1,9 @@
from collections import namedtuple from collections import namedtuple
import logging
import random import random
from Items import ItemFactory from Items import ItemFactory
from Fill import fill_restrictive from Fill import FillError, fill_restrictive
from Dungeons import get_dungeon_item_pool from Dungeons import get_dungeon_item_pool
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. #This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
@ -250,14 +251,36 @@ def generate_itempool(world):
world.required_medallions = (mm_medallion, tr_medallion) world.required_medallions = (mm_medallion, tr_medallion)
# distribute crystals # distribute crystals
fill_prizes(world)
def fill_prizes(world, attempts=15):
crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']) crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'])
crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'), crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'),
world.get_location('Thieves Town - Prize'), world.get_location('Skull Woods - Prize'), world.get_location('Swamp Palace - Prize'), world.get_location('Ice Palace - Prize'), world.get_location('Thieves Town - Prize'), world.get_location('Skull Woods - Prize'), world.get_location('Swamp Palace - Prize'), world.get_location('Ice Palace - Prize'),
world.get_location('Misery Mire - Prize')] world.get_location('Misery Mire - Prize')]
placed_prizes = [loc.item.name for loc in crystal_locations if loc.item is not None]
unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
empty_crystal_locations = [loc for loc in crystal_locations if loc.item is None]
while attempts:
attempts -= 1
try:
prizepool = list(unplaced_prizes)
prize_locs = list(empty_crystal_locations)
random.shuffle(prizepool)
random.shuffle(prize_locs)
fill_restrictive(world, world.get_all_state(keys=True), prize_locs, prizepool)
except FillError:
logging.getLogger('').info("Failed to place dungeon prizes. Will retry %s more times", attempts)
for location in empty_crystal_locations:
location.item = None
continue
break
else:
raise FillError('Unable to place dungeon prizes')
random.shuffle(crystal_locations)
fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals)
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode): def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode):
pool = [] pool = []

View File

@ -83,7 +83,7 @@ Insane 50/50
Standard game completion requiring you to collect the 7 crystals and then beat Ganon. Standard game completion requiring you to collect the 7 crystals and then beat Ganon.
This is only noticeably different if the --shuffleganon option is enabled. This is only noticeably different if the the Ganon shuffle option is enabled.
## Game Difficulty ## Game Difficulty
@ -422,10 +422,10 @@ Use to select a different sprite sheet to use for Link. Path to a binary file of
Enables the "Only Ensure Seed Beatable" option (default: False) Enables the "Only Ensure Seed Beatable" option (default: False)
``` ```
--shuffleganon --no-shuffleganon
``` ```
Enables the "Include Ganon's Tower and Pyramid Hole in Shuffle pool" option. (default: false) Disables the "Include Ganon's Tower and Pyramid Hole in Shuffle pool" option. (default: Enabled)
``` ```
--suppress_rom --suppress_rom

49
Rom.py
View File

@ -655,6 +655,7 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
rom.write_byte(0x180034, 0x0A) # starting max bombs rom.write_byte(0x180034, 0x0A) # starting max bombs
rom.write_byte(0x180035, 30) # starting max bombs rom.write_byte(0x180035, 30) # starting max bombs
rom.write_byte(0x180174, 0x01 if world.fix_fake_world else 0x00)
if world.goal in ['pedestal', 'triforcehunt']: if world.goal in ['pedestal', 'triforcehunt']:
rom.write_byte(0x18003E, 0x01) # make ganon invincible rom.write_byte(0x18003E, 0x01) # make ganon invincible
@ -823,38 +824,46 @@ def apply_rom_settings(rom, beep, quickswap, fastmenu, disable_music, sprite):
0xD2866, 0xD2887, 0xD28A0, 0xD28BA, 0xD28DB, 0xD28F4, 0xD293E, 0xD2BF3, 0xD2C1F, 0xD2C69, 0xD2CA1, 0xD2CC5, 0xD2D05, 0xD2866, 0xD2887, 0xD28A0, 0xD28BA, 0xD28DB, 0xD28F4, 0xD293E, 0xD2BF3, 0xD2C1F, 0xD2C69, 0xD2CA1, 0xD2CC5, 0xD2D05,
0xD2D73, 0xD2DAF, 0xD2E3D, 0xD2F36, 0xD2F46, 0xD2F6F, 0xD2FCF, 0xD2FDF, 0xD302B, 0xD3086, 0xD3099, 0xD30A5, 0xD30CD, 0xD2D73, 0xD2DAF, 0xD2E3D, 0xD2F36, 0xD2F46, 0xD2F6F, 0xD2FCF, 0xD2FDF, 0xD302B, 0xD3086, 0xD3099, 0xD30A5, 0xD30CD,
0xD30F6, 0xD3154, 0xD3184, 0xD333A, 0xD33D9, 0xD349F, 0xD354A, 0xD35E5, 0xD3624, 0xD363C, 0xD3672, 0xD3691, 0xD36B4, 0xD30F6, 0xD3154, 0xD3184, 0xD333A, 0xD33D9, 0xD349F, 0xD354A, 0xD35E5, 0xD3624, 0xD363C, 0xD3672, 0xD3691, 0xD36B4,
0xD36C6, 0xD3724, 0xD3767, 0xD38CB, 0xD3B1D, 0xD3B2F, 0xD3B55, 0xD3B70, 0xD3B81, 0xD3BBF, 0xD3D34, 0xD3D55, 0xD3D6E, 0xD36C6, 0xD3724, 0xD3767, 0xD38CB, 0xD3B1D, 0xD3B2F, 0xD3B55, 0xD3B70, 0xD3B81, 0xD3BBF, 0xD3F65, 0xD3FA6, 0xD404F,
0xD3DC6, 0xD3E04, 0xD3E38, 0xD3F65, 0xD3FA6, 0xD404F, 0xD4087, 0xD417A, 0xD41A0, 0xD425C, 0xD4319, 0xD433C, 0xD43EF, 0xD4087, 0xD417A, 0xD41A0, 0xD425C, 0xD4319, 0xD433C, 0xD43EF, 0xD440C, 0xD4452, 0xD4494, 0xD44B5, 0xD4512, 0xD45D1,
0xD440C, 0xD4452, 0xD4494, 0xD44B5, 0xD4512, 0xD45D1, 0xD45EF, 0xD4682, 0xD46C3, 0xD483C, 0xD4848, 0xD4855, 0xD4862, 0xD45EF, 0xD4682, 0xD46C3, 0xD483C, 0xD4848, 0xD4855, 0xD4862, 0xD486F, 0xD487C, 0xD4A1C, 0xD4A3B, 0xD4A60, 0xD4B27,
0xD486F, 0xD487C, 0xD4A1C, 0xD4A3B, 0xD4A60, 0xD4B27, 0xD4C7A, 0xD4D12, 0xD4D81, 0xD4E90, 0xD4ED6, 0xD4EE2, 0xD5005, 0xD4C7A, 0xD4D12, 0xD4D81, 0xD4E90, 0xD4ED6, 0xD4EE2, 0xD5005, 0xD502E, 0xD503C, 0xD5081, 0xD51B1, 0xD51C7, 0xD51CF,
0xD502E, 0xD503C, 0xD5081, 0xD51B1, 0xD51C7, 0xD51CF, 0xD51EF, 0xD520C, 0xD5214, 0xD5231, 0xD5257, 0xD526D, 0xD5275, 0xD51EF, 0xD520C, 0xD5214, 0xD5231, 0xD5257, 0xD526D, 0xD5275, 0xD52AF, 0xD52BD, 0xD52CD, 0xD52DB, 0xD549C, 0xD5801,
0xD52AF, 0xD52BD, 0xD52CD, 0xD52DB, 0xD549C, 0xD5801, 0xD58A4, 0xD5A68, 0xD5A7F, 0xD5C12, 0xD5D71, 0xD5E10, 0xD5E9A, 0xD58A4, 0xD5A68, 0xD5A7F, 0xD5C12, 0xD5D71, 0xD5E10, 0xD5E9A, 0xD5F8B, 0xD5FA4, 0xD651A, 0xD6542, 0xD65ED, 0xD661D,
0xD5F8B, 0xD5FA4, 0xD651A, 0xD6542, 0xD65ED, 0xD661D, 0xD66D7, 0xD6776, 0xD68BD, 0xD68E5, 0xD6956, 0xD6973, 0xD69A8, 0xD66D7, 0xD6776, 0xD68BD, 0xD68E5, 0xD6956, 0xD6973, 0xD69A8, 0xD6A51, 0xD6A86, 0xD6B96, 0xD6C3E, 0xD6D4A, 0xD6E9C,
0xD6A51, 0xD6A86, 0xD6B96, 0xD6C3E, 0xD6D4A, 0xD6E9C, 0xD6F80, 0xD717E, 0xD7190, 0xD71B9, 0xD811D, 0xD8139, 0xD816B, 0xD6F80, 0xD717E, 0xD7190, 0xD71B9, 0xD811D, 0xD8139, 0xD816B, 0xD818A, 0xD819E, 0xD81BE, 0xD829C, 0xD82E1, 0xD8306,
0xD818A, 0xD819E, 0xD81BE, 0xD829C, 0xD82E1, 0xD8306, 0xD830E, 0xD835E, 0xD83AB, 0xD83CA, 0xD83F0, 0xD83F8, 0xD844B, 0xD830E, 0xD835E, 0xD83AB, 0xD83CA, 0xD83F0, 0xD83F8, 0xD844B, 0xD8479, 0xD849E, 0xD84CB, 0xD84EB, 0xD84F3, 0xD854A,
0xD8479, 0xD849E, 0xD84CB, 0xD84EB, 0xD84F3, 0xD854A, 0xD8573, 0xD859D, 0xD85B4, 0xD85CE, 0xD862A, 0xD8681, 0xD87E3, 0xD8573, 0xD859D, 0xD85B4, 0xD85CE, 0xD862A, 0xD8681, 0xD87E3, 0xD87FF, 0xD887B, 0xD88C6, 0xD88E3, 0xD8944, 0xD897B,
0xD87FF, 0xD887B, 0xD88C6, 0xD88E3, 0xD8944, 0xD897B, 0xD8C97, 0xD8CA4, 0xD8CB3, 0xD8CC2, 0xD8CD1, 0xD8D01, 0xD917B, 0xD8C97, 0xD8CA4, 0xD8CB3, 0xD8CC2, 0xD8CD1, 0xD8D01, 0xD917B, 0xD918C, 0xD919A, 0xD91B5, 0xD91D0, 0xD91DD, 0xD9220,
0xD918C, 0xD919A, 0xD91B5, 0xD91D0, 0xD91DD, 0xD9220, 0xD9273, 0xD9284, 0xD9292, 0xD92AD, 0xD92C8, 0xD92D5, 0xD9311, 0xD9273, 0xD9284, 0xD9292, 0xD92AD, 0xD92C8, 0xD92D5, 0xD9311, 0xD9322, 0xD9330, 0xD934B, 0xD9366, 0xD9373, 0xD93B6,
0xD9322, 0xD9330, 0xD934B, 0xD9366, 0xD9373, 0xD93B6, 0xD97A6, 0xD97C2, 0xD97DC, 0xD97FB, 0xD9811, 0xD98FF, 0xD996F, 0xD97A6, 0xD97C2, 0xD97DC, 0xD97FB, 0xD9811, 0xD98FF, 0xD996F, 0xD99A8, 0xD99D5, 0xD9A30, 0xD9A4E, 0xD9A6B, 0xD9A88,
0xD99A8, 0xD99D5, 0xD9A30, 0xD9A4E, 0xD9A6B, 0xD9A88, 0xD9AF7, 0xD9B1D, 0xD9B43, 0xD9B7C, 0xD9BA9, 0xD9C84, 0xD9C8D, 0xD9AF7, 0xD9B1D, 0xD9B43, 0xD9B7C, 0xD9BA9, 0xD9C84, 0xD9C8D, 0xD9CAC, 0xD9CE8, 0xD9CF3, 0xD9CFD, 0xD9D46, 0xDA35E,
0xD9CAC, 0xD9CE8, 0xD9CF3, 0xD9CFD, 0xD9D46, 0xDA35E, 0xDA37E, 0xDA391, 0xDA478, 0xDA4C3, 0xDA4D7, 0xDA4F6, 0xDA515, 0xDA37E, 0xDA391, 0xDA478, 0xDA4C3, 0xDA4D7, 0xDA4F6, 0xDA515, 0xDA6E2, 0xDA9C2, 0xDA9ED, 0xDAA1B, 0xDAA57, 0xDABAF,
0xDA6E2, 0xDA9C2, 0xDA9ED, 0xDAA1B, 0xDAA57, 0xDABAF, 0xDABC9, 0xDABE2, 0xDAC28, 0xDAC46, 0xDAC63, 0xDACB8, 0xDACEC, 0xDABC9, 0xDABE2, 0xDAC28, 0xDAC46, 0xDAC63, 0xDACB8, 0xDACEC, 0xDAD08, 0xDAD25, 0xDAD42, 0xDAD5F, 0xDAE17, 0xDAE34,
0xDAD08, 0xDAD25, 0xDAD42, 0xDAD5F, 0xDAE17, 0xDAE34, 0xDAE51, 0xDAF2E, 0xDAF55, 0xDAF6B, 0xDAF81, 0xDB14F, 0xDB16B, 0xDAE51, 0xDAF2E, 0xDAF55, 0xDAF6B, 0xDAF81, 0xDB14F, 0xDB16B, 0xDB180, 0xDB195, 0xDB1AA]),
0xDB180, 0xDB195, 0xDB1AA]),
(0xD2, [0xD2B88, 0xD364A, 0xD369F, 0xD3747]), (0xD2, [0xD2B88, 0xD364A, 0xD369F, 0xD3747]),
(0xDC, [0xD213F, 0xD2174, 0xD229E, 0xD2426, 0xD4731, 0xD4753, 0xD4774, 0xD4795, 0xD47B6, 0xD4AA5, 0xD4AE4, 0xD4B96, 0xD4CA5, (0xDC, [0xD213F, 0xD2174, 0xD229E, 0xD2426, 0xD4731, 0xD4753, 0xD4774, 0xD4795, 0xD47B6, 0xD4AA5, 0xD4AE4, 0xD4B96, 0xD4CA5,
0xD5477, 0xD5A3D, 0xD6566, 0xD672C, 0xD67C0, 0xD69B8, 0xD6AB1, 0xD6C05, 0xD6DB3, 0xD71AB, 0xD8E2D, 0xD8F0D, 0xD94E0, 0xD5477, 0xD5A3D, 0xD6566, 0xD672C, 0xD67C0, 0xD69B8, 0xD6AB1, 0xD6C05, 0xD6DB3, 0xD71AB, 0xD8E2D, 0xD8F0D, 0xD94E0,
0xD9544, 0xD95A8, 0xD9982, 0xD9B56, 0xDA694, 0xDA6AB, 0xDAE88, 0xDAEC8, 0xDAEE6, 0xDB1BF]), 0xD9544, 0xD95A8, 0xD9982, 0xD9B56, 0xDA694, 0xDA6AB, 0xDAE88, 0xDAEC8, 0xDAEE6, 0xDB1BF]),
(0xE6, [0xD210A, 0xD22DC, 0xD2447, 0xD5A4D, 0xD5DDC, 0xDA251, 0xDA26C]), (0xE6, [0xD210A, 0xD22DC, 0xD2447, 0xD5A4D, 0xD5DDC, 0xDA251, 0xDA26C]),
(0xF0, [0xD945E, 0xD967D, 0xD96C2, 0xD9C95, 0xD9EE6, 0xDA5C6]), (0xF0, [0xD945E, 0xD967D, 0xD96C2, 0xD9C95, 0xD9EE6, 0xDA5C6]),
(0xFA, [0xD2047, 0xD24C2, 0xD24EC, 0xD25A4, 0xD3DAA, 0xD51A8, 0xD51E6, 0xD524E, 0xD529E, 0xD6045, 0xD81DE, 0xD821E, 0xD94AA, (0xFA, [0xD2047, 0xD24C2, 0xD24EC, 0xD25A4, 0xD51A8, 0xD51E6, 0xD524E, 0xD529E, 0xD6045, 0xD81DE, 0xD821E, 0xD94AA, 0xD9A9E,
0xD9A9E, 0xD9AE4, 0xDA289]), 0xD9AE4, 0xDA289]),
(0xFF, [0xD2085, 0xD21C5, 0xD5F28]) (0xFF, [0xD2085, 0xD21C5, 0xD5F28])
] ]
for volume, addresses in music_volumes: for volume, addresses in music_volumes:
for address in addresses: for address in addresses:
rom.write_byte(address, volume if not disable_music else 0x00) rom.write_byte(address, volume if not disable_music else 0x00)
# restore Mirror sound effect volumes (for existing seeds that lack it)
rom.write_byte(0xD3E04, 0xC8)
rom.write_byte(0xD3DC6, 0xC8)
rom.write_byte(0xD3D6E, 0xC8)
rom.write_byte(0xD3D34, 0xC8)
rom.write_byte(0xD3D55, 0xC8)
rom.write_byte(0xD3E38, 0xC8)
rom.write_byte(0xD3DAA, 0xFA)
# set heart beep rate # set heart beep rate
rom.write_byte(0x180033, {'off': 0x00, 'half': 0x40, 'quarter': 0x80, 'normal': 0x20}[beep]) rom.write_byte(0x180033, {'off': 0x00, 'half': 0x40, 'quarter': 0x80, 'normal': 0x20}[beep])

View File

@ -119,7 +119,6 @@ def global_rules(world):
set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant')) set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant'))
set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant')) set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant'))
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
# FIXME: VT has a can_kill_most_things(8) call on Aga Tower's entrance. I think this is supposed to reflect that a better weapon than 10 bombs is needed to reach the two chests in this tower
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has('Small Key (Agahnims Tower)', 2)) set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has('Small Key (Agahnims Tower)', 2))
set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has('Small Key (Agahnims Tower)')) set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has('Small Key (Agahnims Tower)'))
set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1')) set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1'))
@ -185,6 +184,8 @@ def global_rules(world):
set_rule(world.get_entrance('Fairy Ascension Mirror Spot'), lambda state: state.has_Mirror() and state.has_Pearl()) # need to lift flowers set_rule(world.get_entrance('Fairy Ascension Mirror Spot'), lambda state: state.has_Mirror() and state.has_Pearl()) # need to lift flowers
set_rule(world.get_entrance('Isolated Ledge Mirror Spot'), lambda state: state.has_Mirror()) set_rule(world.get_entrance('Isolated Ledge Mirror Spot'), lambda state: state.has_Mirror())
set_rule(world.get_entrance('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
# Todo: Update the below to reflect that latest ROM has full strengh potions in all difficulties in spike cave
set_rule(world.get_location('Spike Cave'), lambda state: set_rule(world.get_location('Spike Cave'), lambda state:
state.has('Hammer') and state.can_lift_rocks() and state.has('Hammer') and state.can_lift_rocks() and
( (
@ -257,7 +258,7 @@ def global_rules(world):
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)')) set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)'))
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna'))) set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna')))
set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer')) set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer'))
set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)') set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)' and state.has('Hammer'))
set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has('Small Key (Thieves Town)')) set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has('Small Key (Thieves Town)'))
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']: for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']:
forbid_item(world.get_location(location), 'Big Key (Thieves Town)') forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
@ -316,8 +317,8 @@ def global_rules(world):
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'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top 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 Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and
(state.has('Hammer') or state.has_beam_sword() or (state.has_sword and state.can_extend_magic(32)))) (state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32))))
# TODO: Per VT, possibly allow a regular sword with 4x extended magic (ie. quater magic, or half magic+bottle or 3 bottles)
set_trock_key_rules(world) set_trock_key_rules(world)
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.has('Bow')) set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.has('Bow'))
@ -660,6 +661,12 @@ def set_bunny_rules(world):
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge']
if not world.get_region('Dam').is_light_world:
# if Dam is is dark world, then it is required to have the pearl to get the sunken item
add_rule(world.get_location('Sunken Treasure'), lambda state: state.has_Pearl())
# similarly we need perl to get across the swamp palace moat
add_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has_Pearl())
# Add pearl requirements for bunny-impassible caves if they occur in the dark world # Add pearl requirements for bunny-impassible caves if they occur in the dark world
for region in [world.get_region(name) for name in bunny_impassable_caves]: for region in [world.get_region(name) for name in bunny_impassable_caves]: