diff --git a/BaseClasses.py b/BaseClasses.py index 25cee608..8ff91295 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -895,7 +895,7 @@ class CollectionState(object): 'Golden Sword', 'Progressive Sword', 'Progressive Glove', 'Silver Arrows', 'Green Pendant', 'Blue Pendant', 'Red Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7', 'Blue Boomerang', 'Red Boomerang', 'Blue Shield', 'Red Shield', - 'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna', + 'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna', 'Ocarina (Activated)', 'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)', 'Magic Upgrade (1/4)'] or item_name.startswith(('Bottle', 'Small Key', 'Big Key')) @@ -1129,8 +1129,11 @@ class CollectionState(object): return self.has('Fire Rod', player) or self.has('Lamp', player) def can_flute(self, player): + if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)): + return True lw = self.world.get_region('Light World', player) - return self.has('Ocarina', player) and lw.can_reach(self) and self.is_not_bunny(lw, player) + return self.has('Ocarina (Activated)', player) or (self.has('Ocarina', player) and lw.can_reach(self) + and self.is_not_bunny(lw, player)) def can_melt_things(self, player): return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.has_sword(player)) @@ -2371,6 +2374,7 @@ class Spoiler(object): 'retro': self.world.retro, 'bombbag': self.world.bombbag, 'weapons': self.world.swords, + 'flute_mode': self.world.flute_mode, 'goal': self.world.goal, 'shuffle': self.world.shuffle, 'shuffleganon': self.world.shuffle_ganon, @@ -2569,6 +2573,7 @@ class Spoiler(object): outfile.write(f"Restricted Boss Items: {self.metadata['restricted_boss_items'][player]}\n") outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) + outfile.write(f"Flute Mode: {self.metadata['flute_mode'][player]}\n") outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n") outfile.write(f"Bombbag: {yn(self.metadata['bombbag'][player])}\n") outfile.write(f"Pseudoboots: {yn(self.metadata['pseudoboots'][player])}\n") @@ -2873,6 +2878,8 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique # byte 10: settings_version +# byte 11: F???, ???? (flute_mode) +flute_mode = {'normal': 0, 'active': 1} # additions # psuedoboots does not effect code @@ -2917,7 +2924,9 @@ class Settings(object): (rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]), - settings_version]) + settings_version, + + flute_mode[w.flute_mode[p]] << 7]) return base64.b64encode(code, "+-".encode()).decode() @staticmethod @@ -2979,6 +2988,8 @@ class Settings(object): args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0xC0) >> 6] args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3] args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)] + if len(settings) > 11: + args.flute_mode[p] = r(flute_mode)[(settings[11] & 0x80) >> 7] class KeyRuleType(FastEnum): diff --git a/CLI.py b/CLI.py index 987652fd..44c7b76c 100644 --- a/CLI.py +++ b/CLI.py @@ -115,6 +115,7 @@ def parse_cli(argv, no_defaults=False): playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', + 'flute_mode', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items', @@ -156,6 +157,7 @@ def parse_settings(): "crystals_gt": "7", "crystals_ganon": "7", "swords": "random", + 'flute_mode': 'normal', "difficulty": "normal", "item_functionality": "normal", "timer": "none", diff --git a/DoorShuffle.py b/DoorShuffle.py index 3ced8a44..89463230 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -834,7 +834,7 @@ def main_dungeon_pool(dungeon_pool, world, player): for name in pool: builder = world.dungeon_layouts[player][name] region_set = builder.master_sector.region_set() - builder.bk_required = len(builder.bk_door_proposal) > 0 or any(x in region_set for x in special_bk_regions) + builder.bk_required = builder.bk_door_proposal or any(x in region_set for x in special_bk_regions) dungeon = world.get_dungeon(name, player) if not builder.bk_required or builder.bk_provided: dungeon.big_key = None @@ -1793,7 +1793,7 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player) remaining -= len(custom_trap_doors[dungeon]) ttl += len(builder.candidates.trap) if ttl == 0: - return used_doors + continue for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] proportion = len(builder.candidates.trap) @@ -1853,7 +1853,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world, remaining -= len(custom_bk_doors[dungeon]) ttl += len(builder.candidates.big) if ttl == 0: - return used_doors + continue for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] proportion = len(builder.candidates.big) @@ -2004,7 +2004,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, worl remaining_dash -= len(custom_dash_doors[dungeon]) ttl += len(builder.candidates.bomb_dash) if ttl == 0: - return used_doors + continue for dungeon in pool: builder = world.dungeon_layouts[player][dungeon] proportion = len(builder.candidates.bomb_dash) @@ -2508,8 +2508,9 @@ def find_small_key_door_candidates(builder, start_regions, used, world, player): def calc_used_dungeon_items(builder, world, player): - base = max(count_reserved_locations(world, player, builder.location_set), 2) basic_flag = world.doorShuffle[player] == 'basic' + base = 0 if basic_flag else 2 # at least 2 items per dungeon, except in basic + base = max(count_reserved_locations(world, player, builder.location_set), base) if not world.bigkeyshuffle[player]: if builder.bk_required and not builder.bk_provided: base += 1 diff --git a/InitialSram.py b/InitialSram.py index 772e1d46..63c822cd 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -144,6 +144,7 @@ class InitialSram: 'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)} set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04), 'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01), + 'Ocarina (Activated)': (0x34C, 3, 0x38C, 0x01), 'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10), 'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)} keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F], diff --git a/ItemList.py b/ItemList.py index f2500e07..1dffb859 100644 --- a/ItemList.py +++ b/ItemList.py @@ -267,7 +267,7 @@ def generate_itempool(world, player): (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.bombbag[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.bombbag[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.bombbag[player], world.doorShuffle[player], world.logic[player], world.flute_mode[player] == 'active') if player in world.pool_adjustment.keys() and not skip_pool_adjustments: amt = world.pool_adjustment[player] @@ -789,7 +789,8 @@ def add_pot_contents(world, player): world.itempool.append(ItemFactory(item, player)) -def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic): +def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, + door_shuffle, logic, flute_activated): pool = [] placed_items = {} precollected_items = [] @@ -802,6 +803,10 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, pool.extend(alwaysitems) + if flute_activated: + pool.remove('Ocarina') + pool.append('Ocarina (Activated)') + def place_item(loc, item): assert loc not in placed_items placed_items[loc] = item diff --git a/Items.py b/Items.py index ef88991f..da9d7385 100644 --- a/Items.py +++ b/Items.py @@ -30,6 +30,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'), 'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the mirror'), 'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the flute'), + 'Ocarina (Activated)': (True, False, None, 0x4A, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the flute'), 'Pegasus Boots': (True, False, None, 0x4B, 250, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the boots'), 'Power Glove': (True, False, None, 0x1B, 100, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'), 'Cape': (True, False, None, 0x19, 50, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the cape'), diff --git a/Main.py b/Main.py index e63a5ec7..1c6cc442 100644 --- a/Main.py +++ b/Main.py @@ -95,6 +95,7 @@ def main(args, seed=None, fish=None): world.keyshuffle = args.keyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy() world.bombbag = args.bombbag.copy() + world.flute_mode = args.flute_mode.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() @@ -158,7 +159,9 @@ def main(args, seed=None, fish=None): if args.usestartinventory[player]: for tok in filter(None, args.startinventory[player].split(',')): - item = ItemFactory(tok.strip(), player) + name = tok.strip() + name = name if name != 'Ocarina' or world.flute_mode[player] != 'active' else 'Ocarina (Activated)' + item = ItemFactory(name, player) if item: world.push_precollected(item) @@ -451,6 +454,7 @@ def copy_world(world): ret.keyshuffle = world.keyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy() ret.bombbag = world.bombbag.copy() + ret.flute_mode = world.flute_mode.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/Rom.py b/Rom.py index ef640da5..458e24df 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '7b877dcee4ece38713768b74acb333a6' +RANDOMIZERBASEHASH = '0be31dc5cb338e7e85d1ce65e839c99e' class JsonRom(object): @@ -2161,8 +2161,8 @@ def write_strings(rom, world, player, team): while hint_count > 0 and len(items_to_hint) > 0: this_item = items_to_hint.pop(0) this_location = world.find_items_not_key_only(this_item, player) - random.shuffle(this_location) if this_location: + random.shuffle(this_location) item_name = this_location[0].item.hint_text item_name = item_name[0].upper() + item_name[1:] this_hint = f'{item_name} can be found {hint_text(this_location[0])}.' @@ -2847,6 +2847,7 @@ RelevantItems = ['Bow', 'Hookshot', 'Magic Mirror', 'Ocarina', + 'Ocarina (Activated)', 'Pegasus Boots', 'Power Glove', 'Cape', diff --git a/Rules.py b/Rules.py index d12e7341..9d3dad12 100644 --- a/Rules.py +++ b/Rules.py @@ -786,9 +786,9 @@ def default_rules(world, player): set_rule(world.get_entrance('50 Rupee Cave', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Death Mountain Entrance Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Bumper Cave Entrance Mirror Spot', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.has('Ocarina', player)) + set_rule(world.get_entrance('Flute Spot 1', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Lake Hylia Central Island Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.has('Ocarina', player) and state.can_lift_heavy_rocks(player)) + set_rule(world.get_entrance('Dark Desert Teleporter', player), lambda state: state.can_flute(player) and state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: ((state.has('Hammer', player) and state.can_lift_rocks(player)) or state.can_lift_heavy_rocks(player)) and state.has_Pearl(player)) # bunny cannot lift bushes @@ -1514,7 +1514,7 @@ def set_big_bomb_rules(world, player): #2. Mirror and Flute and basic routes (can make difference if accessed via insanity or w/ mirror from connector, and then via hyrule castle gate, because no gloves are needed in that case) #3. Go to south DW and then cross peg bridge: Need Mitts and hammer and moon pearl # -> (Mitts and CPB) or (((G or Flute) and M) and BR)) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.has('Ocarina', player)) and state.has_Mirror(player)) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and cross_peg_bridge(state)) or (((state.can_lift_rocks(player) or state.can_flute(player)) and state.has_Mirror(player)) and basic_routes(state))) elif bombshop_entrance.name in Southern_DW_entrances: #1. Mirror and enter via gate: Need mirror and Aga1 #2. cross peg bridge: Need hammer and moon pearl @@ -1523,52 +1523,52 @@ def set_big_bomb_rules(world, player): elif bombshop_entrance.name in Isolated_DW_entrances: # 1. mirror then flute then basic routes # -> M and Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.has('Ocarina', player) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) and state.can_flute(player) and basic_routes(state)) elif bombshop_entrance.name in Isolated_LW_entrances: # 1. flute then basic routes # Prexisting mirror spot is not permitted, because mirror might have been needed to reach these isolated locations. # -> Flute and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and basic_routes(state)) elif bombshop_entrance.name in West_LW_DM_entrances: # 1. flute then basic routes or mirror # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly. # -> Flute and (M or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and (state.has_Mirror(player) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and (state.has_Mirror(player) or basic_routes(state))) elif bombshop_entrance.name in East_LW_DM_entrances: # 1. flute then basic routes or mirror and hookshot # Prexisting mirror spot is permitted, because flute can be used to reach west DM directly and then east DM via Hookshot # -> Flute and ((M and Hookshot) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player)) or basic_routes(state))) elif bombshop_entrance.name == 'Fairy Ascension Cave (Bottom)': # Same as East_LW_DM_entrances except navigation without BR requires Mitts # -> Flute and ((M and Hookshot and Mitts) or BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has('Ocarina', player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_flute(player) and ((state.has_Mirror(player) and state.has('Hookshot', player) and state.can_lift_heavy_rocks(player)) or basic_routes(state))) elif bombshop_entrance.name in Castle_ledge_entrances: # 1. mirror on pyramid to castle ledge, grab bomb, return through mirror spot: Needs mirror # 2. flute then basic routes # -> M or (Flute and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.has('Ocarina', player) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.has_Mirror(player) or (state.can_flute(player) and basic_routes(state))) elif bombshop_entrance.name in Desert_mirrorable_ledge_entrances: # Cases when you have mire access: Mirror to reach locations, return via mirror spot, move to center of desert, mirror anagin and: # 1. Have mire access, Mirror to reach locations, return via mirror spot, move to center of desert, mirror again and then basic routes # 2. flute then basic routes # -> (Mire access and M) or Flute) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.has('Ocarina', player)) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: ((state.can_reach('Dark Desert', 'Region', player) and state.has_Mirror(player)) or state.can_flute(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Old Man Cave (West)': # 1. Lift rock then basic_routes # 2. flute then basic_routes # -> (Flute or G) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Ocarina', player) or state.can_lift_rocks(player)) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or state.can_lift_rocks(player)) and basic_routes(state)) elif bombshop_entrance.name == 'Graveyard Cave': # 1. flute then basic routes # 2. (has west dark world access) use existing mirror spot (required Pearl), mirror again off ledge # -> (Flute or (M and P and West Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Ocarina', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name in Mirror_from_SDW_entrances: # 1. flute then basic routes # 2. (has South dark world access) use existing mirror spot, mirror again off ledge # -> (Flute or (M and South Dark World access) and BR - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Ocarina', player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_flute(player) or (state.can_reach('South Dark World', 'Region', player) and state.has_Mirror(player))) and basic_routes(state)) elif bombshop_entrance.name == 'Dark World Potion Shop': # 1. walk down by lifting rock: needs gloves and pearl` # 2. walk down by hammering peg: needs hammer and pearl @@ -1580,11 +1580,11 @@ def set_big_bomb_rules(world, player): # (because otherwise mirror was used to reach the grave, so would cancel a pre-existing mirror spot) # to account for insanity, must consider a way to escape without a cave for basic_routes # -> (M and Mitts) or ((Mitts or Flute or (M and P and West Dark World access)) and BR) - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.has('Ocarina', player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.can_lift_heavy_rocks(player) and state.has_Mirror(player)) or ((state.can_lift_heavy_rocks(player) or state.can_flute(player) or (state.can_reach('West Dark World', 'Region', player) and state.has_Pearl(player) and state.has_Mirror(player))) and basic_routes(state))) elif bombshop_entrance.name == 'Waterfall of Wishing': # same as the Normal_LW_entrances case except in insanity it's possible you could be here without Flippers which # means you need an escape route of either Flippers or Flute - add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.has('Ocarina', player)) and (basic_routes(state) or state.has_Mirror(player))) + add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: (state.has('Flippers', player) or state.can_flute(player)) and (basic_routes(state) or state.has_Mirror(player))) def set_inverted_big_bomb_rules(world, player): diff --git a/data/base2current.bps b/data/base2current.bps index 2137836d..9740c94d 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/mystery_example.yml b/mystery_example.yml index 34ce9963..78b47a95 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -45,6 +45,9 @@ bombbag: on: 1 off: 4 + flute_mode: + normal: 3 + active: 1 entrance_shuffle: none: 15 dungeonssimple: 3 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 6e8d0310..d25f542b 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -42,6 +42,12 @@ "vanilla" ] }, + "flute_mode": { + "choices": [ + "normal", + "active" + ] + }, "goal": { "choices": [ "ganon", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 1bfd124a..eab0e34d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -121,6 +121,11 @@ "Hard: Reduced functionality.", "Expert: Greatly reduced functionality." ], + "flute_mode": [ + "Determine if you need to wake up the bird or not on flute pickup (default: %(default)s)", + "Normal: Normal functionality.", + "Active: Flute is activated on pickup." + ], "timer": [ "Select game timer setting. Affects available itempool. (default: %(default)s)", "None: No timer.", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index ef9ffd13..5a6e0a9f 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -285,10 +285,9 @@ "randomizer.item.shopsanity": "Shopsanity", - "randomizer.item.itemfunction": "Item Functionality", - "randomizer.item.itemfunction.normal": "Normal", - "randomizer.item.itemfunction.hard": "Hard", - "randomizer.item.itemfunction.expert": "Expert", + "randomizer.item.flute_mode": "Flute Mode", + "randomizer.item.flute_mode.normal": "Normal", + "randomizer.item.flute_mode.active": "Pre-Activated", "randomizer.item.timer": "Timer Setting", "randomizer.item.timer.none": "No Timer", @@ -298,11 +297,6 @@ "randomizer.item.timer.ohko": "OHKO", "randomizer.item.timer.timed-countdown": "Timed Countdown", - "randomizer.item.progressives": "Progressive Items", - "randomizer.item.progressives.on": "On", - "randomizer.item.progressives.off": "Off", - "randomizer.item.progressives.random": "Random", - "randomizer.item.accessibility": "Accessibility", "randomizer.item.accessibility.items": "100% Inventory", "randomizer.item.accessibility.locations": "100% Locations", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 76537817..b55d6117 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -78,12 +78,11 @@ "expert" ] }, - "itemfunction": { + "flute_mode": { "type": "selectbox", "options": [ "normal", - "hard", - "expert" + "active" ] }, "timer": { @@ -97,14 +96,6 @@ "timed-countdown" ] }, - "progressives": { - "type": "selectbox", - "options": [ - "on", - "off", - "random" - ] - }, "accessibility": { "type": "selectbox", "options": [ diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 64ef8b3d..82cf0ac9 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -68,6 +68,7 @@ class CustomSettings(object): args.logic[p] = get_setting(settings['logic'], args.logic[p]) args.mode[p] = get_setting(settings['mode'], args.mode[p]) args.swords[p] = get_setting(settings['swords'], args.swords[p]) + args.flute_mode[p] = get_setting(settings['flute_mode'], args.flute_mode[p]) args.item_functionality[p] = get_setting(settings['item_functionality'], args.item_functionality[p]) args.goal[p] = get_setting(settings['goal'], args.goal[p]) args.difficulty[p] = get_setting(settings['difficulty'], args.difficulty[p]) @@ -189,6 +190,7 @@ class CustomSettings(object): settings_dict[p]['logic'] = world.logic[p] settings_dict[p]['mode'] = world.mode[p] settings_dict[p]['swords'] = world.swords[p] + settings_dict[p]['flute_mode'] = world.flute_mode[p] settings_dict[p]['difficulty'] = world.difficulty[p] settings_dict[p]['goal'] = world.goal[p] settings_dict[p]['accessibility'] = world.accessibility[p] diff --git a/source/classes/constants.py b/source/classes/constants.py index 2dccc1fe..3fb8da88 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -67,9 +67,8 @@ SETTINGSTOPROCESS = { "crystals_ganon": "crystals_ganon", "weapons": "swords", "itempool": "difficulty", - "itemfunction": "item_functionality", + "flute_mode": "flute_mode", "timer": "timer", - "progressives": "progressive", "accessibility": "accessibility", "sortingalgo": "algorithm", "beemizer": "beemizer", diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 9614eeef..6f60b72e 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -454,6 +454,7 @@ vanilla_mapping = { 'Hookshot': ['Swamp Palace - Big Chest'], 'Magic Mirror': ['Old Man'], 'Ocarina': ['Flute Spot'], + 'Ocarina (Activated)': ['Flute Spot'], 'Pegasus Boots': ['Sahasrahla'], 'Power Glove': ['Desert Palace - Big Chest'], 'Cape': ["King's Tomb"], @@ -779,7 +780,7 @@ major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod 'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang', 'Progressive Glove', 'Power Glove', 'Titans Mitts', 'Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Magic Mirror', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)', 'Magic Upgrade (1/2)', - 'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', + 'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', 'Ocarina (Activated)', 'Mirror Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail', 'Progressive Sword', 'Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl', 'Progressive Bow', 'Progressive Bow (Alt)'} diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index 5ca41b6d..ae628b7c 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -144,6 +144,7 @@ def roll_settings(weights): }[swords] ret.difficulty = get_choice('item_pool') + ret.flute_mode = get_choice_default('flute_mode', default='normal') ret.item_functionality = get_choice('item_functionality')