diff --git a/BaseClasses.py b/BaseClasses.py index 0e3e8af4..b7a80af7 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -114,6 +114,7 @@ class World(object): set_player_attr('compassshuffle', False) set_player_attr('keyshuffle', False) set_player_attr('bigkeyshuffle', False) + set_player_attr('bomblogic', False) set_player_attr('difficulty_requirements', None) set_player_attr('boss_shuffle', 'none') set_player_attr('enemy_shuffle', 'none') @@ -686,8 +687,7 @@ class CollectionState(object): # In the future, this can be used to check if the player starts without bombs def can_use_bombs(self, player): - StartingBombs = True - return StartingBombs or self.has('Bomb Upgrade (+10)', player) + return (not self.world.bomblogic[player] or self.has('Bomb Upgrade (+10)', player)) def can_hit_crystal(self, player): return (self.can_use_bombs(player) @@ -719,6 +719,7 @@ class CollectionState(object): def can_get_good_bee(self, player): cave = self.world.get_region('Good Bee Cave', player) return ( + self.can_use_bombs(player) and self.has_bottle(player) and self.has('Bug Catching Net', player) and (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and @@ -2013,6 +2014,7 @@ class Spoiler(object): 'logic': self.world.logic, 'mode': self.world.mode, 'retro': self.world.retro, + 'bomblogic': self.world.bomblogic, 'weapons': self.world.swords, 'goal': self.world.goal, 'shuffle': self.world.shuffle, @@ -2111,6 +2113,7 @@ class Spoiler(object): outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No')) outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No')) outfile.write(f"Shopsanity: {'Yes' if self.metadata['shopsanity'][player] else 'No'}\n") + outfile.write('Bomblogic: %s\n' % ('Yes' if self.metadata['bomblogic'][player] else 'No')) if self.doors: outfile.write('\n\nDoors:\n\n') outfile.write('\n'.join( diff --git a/CLI.py b/CLI.py index d87e1e2c..10fb211c 100644 --- a/CLI.py +++ b/CLI.py @@ -96,6 +96,7 @@ def parse_cli(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', + 'bomblogic', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', @@ -126,6 +127,7 @@ def parse_settings(): settings = { "lang": "en", "retro": False, + "bomblogic": False, "mode": "open", "logic": "noglitches", "goal": "ganon", diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 1b8ce162..1c1a6ab8 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2373,7 +2373,7 @@ Cave_Exits_Base = [['Elder House Exit (East)', 'Elder House Exit (West)'], ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)'], ['Fairy Ascension Cave Exit (Bottom)', 'Fairy Ascension Cave Exit (Top)'], ['Bumper Cave Exit (Top)', 'Bumper Cave Exit (Bottom)'], - ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']] + ['Hookshot Cave Back Exit', 'Hookshot Cave Front Exit']] Cave_Exits_Base += [('Superbunny Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)'), ('Spiral Cave Exit (Top)', 'Spiral Cave Exit')] @@ -3115,6 +3115,10 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Dark Death Mountain Drop (West)', 'Dark Death Mountain (West Bottom)'), ('East Death Mountain (Top) Mirror Spot', 'East Death Mountain (Top)'), ('Superbunny Cave Climb', 'Superbunny Cave (Top)'), + ('Hookshot Cave Front to Middle', 'Hookshot Cave (Middle)'), + ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), + ('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'), + ('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'), ('Turtle Rock Teleporter', 'Turtle Rock (Top)'), ('Turtle Rock Drop', 'Dark Death Mountain (Top)'), ('Floating Island Drop', 'Dark Death Mountain (Top)'), @@ -3233,6 +3237,10 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Turtle Rock Tail Drop', 'Turtle Rock (Top)'), ('Turtle Rock Drop', 'Dark Death Mountain'), ('Superbunny Cave Climb', 'Superbunny Cave (Top)'), + ('Hookshot Cave Front to Middle', 'Hookshot Cave (Middle)'), + ('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'), + ('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'), + ('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'), ('Desert Ledge Drop', 'Light World'), ('Floating Island Drop', 'Dark Death Mountain'), ('Dark Lake Hylia Central Island Teleporter', 'Lake Hylia Central Island'), @@ -3428,16 +3436,16 @@ default_connections = [('Links House', 'Links House'), ('Dark Desert Hint', 'Dark Desert Hint'), ('Dark Desert Fairy', 'Dark Desert Healer Fairy'), ('Spike Cave', 'Spike Cave'), - ('Hookshot Cave', 'Hookshot Cave'), + ('Hookshot Cave', 'Hookshot Cave (Front)'), ('Superbunny Cave (Top)', 'Superbunny Cave (Top)'), ('Cave Shop (Dark Death Mountain)', 'Cave Shop (Dark Death Mountain)'), ('Dark Death Mountain Fairy', 'Dark Death Mountain Healer Fairy'), ('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'), ('Superbunny Cave Exit (Top)', 'Dark Death Mountain (Top)'), ('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'), - ('Hookshot Cave Exit (South)', 'Dark Death Mountain (Top)'), - ('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'), - ('Hookshot Cave Back Entrance', 'Hookshot Cave'), + ('Hookshot Cave Front Exit', 'Dark Death Mountain (Top)'), + ('Hookshot Cave Back Exit', 'Death Mountain Floating Island (Dark World)'), + ('Hookshot Cave Back Entrance', 'Hookshot Cave (Back)'), ('Mimic Cave', 'Mimic Cave'), ('Pyramid Hole', 'Pyramid'), @@ -3562,13 +3570,13 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Dark Desert Hint', 'Dark Desert Hint'), ('Dark Desert Fairy', 'Dark Desert Healer Fairy'), ('Spike Cave', 'Spike Cave'), - ('Hookshot Cave', 'Hookshot Cave'), + ('Hookshot Cave', 'Hookshot Cave (Front)'), ('Superbunny Cave (Top)', 'Superbunny Cave (Top)'), ('Cave Shop (Dark Death Mountain)', 'Cave Shop (Dark Death Mountain)'), ('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'), ('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'), - ('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'), - ('Hookshot Cave Back Entrance', 'Hookshot Cave'), + ('Hookshot Cave Back Exit', 'Death Mountain Floating Island (Dark World)'), + ('Hookshot Cave Back Entrance', 'Hookshot Cave (Back)'), ('Mimic Cave', 'Mimic Cave'), ('Inverted Pyramid Hole', 'Pyramid'), ('Inverted Links House', 'Inverted Links House'), @@ -3589,7 +3597,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Death Mountain Return Cave (East)', 'Death Mountain Return Cave'), ('Death Mountain Return Cave Exit (West)', 'Death Mountain'), ('Death Mountain Return Cave Exit (East)', 'Death Mountain'), - ('Hookshot Cave Exit (South)', 'Dark Death Mountain'), + ('Hookshot Cave Front Exit', 'Dark Death Mountain'), ('Superbunny Cave Exit (Top)', 'Dark Death Mountain'), ('Pyramid Exit', 'Light World'), ('Inverted Pyramid Entrance', 'Bottom of Pyramid')] @@ -3937,8 +3945,8 @@ exit_ids = {'Links House Exit': (0x01, 0x00), 'Bumper Cave Exit (Bottom)': (0x16, 0x17), 'Superbunny Cave Exit (Top)': (0x14, 0x15), 'Superbunny Cave Exit (Bottom)': (0x13, 0x14), - 'Hookshot Cave Exit (South)': (0x3A, 0x3B), - 'Hookshot Cave Exit (North)': (0x3B, 0x3C), + 'Hookshot Cave Front Exit': (0x3A, 0x3B), + 'Hookshot Cave Back Exit': (0x3B, 0x3C), 'Ganons Tower Exit': (0x37, 0x38), 'Inverted Ganons Tower Exit': (0x37, 0x38), 'Pyramid Exit': (0x36, 0x37), diff --git a/InvertedRegions.py b/InvertedRegions.py index d935ed28..589f6f87 100644 --- a/InvertedRegions.py +++ b/InvertedRegions.py @@ -199,8 +199,11 @@ def create_inverted_regions(world, player): create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']), create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']), create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), - create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], - ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), + create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], + ['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit']), + create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']), + create_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']), + create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance']), create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']), diff --git a/ItemList.py b/ItemList.py index a16219f4..f3853e4f 100644 --- a/ItemList.py +++ b/ItemList.py @@ -37,7 +37,7 @@ Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', - 'retro', + 'retro', 'bomblogic', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', 'progressive_armor_limit', 'progressive_bottle_limit', 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) @@ -61,6 +61,7 @@ difficulties = { timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10, + bomblogic = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 4, progressive_shield_limit = 3, @@ -86,6 +87,7 @@ difficulties = { timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, + bomblogic = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 3, progressive_shield_limit = 2, @@ -111,6 +113,7 @@ difficulties = { timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, + bomblogic = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 2, progressive_shield_limit = 1, @@ -251,10 +254,10 @@ def generate_itempool(world, player): # set up item pool if world.custom: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bomblogic[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999) else: - (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player], world.logic[player]) + (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bomblogic[player], world.doorShuffle[player], world.logic[player]) if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] @@ -284,7 +287,7 @@ def generate_itempool(world, player): if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: if item not in possible_weapons: possible_weapons.append(item) - if item in ['Bombs (10)']: + if not world.bomblogic[player] and item in ['Bombs (10)']: if item not in possible_weapons and world.doorShuffle[player] != 'crossed': possible_weapons.append(item) starting_weapon = random.choice(possible_weapons) @@ -520,6 +523,15 @@ def set_up_shops(world, player): rss.locked = True cap_shop = world.get_region('Capacity Upgrade', player).shop cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro + if world.bomblogic[player]: + if world.shopsanity[player]: + removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player] + for remove in removals: + world.itempool.remove(remove) + world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade + else: + cap_shop = world.get_region('Capacity Upgrade', player).shop + cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bomblogic def customize_shops(world, player): @@ -564,7 +576,7 @@ def customize_shops(world, player): if not found_bomb_upgrade and len(possible_replacements) > 0: choices = [] for shop, idx, loc, item in possible_replacements: - if item.name in ['Bombs (3)', 'Bombs (10)']: + if item.name in ['Bombs (3)', 'Bombs (10)'] and not world.bomblogic[player]: choices.append((shop, idx, loc, item)) if len(choices) > 0: shop, idx, loc, item = random.choice(choices) @@ -709,7 +721,7 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)' 'Rupees (100)': 100, 'Rupees (300)': 300} -def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, door_shuffle, logic): +def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bomblogic, door_shuffle, logic): pool = [] placed_items = {} precollected_items = [] @@ -756,6 +768,11 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, diff = difficulties[difficulty] pool.extend(diff.baseitems) + if bomblogic: + pool = [item.replace('Bomb Upgrade (+5)','Rupees (5)') for item in pool] + pool = [item.replace('Bomb Upgrade (+10)','Rupees (5)') for item in pool] + pool.extend(diff.bomblogic) + # expert+ difficulties produce the same contents for # all bottles, since only one bottle is available if diff.same_bottle: @@ -850,7 +867,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(['Small Key (Universal)']) return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) -def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray): +def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bomblogic, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: customitemarray = customitemarray[1] pool = [] @@ -966,20 +983,21 @@ def test(): for shuffle in ['full', 'insanity_legacy']: for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: for retro in [True, False]: - for door_shuffle in ['basic', 'crossed', 'vanilla']: - out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, door_shuffle, logic) - count = len(out[0]) + len(out[1]) + for bomblogic in [True, False]: + for door_shuffle in ['basic', 'crossed', 'vanilla']: + out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bomblogic, door_shuffle, logic) + count = len(out[0]) + len(out[1]) - correct_count = total_items_to_place - if goal == 'pedestal' and swords != 'vanilla': - # pedestal goals generate one extra item - correct_count += 1 - if retro: - correct_count += 28 - try: - assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro)) - except AssertionError as e: - print(e) + correct_count = total_items_to_place + if goal == 'pedestal' and swords != 'vanilla': + # pedestal goals generate one extra item + correct_count += 1 + if retro: + correct_count += 28 + try: + assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bomblogic)) + except AssertionError as e: + print(e) if __name__ == '__main__': test() diff --git a/Items.py b/Items.py index 808a0740..279cc33d 100644 --- a/Items.py +++ b/Items.py @@ -81,7 +81,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), 'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), 'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), - 'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (+10)': (True, False, None, 0x52, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), 'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), 'Blue Mail': (False, True, None, 0x22, 50, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the blue mail'), 'Red Mail': (False, True, None, 0x23, 100, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the red mail'), diff --git a/Main.py b/Main.py index d8ecc30f..0a8519b6 100644 --- a/Main.py +++ b/Main.py @@ -79,6 +79,7 @@ def main(args, seed=None, fish=None): world.compassshuffle = args.compassshuffle.copy() world.keyshuffle = args.keyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.bomblogic = args.bomblogic.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} world.crystals_ganon_orig = args.crystals_ganon.copy() @@ -382,6 +383,7 @@ def copy_world(world): ret.compassshuffle = world.compassshuffle.copy() ret.keyshuffle = world.keyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy() + ret.bomblogic = world.bomblogic.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() diff --git a/Mystery.py b/Mystery.py index 1ab4a90b..a24217e3 100644 --- a/Mystery.py +++ b/Mystery.py @@ -176,6 +176,8 @@ def roll_settings(weights): ret.retro = True ret.retro = get_choice('retro') == 'on' # this overrides world_state if used + ret.bomblogic = get_choice('bomblogic') == 'on' + ret.hints = get_choice('hints') == 'on' ret.swords = {'randomized': 'random', diff --git a/PotShuffle.py b/PotShuffle.py index d62f52eb..a0c048bf 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -51,7 +51,7 @@ vanilla_pots = { 43: [Pot(16, 5, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 5, PotItem.Switch, 'PoD Sexy Statue'), Pot(16, 6, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 6, PotItem.Bomb, 'PoD Sexy Statue'), Pot(16, 7, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 7, PotItem.Bomb, 'PoD Sexy Statue'), Pot(146, 21, PotItem.Bomb, 'PoD Map Balcony'), Pot(170, 21, PotItem.FiveArrows, 'PoD Map Balcony'), Pot(146, 22, PotItem.Bomb, 'PoD Map Balcony'), Pot(170, 22, PotItem.FiveArrows, 'PoD Map Balcony')], - 44: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave'), Pot(112, 24, PotItem.Heart, 'Hookshot Cave')], + 44: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave (Middle)'), Pot(112, 24, PotItem.Heart, 'Hookshot Cave (Middle)')], 47: [Pot(28, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(32, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(28, 9, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(32, 9, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(172, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(180, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(104, 27, PotItem.Heart, 'Kakariko Well (bottom)'), Pot(104, 28, PotItem.Heart, 'Kakariko Well (bottom)')], 49: [Pot(92, 28, PotItem.Bomb, 'Hera Beetles'), Pot(96, 28, PotItem.Nothing, 'Hera Beetles')], @@ -66,8 +66,8 @@ vanilla_pots = { 55: [Pot(60, 6, PotItem.Key, 'Swamp Trench 1 Alcove'), Pot(48, 20, PotItem.Nothing, 'Swamp Trench 1 Key Ledge')], 56: [Pot(164, 12, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 13, PotItem.FiveRupees, 'Swamp Pot Row'), Pot(164, 18, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 19, PotItem.Key, 'Swamp Pot Row')], 57: [Pot(12, 20, PotItem.Heart, 'Skull Spike Corner'), Pot(48, 28, PotItem.FiveArrows, 'Skull Spike Corner'), Pot(100, 22, PotItem.SmallMagic, 'Skull Final Drop'), Pot(100, 26, PotItem.FiveArrows, 'Skull Final Drop')], - 60: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave'), Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave'), Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave'), Pot(20, 19, PotItem.Nothing, 'Hookshot Cave'), - Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave'), Pot(96, 19, PotItem.Heart, 'Hookshot Cave'), Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave'), Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave')], + 60: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave (Front)'), Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave (Front)'), Pot(20, 19, PotItem.Nothing, 'Hookshot Cave (Front)'), + Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(96, 19, PotItem.Heart, 'Hookshot Cave (Front)'), Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave (Front)')], 61: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle'), Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle'), Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle')], 62: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint'), Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint'), Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint'), Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint')], diff --git a/Regions.py b/Regions.py index b2af454a..35a7eda3 100644 --- a/Regions.py +++ b/Regions.py @@ -190,8 +190,11 @@ def create_regions(world, player): create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']), create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']), create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']), - create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], - ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), + create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], + ['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit']), + create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']), + create_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']), + create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']), create_lw_region(player, 'Death Mountain Floating Island (Light World)', ['Floating Island']), create_dw_region(player, 'Turtle Rock (Top)', None, ['Turtle Rock Drop']), diff --git a/Rom.py b/Rom.py index dd4e287e..0581bf71 100644 --- a/Rom.py +++ b/Rom.py @@ -1049,7 +1049,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x184000, [ # original_item, limit, replacement_item, filler 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees - 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade + 0x51, 0x00 if world.bomblogic[player] else 0x06, 0x31 if world.bomblogic[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bomblogic -> turns into Bombs (10) 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade 0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 @@ -1186,7 +1186,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): equip[0x36C] = 0x18 equip[0x36D] = 0x18 equip[0x379] = 0x68 - starting_max_bombs = 10 + if world.bomblogic[player]: + starting_max_bombs = 0 + else: + starting_max_bombs = 10 starting_max_arrows = 30 startingstate = CollectionState(world) @@ -1478,7 +1481,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows) bow_max, bow_small = 70, 10 - elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']: + elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)']: rom.write_byte(0x18004E, 2) # Escape Fill (bombs) rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows) diff --git a/Rules.py b/Rules.py index c9dc03f9..15847491 100644 --- a/Rules.py +++ b/Rules.py @@ -561,7 +561,8 @@ def global_rules(world, player): def bomb_rules(world, player): bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended. bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Mini Moldorm Cave', - 'Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)', 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery'] + 'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front','Hookshot Cave Middle to Back', + 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery'] for entrance in bonkable_doors: add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player)) for entrance in bombable_doors: @@ -576,9 +577,10 @@ def bomb_rules(world, player): for location in bombable_items: add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player)) - cave_kill_locations = ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy'] + cave_kill_locations = ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Spiral Cave'] for location in cave_kill_locations: add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player) or state.can_use_bombs(player)) + add_rule(world.get_entrance('Spiral Cave (top to bottom)', player), lambda state: state.can_kill_most_things(player) or state.can_use_bombs(player)) paradox_switch_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle'] for location in paradox_switch_chests: @@ -1160,7 +1162,7 @@ def standard_rules(world, player): def bomb_escape_rule(): loc = world.get_location("Link's Uncle", player) - return loc.item and loc.item.name == 'Bombs (10)' + return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)'] def standard_escape_rule(state): return state.can_kill_most_things(player) or bomb_escape_rule() @@ -1671,7 +1673,7 @@ def set_bunny_rules(world, player, inverted): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. - bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', + bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave (Middle)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 47bb3987..817e2607 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -220,6 +220,10 @@ "type": "bool", "help": "suppress" }, + "bomblogic": { + "action": "store_true", + "type": "bool" + }, "retro": { "action": "store_true", "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index da95965c..077f3d0d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -264,6 +264,7 @@ "and a few other little things make this more like Zelda-1. (default: %(default)s)" ], "pseudoboots": [ " Players starts with pseudo boots that allow dashing but no item checks (default: %(default)s"], + "bomblogic": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], "usestartinventory": [ "Toggle usage of Starting Inventory." ], "custom": [ "Not supported." ], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 0d9e3836..2d8bf3f8 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -190,6 +190,7 @@ "randomizer.item.hints": "Include Helpful Hints", "randomizer.item.retro": "Retro mode (universal keys)", "randomizer.item.pseudoboots": "Start with Pseudo Boots", + "randomizer.item.bomblogic": "Bomblogic", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index a6f10a14..15d049c8 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -1,6 +1,7 @@ { "checkboxes": { "retro": { "type": "checkbox" }, + "bomblogic": { "type": "checkbox" }, "shopsanity": { "type": "checkbox" }, "hints": { "type": "checkbox" diff --git a/source/classes/constants.py b/source/classes/constants.py index 04cbde2e..7c76913f 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -57,6 +57,7 @@ SETTINGSTOPROCESS = { "item": { "hints": "hints", "retro": "retro", + "bomblogic": "bomblogic", "shopsanity": "shopsanity", "pseudoboots": "pseudoboots", "worldstate": "mode",