diff --git a/BaseClasses.py b/BaseClasses.py index 71760838..6e05c369 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -57,6 +57,7 @@ class World(object): self.customitemarray = customitemarray self.can_take_damage = True self.difficulty_requirements = None + self.fix_fake_world = True self.spoiler = Spoiler(self) def intialize_regions(self): diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 91b3a633..fc759ec6 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -174,10 +174,12 @@ def start(): ensure all locations are reachable. This only has an effect on the restrictive algorithm currently. ''', action='store_true') - parser.add_argument('--shuffleganon', help='''\ - If set, include the Pyramid Hole and Ganon's Tower in the - entrance shuffle pool. - ''', action='store_true') + # included for backwards compatibility + parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=True) + parser.add_argument('--no-shuffleganon', help='''\ + 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'], help='''\ Select the rate at which the heart beep sound is played at diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 87671a86..3ebf82cb 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -499,6 +499,7 @@ def link_entrances(world): connect_doors(world, single_doors, door_targets) elif world.shuffle == 'insanity': + world.fix_fake_world = False # 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)'] diff --git a/Fill.py b/Fill.py index 2ac444de..b663e088 100644 --- a/Fill.py +++ b/Fill.py @@ -1,6 +1,8 @@ import random import logging +class FillError(RuntimeError): + pass def distribute_items_cutoff(world, cutoffrate=0.33): # 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.') progress_done = True continue - raise RuntimeError('No more progress items left to place.') + raise FillError('No more progress items left to place.') spot_to_fill = None 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(): logging.getLogger('').warning('Not all items placed. Game beatable anyway.') 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) itempool.remove(item_to_place) @@ -121,7 +123,7 @@ def distribute_items_staleness(world): logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.') progress_done = True continue - raise RuntimeError('No more progress items left to place.') + raise FillError('No more progress items left to place.') spot_to_fill = None for location in fill_locations: @@ -147,7 +149,7 @@ def distribute_items_staleness(world): if world.can_beat_game(): logging.getLogger('').warning('Not all items placed. Game beatable anyway.') break - raise RuntimeError('No more spots to place %s' % item_to_place) + raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, True) itempool.remove(item_to_place) @@ -185,7 +187,7 @@ def fill_restrictive(world, base_state, locations, itempool): if not world.check_beatable_only: logging.getLogger('').warning('Not all items placed. Game beatable anyway.') 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) locations.remove(spot_to_fill) @@ -281,7 +283,7 @@ def flood_items(world): if candidate_item_to_place is not None: item_to_place = candidate_item_to_place else: - raise RuntimeError('No more progress items left to place.') + raise FillError('No more progress items left to place.') # find item to replace with progress item location_list = world.get_reachable_locations() diff --git a/Gui.py b/Gui.py index c04ba514..0a806d1d 100755 --- a/Gui.py +++ b/Gui.py @@ -69,6 +69,7 @@ def guiMain(args=None): disableMusicVar = IntVar() disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar) shuffleGanonVar = IntVar() + shuffleGanonVar.set(1) #set default shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar) customVar = IntVar() customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar) diff --git a/ItemList.py b/ItemList.py index a5d069ff..918b4470 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1,8 +1,9 @@ from collections import namedtuple +import logging import random from Items import ItemFactory -from Fill import fill_restrictive +from Fill import FillError, fill_restrictive 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. @@ -250,14 +251,36 @@ def generate_itempool(world): world.required_medallions = (mm_medallion, tr_medallion) # 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']) 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('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): pool = [] diff --git a/README.md b/README.md index e5455b9b..1c68328a 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Insane 50/50 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 @@ -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) ``` ---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 diff --git a/Rom.py b/Rom.py index ac20671a..f5204780 100644 --- a/Rom.py +++ b/Rom.py @@ -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(0x180034, 0x0A) # 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']: rom.write_byte(0x18003E, 0x01) # make ganon invincible @@ -796,65 +797,73 @@ def apply_rom_settings(rom, beep, quickswap, fastmenu, disable_music, sprite): 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) music_volumes = [ - (0x00, [0xD373B, 0xD375B, 0xD90F8]), - (0x14, [0xDA710, 0xDA7A4, 0xDA7BB, 0xDA7D2]), - (0x3C, [0xD5954, 0xD653B, 0xDA736, 0xDA752, 0xDA772, 0xDA792]), - (0x50, [0xD5B47, 0xD5B5E]), - (0x54, [0xD4306]), - (0x64, [0xD6878, 0xD6883, 0xD6E48, 0xD6E76, 0xD6EFB, 0xD6F2D, 0xDA211, 0xDA35B, 0xDA37B, 0xDA38E, 0xDA39F, 0xDA5C3, 0xDA691, 0xDA6A8, 0xDA6DF]), - (0x78, [0xD2349, 0xD3F45, 0xD42EB, 0xD48B9, 0xD48FF, 0xD543F, 0xD5817, 0xD5957, 0xD5ACB, 0xD5AE8, 0xD5B4A, 0xDA5DE, 0xDA608, 0xDA635, - 0xDA662, 0xDA71F, 0xDA7AF, 0xDA7C6, 0xDA7DD]), - (0x82, [0xD2F00, 0xDA3D5]), - (0xA0, [0xD249C, 0xD24CD, 0xD2C09, 0xD2C53, 0xD2CAF, 0xD2CEB, 0xD2D91, 0xD2EE6, 0xD38ED, 0xD3C91, 0xD3CD3, 0xD3CE8, 0xD3F0C, - 0xD3F82, 0xD405F, 0xD4139, 0xD4198, 0xD41D5, 0xD41F6, 0xD422B, 0xD4270, 0xD42B1, 0xD4334, 0xD4371, 0xD43A6, 0xD43DB, - 0xD441E, 0xD4597, 0xD4B3C, 0xD4BAB, 0xD4C03, 0xD4C53, 0xD4C7F, 0xD4D9C, 0xD5424, 0xD65D2, 0xD664F, 0xD6698, 0xD66FF, - 0xD6985, 0xD6C5C, 0xD6C6F, 0xD6C8E, 0xD6CB4, 0xD6D7D, 0xD827D, 0xD960C, 0xD9828, 0xDA233, 0xDA3A2, 0xDA49E, 0xDA72B, - 0xDA745, 0xDA765, 0xDA785, 0xDABF6, 0xDAC0D, 0xDAEBE, 0xDAFAC]), - (0xAA, [0xD9A02, 0xD9BD6]), - (0xB4, [0xD21CD, 0xD2279, 0xD2E66, 0xD2E70, 0xD2EAB, 0xD3B97, 0xD3BAC, 0xD3BE8, 0xD3C0D, 0xD3C39, 0xD3C68, 0xD3C9F, 0xD3CBC, - 0xD401E, 0xD4290, 0xD443E, 0xD456F, 0xD47D3, 0xD4D43, 0xD4DCC, 0xD4EBA, 0xD4F0B, 0xD4FE5, 0xD5012, 0xD54BC, 0xD54D5, - 0xD54F0, 0xD5509, 0xD57D8, 0xD59B9, 0xD5A2F, 0xD5AEB, 0xD5E5E, 0xD5FE9, 0xD658F, 0xD674A, 0xD6827, 0xD69D6, 0xD69F5, - 0xD6A05, 0xD6AE9, 0xD6DCF, 0xD6E20, 0xD6ECB, 0xD71D4, 0xD71E6, 0xD7203, 0xD721E, 0xD8724, 0xD8732, 0xD9652, 0xD9698, - 0xD9CBC, 0xD9DC0, 0xD9E49, 0xDAA68, 0xDAA77, 0xDAA88, 0xDAA99, 0xDAF04]), - (0x8c, [0xD1D28, 0xD1D41, 0xD1D5C, 0xD1D77, 0xD1EEE, 0xD311D, 0xD31D1, 0xD4148, 0xD5543, 0xD5B6F, 0xD65B3, 0xD6760, 0xD6B6B, - 0xD6DF6, 0xD6E0D, 0xD73A1, 0xD814C, 0xD825D, 0xD82BE, 0xD8340, 0xD8394, 0xD842C, 0xD8796, 0xD8903, 0xD892A, 0xD91E8, - 0xD922B, 0xD92E0, 0xD937E, 0xD93C1, 0xDA958, 0xDA971, 0xDA98C, 0xDA9A7]), - (0xC8, [0xD1D92, 0xD1DBD, 0xD1DEB, 0xD1F5D, 0xD1F9F, 0xD1FBD, 0xD1FDC, 0xD1FEA, 0xD20CA, 0xD21BB, 0xD22C9, 0xD2754, 0xD284C, - 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, - 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, - 0xD3DC6, 0xD3E04, 0xD3E38, 0xD3F65, 0xD3FA6, 0xD404F, 0xD4087, 0xD417A, 0xD41A0, 0xD425C, 0xD4319, 0xD433C, 0xD43EF, - 0xD440C, 0xD4452, 0xD4494, 0xD44B5, 0xD4512, 0xD45D1, 0xD45EF, 0xD4682, 0xD46C3, 0xD483C, 0xD4848, 0xD4855, 0xD4862, - 0xD486F, 0xD487C, 0xD4A1C, 0xD4A3B, 0xD4A60, 0xD4B27, 0xD4C7A, 0xD4D12, 0xD4D81, 0xD4E90, 0xD4ED6, 0xD4EE2, 0xD5005, - 0xD502E, 0xD503C, 0xD5081, 0xD51B1, 0xD51C7, 0xD51CF, 0xD51EF, 0xD520C, 0xD5214, 0xD5231, 0xD5257, 0xD526D, 0xD5275, - 0xD52AF, 0xD52BD, 0xD52CD, 0xD52DB, 0xD549C, 0xD5801, 0xD58A4, 0xD5A68, 0xD5A7F, 0xD5C12, 0xD5D71, 0xD5E10, 0xD5E9A, - 0xD5F8B, 0xD5FA4, 0xD651A, 0xD6542, 0xD65ED, 0xD661D, 0xD66D7, 0xD6776, 0xD68BD, 0xD68E5, 0xD6956, 0xD6973, 0xD69A8, - 0xD6A51, 0xD6A86, 0xD6B96, 0xD6C3E, 0xD6D4A, 0xD6E9C, 0xD6F80, 0xD717E, 0xD7190, 0xD71B9, 0xD811D, 0xD8139, 0xD816B, - 0xD818A, 0xD819E, 0xD81BE, 0xD829C, 0xD82E1, 0xD8306, 0xD830E, 0xD835E, 0xD83AB, 0xD83CA, 0xD83F0, 0xD83F8, 0xD844B, - 0xD8479, 0xD849E, 0xD84CB, 0xD84EB, 0xD84F3, 0xD854A, 0xD8573, 0xD859D, 0xD85B4, 0xD85CE, 0xD862A, 0xD8681, 0xD87E3, - 0xD87FF, 0xD887B, 0xD88C6, 0xD88E3, 0xD8944, 0xD897B, 0xD8C97, 0xD8CA4, 0xD8CB3, 0xD8CC2, 0xD8CD1, 0xD8D01, 0xD917B, - 0xD918C, 0xD919A, 0xD91B5, 0xD91D0, 0xD91DD, 0xD9220, 0xD9273, 0xD9284, 0xD9292, 0xD92AD, 0xD92C8, 0xD92D5, 0xD9311, - 0xD9322, 0xD9330, 0xD934B, 0xD9366, 0xD9373, 0xD93B6, 0xD97A6, 0xD97C2, 0xD97DC, 0xD97FB, 0xD9811, 0xD98FF, 0xD996F, - 0xD99A8, 0xD99D5, 0xD9A30, 0xD9A4E, 0xD9A6B, 0xD9A88, 0xD9AF7, 0xD9B1D, 0xD9B43, 0xD9B7C, 0xD9BA9, 0xD9C84, 0xD9C8D, - 0xD9CAC, 0xD9CE8, 0xD9CF3, 0xD9CFD, 0xD9D46, 0xDA35E, 0xDA37E, 0xDA391, 0xDA478, 0xDA4C3, 0xDA4D7, 0xDA4F6, 0xDA515, - 0xDA6E2, 0xDA9C2, 0xDA9ED, 0xDAA1B, 0xDAA57, 0xDABAF, 0xDABC9, 0xDABE2, 0xDAC28, 0xDAC46, 0xDAC63, 0xDACB8, 0xDACEC, - 0xDAD08, 0xDAD25, 0xDAD42, 0xDAD5F, 0xDAE17, 0xDAE34, 0xDAE51, 0xDAF2E, 0xDAF55, 0xDAF6B, 0xDAF81, 0xDB14F, 0xDB16B, - 0xDB180, 0xDB195, 0xDB1AA]), - (0xD2, [0xD2B88, 0xD364A, 0xD369F, 0xD3747]), - (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, - 0xD9544, 0xD95A8, 0xD9982, 0xD9B56, 0xDA694, 0xDA6AB, 0xDAE88, 0xDAEC8, 0xDAEE6, 0xDB1BF]), - (0xE6, [0xD210A, 0xD22DC, 0xD2447, 0xD5A4D, 0xD5DDC, 0xDA251, 0xDA26C]), - (0xF0, [0xD945E, 0xD967D, 0xD96C2, 0xD9C95, 0xD9EE6, 0xDA5C6]), - (0xFA, [0xD2047, 0xD24C2, 0xD24EC, 0xD25A4, 0xD3DAA, 0xD51A8, 0xD51E6, 0xD524E, 0xD529E, 0xD6045, 0xD81DE, 0xD821E, 0xD94AA, - 0xD9A9E, 0xD9AE4, 0xDA289]), - (0xFF, [0xD2085, 0xD21C5, 0xD5F28]) + (0x00, [0xD373B, 0xD375B, 0xD90F8]), + (0x14, [0xDA710, 0xDA7A4, 0xDA7BB, 0xDA7D2]), + (0x3C, [0xD5954, 0xD653B, 0xDA736, 0xDA752, 0xDA772, 0xDA792]), + (0x50, [0xD5B47, 0xD5B5E]), + (0x54, [0xD4306]), + (0x64, [0xD6878, 0xD6883, 0xD6E48, 0xD6E76, 0xD6EFB, 0xD6F2D, 0xDA211, 0xDA35B, 0xDA37B, 0xDA38E, 0xDA39F, 0xDA5C3, 0xDA691, 0xDA6A8, 0xDA6DF]), + (0x78, [0xD2349, 0xD3F45, 0xD42EB, 0xD48B9, 0xD48FF, 0xD543F, 0xD5817, 0xD5957, 0xD5ACB, 0xD5AE8, 0xD5B4A, 0xDA5DE, 0xDA608, 0xDA635, + 0xDA662, 0xDA71F, 0xDA7AF, 0xDA7C6, 0xDA7DD]), + (0x82, [0xD2F00, 0xDA3D5]), + (0xA0, [0xD249C, 0xD24CD, 0xD2C09, 0xD2C53, 0xD2CAF, 0xD2CEB, 0xD2D91, 0xD2EE6, 0xD38ED, 0xD3C91, 0xD3CD3, 0xD3CE8, 0xD3F0C, + 0xD3F82, 0xD405F, 0xD4139, 0xD4198, 0xD41D5, 0xD41F6, 0xD422B, 0xD4270, 0xD42B1, 0xD4334, 0xD4371, 0xD43A6, 0xD43DB, + 0xD441E, 0xD4597, 0xD4B3C, 0xD4BAB, 0xD4C03, 0xD4C53, 0xD4C7F, 0xD4D9C, 0xD5424, 0xD65D2, 0xD664F, 0xD6698, 0xD66FF, + 0xD6985, 0xD6C5C, 0xD6C6F, 0xD6C8E, 0xD6CB4, 0xD6D7D, 0xD827D, 0xD960C, 0xD9828, 0xDA233, 0xDA3A2, 0xDA49E, 0xDA72B, + 0xDA745, 0xDA765, 0xDA785, 0xDABF6, 0xDAC0D, 0xDAEBE, 0xDAFAC]), + (0xAA, [0xD9A02, 0xD9BD6]), + (0xB4, [0xD21CD, 0xD2279, 0xD2E66, 0xD2E70, 0xD2EAB, 0xD3B97, 0xD3BAC, 0xD3BE8, 0xD3C0D, 0xD3C39, 0xD3C68, 0xD3C9F, 0xD3CBC, + 0xD401E, 0xD4290, 0xD443E, 0xD456F, 0xD47D3, 0xD4D43, 0xD4DCC, 0xD4EBA, 0xD4F0B, 0xD4FE5, 0xD5012, 0xD54BC, 0xD54D5, + 0xD54F0, 0xD5509, 0xD57D8, 0xD59B9, 0xD5A2F, 0xD5AEB, 0xD5E5E, 0xD5FE9, 0xD658F, 0xD674A, 0xD6827, 0xD69D6, 0xD69F5, + 0xD6A05, 0xD6AE9, 0xD6DCF, 0xD6E20, 0xD6ECB, 0xD71D4, 0xD71E6, 0xD7203, 0xD721E, 0xD8724, 0xD8732, 0xD9652, 0xD9698, + 0xD9CBC, 0xD9DC0, 0xD9E49, 0xDAA68, 0xDAA77, 0xDAA88, 0xDAA99, 0xDAF04]), + (0x8c, [0xD1D28, 0xD1D41, 0xD1D5C, 0xD1D77, 0xD1EEE, 0xD311D, 0xD31D1, 0xD4148, 0xD5543, 0xD5B6F, 0xD65B3, 0xD6760, 0xD6B6B, + 0xD6DF6, 0xD6E0D, 0xD73A1, 0xD814C, 0xD825D, 0xD82BE, 0xD8340, 0xD8394, 0xD842C, 0xD8796, 0xD8903, 0xD892A, 0xD91E8, + 0xD922B, 0xD92E0, 0xD937E, 0xD93C1, 0xDA958, 0xDA971, 0xDA98C, 0xDA9A7]), + (0xC8, [0xD1D92, 0xD1DBD, 0xD1DEB, 0xD1F5D, 0xD1F9F, 0xD1FBD, 0xD1FDC, 0xD1FEA, 0xD20CA, 0xD21BB, 0xD22C9, 0xD2754, 0xD284C, + 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, + 0xD30F6, 0xD3154, 0xD3184, 0xD333A, 0xD33D9, 0xD349F, 0xD354A, 0xD35E5, 0xD3624, 0xD363C, 0xD3672, 0xD3691, 0xD36B4, + 0xD36C6, 0xD3724, 0xD3767, 0xD38CB, 0xD3B1D, 0xD3B2F, 0xD3B55, 0xD3B70, 0xD3B81, 0xD3BBF, 0xD3F65, 0xD3FA6, 0xD404F, + 0xD4087, 0xD417A, 0xD41A0, 0xD425C, 0xD4319, 0xD433C, 0xD43EF, 0xD440C, 0xD4452, 0xD4494, 0xD44B5, 0xD4512, 0xD45D1, + 0xD45EF, 0xD4682, 0xD46C3, 0xD483C, 0xD4848, 0xD4855, 0xD4862, 0xD486F, 0xD487C, 0xD4A1C, 0xD4A3B, 0xD4A60, 0xD4B27, + 0xD4C7A, 0xD4D12, 0xD4D81, 0xD4E90, 0xD4ED6, 0xD4EE2, 0xD5005, 0xD502E, 0xD503C, 0xD5081, 0xD51B1, 0xD51C7, 0xD51CF, + 0xD51EF, 0xD520C, 0xD5214, 0xD5231, 0xD5257, 0xD526D, 0xD5275, 0xD52AF, 0xD52BD, 0xD52CD, 0xD52DB, 0xD549C, 0xD5801, + 0xD58A4, 0xD5A68, 0xD5A7F, 0xD5C12, 0xD5D71, 0xD5E10, 0xD5E9A, 0xD5F8B, 0xD5FA4, 0xD651A, 0xD6542, 0xD65ED, 0xD661D, + 0xD66D7, 0xD6776, 0xD68BD, 0xD68E5, 0xD6956, 0xD6973, 0xD69A8, 0xD6A51, 0xD6A86, 0xD6B96, 0xD6C3E, 0xD6D4A, 0xD6E9C, + 0xD6F80, 0xD717E, 0xD7190, 0xD71B9, 0xD811D, 0xD8139, 0xD816B, 0xD818A, 0xD819E, 0xD81BE, 0xD829C, 0xD82E1, 0xD8306, + 0xD830E, 0xD835E, 0xD83AB, 0xD83CA, 0xD83F0, 0xD83F8, 0xD844B, 0xD8479, 0xD849E, 0xD84CB, 0xD84EB, 0xD84F3, 0xD854A, + 0xD8573, 0xD859D, 0xD85B4, 0xD85CE, 0xD862A, 0xD8681, 0xD87E3, 0xD87FF, 0xD887B, 0xD88C6, 0xD88E3, 0xD8944, 0xD897B, + 0xD8C97, 0xD8CA4, 0xD8CB3, 0xD8CC2, 0xD8CD1, 0xD8D01, 0xD917B, 0xD918C, 0xD919A, 0xD91B5, 0xD91D0, 0xD91DD, 0xD9220, + 0xD9273, 0xD9284, 0xD9292, 0xD92AD, 0xD92C8, 0xD92D5, 0xD9311, 0xD9322, 0xD9330, 0xD934B, 0xD9366, 0xD9373, 0xD93B6, + 0xD97A6, 0xD97C2, 0xD97DC, 0xD97FB, 0xD9811, 0xD98FF, 0xD996F, 0xD99A8, 0xD99D5, 0xD9A30, 0xD9A4E, 0xD9A6B, 0xD9A88, + 0xD9AF7, 0xD9B1D, 0xD9B43, 0xD9B7C, 0xD9BA9, 0xD9C84, 0xD9C8D, 0xD9CAC, 0xD9CE8, 0xD9CF3, 0xD9CFD, 0xD9D46, 0xDA35E, + 0xDA37E, 0xDA391, 0xDA478, 0xDA4C3, 0xDA4D7, 0xDA4F6, 0xDA515, 0xDA6E2, 0xDA9C2, 0xDA9ED, 0xDAA1B, 0xDAA57, 0xDABAF, + 0xDABC9, 0xDABE2, 0xDAC28, 0xDAC46, 0xDAC63, 0xDACB8, 0xDACEC, 0xDAD08, 0xDAD25, 0xDAD42, 0xDAD5F, 0xDAE17, 0xDAE34, + 0xDAE51, 0xDAF2E, 0xDAF55, 0xDAF6B, 0xDAF81, 0xDB14F, 0xDB16B, 0xDB180, 0xDB195, 0xDB1AA]), + (0xD2, [0xD2B88, 0xD364A, 0xD369F, 0xD3747]), + (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, + 0xD9544, 0xD95A8, 0xD9982, 0xD9B56, 0xDA694, 0xDA6AB, 0xDAE88, 0xDAEC8, 0xDAEE6, 0xDB1BF]), + (0xE6, [0xD210A, 0xD22DC, 0xD2447, 0xD5A4D, 0xD5DDC, 0xDA251, 0xDA26C]), + (0xF0, [0xD945E, 0xD967D, 0xD96C2, 0xD9C95, 0xD9EE6, 0xDA5C6]), + (0xFA, [0xD2047, 0xD24C2, 0xD24EC, 0xD25A4, 0xD51A8, 0xD51E6, 0xD524E, 0xD529E, 0xD6045, 0xD81DE, 0xD821E, 0xD94AA, 0xD9A9E, + 0xD9AE4, 0xDA289]), + (0xFF, [0xD2085, 0xD21C5, 0xD5F28]) ] for volume, addresses in music_volumes: for address in addresses: 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 rom.write_byte(0x180033, {'off': 0x00, 'half': 0x40, 'quarter': 0x80, 'normal': 0x20}[beep]) diff --git a/Rules.py b/Rules.py index 36b044f1..f9f02d3d 100644 --- a/Rules.py +++ b/Rules.py @@ -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('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 - # 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_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')) @@ -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('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 + + # 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: 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('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_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)')) 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)') @@ -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 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 - (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) + (state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32)))) + set_trock_key_rules(world) 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'] + 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 for region in [world.get_region(name) for name in bunny_impassable_caves]: