diff --git a/Adjuster.py b/Adjuster.py index 9e003b3f..68ac73ad 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -37,6 +37,8 @@ def main(): parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true') parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true') + parser.add_argument('--shuffle_sfxinstruments', help='Shuffles sound instruments', action='store_true') + parser.add_argument('--shuffle_songinstruments', help='Shuffles song instruments', action='store_true') parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in diff --git a/AdjusterMain.py b/AdjusterMain.py index 72b0a1c5..a1be13c8 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -33,7 +33,7 @@ def adjust(args): apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, - args.msu_resume) + args.shuffle_sfxinstruments, args.shuffle_songinstruments, args.msu_resume) # output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) @@ -68,7 +68,7 @@ def patch(args): apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx, - args.msu_resume) + args.shuffle_sfxinstruments, args.shuffle_songinstruments, args.msu_resume) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfile_base)) diff --git a/BaseClasses.py b/BaseClasses.py index 78a1f2de..55ba0045 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -64,7 +64,6 @@ class World(object): self.dark_world_light_cone = False self.clock_mode = 'none' self.rupoor_cost = 10 - self.aga_randomness = True self.lock_aga_door_in_escape = False self.save_and_quit_from_boss = True self.override_bomb_check = False @@ -87,6 +86,7 @@ class World(object): self.owedges = [] self._owedge_cache = {} self.owswaps = {} + self.owcrossededges = {} self.owwhirlpools = {} self.owflutespots = {} self.owsectors = {} @@ -114,6 +114,7 @@ class World(object): set_player_attr('_region_cache', {}) set_player_attr('player_names', []) set_player_attr('owswaps', [[],[],[]]) + set_player_attr('owcrossededges', []) set_player_attr('owwhirlpools', []) set_player_attr('owsectors', None) set_player_attr('remote_items', False) @@ -132,7 +133,7 @@ class World(object): set_player_attr('can_access_trock_front', None) set_player_attr('can_access_trock_big_chest', None) set_player_attr('can_access_trock_middle', None) - set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] + set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'hybridglitches', 'nologic'] or shuffle[player] in ['lean', 'swapped', 'crossed', 'insanity']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) @@ -170,7 +171,8 @@ class World(object): set_player_attr('door_self_loops', False) set_player_attr('door_type_mode', 'original') set_player_attr('trap_door_mode', 'optional') - set_player_attr('key_logic_algorithm', 'default') + set_player_attr('key_logic_algorithm', 'partial') + set_player_attr('aga_randomness', True) set_player_attr('shopsanity', False) set_player_attr('mixed_travel', 'prevent') @@ -240,29 +242,16 @@ class World(object): raise RuntimeError('No such region %s for player %d' % (regionname, player)) def get_owedge(self, edgename, player): - if isinstance(edgename, OWEdge): - return edgename - try: - return self._owedge_cache[(edgename, player)] - except KeyError: - for edge in self.owedges: - if edge.name == edgename and edge.player == player: - self._owedge_cache[(edgename, player)] = edge - return edge + edge = self.check_for_owedge(edgename, player) + if edge is None: raise RuntimeError('No such edge %s for player %d' % (edgename, player)) + return edge def get_entrance(self, entrance, player): - if isinstance(entrance, Entrance): - return entrance - try: - return self._entrance_cache[(entrance, player)] - except KeyError: - for region in self.regions: - for exit in region.exits: - if exit.name == entrance and exit.player == player: - self._entrance_cache[(entrance, player)] = exit - return exit + ent = self.check_for_entrance(entrance, player) + if ent is None: raise RuntimeError('No such entrance %s for player %d' % (entrance, player)) + return ent def remove_entrance(self, entrance, player): if (entrance, player) in self._entrance_cache.keys(): @@ -296,16 +285,10 @@ class World(object): raise RuntimeError('No such dungeon %s for player %d' % (dungeonname, player)) def get_door(self, doorname, player): - if isinstance(doorname, Door): - return doorname - try: - return self._door_cache[(doorname, player)] - except KeyError: - for door in self.doors: - if door.name == doorname and door.player == player: - self._door_cache[(doorname, player)] = door - return door + door = self.check_for_door(doorname, player) + if door is None: raise RuntimeError('No such door %s for player %d' % (doorname, player)) + return door def get_portal(self, portal_name, player): if isinstance(portal_name, Portal): @@ -319,18 +302,6 @@ class World(object): return portal raise RuntimeError('No such portal %s for player %d' % (portal_name, player)) - def check_for_owedge(self, edgename, player): - if isinstance(edgename, OWEdge): - return edgename - try: - return self._owedge_cache[(edgename, player)] - except KeyError: - for edge in self.owedges: - if edge.name == edgename and edge.player == player: - self._owedge_cache[(edgename, player)] = edge - return edge - return None - def is_tile_swapped(self, owid, player): return (self.mode[player] == 'inverted') != (owid in self.owswaps[player][0] and self.owMixed[player]) @@ -341,7 +312,7 @@ class World(object): return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player) def is_bombshop_start(self, player): - return self.is_tile_swapped(0x2c, player) and (self.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not self.shufflelinks[player]) + return self.is_tile_swapped(0x2c, player) def is_pyramid_open(self, player): if self.open_pyramid[player] == 'yes': @@ -349,13 +320,35 @@ class World(object): elif self.open_pyramid[player] == 'no': return False else: - if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']: return False elif self.goal[player] in ['crystals', 'trinity', 'z1', 'ganonhunt']: return True else: return False + def check_for_owedge(self, edgename, player): + if isinstance(edgename, OWEdge): + return edgename + try: + if edgename[-1] == '*': + edgename = edgename[:-1] + edge = self.check_for_owedge(edgename, player) + if self.is_tile_swapped(edge.owIndex, player): + from OverworldShuffle import parallel_links + if edgename in parallel_links.keys() or edgename in parallel_links.inverse.keys(): + edgename = parallel_links[edgename] if edgename in parallel_links.keys() else parallel_links.inverse[edgename][0] + return self.check_for_owedge(edgename, player) + else: + raise Exception("Edge notated with * doesn't have a parallel edge: %s" & edgename) + return self._owedge_cache[(edgename, player)] + except KeyError: + for edge in self.owedges: + if edge.name == edgename and edge.player == player: + self._owedge_cache[(edgename, player)] = edge + return edge + return None + def check_for_door(self, doorname, player): if isinstance(doorname, Door): return doorname @@ -618,7 +611,7 @@ class World(object): if not sphere: # ran out of places and did not finish yet, quit if log_error: - missing_locations = ", ".join([x.name for x in prog_locations]) + missing_locations = ", ".join([f'{x.name} (#{x.player})' for x in prog_locations]) logging.getLogger('').error(f'Cannot reach the following locations: {missing_locations}') return False @@ -653,9 +646,45 @@ class CollectionState(object): self.opened_doors = {player: set() for player in range(1, parent.players + 1)} self.dungeons_to_check = {player: defaultdict(dict) for player in range(1, parent.players + 1)} self.dungeon_limits = None - self.placing_item = None + self.placing_items = None # self.trace = None + def can_reach_from(self, spot, start, player=None): + old_state = self.copy() + # old_state.path = {old_state.world.get_region(start, player)} + old_state.stale[player] = False + old_state.reachable_regions[player] = dict() + old_state.blocked_connections[player] = dict() + rrp = old_state.reachable_regions[player] + bc = old_state.blocked_connections[player] + + # init on first call - this can't be done on construction since the regions don't exist yet + start = self.world.get_region(start, player) + if start in self.reachable_regions[player]: + rrp[start] = self.reachable_regions[player][start] + for conn in start.exits: + bc[conn] = self.blocked_connections[player][conn] + else: + rrp[start] = CrystalBarrier.Orange + for conn in start.exits: + bc[conn] = CrystalBarrier.Orange + + queue = deque(old_state.blocked_connections[player].items()) + + old_state.traverse_world(queue, rrp, bc, player) + if old_state.world.key_logic_algorithm[player] == 'default': + unresolved_events = [x for y in old_state.reachable_regions[player] for x in y.locations + if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) + and x not in old_state.locations_checked and x.can_reach(old_state)] + unresolved_events = old_state._do_not_flood_the_keys(unresolved_events) + if len(unresolved_events) == 0: + old_state.check_key_doors_in_dungeons(rrp, player) + + if self.world.get_region(spot, player) in rrp: + return True + else: + return False + def update_reachable_regions(self, player): self.stale[player] = False rrp = self.reachable_regions[player] @@ -931,7 +960,7 @@ class CollectionState(object): return door_candidates door_candidates, skip = [], set() if (state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name != 'Universal' - and state.placing_item and state.placing_item.name == small_key_name): + and state.placing_items and any(i.name == small_key_name and i.player == player for i in state.placing_items)): key_logic = state.world.key_logic[player][dungeon_name] for door, paired in key_logic.sm_doors.items(): if door.name in key_logic.door_rules: @@ -976,7 +1005,7 @@ class CollectionState(object): player: defaultdict(dict, {name: copy.copy(checklist) for name, checklist in self.dungeons_to_check[player].items()}) for player in range(1, self.world.players + 1)} - ret.placing_item = self.placing_item + ret.placing_items = self.placing_items return ret def apply_dungeon_exploration(self, rrp, player, dungeon_name, checklist): @@ -1074,7 +1103,7 @@ class CollectionState(object): # try to resolve a name if resolution_hint == 'Location': spot = self.world.get_location(spot, player) - elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'Ledge', 'Portal', 'Whirlpool', 'Mirror', 'Flute']: + elif resolution_hint in ['Entrance', 'OWEdge', 'OWTerrain', 'OpenTerrain', 'Ledge', 'OWG', 'Portal', 'Whirlpool', 'Mirror', 'Flute']: spot = self.world.get_entrance(spot, player) else: # default to Region @@ -1222,6 +1251,12 @@ class CollectionState(object): def can_lift_rocks(self, player): return self.has('Power Glove', player) or self.has('Titans Mitts', player) + + def can_bomb_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) and self.can_use_bombs(player) + + def can_dash_clip(self, region, player: int) -> bool: + return self.is_not_bunny(region, player) and self.has('Pegasus Boots', player) def has_bottle(self, player): return self.bottle_count(player) > 0 @@ -1463,7 +1498,9 @@ class CollectionState(object): return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.can_use_medallions(player)) def can_avoid_lasers(self, player): - return self.has('Mirror Shield', player) or self.has('Cane of Byrna', player) or self.has('Cape', player) + return (self.has('Mirror Shield', player) or + self.has('Cape', player) or + (self.has('Cane of Byrna', player) and self.world.difficulty_adjustments[player] not in ['hard', 'expert'])) def is_not_bunny(self, region, player): return self.has_Pearl(player) or not region.can_cause_bunny(player) @@ -1508,6 +1545,9 @@ class CollectionState(object): def can_superbunny_mirror_with_sword(self, player): return self.has_Mirror(player) and self.has_sword(player) + + def can_bunny_pocket(self, player): + return self.has_Boots(player) and (self.has_Mirror(player) or self.has_bottle(player)) def collect(self, item, event=False, location=None): if location: @@ -1826,6 +1866,9 @@ class Region(object): return self.is_dark_world if self.world.mode[player] != 'inverted' else self.is_light_world + def is_outdoors(self): + return self.type in {RegionType.LightWorld, RegionType.DarkWorld} + def __str__(self): return str(self.__unicode__()) @@ -1897,8 +1940,8 @@ class Entrance(object): if region not in explored_regions: explored_regions[region] = path for exit in region.exits: - if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \ - and exit.connected_region.name not in ['Dig Game Area'] \ + if exit.connected_region and (not ignore_ledges or exit.spot_type not in ['Ledge', 'OWG']) \ + and exit.name not in ['Dig Game To Ledge Drop'] \ and exit.access_rule(state): if exit.connected_region == destination: found = True @@ -2512,7 +2555,7 @@ class OWEdge(object): self.unknownX = 0x0 self.unknownY = 0x0 - if self.owIndex < 0x40 or self.owIndex >= 0x80: + if self.owIndex & 0x40 == 0: self.worldType = WorldType.Light else: self.worldType = WorldType.Dark @@ -2553,6 +2596,12 @@ class OWEdge(object): self.specialID = special_id return self + def is_tile_swapped(self, world): + return world.is_tile_swapped(self.owIndex, self.player) + + def is_lw(self, world): + return (self.worldType == WorldType.Light) != self.is_tile_swapped(world) + def __eq__(self, other): return isinstance(other, self.__class__) and self.name == other.name @@ -3048,6 +3097,7 @@ class Spoiler(object): self.world = world self.hashes = {} self.overworlds = {} + self.whirlpools = {} self.maps = {} self.entrances = {} self.doors = {} @@ -3072,6 +3122,12 @@ class Spoiler(object): else: self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) + def set_whirlpool(self, entrance, exit, direction, player): + if self.world.players == 1: + self.whirlpools[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)]) + else: + self.whirlpools[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) + def set_map(self, type, text, data, player): if self.world.players == 1: self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)]) @@ -3275,6 +3331,7 @@ class Spoiler(object): self.parse_data() out = OrderedDict() out['Overworld'] = list(self.overworlds.values()) + out['Whirlpools'] = list(self.whirlpools.values()) out['Maps'] = list(self.maps.values()) out['Entrances'] = list(self.entrances.values()) out['Doors'] = list(self.doors.values()) @@ -3447,46 +3504,50 @@ class Spoiler(object): for fairy, bottle in self.bottles.items(): outfile.write(f'{fairy}: {bottle}\n') - if self.overworlds or self.maps: - outfile.write('\n\nOverworld:\n\n') - + if self.maps: # flute shuffle for player in range(1, self.world.players + 1): if ('flute', player) in self.maps: - outfile.write('Flute Spots:\n') + outfile.write('\n\nFlute Spots:\n\n') break for player in range(1, self.world.players + 1): if ('flute', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps[('flute', player)]['text'] + '\n\n') + outfile.write(self.maps[('flute', player)]['text']) # overworld tile flips for player in range(1, self.world.players + 1): if ('swaps', player) in self.maps: - outfile.write('OW Tile Flips:\n') + outfile.write('\n\nOW Tile Flips:\n\n') break for player in range(1, self.world.players + 1): if ('swaps', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps[('swaps', player)]['text'] + '\n\n') + outfile.write(self.maps[('swaps', player)]['text']) # crossed groups for player in range(1, self.world.players + 1): if ('groups', player) in self.maps: - outfile.write('OW Crossed Groups:\n') + outfile.write('\n\nOW Crossed Groups:\n\n') break for player in range(1, self.world.players + 1): if ('groups', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps[('groups', player)]['text'] + '\n\n') + outfile.write(self.maps[('groups', player)]['text']) if self.overworlds: + outfile.write('\n\nOverworld Edges:\n\n') # overworld transitions outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) + if self.whirlpools: + outfile.write('\n\nWhirlpools:\n\n') + # whirlpools + outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()])) + if self.entrances: # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly outfile.write('\n\nEntrances:\n\n') @@ -3669,10 +3730,10 @@ class Pot(object): # byte 0: DDDE EEEE (DR, ER) dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4} er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8, - 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10} + 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10, "district": 11} -# byte 1: LLLW WSSS (logic, mode, sword) -logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} +# byte 1: LLLW WSS? (logic, mode, sword) +logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5} world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "swordless_b": 2, "vanilla": 3, "bombs": 4, "pseudo": 5, "assured_pseudo": 5, "byrna": 6, "somaria": 6, "cane": 6, "bees": 7, "bugnet": 7} @@ -3717,7 +3778,7 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique # byte 11: OOOT WCCC (OWR layout, free terrain, whirlpools, OWR crossed) or_mode = {"vanilla": 0, "parallel": 1, "full": 2} -orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "limited": 3, "chaos": 4} +orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "unrestricted": 4} # byte 12: KMB? FF?? (keep similar, mixed/tile flip, bonk drops, flute spots) flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2} diff --git a/Bosses.py b/Bosses.py index 4646056f..130fe2b8 100644 --- a/Bosses.py +++ b/Bosses.py @@ -4,11 +4,12 @@ import RaceRandom as random from BaseClasses import Boss, FillError -def BossFactory(boss, player): +def BossFactory(boss, player, on_ice=False): if boss is None: return None if boss in boss_table: - enemizer_name, defeat_rule = boss_table[boss] + enemizer_name, normal_defeat_rule, ice_defeat_rule = boss_table[boss] + defeat_rule = ice_defeat_rule if on_ice else normal_defeat_rule return Boss(boss, enemizer_name, defeat_rule, player) logging.getLogger('').error('Unknown Boss: %s', boss) @@ -46,6 +47,11 @@ def HelmasaurKingDefeatRule(state, player): (state.has('Hammer', player) or state.can_use_bombs(player)) and (state.has_real_sword(player) or state.can_shoot_arrows(player) or state.has_special_weapon_level(player, 2))) + +def IceHelmasaurKingDefeatRule(state, player): + return state.can_use_bombs(player) and (state.has_sword(player) or state.can_shoot_arrows(player)) + + def ArrghusDefeatRule(state, player): if not state.has('Hookshot', player): return False @@ -53,13 +59,13 @@ def ArrghusDefeatRule(state, player): if not state.special_weapon_check(player, 2): return False - # TODO: ideally we would have a check for bow and silvers, which combined with the - # hookshot is enough. This is not coded yet because the silvers that only work in pyramid feature - # makes this complicated if state.has_blunt_weapon(player) or state.has_special_weapon_level(player, 2): return True - return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot + if state.can_shoot_arrows(player) and state.has('Silver Arrows', player) and state.world.difficulty_adjustments[player] not in ['hard', 'expert']: + return True + + return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or # assuming mostly getting two puffs with one shot (state.has('Ice Rod', player) and state.can_use_bombs(player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) @@ -73,11 +79,29 @@ def MothulaDefeatRule(state, player): (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or state.has_special_weapon_level(player, 1))) + def BlindDefeatRule(state, player): return (state.special_weapon_check(player, 1) and (state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player) or state.has_special_weapon_level(player, 1))) + +def IceBlindDefeatRule(state, player): + return ( + ( + # weapon + state.has_beam_sword(player) or + state.has('Cane of Somaria', player) or + (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) + ) and + ( + # protection + state.has('Red Shield', player) or + (state.has('Cane of Byrna', player) and state.world.difficulty_adjustments[player] not in ['hard', 'expert']) + ) + ) + + def KholdstareDefeatRule(state, player): return (state.special_weapon_check(player, 2) and ( @@ -100,12 +124,42 @@ def KholdstareDefeatRule(state, player): ) )) + +def IceKholdstareDefeatRule(state, player): + return ( + ( + state.has('Fire Rod', player) or + ( + state.has('Bombos', player) and + # FIXME: the following only actually works for the vanilla location for swordless + (state.has_sword(player) or state.world.swords[player] == 'swordless') + ) + ) and + ( + state.has_beam_sword(player) or + (state.has('Fire Rod', player) and state.can_extend_magic(player, 20)) or + # FIXME: this actually only works for the vanilla location for swordless + ( + state.has('Fire Rod', player) and + state.has('Bombos', player) and + (state.has_sword(player) or state.world.swords[player] == 'swordless') and + state.can_extend_magic(player, 16) + ) + ) + ) + + def VitreousDefeatRule(state, player): return (state.special_weapon_check(player, 2) and ((state.can_shoot_arrows(player) and state.can_kill_with_bombs(player)) or state.has_blunt_weapon(player) or state.has_special_weapon_level(player, 2))) + +def IceVitreousDefeatRule(state, player): + return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_beam_sword(player) + + def TrinexxDefeatRule(state, player): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): return False @@ -119,24 +173,36 @@ def TrinexxDefeatRule(state, player): ((state.has_real_sword(player) or state.has_special_weapon_level(player, 2)) and state.can_extend_magic(player, 32))) + +def IceTrinexxDefeatRule(state, player): + if not (state.has('Fire Rod', player) and state.has('Ice Rod', player) and state.has_Boots(player)): + return False + return (state.has('Golden Sword', player) or + (state.has('Tempered Sword', player) and state.can_extend_magic(player, 16)) or + ((state.has('Hammer', player) or + state.has('Master Sword', player)) and state.can_extend_magic(player, 32))) # rod spam rule + + def AgahnimDefeatRule(state, player): return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player) + boss_table = { - 'Armos Knights': ('Armos', ArmosKnightsDefeatRule), - 'Lanmolas': ('Lanmola', LanmolasDefeatRule), - 'Moldorm': ('Moldorm', MoldormDefeatRule), - 'Helmasaur King': ('Helmasaur', HelmasaurKingDefeatRule), - 'Arrghus': ('Arrghus', ArrghusDefeatRule), - 'Mothula': ('Mothula', MothulaDefeatRule), - 'Blind': ('Blind', BlindDefeatRule), - 'Kholdstare': ('Kholdstare', KholdstareDefeatRule), - 'Vitreous': ('Vitreous', VitreousDefeatRule), - 'Trinexx': ('Trinexx', TrinexxDefeatRule), - 'Agahnim': ('Agahnim', AgahnimDefeatRule), - 'Agahnim2': ('Agahnim2', AgahnimDefeatRule) + 'Armos Knights': ('Armos', ArmosKnightsDefeatRule, ArmosKnightsDefeatRule), + 'Lanmolas': ('Lanmola', LanmolasDefeatRule, LanmolasDefeatRule), + 'Moldorm': ('Moldorm', MoldormDefeatRule, MoldormDefeatRule), + 'Helmasaur King': ('Helmasaur', HelmasaurKingDefeatRule, IceHelmasaurKingDefeatRule), + 'Arrghus': ('Arrghus', ArrghusDefeatRule, ArrghusDefeatRule), + 'Mothula': ('Mothula', MothulaDefeatRule, MothulaDefeatRule), + 'Blind': ('Blind', BlindDefeatRule, IceBlindDefeatRule), + 'Kholdstare': ('Kholdstare', KholdstareDefeatRule, IceKholdstareDefeatRule), + 'Vitreous': ('Vitreous', VitreousDefeatRule, IceVitreousDefeatRule), + 'Trinexx': ('Trinexx', TrinexxDefeatRule, IceTrinexxDefeatRule), + 'Agahnim': ('Agahnim', AgahnimDefeatRule, AgahnimDefeatRule), + 'Agahnim2': ('Agahnim2', AgahnimDefeatRule, AgahnimDefeatRule) } + def can_place_boss(world, player, boss, dungeon_name, level=None): if world.swords[player] in ['swordless'] and boss == 'Kholdstare' and dungeon_name != 'Ice Palace': return False @@ -149,6 +215,11 @@ def can_place_boss(world, player, boss, dungeon_name, level=None): if boss in ["Blind"]: return False + # no Trinexx on Ice in doors without doing some health modelling + if world.doorShuffle[player] != 'vanilla' and boss == 'Trinexx': + if dungeon_name == 'Ganons Tower' and level == 'bottom': + return False + if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]: return False @@ -168,6 +239,7 @@ def place_bosses(world, player): ['Tower of Hera', None], ['Skull Woods', None], ['Ganons Tower', 'middle'], + ['Ganons Tower', 'bottom'], ['Eastern Palace', None], ['Desert Palace', None], ['Palace of Darkness', None], @@ -176,7 +248,6 @@ def place_bosses(world, player): ['Ice Palace', None], ['Misery Mire', None], ['Turtle Rock', None], - ['Ganons Tower', 'bottom'], ] all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons @@ -275,4 +346,4 @@ def place_boss(boss, level, loc, loc_text, world, player): loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] loc_text = loc + ' (' + level + ')' logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) - world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) + world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player, level == 'bottom') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f1c8fbb..7044b65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 0.3.4.2 +- Added Shuffle SFX Instruments as post-gen option +- Fixed some issues with Swapped ER failing to place Old Man Cave +- Changed Inverted 2.0 spawn prompt to display Bomb Shop +- Fixed some minor issues with ER and Vanilla GT + +## 0.3.4.1 +- Implemented new District ER mode option +- Added alternate boss logic when in GT Ice Basement +- Updated Inverted 2.0 to start in Bomb Shop regardless of ER mode +- Fixed broken customizer features with OWR Tile Flip +- Allowing Insanity ER + Standard to decouple standard entrances + +## 0.3.4.0 +- \~Merged in DR v1.2.0.23~ + - Improved bunny-walking algorithm + - Improved multiworld balancing +- \~Merged in some things from DR v1.4.0.0-v~ +- Implemented Hybrid Major Glitches logic (thanks Muffins/Espeon) +- Added sparkles to Bonk Drop locations for better visibility +- Some tweaks/improvements to Shuffle Song Instruments +- Replaced Save Settings on Exit with Settings on Load +- Added new button in GUI to export a Yaml file based on current settings +- Allow starting Aga-Defeated and Old-Man-Rescued in inventory + +## 0.3.3.2 +- \~Merged in DR v1.2.0.22~ +- Added Shuffle Song Instruments as post-gen option +- Allow user to change and save output directory within the GUI +- Fixed issue with Smith not deleting on S+Q when no path is possible +- Fixed various MSU inaccuracies + +## 0.3.3.1 +- \~Merged in DR v1.2.0.21~ +- Fixed issue with Old Man death spawning on Pyramid/Castle +- Fixed issue with Mixed OWR + Flute Shuffle placing spots on large screens +- Fixed issue with mirror portals disappearing in Crossed OWR +- Added more OWR preset yamls + some fixes to the existing ones + +## 0.3.3.0 +- Added Customizer support for all remaining OWR options +- Added several OWR preset yamls (many ideas are thanks to Catobat) +- Removed Limited Crossed OWR and renamed Chaos Crossed to Unrestricted Crossed + ## 0.3.2.2 - Added Customizer support for Flute Shuffle (thanks Catobat) - Fixed bad Old Man rescue possibility in Swapped ER diff --git a/CLI.py b/CLI.py index 1b2b86c7..da68221a 100644 --- a/CLI.py +++ b/CLI.py @@ -34,7 +34,9 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--settingsfile', help="input json file of settings", type=str) parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--customizer', help='input yaml file for customizations', type=str) - parser.add_argument('--print_custom_yaml', help='print example yaml for current settings', + parser.add_argument('--print_template_yaml', help='print example yaml for current settings', + default=False, action="store_true") + parser.add_argument('--print_custom_yaml', help='print example plando yaml for current settings and placements', default=False, action="store_true") parser.add_argument('--mystery', dest="mystery", default=False, action="store_true") @@ -94,6 +96,7 @@ def parse_cli(argv, no_defaults=False): parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str) parser.add_argument('--customizer', dest="customizer", help='input yaml file for customizations', type=str) + parser.add_argument('--print_template_yaml', dest="print_template_yaml", default=False, action="store_true") parser.add_argument('--print_custom_yaml', dest="print_custom_yaml", default=False, action="store_true") if player_num: @@ -145,9 +148,9 @@ def parse_cli(argv, no_defaults=False): 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', - 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', - 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', - 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']: + 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'shuffle_sfxinstruments', + 'shuffle_songinstruments', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', + 'bonk_drops', 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops', 'aga_randomness']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -231,13 +234,14 @@ def parse_settings(): "intensity": 3, "door_type_mode": "original", "trap_door_mode": "optional", - "key_logic_algorithm": "default", + "key_logic_algorithm": "partial", "decoupledoors": False, "door_self_loops": False, "experimental": False, "dungeon_counters": "default", "mixed_travel": "prevent", "standardize_palettes": "standardize", + 'aga_randomness': True, "triforce_pool": 0, "triforce_goal": 0, @@ -264,6 +268,8 @@ def parse_settings(): "uw_palettes": "default", "reduce_flashing": False, "shuffle_sfx": False, + "shuffle_sfxinstruments": False, + "shuffle_songinstruments": False, "msu_resume": False, "collection_rate": False, @@ -360,7 +366,7 @@ def parse_settings(): }, "randomSprite": False, "outputpath": os.path.join("."), - "saveonexit": "ask", + "settingsonload": "saved", "outputname": "", "startinventoryarray": {}, "notes": "" @@ -372,6 +378,12 @@ def parse_settings(): # read saved settings file if it exists and set these settings_path = os.path.join(".", "resources", "user", "settings.json") settings = apply_settings_file(settings, settings_path) + if settings["settingsonload"] == "saved": + settings_path = os.path.join(".", "resources", "user", "saved.json") + settings = apply_settings_file(settings, settings_path) + elif settings["settingsonload"] == "lastused": + settings_path = os.path.join(".", "resources", "user", "last.json") + settings = apply_settings_file(settings, settings_path) return settings # Priority fallback is: diff --git a/DoorShuffle.py b/DoorShuffle.py index 3eb02a6f..3ae1c709 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -7,7 +7,7 @@ from typing import DefaultDict, Dict, List from itertools import chain from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys -from BaseClasses import PotFlags, LocationType, Direction +from BaseClasses import PotFlags, LocationType, Direction, KeyRuleType from Doors import reset_portals from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts from Dungeons import dungeon_bigs, dungeon_hints @@ -178,6 +178,7 @@ def create_door_spoiler(world, player): queue = deque(world.dungeon_layouts[player].values()) while len(queue) > 0: builder = queue.popleft() + std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' and world.shuffle[player] == 'vanilla' done = set() start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic reg_queue = deque(start_regions) @@ -206,11 +207,15 @@ def create_door_spoiler(world, player): logger.warning('This is a bug during door spoiler') elif not isinstance(door_b, Region): logger.warning('Door not connected: %s', door_a.name) - if connect and connect.type == RegionType.Dungeon and connect not in visited: + if valid_connection(connect, std_flag, world, player) and connect not in visited: visited.add(connect) reg_queue.append(connect) +def valid_connection(region, std_flag, world, player): + return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player] or + (std_flag and region.name == 'Hyrule Castle Ledge')) + def vanilla_key_logic(world, player): builders = [] world.dungeon_layouts[player] = {} @@ -256,8 +261,25 @@ def vanilla_key_logic(world, player): world.key_logic[player][builder.name] = key_layout.key_logic world.key_layout[player][builder.name] = key_layout log_key_logic(builder.name, key_layout.key_logic) - # if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: - # validate_vanilla_key_logic(world, player) + # special adjustments for vanilla + if world.keyshuffle[player] != 'universal': + if world.mode[player] != 'standard' and not world.dropshuffle[player]: + # adjust hc doors + def adjust_hc_door(door_rule): + if door_rule.new_rules[KeyRuleType.WorstCase] == 3: + door_rule.new_rules[KeyRuleType.WorstCase] = 2 + door_rule.small_key_num = 2 + + rules = world.key_logic[player]['Hyrule Castle'].door_rules + adjust_hc_door(rules['Sewers Secret Room Key Door S']) + adjust_hc_door(rules['Hyrule Dungeon Map Room Key Door S']) + adjust_hc_door(rules['Sewers Dark Cross Key Door N']) + # adjust pod front door + pod_front = world.key_logic[player]['Palace of Darkness'].door_rules['PoD Middle Cage N'] + if pod_front.new_rules[KeyRuleType.WorstCase] == 6: + pod_front.new_rules[KeyRuleType.WorstCase] = 1 + pod_front.small_key_num = 1 + # gt logic? I'm unsure it needs adjusting def validate_vanilla_reservation(dungeon, world, player): @@ -3331,6 +3353,9 @@ def find_inaccessible_regions(world, player): ledge = world.get_region('Hyrule Castle Ledge', player) if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'): world.inaccessible_regions[player].append('Hyrule Castle Ledge') + # this should be considered as part of the inaccessible regions, dungeonssimple? + if world.mode[player] == 'standard' and world.shuffle[player] == 'vanilla': + world.inaccessible_regions[player].append('Hyrule Castle Ledge') logger = logging.getLogger('') #logger.debug('Inaccessible Regions:') #for r in world.inaccessible_regions[player]: diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ff875c59..9bae0b11 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -814,7 +814,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, must # Keeps track of entrances that cannot be used to access each exit / cave invalid_cave_connections = defaultdict(set) - # if world.logic[player] in ['owglitches', 'nologic']: + # if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: # import OverworldGlitchRules # for entrance in OverworldGlitchRules.get_non_mandatory_exits(world, player): # if entrance in must_be_exits: diff --git a/Fill.py b/Fill.py index 3121e291..70832132 100644 --- a/Fill.py +++ b/Fill.py @@ -3,6 +3,7 @@ import collections import itertools import logging import math +from collections import Counter from contextlib import suppress from BaseClasses import CollectionState, FillError, LocationType @@ -71,13 +72,13 @@ def fill_dungeons_restrictive(world, shuffled_locations): def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, vanilla=False): - def sweep_from_pool(placing_item=None): + def sweep_from_pool(placing_items=None): new_state = base_state.copy() for item in itempool: new_state.collect(item, True) - new_state.placing_item = placing_item + new_state.placing_items = placing_items new_state.sweep_for_events() - new_state.placing_item = None + new_state.placing_items = None return new_state unplaced_items = [] @@ -94,7 +95,7 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing while any(player_items.values()) and locations: items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items] - maximum_exploration_state = sweep_from_pool(placing_item=items_to_place[0]) + maximum_exploration_state = sweep_from_pool(placing_items=items_to_place) has_beaten_game = world.has_beaten_game(maximum_exploration_state) for item_to_place in items_to_place: @@ -174,6 +175,9 @@ def valid_key_placement(item, location, key_pool, collection_state, world): if dungeon: if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True + # Small key and big key in Swamp and Hera are placed without logic + if world.logic[item.player] == 'hybridglitches' and dungeon.name in ['Tower of Hera', 'Swamp Palace'] and dungeon.name in item.name: + return True key_logic = world.key_logic[item.player][dungeon.name] unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None @@ -419,7 +423,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # fill in gtower locations with trash first for player in range(1, world.players + 1): if (not gftower_trash or not world.ganonstower_vanilla[player] - or world.logic[player] in ['owglitches', 'nologic']): + or world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']): continue gt_count, total_count = calc_trash_locations(world, player) scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7) @@ -734,24 +738,44 @@ def balance_multiworld_progression(world): checked_locations = set() unchecked_locations = set(world.get_locations()) + total_locations_count = Counter(location.player for location in world.get_locations() if not location.locked and not location.forced_item) + reachable_locations_count = {} for player in range(1, world.players + 1): reachable_locations_count[player] = 0 + sphere_num = 1 + moved_item_count = 0 def get_sphere_locations(sphere_state, locations): sphere_state.sweep_for_events(key_only=True, locations=locations) return {loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)} + def item_percentage(player, num): + return num / total_locations_count[player] + while True: sphere_locations = get_sphere_locations(state, unchecked_locations) for location in sphere_locations: unchecked_locations.remove(location) - reachable_locations_count[location.player] += 1 + if not location.locked and not location.forced_item: + reachable_locations_count[location.player] += 1 + + logging.debug(f'Sphere {sphere_num}') + logging.debug(f'Reachable locations: {reachable_locations_count}') + debug_percentages = { + player: round(item_percentage(player, num), 2) + for player, num in reachable_locations_count.items() + } + logging.debug(f'Reachable percentages: {debug_percentages}\n') + sphere_num += 1 if checked_locations: - threshold = max(reachable_locations_count.values()) - 20 + max_percentage = max(map(lambda p: item_percentage(p, reachable_locations_count[p]), reachable_locations_count)) + threshold_percentages = {player: max_percentage * .8 for player in range(1, world.players + 1)} + logging.debug(f'Thresholds: {threshold_percentages}') - balancing_players = {player for player, reachables in reachable_locations_count.items() if reachables < threshold} + balancing_players = {player for player, reachables in reachable_locations_count.items() + if item_percentage(player, reachables) < threshold_percentages[player]} if balancing_players: balancing_state = state.copy() balancing_unchecked_locations = unchecked_locations.copy() @@ -769,7 +793,8 @@ def balance_multiworld_progression(world): for location in balancing_sphere: balancing_unchecked_locations.remove(location) balancing_reachables[location.player] += 1 - if world.has_beaten_game(balancing_state) or all(reachables >= threshold for reachables in balancing_reachables.values()): + if world.has_beaten_game(balancing_state) or all(item_percentage(player, reachables) >= threshold_percentages[player] + for player, reachables in balancing_reachables.items()): break elif not balancing_sphere: raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') @@ -796,7 +821,8 @@ def balance_multiworld_progression(world): items_to_replace.append(testing) else: reduced_sphere = get_sphere_locations(reducing_state, locations_to_test) - if reachable_locations_count[player] + len(reduced_sphere) < threshold: + p = item_percentage(player, reachable_locations_count[player] + len(reduced_sphere)) + if p < threshold_percentages[player]: items_to_replace.append(testing) replaced_items = False @@ -821,6 +847,7 @@ def balance_multiworld_progression(world): new_location.event, old_location.event = True, False logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, " f"displacing {old_location.item} into {old_location}") + moved_item_count += 1 state.collect(new_location.item, True, new_location) replaced_items = True break @@ -828,6 +855,7 @@ def balance_multiworld_progression(world): logging.warning(f"Could not Progression Balance {old_location.item}") if replaced_items: + logging.debug(f'Moved {moved_item_count} items so far\n') unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]} for location in get_sphere_locations(state, unlocked): unchecked_locations.remove(location) @@ -842,7 +870,8 @@ def balance_multiworld_progression(world): if world.has_beaten_game(state): break elif not sphere_locations: - raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') + logging.warning('Progression Balancing ran out of paths.') + break def check_shop_swap(l, make_item_free=False): @@ -1007,6 +1036,7 @@ def balance_money_progression(world): logger.debug(f'Money balancing needed: Player {target_player} short {difference}') else: difference = 0 + target_player = next(p for p in solvent) while difference > 0: swap_targets = [x for x in unchecked_locations if x not in sphere_locations and x.item.name.startswith('Rupees') and x.item.player == target_player] if len(swap_targets) == 0: diff --git a/Gui.py b/Gui.py index a0f4726f..899935ea 100755 --- a/Gui.py +++ b/Gui.py @@ -34,22 +34,34 @@ def check_python_version(fish): import sys version = sys.version_info if version.major < 3 or version.minor < 7: - messagebox.showinfo("Door Shuffle " + ESVersion, fish.translate("cli","cli","old.python.version") % sys.version) + messagebox.showinfo("Overworld Shuffle %s (DR %s)" % (ORVersion, ESVersion), fish.translate("cli","cli","old.python.version") % sys.version) +# Save settings to file +def save_settings(gui, args, filename): + user_resources_path = os.path.join(".", "resources", "user") + settings_path = os.path.join(user_resources_path) + if not os.path.exists(settings_path): + os.makedirs(settings_path) + output_args = {} + settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler", + "calc_playthrough", "skip_playthrough", "print_custom_yaml", + "settingsonload", "rom", "enemizercli", "outputpath"] + if filename == "settings.json": + for s in settings: + output_args[s] = args[s] + for widget in gui.pages["adjust"].content.widgets: + output_args["adjust." + widget] = gui.pages["adjust"].content.widgets[widget].storageVar.get() + else: + for k, v in args.items(): + if k not in settings and not k.startswith("adjust."): + output_args[k] = v + with open(os.path.join(settings_path, filename), "w+") as f: + f.write(json.dumps(output_args, indent=2)) + os.chmod(os.path.join(settings_path, filename),0o755) + + def guiMain(args=None): - # Save settings to file - def save_settings(args): - user_resources_path = os.path.join(".", "resources", "user") - settings_path = os.path.join(user_resources_path) - if not os.path.exists(settings_path): - os.makedirs(settings_path) - for widget in self.pages["adjust"].content.widgets: - args["adjust." + widget] = self.pages["adjust"].content.widgets[widget].storageVar.get() - with open(os.path.join(settings_path, "settings.json"), "w+") as f: - f.write(json.dumps(args, indent=2)) - os.chmod(os.path.join(settings_path, "settings.json"),0o755) - # Save settings from GUI def save_settings_from_gui(confirm): gui_args = vars(create_guiargs(self)) @@ -57,23 +69,14 @@ def guiMain(args=None): gui_args['sprite'] = 'random' elif gui_args['sprite']: gui_args['sprite'] = gui_args['sprite'].name - save_settings(gui_args) + save_settings(self, gui_args, "saved.json") + save_settings(self, gui_args, "settings.json") if confirm: messagebox.showinfo("Overworld Shuffle " + ORVersion, "Settings saved from GUI.") # routine for exiting the app def guiExit(): - skip_exit = False - if self.settings['saveonexit'] == 'ask': - dosave = messagebox.askyesnocancel("Overworld Shuffle " + ORVersion, "Save settings before exit?") - if dosave: - save_settings_from_gui(True) - if dosave is None: - skip_exit = True - elif self.settings['saveonexit'] == 'always': - save_settings_from_gui(False) - if not skip_exit: - sys.exit(0) + sys.exit(0) # make main window # add program title & version number @@ -172,8 +175,8 @@ def guiMain(args=None): self.pages["bottom"].pages = {} self.pages["bottom"].pages["content"] = bottom_frame(self, self, None) ## Save Settings Button - savesettingsButton = Button(self.pages["bottom"].pages["content"], text='Save Settings to File', command=lambda: save_settings_from_gui(True)) - savesettingsButton.pack(side=RIGHT) + savesettingsButton = self.pages["bottom"].pages["content"].widgets["savesettings"].pieces["button"] + savesettingsButton.configure(command=lambda: save_settings_from_gui(True)) # set bottom frame to main window self.pages["bottom"].pages["content"].pack(side=BOTTOM, fill=X, padx=5, pady=5) diff --git a/InitialSram.py b/InitialSram.py index b1177cb4..9460e9f4 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -132,6 +132,16 @@ class InitialSram: equip[0x37B] = 1 equip[0x36E] = 0x80 + if startingstate.has('Return Old Man', player): + self._initial_sram_bytes[0x410] |= 0x01 + + if startingstate.has('Beat Agahnim 1', player): + self.pre_open_lumberjack() + if world.mode[player] == 'standard': + self.set_progress_indicator(0x80) + else: + self.set_progress_indicator(0x03) + for item in world.precollected_items: if item.player != player: continue @@ -144,7 +154,8 @@ class InitialSram: 'L5 Net', 'L4 Net', 'L3 Net', 'L2 Net', 'L1 Net', 'Progressive Net', 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', 'Red Mail', 'Blue Mail', 'Progressive Armor', - 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', + 'Return Old Man', 'Beat Agahnim 1']: continue set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), diff --git a/ItemList.py b/ItemList.py index 9fca3550..1e321ed2 100644 --- a/ItemList.py +++ b/ItemList.py @@ -244,6 +244,12 @@ def generate_itempool(world, player): loc.locked = True loc.forced_item = loc.item + if 'Return Old Man' in list(map(str, [i for i in world.precollected_items if i.player == player])): + old_man = world.get_location('Old Man', player) + world.push_item(old_man, ItemFactory('Nothing', player), False) + old_man.forced_item = old_man.item + old_man.skip = True + world.get_location('Ganon', player).event = True world.get_location('Ganon', player).locked = True world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False) @@ -338,6 +344,13 @@ def generate_itempool(world, player): for _ in range(0, amt): pool.append('Rupees (20)') + if world.logic[player] == 'hybridglitches' and world.pottery[player] not in ['none', 'cave']: + # In HMG force swamp smalls in pots to allow getting out of swamp palace + placed_items['Swamp Palace - Trench 1 Pot Key'] = 'Small Key (Swamp Palace)' + placed_items['Swamp Palace - Pot Row Pot Key'] = 'Small Key (Swamp Palace)' + pool.remove('Small Key (Swamp Palace)') + pool.remove('Small Key (Swamp Palace)') + start_inventory = list(world.precollected_items) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -360,6 +373,8 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] not in ['swordless', 'swordless_b']: found_sword = True possible_weapons.append(item) + if world.algorithm == 'vanilla_fill': # skip other possibilities + continue if item in ['Progressive Cane', 'Progressive Net']: if world.swords[player] != 'cane': possible_weapons.append(item) @@ -458,9 +473,15 @@ def generate_itempool(world, player): if tr_medallion == 'Random': tr_medallion = None if not mm_medallion: - mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] + if world.algorithm == 'vanilla_fill': + mm_medallion = 'Ether' + else: + mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] if not tr_medallion: - tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] + if world.algorithm == 'vanilla_fill': + tr_medallion = 'Quake' + else: + tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] world.required_medallions[player] = (mm_medallion, tr_medallion) # shuffle bottle refills @@ -573,7 +594,7 @@ def set_up_take_anys(world, player, skip_adjustments=False): else: if world.shopsanity[player] and not skip_adjustments: world.itempool.append(ItemFactory('Rupees (300)', player)) - old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0, create_location=world.shopsanity[player]) + old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0, create_location=True) take_any_type = ShopType.Shop if world.shopsanity[player] else ShopType.TakeAny for num in range(4): @@ -582,13 +603,15 @@ def set_up_take_anys(world, player, skip_adjustments=False): world.dynamic_regions.append(take_any) target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) reg = regions.pop() - entrance = world.get_region(reg, player).entrances[0] + entrance = next((ent for ent in world.get_region(reg, player).entrances if ent.parent_region.is_outdoors()), None) + if entrance is None: + raise Exception(f'No outside entrance found for {reg}') connect_entrance(world, entrance, take_any, player) entrance.target = target take_any.shop = Shop(take_any, room_id, take_any_type, 0xE3, True, not world.shopsanity[player], 33 + num*2) world.shops[player].append(take_any.shop) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0, create_location=world.shopsanity[player]) - take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0, create_location=world.shopsanity[player]) + take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0, create_location=True) if world.shopsanity[player] and not skip_adjustments: world.itempool.append(ItemFactory('Blue Potion', player)) world.itempool.append(ItemFactory('Boss Heart Container', player)) @@ -1011,7 +1034,8 @@ def balance_prices(world, player): def check_hints(world, player): - if world.shuffle[player] in ['simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity']: + if (world.shuffle[player] in ['simple', 'restricted', 'full', 'district', 'swapped', 'crossed', 'insanity'] + or (world.shuffle[player] in ['lite', 'lean'] and world.shopsanity[player])): for shop, location_list in shop_to_location_table.items(): if shop in ['Capacity Upgrade', 'Paradox Shop', 'Potion Shop']: continue # near the queen, near potions, and near 7 chests are fine @@ -1101,7 +1125,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt pool.remove('Pegasus Boots') # provide boots to boots glitch dependent modes - if logic in ['owglitches', 'nologic']: + if logic in ['owglitches', 'hybridglitches', 'nologic']: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.extend(['Rupees (20)']) @@ -1454,12 +1478,12 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer start_inventory = [x for x in world.precollected_items if x.player == player] if not start_inventory: - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and all(x.name != 'Pegasus Boots' for x in start_inventory): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: pool.remove('Pegasus Boots') pool.append('Rupees (20)') - if world.swords[player] == 'assured': + if world.swords[player] == 'assured' and all(' Sword' not in x.name for x in start_inventory): precollected_items.append('Progressive Sword') if 'Progressive Sword' in pool: pool.remove('Progressive Sword') @@ -1595,7 +1619,7 @@ def make_customizer_pool(world, player): sphere_0 = world.customizer.get_start_inventory() no_start_inventory = not sphere_0 or not sphere_0[player] init_equip = [] if no_start_inventory else sphere_0[player] - if (world.logic[player] in ['owglitches', 'nologic'] + if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and (no_start_inventory or all(x != 'Pegasus Boots' for x in init_equip))): precollected_items.append('Pegasus Boots') if 'Pegasus Boots' in pool: diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 3f154ade..6f0cce5d 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -1800,7 +1800,7 @@ def imp_locations_factory(world, player): imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] if world.mode[player] == 'standard': imp_locations.append('Zelda Pickup') - imp_locations.append('Zelda Dropoff') + imp_locations.append('Zelda Drop Off') return imp_locations @@ -2106,6 +2106,22 @@ def validate_key_placement(key_layout, world, player): if i.player == player and i.name == smallkey_name: keys_outside += 1 + if world.logic[player] == 'hybridglitches': + # Swamp keylogic + if smallkey_name.endswith('(Swamp Palace)'): + swamp_entrance = world.get_location('Swamp Palace - Entrance', player) + # Swamp small not vanilla + if swamp_entrance.item is None or (swamp_entrance.item.name != smallkey_name or swamp_entrance.item.player != player): + mire_keylayout = world.key_layout[player]['Misery Mire'] + mire_smallkey_name = dungeon_keys[mire_keylayout.sector.name] + # Check if any mire keys are in swamp (excluding entrance), if none then add one to keys_outside + mire_keys_in_swamp = sum([1 if x.item.name == mire_smallkey_name else 0 for x in key_layout.item_locations if x.item is not None and x != swamp_entrance]) + if mire_keys_in_swamp == 0: + keys_outside +=1 + # Mire keylogic + if smallkey_name.endswith('(Tower of Hera)'): + # TODO: Make sure that mire medallion isn't in hera basement, or if it is, the small key is available downstairs + big_key_outside = True for code, counter in key_layout.key_counters.items(): if len(counter.child_doors) == 0: continue diff --git a/Main.py b/Main.py index a7b1e38b..2e932eb3 100644 --- a/Main.py +++ b/Main.py @@ -28,6 +28,7 @@ from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dunge from Fill import dungeon_tracking from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, fill_specific_items, create_farm_locations +from UnderworldGlitchRules import create_hybridmajor_connections, create_hybridmajor_connectors from Utils import output_path, parse_player_names from source.item.District import init_districts @@ -36,7 +37,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new from source.tools.BPS import create_bps_from_data from source.classes.CustomSettings import CustomSettings -version_number = '1.2.0.20' +version_number = '1.2.0.23' version_branch = '-u' __version__ = f'{version_number}{version_branch}' @@ -81,32 +82,33 @@ def check_python_version(): def main(args, seed=None, fish=None): check_python_version() + + if args.print_template_yaml: + return export_yaml(args, fish) + if args.outputpath: os.makedirs(args.outputpath, exist_ok=True) output_path.cached_path = args.outputpath start = time.perf_counter() + world = init_world(args, fish) + + logger = logging.getLogger('') + if args.securerandom: random.use_secure() seeded = False - # initialize the world - if args.code: - for player, code in args.code.items(): - if code: - Settings.adjust_args_from_code(code, player, args) - customized = None - if args.customizer: - customized = CustomSettings() - customized.load_yaml(args.customizer) - seed = customized.determine_seed(seed) + if world.customizer: + seed = world.customizer.determine_seed(seed) seeded = True - customized.adjust_args(args) - world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, - args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, - args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) - world.customizer = customized if customized else None - logger = logging.getLogger('') + world.customizer.adjust_args(args) + world = init_world(args, fish) + + for i in zip(args.logic.values(), args.door_shuffle.values()): + if i[0] == 'hybridglitches' and i[1] != 'vanilla': + raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle")) + if seed is None: random.seed(None) world.seed = random.randint(0, 999999999) @@ -118,55 +120,10 @@ def main(args, seed=None, fish=None): if args.securerandom: world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9)) - world.boots_hint = args.boots_hint.copy() - world.remote_items = args.remote_items.copy() - world.mapshuffle = args.mapshuffle.copy() - world.compassshuffle = args.compassshuffle.copy() - world.keyshuffle = args.keyshuffle.copy() - world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.bombbag = args.bombbag.copy() - world.flute_mode = args.flute_mode.copy() - world.bow_mode = args.bow_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() - world.crystals_gt_orig = args.crystals_gt.copy() world.ganon_item = {player: get_random_ganon_item(args.swords) if args.ganon_item[player] == 'random' else args.ganon_item[player] for player in range(1, world.players + 1)} - world.ganon_item_orig = args.ganon_item.copy() - world.owTerrain = args.ow_terrain.copy() - world.owKeepSimilar = args.ow_keepsimilar.copy() - world.owWhirlpoolShuffle = args.ow_whirlpool.copy() - world.owFluteShuffle = args.ow_fluteshuffle.copy() - world.shuffle_bonk_drops = args.bonk_drops.copy() - world.open_pyramid = args.openpyramid.copy() - world.boss_shuffle = args.shufflebosses.copy() - world.enemy_shuffle = args.shuffleenemies.copy() - world.enemy_health = args.enemy_health.copy() - world.enemy_damage = args.enemy_damage.copy() - world.beemizer = args.beemizer.copy() world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} - world.door_type_mode = args.door_type_mode.copy() - world.trap_door_mode = args.trap_door_mode.copy() - world.key_logic_algorithm = args.key_logic_algorithm.copy() - world.decoupledoors = args.decoupledoors.copy() - world.door_self_loops = args.door_self_loops.copy() - world.experimental = args.experimental.copy() - world.dungeon_counters = args.dungeon_counters.copy() - world.fish = fish - world.shopsanity = args.shopsanity.copy() - world.dropshuffle = args.dropshuffle.copy() - world.pottery = args.pottery.copy() - world.potshuffle = args.shufflepots.copy() - world.mixed_travel = args.mixed_travel.copy() - world.standardize_palettes = args.standardize_palettes.copy() - world.shufflelinks = args.shufflelinks.copy() - world.shuffletavern = args.shuffletavern.copy() - world.pseudoboots = args.pseudoboots.copy() - world.overworld_map = args.overworld_map.copy() - world.take_any = args.take_any.copy() - world.restrict_boss_items = args.restrict_boss_items.copy() - world.collection_rate = args.collection_rate.copy() - world.colorizepots = args.colorizepots.copy() world.treasure_hunt_count = {} world.treasure_hunt_total = {} @@ -210,8 +167,6 @@ def main(args, seed=None, fish=None): for player, name in enumerate(team, 1): world.player_names[player].append(name) logger.info('') - world.settings = CustomSettings() - world.settings.create_from_world(world, args) outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' @@ -222,13 +177,10 @@ def main(args, seed=None, fish=None): if hasattr(world,"escape_assist") and player in world.escape_assist: world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it - if args.usestartinventory[player]: - for tok in filter(None, args.startinventory[player].split(',')): - name = tok.replace("_", " ").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) + set_starting_inventory(world, args) + + world.settings = CustomSettings() + world.settings.create_from_world(world, args) if args.create_spoiler and not args.jsonout: logger.info(world.fish.translate("cli", "cli", "create.meta")) @@ -246,15 +198,8 @@ def main(args, seed=None, fish=None): create_dungeons(world, player) adjust_locations(world, player) place_bosses(world, player) - - if world.customizer and world.customizer.get_start_inventory(): - for p, inv_list in world.customizer.get_start_inventory().items(): - for inv_item in inv_list: - item = ItemFactory(inv_item.strip(), p) - if item: - world.push_precollected(item) - if args.print_custom_yaml: - world.settings.record_info(world) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(world, player) if any(world.potshuffle.values()): logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) @@ -273,8 +218,6 @@ def main(args, seed=None, fish=None): update_world_regions(world, player) mark_light_dark_world_regions(world, player) create_dynamic_exits(world, player) - if args.print_custom_yaml: - world.settings.record_overworld(world) init_districts(world) @@ -282,14 +225,14 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_entrances_new(world, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(world, player) logger.info(world.fish.translate("cli", "cli", "shuffling.prep")) for player in range(1, world.players + 1): link_doors_prep(world, player) - if args.print_custom_yaml: - world.settings.record_entrances(world) create_item_pool_config(world) logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons")) @@ -297,8 +240,6 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_doors(world, player) mark_light_dark_world_regions(world, player) - if args.print_custom_yaml: - world.settings.record_doors(world) logger.info(world.fish.translate("cli", "cli", "generating.itempool")) @@ -326,8 +267,6 @@ def main(args, seed=None, fish=None): lock_shop_locations(world, player) massage_item_pool(world) - if args.print_custom_yaml: - world.settings.record_item_pool(world) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) fill_prizes(world) @@ -377,6 +316,11 @@ def main(args, seed=None, fish=None): ensure_good_pots(world, True) if args.print_custom_yaml: + world.settings.record_info(world) + world.settings.record_overworld(world) + world.settings.record_entrances(world) + world.settings.record_doors(world) + world.settings.record_item_pool(world) world.settings.record_item_placements(world) world.settings.write_to_file(output_path(f'{outfilebase}_custom.yaml')) @@ -420,7 +364,8 @@ def main(args, seed=None, fish=None): apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player], - args.shuffle_sfx[player], args.msu_resume[player]) + args.shuffle_sfx[player], args.shuffle_sfxinstruments[player], args.shuffle_songinstruments[player], + args.msu_resume[player]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches @@ -494,6 +439,124 @@ def main(args, seed=None, fish=None): return world +def export_yaml(args, fish): + if args.outputpath: + os.makedirs(args.outputpath, exist_ok=True) + output_path.cached_path = args.outputpath + + outfilebase = f'{args.outputname if args.outputname else "export"}' + logger = logging.getLogger('') + + world = init_world(args, fish) + + from OverworldShuffle import __version__ as ORVersion + logger.info( + world.fish.translate("cli","cli","app.title") + "\n", + ORVersion, + "(%s)" % outfilebase, + Settings.make_code(world, 1) if world.players == 1 else '' + ) + + for k,v in {"DR":__version__,"OR":ORVersion}.items(): + logger.info((k + ' Version:').ljust(16) + '%s' % v) + + set_starting_inventory(world, args) + + world.settings = CustomSettings() + world.settings.create_from_world(world, args) + + world.settings.record_item_pool(world, True) + world.settings.write_to_file(output_path(f'{outfilebase}.yaml')) + + return world + + +def init_world(args, fish): + if args.code: + for player, code in args.code.items(): + if code: + Settings.adjust_args_from_code(code, player, args) + + customized = None + if args.customizer: + customized = CustomSettings() + customized.load_yaml(args.customizer) + customized.adjust_args(args, False) + + world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) + + world.customizer = customized + world.boots_hint = args.boots_hint.copy() + world.remote_items = args.remote_items.copy() + world.mapshuffle = args.mapshuffle.copy() + world.compassshuffle = args.compassshuffle.copy() + world.keyshuffle = args.keyshuffle.copy() + world.bigkeyshuffle = args.bigkeyshuffle.copy() + world.bombbag = args.bombbag.copy() + world.flute_mode = args.flute_mode.copy() + world.bow_mode = args.bow_mode.copy() + world.crystals_ganon_orig = args.crystals_ganon.copy() + world.crystals_gt_orig = args.crystals_gt.copy() + world.owTerrain = args.ow_terrain.copy() + world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owWhirlpoolShuffle = args.ow_whirlpool.copy() + world.owFluteShuffle = args.ow_fluteshuffle.copy() + world.shuffle_bonk_drops = args.bonk_drops.copy() + world.open_pyramid = args.openpyramid.copy() + world.boss_shuffle = args.shufflebosses.copy() + world.enemy_shuffle = args.shuffleenemies.copy() + world.enemy_health = args.enemy_health.copy() + world.enemy_damage = args.enemy_damage.copy() + world.beemizer = args.beemizer.copy() + world.intensity = {player: 'random' if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} + world.door_type_mode = args.door_type_mode.copy() + world.trap_door_mode = args.trap_door_mode.copy() + world.key_logic_algorithm = args.key_logic_algorithm.copy() + world.decoupledoors = args.decoupledoors.copy() + world.door_self_loops = args.door_self_loops.copy() + world.experimental = args.experimental.copy() + world.dungeon_counters = args.dungeon_counters.copy() + world.fish = fish + world.shopsanity = args.shopsanity.copy() + world.dropshuffle = args.dropshuffle.copy() + world.pottery = args.pottery.copy() + world.potshuffle = args.shufflepots.copy() + world.mixed_travel = args.mixed_travel.copy() + world.standardize_palettes = args.standardize_palettes.copy() + world.shufflelinks = args.shufflelinks.copy() + world.shuffletavern = args.shuffletavern.copy() + world.pseudoboots = args.pseudoboots.copy() + world.overworld_map = args.overworld_map.copy() + world.take_any = args.take_any.copy() + world.restrict_boss_items = args.restrict_boss_items.copy() + world.collection_rate = args.collection_rate.copy() + world.colorizepots = args.colorizepots.copy() + world.aga_randomness = args.aga_randomness.copy() + + + return world + + +def set_starting_inventory(world, args): + for player in range(1, world.players + 1): + if args.usestartinventory[player]: + for tok in filter(None, args.startinventory[player].split(',')): + 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) + + if world.customizer and world.customizer.get_start_inventory(): + for p, inv_list in world.customizer.get_start_inventory().items(): + for inv_item in inv_list: + item = ItemFactory(inv_item.strip(), p) + if item: + world.push_precollected(item) + + def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, @@ -550,6 +613,10 @@ def copy_world(world): ret.intensity = world.intensity.copy() ret.decoupledoors = world.decoupledoors.copy() ret.door_self_loops = world.door_self_loops.copy() + ret.door_type_mode = world.door_type_mode.copy() + ret.trap_door_mode = world.trap_door_mode.copy() + ret.key_logic_algorithm = world.key_logic_algorithm.copy() + ret.aga_randomness = world.aga_randomness.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() @@ -566,8 +633,10 @@ def copy_world(world): for player in range(1, world.players + 1): create_regions(ret, player) update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) @@ -613,7 +682,12 @@ def copy_world(world): for exit in region.exits: if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) - ret.get_entrance(exit.name, exit.player).connect(dest_region) + src_exit = ret.get_entrance(exit.name, exit.player) + if exit.name not in [e.name for e in dest_region.entrances if e.connected_region is not None]: + if exit.name in [e.name for e in dest_region.entrances]: + src_exit.connected_region = dest_region + else: + src_exit.connect(dest_region) # fill locations for location in world.get_locations(): @@ -670,6 +744,8 @@ def copy_world(world): for player in range(1, world.players + 1): categorize_world_regions(ret, player) create_farm_locations(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) set_rules(ret, player) return ret @@ -726,6 +802,12 @@ def copy_world_premature(world, player): ret.enemy_damage = world.enemy_damage.copy() ret.beemizer = world.beemizer.copy() ret.intensity = world.intensity.copy() + ret.decoupledoors = world.decoupledoors.copy() + ret.door_self_loops = world.door_self_loops.copy() + ret.door_type_mode = world.door_type_mode.copy() + ret.trap_door_mode = world.trap_door_mode.copy() + ret.key_logic_algorithm = world.key_logic_algorithm.copy() + ret.aga_randomness = world.aga_randomness.copy() ret.experimental = world.experimental.copy() ret.shopsanity = world.shopsanity.copy() ret.dropshuffle = world.dropshuffle.copy() @@ -743,8 +825,10 @@ def copy_world_premature(world, player): create_regions(ret, player) update_world_regions(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connections(ret, player) create_dynamic_exits(ret, player) create_dungeon_regions(ret, player) create_owedges(ret, player) @@ -775,7 +859,12 @@ def copy_world_premature(world, player): for exit in region.exits: if exit.connected_region: dest_region = ret.get_region(exit.connected_region.name, region.player) - ret.get_entrance(exit.name, exit.player).connect(dest_region) + src_exit = ret.get_entrance(exit.name, exit.player) + if exit.name not in [e.name for e in dest_region.entrances if e.connected_region is not None]: + if exit.name in [e.name for e in dest_region.entrances]: + src_exit.connected_region = dest_region + else: + src_exit.connect(dest_region) from OverworldShuffle import categorize_world_regions categorize_world_regions(ret, player) @@ -800,6 +889,9 @@ def copy_world_premature(world, player): for portal in world.dungeon_portals[player]: connect_portal(portal, ret, player) + if world.logic[player] in ('nologic', 'hybridglitches'): + create_hybridmajor_connectors(ret, player) + set_rules(ret, player) return ret diff --git a/OWEdges.py b/OWEdges.py index f4da280e..ef9c9182 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -332,8 +332,8 @@ def create_owedge(player, name, owIndex, direction, terrain, edge_id, owSlotInde OWEdgeGroups = { - #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup) - (St, LW, Vt, Ld, PL, 1): ( + #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup, CustomizerGroup) + (St, LW, Vt, Ld, PL, 1, None): ( [ ['Hyrule Castle SW'], ['Hyrule Castle SE'] @@ -343,7 +343,7 @@ OWEdgeGroups = { ['Links House NE'] ] ), - (St, LW, Hz, Ld, PL, 3): ( + (St, LW, Hz, Ld, PL, 3, None): ( [ ['Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES'] ], @@ -351,7 +351,7 @@ OWEdgeGroups = { ['Links House WN', 'Links House WC', 'Links House WS'] ] ), - (Op, LW, Hz, Ld, PL, 1): ( + (Op, LW, Hz, Ld, PL, 1, None): ( [ ['Lost Woods EN'], ['East Death Mountain EN'], @@ -391,7 +391,7 @@ OWEdgeGroups = { ['East Death Mountain WS'] ] ), - (Op, LW, Hz, Ld, NP, 1): ( + (Op, LW, Hz, Ld, NP, 1, None): ( [ ['Forgotten Forest ES'] ], @@ -399,7 +399,7 @@ OWEdgeGroups = { ['Hyrule Castle WN'] ] ), - (Op, LW, Vt, Ld, PL, 1): ( + (Op, LW, Vt, Ld, PL, 1, None): ( [ ['Lumberjack SW'], ['Mountain Pass SE'], @@ -439,7 +439,7 @@ OWEdgeGroups = { ['Octoballoon NE'] ] ), - (Op, LW, Vt, Ld, NP, 1): ( + (Op, LW, Vt, Ld, NP, 1, None): ( [ ['Master Sword Meadow SC'], ['Zoras Domain SW'] @@ -449,7 +449,7 @@ OWEdgeGroups = { ['Zora Waterfall NE'] ] ), - (Op, LW, Hz, Ld, PL, 2): ( + (Op, LW, Hz, Ld, PL, 2, None): ( [ ['Kakariko Fortune EN', 'Kakariko Fortune ES'], ['Kakariko Pond EN', 'Kakariko Pond ES'], @@ -465,7 +465,7 @@ OWEdgeGroups = { ['Statues WN', 'Statues WS'] ] ), - (Op, LW, Hz, Ld, NP, 2): ( + (Op, LW, Hz, Ld, NP, 2, None): ( [ ['Desert EC', 'Desert ES'] ], @@ -473,7 +473,7 @@ OWEdgeGroups = { ['Desert Pass WC', 'Desert Pass WS'] ] ), - (Op, LW, Vt, Ld, PL, 2): ( + (Op, LW, Vt, Ld, PL, 2, None): ( [ ['Lost Woods SW', 'Lost Woods SC'], ['Lost Woods Pass SW', 'Lost Woods Pass SE'], @@ -489,7 +489,7 @@ OWEdgeGroups = { ['Wooden Bridge NW', 'Wooden Bridge NE'] ] ), - (Op, LW, Hz, Wr, PL, 1): ( + (Op, LW, Hz, Wr, PL, 1, None): ( [ ['Potion Shop EN'], ['Lake Hylia EC'], @@ -505,7 +505,7 @@ OWEdgeGroups = { ['Statues WC'] ] ), - (Op, LW, Hz, Wr, NP, 1): ( + (Op, LW, Hz, Wr, NP, 1, None): ( [ ['Hobo EC'] ], @@ -513,7 +513,7 @@ OWEdgeGroups = { ['Stone Bridge WC'] ] ), - (Op, LW, Vt, Wr, PL, 1): ( + (Op, LW, Vt, Wr, PL, 1, None): ( [ ['Tree Line SC'], ['Ice Cave SW'], @@ -525,7 +525,7 @@ OWEdgeGroups = { ['Wooden Bridge NC'] ] ), - (Op, DW, Hz, Ld, PL, 1): ( + (Op, DW, Hz, Ld, PL, 1, None): ( [ ['Skull Woods EN'], ['East Dark Death Mountain EN'], @@ -563,7 +563,7 @@ OWEdgeGroups = { ['East Dark Death Mountain WS'] ] ), - (Op, DW, Vt, Ld, PL, 1): ( + (Op, DW, Vt, Ld, PL, 1, None): ( [ ['Dark Lumberjack SW'], ['Bumper Cave SE'], @@ -607,11 +607,11 @@ OWEdgeGroups = { ['Bomber Corner NE'] ] ), - (Op, DW, Hz, Ld, NP, 1): ( + (Op, DW, Hz, Ld, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, Ld, PL, 2): ( + (Op, DW, Hz, Ld, PL, 2, None): ( [ ['Dark Fortune EN', 'Dark Fortune ES'], ['Outcast Pond EN', 'Outcast Pond ES'], @@ -627,11 +627,11 @@ OWEdgeGroups = { ['Hype Cave WN', 'Hype Cave WS'] ] ), - (Op, DW, Vt, Ld, NP, 1): ( + (Op, DW, Vt, Ld, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, Ld, NP, 2): ( + (Op, DW, Hz, Ld, NP, 2, None): ( [ ['Dig Game EC', 'Dig Game ES'] ], @@ -639,7 +639,7 @@ OWEdgeGroups = { ['Frog WC', 'Frog WS'] ] ), - (Op, DW, Vt, Ld, PL, 2): ( + (Op, DW, Vt, Ld, PL, 2, None): ( [ ['Skull Woods SW', 'Skull Woods SC'], ['Skull Woods Pass SW', 'Skull Woods Pass SE'], @@ -655,7 +655,7 @@ OWEdgeGroups = { ['Broken Bridge NW', 'Broken Bridge NE'] ] ), - (Op, DW, Hz, Ld, PL, 3): ( + (Op, DW, Hz, Ld, PL, 3, None): ( [ ['Dark Bonk Rocks EN', 'Dark Bonk Rocks EC', 'Dark Bonk Rocks ES'] ], @@ -663,7 +663,7 @@ OWEdgeGroups = { ['Big Bomb Shop WN', 'Big Bomb Shop WC', 'Big Bomb Shop WS'] ] ), - (Op, DW, Hz, Wr, PL, 1): ( + (Op, DW, Hz, Wr, PL, 1, None): ( [ ['Dark Witch EN'], ['Ice Lake EC'], @@ -679,11 +679,11 @@ OWEdgeGroups = { ['Hype Cave WC'] ] ), - (Op, DW, Hz, Wr, NP, 1): ( + (Op, DW, Hz, Wr, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Vt, Wr, PL, 1): ( + (Op, DW, Vt, Wr, PL, 1, None): ( [ ['Dark Tree Line SC'], ['Shopping Mall SW'], @@ -699,7 +699,7 @@ OWEdgeGroups = { OWEdgeGroupsTerrain = { #(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup) - (St, LW, Vt, None, PL, 1): ( + (St, LW, Vt, None, PL, 1, None): ( [ ['Hyrule Castle SW'], ['Hyrule Castle SE'] @@ -709,7 +709,7 @@ OWEdgeGroupsTerrain = { ['Links House NE'] ] ), - (St, LW, Hz, None, PL, 3): ( + (St, LW, Hz, None, PL, 3, None): ( [ ['Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES'] ], @@ -717,7 +717,7 @@ OWEdgeGroupsTerrain = { ['Links House WN', 'Links House WC', 'Links House WS'] ] ), - (Op, LW, Hz, None, PL, 1): ( + (Op, LW, Hz, None, PL, 1, None): ( [ ['Lost Woods EN'], ['East Death Mountain EN'], @@ -751,7 +751,7 @@ OWEdgeGroupsTerrain = { ['East Death Mountain WS'] ] ), - (Op, LW, Hz, None, NP, 1): ( + (Op, LW, Hz, None, NP, 1, None): ( [ ['Forgotten Forest ES'], ['Hobo EC'] @@ -761,7 +761,7 @@ OWEdgeGroupsTerrain = { ['Stone Bridge WC'] ] ), - (Op, LW, Vt, None, PL, 1): ( + (Op, LW, Vt, None, PL, 1, None): ( [ ['Lumberjack SW'], ['Mountain Pass SE'], @@ -797,7 +797,7 @@ OWEdgeGroupsTerrain = { ['South Pass NC'] ] ), - (Op, LW, Vt, None, NP, 1): ( + (Op, LW, Vt, None, NP, 1, None): ( [ ['Master Sword Meadow SC'], ['Zoras Domain SW'] @@ -807,7 +807,7 @@ OWEdgeGroupsTerrain = { ['Zora Waterfall NE'] ] ), - (Op, LW, Hz, None, PL, 2): ( + (Op, LW, Hz, None, PL, 2, None): ( [ ['Kakariko Fortune EN', 'Kakariko Fortune ES'], ['Kakariko Pond EN', 'Kakariko Pond ES'], @@ -825,7 +825,7 @@ OWEdgeGroupsTerrain = { ['Tree Line WN', 'Tree Line WC'] ] ), - (Op, LW, Hz, None, NP, 2): ( + (Op, LW, Hz, None, NP, 2, None): ( [ ['Desert EC', 'Desert ES'] ], @@ -833,7 +833,7 @@ OWEdgeGroupsTerrain = { ['Desert Pass WC', 'Desert Pass WS'] ] ), - (Op, LW, Vt, None, PL, 2): ( + (Op, LW, Vt, None, PL, 2, None): ( [ ['Lost Woods SW', 'Lost Woods SC'], ['Lost Woods Pass SW', 'Lost Woods Pass SE'], @@ -851,7 +851,7 @@ OWEdgeGroupsTerrain = { ['Octoballoon NW', 'Octoballoon NE'] ] ), - (Op, LW, Hz, None, PL, 3): ( + (Op, LW, Hz, None, PL, 3, None): ( [ ['River Bend EN', 'River Bend EC', 'River Bend ES'], ['C Whirlpool EN', 'C Whirlpool EC', 'C Whirlpool ES'] @@ -861,7 +861,7 @@ OWEdgeGroupsTerrain = { ['Statues WN', 'Statues WC', 'Statues WS'] ] ), - (Op, LW, Vt, None, PL, 3): ( + (Op, LW, Vt, None, PL, 3, None): ( [ ['River Bend SW', 'River Bend SC', 'River Bend SE'] ], @@ -869,7 +869,7 @@ OWEdgeGroupsTerrain = { ['Wooden Bridge NW', 'Wooden Bridge NC', 'Wooden Bridge NE'] ] ), - (Op, DW, Hz, None, PL, 1): ( + (Op, DW, Hz, None, PL, 1, None): ( [ ['Skull Woods EN'], ['East Dark Death Mountain EN'], @@ -901,7 +901,7 @@ OWEdgeGroupsTerrain = { ['East Dark Death Mountain WS'] ] ), - (Op, DW, Vt, None, PL, 1): ( + (Op, DW, Vt, None, PL, 1, None): ( [ ['Dark Lumberjack SW'], ['Bumper Cave SE'], @@ -941,11 +941,11 @@ OWEdgeGroupsTerrain = { ['Dark South Pass NC'] ] ), - (Op, DW, Hz, None, NP, 1): ( + (Op, DW, Hz, None, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, None, PL, 2): ( + (Op, DW, Hz, None, PL, 2, None): ( [ ['Dark Fortune EN', 'Dark Fortune ES'], ['Outcast Pond EN', 'Outcast Pond ES'], @@ -963,11 +963,11 @@ OWEdgeGroupsTerrain = { ['Dark Tree Line WN', 'Dark Tree Line WC'] ] ), - (Op, DW, Vt, None, NP, 1): ( + (Op, DW, Vt, None, NP, 1, None): ( [ ], [ ] ), - (Op, DW, Hz, None, NP, 2): ( + (Op, DW, Hz, None, NP, 2, None): ( [ ['Dig Game EC', 'Dig Game ES'] ], @@ -975,7 +975,7 @@ OWEdgeGroupsTerrain = { ['Frog WC', 'Frog WS'] ] ), - (Op, DW, Vt, None, PL, 2): ( + (Op, DW, Vt, None, PL, 2, None): ( [ ['Skull Woods SW', 'Skull Woods SC'], ['Skull Woods Pass SW', 'Skull Woods Pass SE'], @@ -993,7 +993,7 @@ OWEdgeGroupsTerrain = { ['Bomber Corner NW', 'Bomber Corner NE'] ] ), - (Op, DW, Hz, None, PL, 3): ( + (Op, DW, Hz, None, PL, 3, None): ( [ ['Dark Bonk Rocks EN', 'Dark Bonk Rocks EC', 'Dark Bonk Rocks ES'], ['Qirn Jump EN', 'Qirn Jump EC', 'Qirn Jump ES'], @@ -1005,7 +1005,7 @@ OWEdgeGroupsTerrain = { ['Hype Cave WN', 'Hype Cave WC', 'Hype Cave WS'] ] ), - (Op, DW, Vt, None, PL, 3): ( + (Op, DW, Vt, None, PL, 3, None): ( [ ['Qirn Jump SW', 'Qirn Jump SC', 'Qirn Jump SE'] ], @@ -1558,23 +1558,39 @@ OWExitTypes = { 'Ice Lake Southeast Water Drop', 'Bomber Corner Waterfall Water Drop' ], - 'OWTerrain': ['Lost Woods Bush (West)', - 'Lost Woods Bush (East)', - 'Old Man Drop Off', + 'OpenTerrain': ['Old Man Drop Off', 'Spectacle Rock Approach', 'Spectacle Rock Leave', - 'DM Hammer Bridge (West)', - 'DM Hammer Bridge (East)', 'Floating Island Bridge (East)', - 'Fairy Ascension Rocks (Inner)', - 'DM Broken Bridge (West)', - 'DM Broken Bridge (East)', 'Spiral Mimic Bridge (West)', 'Spiral Mimic Bridge (East)', 'Spiral Ledge Approach', 'Mimic Ledge Approach', - 'Fairy Ascension Rocks (Outer)', 'Floating Island Bridge (West)', + 'Graveyard Ladder (Bottom)', + 'Graveyard Ladder (Top)', + 'Hyrule Castle Main Gate (South)', + 'Hyrule Castle Main Gate (North)', + 'Stone Bridge (Northbound)', + 'Stone Bridge (Southbound)', + 'Checkerboard Ledge Approach', + 'Checkerboard Ledge Leave', + 'Cave 45 Approach', + 'Cave 45 Leave', + 'Middle Aged Man', + 'Desert Pass Ladder (South)', + 'Desert Pass Ladder (North)', + 'GT Approach', + 'GT Leave', + ], + 'OWTerrain': ['Lost Woods Bush (West)', + 'Lost Woods Bush (East)', + 'DM Hammer Bridge (West)', + 'DM Hammer Bridge (East)', + 'Fairy Ascension Rocks (Inner)', + 'DM Broken Bridge (West)', + 'DM Broken Bridge (East)', + 'Fairy Ascension Rocks (Outer)', 'TR Pegs Ledge Entry', 'TR Pegs Ledge Leave', 'Mountain Pass Rock (Outer)', @@ -1587,8 +1603,6 @@ OWExitTypes = { 'Lost Woods Pass Rock (North)', 'Lost Woods Pass Rock (South)', 'Kings Grave Rocks (Outer)', - 'Graveyard Ladder (Bottom)', - 'Graveyard Ladder (Top)', 'Kings Grave Rocks (Inner)', 'River Bend Water Drop', 'River Bend West Pier', @@ -1602,12 +1616,10 @@ OWExitTypes = { 'Kakariko Yard Bush (South)', 'Kakariko Southwest Bush (South)', 'Kakariko Yard Bush (North)', - 'Hyrule Castle Main Gate (South)', 'Hyrule Castle East Rock (Inner)', 'Hyrule Castle Southwest Bush (North)', 'Hyrule Castle Southwest Bush (South)', 'Hyrule Castle Courtyard Bush (South)', - 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle East Rock (Outer)', 'Wooden Bridge Bush (South)', @@ -1615,17 +1627,11 @@ OWExitTypes = { 'Blacksmith Ledge Peg (West)', 'Blacksmith Ledge Peg (East)', 'Maze Race Game', - 'Stone Bridge (Northbound)', - 'Stone Bridge (Southbound)', 'Desert Statue Move', - 'Checkerboard Ledge Approach', 'Desert Ledge Rocks (Outer)', 'Desert Ledge Rocks (Inner)', - 'Checkerboard Ledge Leave', 'Flute Boy Bush (South)', - 'Cave 45 Approach', 'Flute Boy Bush (North)', - 'Cave 45 Leave', 'C Whirlpool Rock (Bottom)', 'C Whirlpool Rock (Top)', 'C Whirlpool Pegs (Outer)', @@ -1643,19 +1649,15 @@ OWExitTypes = { 'Lake Hylia East Pier', 'Lake Hylia Water D Approach', 'Lake Hylia Water D Leave', - 'Desert Pass Ladder (South)', + 'Ice Cave Pier', 'Desert Pass Rocks (North)', 'Desert Pass Rocks (South)', - 'Desert Pass Ladder (North)', - 'Middle Aged Man', 'Octoballoon Water Drop', 'Octoballoon Pier', 'Skull Woods Rock (East)', 'Skull Woods Rock (West)', 'Skull Woods Forgotten Bush (West)', 'Skull Woods Forgotten Bush (East)', - 'GT Approach', - 'GT Leave', 'East Dark Death Mountain Bushes', 'Bumper Cave Rock (Outer)', 'Bumper Cave Rock (Inner)', @@ -1702,6 +1704,7 @@ OWExitTypes = { 'Ice Lake Northeast Pier', 'Ice Lake Northeast Pier Hop', 'Ice Lake Iceberg Water Entry', + 'Shopping Mall Pier', 'Bomber Corner Water Drop', 'Bomber Corner Pier' ], diff --git a/OverworldGlitchRules.py b/OverworldGlitchRules.py index 0e05246d..4f6c2454 100644 --- a/OverworldGlitchRules.py +++ b/OverworldGlitchRules.py @@ -5,102 +5,86 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. from BaseClasses import Entrance from OWEdges import OWTileRegions +# Cave regions that superbunny can get through - but only with a sword. +sword_required_superbunny_mirror_regions = ["Spiral Cave (Top)"] -def get_sword_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with a sword. - """ - yield 'Spiral Cave (Top)' +# Cave regions that superbunny can get through - but only with boots. +boots_required_superbunny_mirror_regions = ["Two Brothers House"] -def get_boots_required_superbunny_mirror_regions(): - """ - Cave regions that superbunny can get through - but only with boots. - """ - yield 'Two Brothers House' +# Cave locations that superbunny can access - but only with boots. +boots_required_superbunny_mirror_locations = [ + "Sahasrahla's Hut - Left", + "Sahasrahla's Hut - Middle", + "Sahasrahla's Hut - Right", +] -def get_boots_required_superbunny_mirror_locations(): - """ - Cave locations that superbunny can access - but only with boots. - """ - yield 'Sahasrahla\'s Hut - Left' - yield 'Sahasrahla\'s Hut - Middle' - yield 'Sahasrahla\'s Hut - Right' +# Entrances that can't be superbunny-mirrored into. +invalid_mirror_bunny_entrances = [ + "Hype Cave", + "Bonk Fairy (Dark)", + "Thieves Town", + "Hammer Peg Cave", + "Brewery", + "Hookshot Cave", + "Dark Lake Hylia Ledge Fairy", + "Dark Lake Hylia Ledge Spike Cave", + "Palace of Darkness", + "Misery Mire", + "Turtle Rock", + "Bonk Rock Cave", + "Bonk Fairy (Light)", + "50 Rupee Cave", + "20 Rupee Cave", + "Checkerboard Cave", + "Light Hype Fairy", + "Waterfall of Wishing", + "Light World Bomb Hut", + "Mini Moldorm Cave", + "Ice Rod Cave", + "Sanctuary Grave", + "Kings Grave", + "Sanctuary Grave", + "Hyrule Castle Secret Entrance Drop", + "Skull Woods Second Section Hole", + "Skull Woods First Section Hole (North)", +] + +# Interior locations that can be accessed with superbunny state. +superbunny_accessible_locations = [ + "Waterfall of Wishing - Left", + "Waterfall of Wishing - Right", + "King's Tomb", + "Floodgate", + "Floodgate Chest", + "Cave 45", + "Bonk Rock Cave", + "Brewery", + "C-Shaped House", + "Chest Game", + "Mire Shed - Left", + "Mire Shed - Right", + "Secret Passage", + "Ice Rod Cave", + "Pyramid Fairy - Left", + "Pyramid Fairy - Right", + "Superbunny Cave - Top", + "Superbunny Cave - Bottom", + "Blind's Hideout - Left", + "Blind's Hideout - Right", + "Blind's Hideout - Far Left", + "Blind's Hideout - Far Right", + "Kakariko Well - Left", + "Kakariko Well - Middle", + "Kakariko Well - Right", + "Kakariko Well - Bottom", + "Kakariko Tavern", + "Library", + "Spiral Cave", +] + boots_required_superbunny_mirror_locations # TODO: Add pottery locations -def get_invalid_mirror_bunny_entrances(): - """ - Entrances that can't be superbunny-mirrored into. - """ - yield 'Skull Woods Final Section' - yield 'Hype Cave' - yield 'Bonk Fairy (Dark)' - yield 'Thieves Town' - yield 'Hammer Peg Cave' - yield 'Brewery' - yield 'Hookshot Cave' - yield 'Dark Lake Hylia Ledge Fairy' - yield 'Dark Lake Hylia Ledge Spike Cave' - yield 'Palace of Darkness' - yield 'Misery Mire' - yield 'Turtle Rock' - yield 'Bonk Rock Cave' - yield 'Bonk Fairy (Light)' - yield '50 Rupee Cave' - yield '20 Rupee Cave' - yield 'Checkerboard Cave' - yield 'Light Hype Fairy' - yield 'Waterfall of Wishing' - yield 'Light World Bomb Hut' - yield 'Mini Moldorm Cave' - yield 'Ice Rod Cave' - yield 'Sanctuary Grave' - yield 'Kings Grave' - yield 'Sanctuary Grave' - yield 'Hyrule Castle Secret Entrance Drop' - yield 'Skull Woods Second Section Hole' - yield 'Skull Woods First Section Hole (North)' - - -def get_superbunny_accessible_locations(): - """ - Interior locations that can be accessed with superbunny state. - """ - - yield 'Waterfall of Wishing - Left' - yield 'Waterfall of Wishing - Right' - yield 'King\'s Tomb' - yield 'Floodgate' - yield 'Floodgate Chest' - yield 'Cave 45' - yield 'Bonk Rock Cave' - yield 'Brewery' - yield 'C-Shaped House' - yield 'Chest Game' - yield 'Mire Shed - Left' - yield 'Mire Shed - Right' - yield 'Secret Passage' - yield 'Ice Rod Cave' - yield 'Pyramid Fairy - Left' - yield 'Pyramid Fairy - Right' - yield 'Superbunny Cave - Top' - yield 'Superbunny Cave - Bottom' - yield 'Blind\'s Hideout - Left' - yield 'Blind\'s Hideout - Right' - yield 'Blind\'s Hideout - Far Left' - yield 'Blind\'s Hideout - Far Right' - yield 'Kakariko Well - Left' - yield 'Kakariko Well - Middle' - yield 'Kakariko Well - Right' - yield 'Kakariko Well - Bottom' - yield 'Kakariko Tavern' - yield 'Library' - yield 'Spiral Cave' - for location in get_boots_required_superbunny_mirror_locations(): - yield location - - def get_non_mandatory_exits(world, player): """ Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit. @@ -295,8 +279,9 @@ def overworld_glitches_rules(world, player): #set_owg_rules(player, world, get_glitched_speed_drops_dw(world, player), lambda state: state.can_get_glitched_speed_dw(player)) # Mirror clip spots. + # TODO: Should this also require can_boots_clip set_owg_rules(player, world, get_mirror_clip_spots(world, player), lambda state: state.has_Mirror(player)) - + # Mirror offset spots. for data in get_mirror_offset_spots(world, player): set_owg_rules(player, world, [data[0:3]], lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player) and state.can_reach(data[3], None, player)) @@ -319,6 +304,18 @@ def overworld_glitches_rules(world, player): add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) add_additional_rule(world.get_entrance('Dark Tree Line Water Clip', player), lambda state: state.has('Flippers', player)) + # Bunny pocket + if not world.is_tile_swapped(0x00, player): + add_alternate_rule(world.get_entrance("Skull Woods Final Section", player), lambda state: state.can_bunny_pocket(player) and state.has("Fire Rod", player)) + if world.is_tile_swapped(0x05, player): + add_alternate_rule(world.get_entrance("DM Hammer Bridge (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + add_alternate_rule(world.get_entrance("DM Hammer Bridge (East)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + if not world.is_tile_swapped(0x18, player): + add_alternate_rule(world.get_entrance("Bush Yard Pegs (Inner)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + add_alternate_rule(world.get_entrance("Bush Yard Pegs (Outer)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + if world.is_tile_swapped(0x22, player): + add_alternate_rule(world.get_entrance("Blacksmith Ledge Peg (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player)) + def add_alternate_rule(entrance, rule): old_rule = entrance.access_rule @@ -335,7 +332,7 @@ def create_no_logic_connections(player, world, connections): parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) connection = Entrance(player, entrance, parent) - connection.spot_type = 'Ledge' + connection.spot_type = 'OWG' parent.exits.append(connection) connection.connect(target) @@ -387,6 +384,8 @@ boots_clips_local = [ # (name, from_region, to_region) ('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize'), + #('Hobo Water Clip', 'Stone Bridge South Area', 'Stone Bridge Water'), # TODO: Doesn't work with OW Free Terrain + ('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water'), #requires flippers ('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water'), #requires flippers diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d46ddac1..d64cf865 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -3,27 +3,30 @@ from collections import OrderedDict, defaultdict from DungeonGenerator import GenerationException from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_light_dark_world_regions +from source.overworld.EntranceShuffle2 import connect_simple from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel from OverworldGlitchRules import create_owg_connections from Utils import bidict -version_number = '0.3.2.2' +version_number = '0.3.4.2' # branch indicator is intentionally different across branches version_branch = '' __version__ = '%s%s' % (version_number, version_branch) +parallel_links_new = None # needs to be globally available, reset every new generation/player + def link_overworld(world, player): + global parallel_links_new + # setup mandatory connections for exitname, regionname in mandatory_connections: connect_simple(world, exitname, regionname, player) def performSwap(groups, swaps): def getParallel(edgename): - if edgename in parallel_links: - return parallel_links[edgename] - elif edgename in parallel_links.inverse: - return parallel_links.inverse[edgename][0] + if edgename in parallel_links_new: + return parallel_links_new[edgename] else: raise Exception('No parallel edge found for edge %s', edgename) @@ -57,7 +60,7 @@ def link_overworld(world, player): new_groups[group] = ([],[]) for group in groups.keys(): - (mode, wrld, dir, terrain, parallel, count) = group + (mode, wrld, dir, terrain, parallel, count, custom) = group for (forward_set, back_set) in zip(groups[group][0], groups[group][1]): anyF = any(edge in orig_swaps for edge in forward_set) anyB = any(edge in orig_swaps for edge in back_set) @@ -72,11 +75,11 @@ def link_overworld(world, player): if parallel == IsParallel.Yes and not (all(edge in orig_swaps for edge in map(getParallel, forward_set)) and all(edge in orig_swaps for edge in map(getParallel, back_set))): raise Exception('Cannot move a parallel edge without the other') new_mode = OpenStd.Open - if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)) not in new_groups.keys(): + if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)) not in new_groups.keys(): # when Links House tile is flipped, the DW edges need to get put into existing Standard group new_mode = OpenStd.Standard - new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][0].append(forward_set) - new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][1].append(back_set) + new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)][0].append(forward_set) + new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count, custom)][1].append(back_set) for edge in forward_set: swaps.remove(edge) for edge in back_set: @@ -106,36 +109,39 @@ def link_overworld(world, player): raise NotImplementedError('Invalid OW Edge flip scenario') return new_groups - tile_groups = define_tile_groups(world, player, False) trimmed_groups = copy.deepcopy(OWEdgeGroupsTerrain if world.owTerrain[player] else OWEdgeGroups) swapped_edges = list() # restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship + parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken) if world.owKeepSimilar[player]: + del parallel_links_new['Maze Race ES'] + del parallel_links_new['Kakariko Suburb WS'] for group in trimmed_groups.keys(): - (std, region, axis, terrain, parallel, _) = group + (std, region, axis, terrain, parallel, _, custom) = group if parallel == IsParallel.Yes: (forward_edges, back_edges) = trimmed_groups[group] if ['Maze Race ES'] in forward_edges: - forward_edges = list(filter((['Maze Race ES']).__ne__, forward_edges)) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][0].append(['Maze Race ES']) + forward_edges.remove(['Maze Race ES']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES']) if ['Kakariko Suburb WS'] in back_edges: - back_edges = list(filter((['Kakariko Suburb WS']).__ne__, back_edges)) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Kakariko Suburb WS']) + back_edges.remove(['Kakariko Suburb WS']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS']) trimmed_groups[group] = (forward_edges, back_edges) else: for group in trimmed_groups.keys(): - (std, region, axis, terrain, _, _) = group + (std, region, axis, terrain, _, _, custom) = group (forward_edges, back_edges) = trimmed_groups[group] if ['Dig Game EC', 'Dig Game ES'] in forward_edges: - forward_edges = list(filter((['Dig Game EC', 'Dig Game ES']).__ne__, forward_edges)) - trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1)][0].append(['Dig Game ES']) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][0].append(['Dig Game EC']) + forward_edges.remove(['Dig Game EC', 'Dig Game ES']) + trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC']) if ['Frog WC', 'Frog WS'] in back_edges: - back_edges = list(filter((['Frog WC', 'Frog WS']).__ne__, back_edges)) - trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1)][1].append(['Frog WS']) - trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1)][1].append(['Frog WC']) + back_edges.remove(['Frog WC', 'Frog WS']) + trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS']) + trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC']) trimmed_groups[group] = (forward_edges, back_edges) + parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})} connected_edges = [] if world.owShuffle[player] != 'vanilla': @@ -145,7 +151,8 @@ def link_overworld(world, player): # tile shuffle logging.getLogger('').debug('Flipping overworld tiles') if world.owMixed[player]: - swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player) + tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, False, player) + swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, (force_flipped, force_nonflipped, undefined_chance), player) update_world_regions(world, player) @@ -193,82 +200,146 @@ def link_overworld(world, player): categorize_world_regions(world, player) - if world.logic[player] in ('owglitches', 'nologic'): + if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'): create_owg_connections(world, player) # crossed shuffle logging.getLogger('').debug('Crossing overworld edges') - crossed_edges = list() - # more Maze Race/Suburb/Frog/Dig Game fixes - parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken) - if world.owKeepSimilar[player]: - del parallel_links_new['Maze Race ES'] - del parallel_links_new['Kakariko Suburb WS'] + # customizer setup + force_crossed = set() + force_noncrossed = set() + count_crossed = 0 + limited_crossed = -1 + undefined_chance = 50 + if world.customizer: + custom_crossed = world.customizer.get_owcrossed() + if custom_crossed and player in custom_crossed: + custom_crossed = custom_crossed[player] + if 'force_crossed' in custom_crossed and len(custom_crossed['force_crossed']) > 0: + for edgename in custom_crossed['force_crossed']: + edge = world.get_owedge(edgename, player) + force_crossed.add(edge.name) + if 'force_noncrossed' in custom_crossed and len(custom_crossed['force_noncrossed']) > 0: + for edgename in custom_crossed['force_noncrossed']: + edge = world.get_owedge(edgename, player) + force_noncrossed.add(edge.name) + if 'limit_crossed' in custom_crossed: + if world.owCrossed[player] == 'unrestricted': + limited_crossed = custom_crossed['limit_crossed'] + if 'undefined_chance' in custom_crossed: + undefined_chance = custom_crossed['undefined_chance'] - #TODO: Revisit with changes to Limited/Allowed - if world.owCrossed[player] not in ['none', 'grouped', 'polar', 'chaos']: + if limited_crossed > -1: + # connect forced crossed non-parallel edges based on previously determined tile flips for edge in swapped_edges: - if edge not in parallel_links_new and edge not in parallel_links_new.inverse: - crossed_edges.append(edge) - - if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'): - if world.owCrossed[player] == 'grouped': - # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work - # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask - ow_crossed_tiles_mask = [[],[],[]] - crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player) - ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])] + if edge not in parallel_links_new: + world.owcrossededges[player].append(edge) + count_crossed = count_crossed + 1 - # update spoiler - s = list(map(lambda x: 'O' if x not in ow_crossed_tiles else 'X', [i for i in range(0x40, 0x82)])) - text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], - s[0x00], s[0x03], s[0x05], - s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], - s[0x0a], s[0x0f], - s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], - s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e], - s[0x22], s[0x25], s[0x1a], s[0x1d], - s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e], - s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25], - s[0x3a],s[0x3b],s[0x3c], s[0x3f], - s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], - s[0x40], s[0x32],s[0x33],s[0x34], s[0x37], - s[0x30], s[0x35], - s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f]) - world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player) - else: - crossed_candidates = list() - for group in trimmed_groups.keys(): - (mode, wrld, dir, terrain, parallel, count) = group - if wrld == WorldType.Light and mode != OpenStd.Standard: - for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): - if forward_set[0] in parallel_links_new or forward_set[0] in parallel_links_new.inverse: - if world.owKeepSimilar[player]: - if world.owCrossed[player] == 'chaos' and random.randint(0, 1): - for edge in forward_set: - crossed_edges.append(edge) - elif world.owCrossed[player] == 'limited': - crossed_candidates.append(forward_set) - else: - for edge in forward_set: - if world.owCrossed[player] == 'chaos' and random.randint(0, 1): - crossed_edges.append(edge) - elif world.owCrossed[player] == 'limited': - crossed_candidates.append([edge]) - if world.owCrossed[player] == 'limited': + if world.owCrossed[player] == 'grouped': + # the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work + # Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask + ow_crossed_tiles_mask = [[],[],[]] + tile_groups, force_flipped, force_nonflipped, undefined_chance = define_tile_groups(world, True, player) + world.owcrossededges[player] = shuffle_tiles(world, tile_groups, ow_crossed_tiles_mask, True, (force_flipped, force_nonflipped, undefined_chance), player) + ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])] + + # update spoiler + s = list(map(lambda x: 'O' if x not in ow_crossed_tiles else 'X', [i for i in range(0x40, 0x82)])) + text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], + s[0x00], s[0x03], s[0x05], + s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f], + s[0x0a], s[0x0f], + s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], + s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e], + s[0x22], s[0x25], s[0x1a], s[0x1d], + s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e], + s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25], + s[0x3a],s[0x3b],s[0x3c], s[0x3f], + s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], + s[0x40], s[0x32],s[0x33],s[0x34], s[0x37], + s[0x30], s[0x35], + s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f]) + world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player) + elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'): + crossed_candidates = list() + for group in trimmed_groups.keys(): + (mode, wrld, dir, terrain, parallel, count, _) = group + if wrld == WorldType.Light and mode != OpenStd.Standard: + for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]): + if forward_set[0] in parallel_links_new: + forward_parallel = [parallel_links_new[e] for e in forward_set] + back_parallel = [parallel_links_new[e] for e in back_set] + forward_combine = forward_set+forward_parallel + back_combine = back_set+back_parallel + combine_set = forward_combine+back_combine + + skip_forward = False + if world.owShuffle[player] == 'vanilla': + if any(edge in force_crossed for edge in combine_set): + if not any(edge in force_noncrossed for edge in combine_set): + if any(edge in force_crossed for edge in forward_combine): + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + continue + else: + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + continue + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in combine_set): + continue + else: + skip_back = False + if any(edge in force_crossed for edge in forward_combine): + if not any(edge in force_noncrossed for edge in forward_combine): + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + skip_forward = True + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in force_crossed for edge in back_combine): + if not any(edge in force_noncrossed for edge in back_combine): + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + skip_back = True + else: + raise GenerationException('Conflict detected in force_crossed and force_noncrossed') + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in forward_combine): + skip_forward = True + if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in back_combine): + skip_back = True + if not skip_back: + if limited_crossed > -1: + crossed_candidates.append(back_set) + elif random.randint(1, 100) <= undefined_chance: + world.owcrossededges[player].extend(back_set) + count_crossed = count_crossed + 1 + if not skip_forward: + if limited_crossed > -1: + crossed_candidates.append(forward_set) + elif random.randint(1, 100) <= undefined_chance: + world.owcrossededges[player].extend(forward_set) + count_crossed = count_crossed + 1 + assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges" + + if limited_crossed > -1: + limit = limited_crossed - count_crossed + if limit > 1: random.shuffle(crossed_candidates) - for edge_set in crossed_candidates[:9]: - for edge in edge_set: - crossed_edges.append(edge) - for edge in copy.deepcopy(crossed_edges): - if edge in parallel_links_new: - crossed_edges.append(parallel_links_new[edge]) - elif edge in parallel_links_new.inverse: - crossed_edges.append(parallel_links_new.inverse[edge][0]) + for edge_set in crossed_candidates[:limit]: + world.owcrossededges[player].extend(edge_set) + assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges" + + for edge in copy.deepcopy(world.owcrossededges[player]): + if edge in parallel_links_new: + if parallel_links_new[edge] not in world.owcrossededges[player]: + world.owcrossededges[player].append(parallel_links_new[edge]) # after tile flip and crossed, determine edges that need to flip - edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)] + edges_to_swap = [e for e in swapped_edges+world.owcrossededges[player] if (e not in swapped_edges) or (e not in world.owcrossededges[player])] # whirlpool shuffle logging.getLogger('').debug('Shuffling whirlpools') @@ -278,16 +349,24 @@ def link_overworld(world, player): connect_simple(world, from_whirlpool, to_region, player) connect_simple(world, to_whirlpool, from_region, player) else: + def connect_whirlpool(from_whirlpool, to_whirlpool): + (from_owid, from_name, from_region) = from_whirlpool + (to_owid, to_name, to_region) = to_whirlpool + connect_simple(world, from_name, to_region, player) + connect_simple(world, to_name, from_region, player) + world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid + world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid + connected_whirlpools.append(tuple((from_name, to_name))) + world.spoiler.set_whirlpool(from_name, to_name, 'both', player) + + whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ] whirlpool_candidates = [[],[]] + connected_whirlpools = [] world.owwhirlpools[player] = [None] * 8 for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections: if world.owCrossed[player] == 'polar' and world.owMixed[player] and from_owid == 0x55: # connect the 2 DW whirlpools in Polar Mixed - connect_simple(world, from_whirlpool, to_region, player) - connect_simple(world, to_whirlpool, from_region, player) - world.owwhirlpools[player][7] = from_owid - world.owwhirlpools[player][6] = to_owid - world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player) + connect_whirlpool((from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region)) else: if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \ or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \ @@ -304,19 +383,29 @@ def link_overworld(world, player): whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region))) # shuffle happens here - whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ] + if world.customizer: + custom_whirlpools = world.customizer.get_whirlpools() + if custom_whirlpools and player in custom_whirlpools: + custom_whirlpools = custom_whirlpools[player] + if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0: + for whirlpools in whirlpool_candidates: + for whirlname1, whirlname2 in custom_whirlpools['two-way'].items(): + whirl1 = next((w for w in whirlpools if w[1] == whirlname1), None) + whirl2 = next((w for w in whirlpools if w[1] == whirlname2), None) + if whirl1 and whirl2: + whirlpools.remove(whirl1) + whirlpools.remove(whirl2) + connect_whirlpool(whirl1, whirl2) + elif whirl1 != whirl2: + raise GenerationException('Attempting to connect whirlpools not in same pool: \'%s\' <-> \'%s\'', whirlname1, whirlname2) + elif any(w for w in connected_whirlpools if (whirlname1 in w) != (whirlname2 in w)): + raise GenerationException('Attempting to connect whirlpools already connected: \'%s\' <-> \'%s\'', whirlname1, whirlname2) for whirlpools in whirlpool_candidates: random.shuffle(whirlpools) while len(whirlpools): if len(whirlpools) % 2 == 1: x=0 - from_owid, from_whirlpool, from_region = whirlpools.pop() - to_owid, to_whirlpool, to_region = whirlpools.pop() - connect_simple(world, from_whirlpool, to_region, player) - connect_simple(world, to_whirlpool, from_region, player) - world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid - world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid - world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player) + connect_whirlpool(whirlpools.pop(), whirlpools.pop()) # layout shuffle logging.getLogger('').debug('Shuffling overworld layout') @@ -337,64 +426,135 @@ def link_overworld(world, player): world.owsectors[player] = build_sectors(world, player) else: - if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: + if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel': for exitname, destname in parallelsimilar_connections: connect_two_way(world, exitname, destname, player, connected_edges) #TODO: Remove, just for testing for exitname, destname in test_connections: connect_two_way(world, exitname, destname, player, connected_edges) - - connect_custom(world, connected_edges, player) - + # layout shuffle groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) + + connect_custom(world, connected_edges, groups, (force_crossed, force_noncrossed), player) tries = 100 valid_layout = False connected_edge_cache = connected_edges.copy() + groups_cache = copy.deepcopy(groups) while not valid_layout and tries > 0: + def remove_connected(forward_sets, back_sets): + deleted_edges = [] + def remove_from_sets(sets): + s = 0 + while s < len(sets): + if sets[s][0] in connected_edges: + deleted_edges.extend(sets[s]) + del sets[s] + continue + s += 1 + remove_from_sets(forward_sets) + remove_from_sets(back_sets) + if len(forward_sets) != len(back_sets): + x=', '.join(deleted_edges) + x=0 + assert len(forward_sets) == len(back_sets), "OW edge pool is uneven due to prior connections: " + ', '.join(deleted_edges) + + def connect_set(forward_set, back_set, connected_edges): + if forward_set is not None and back_set is not None: + assert len(forward_set) == len(back_set) + for (forward_edge, back_edge) in zip(forward_set, back_set): + connect_two_way(world, forward_edge, back_edge, player, connected_edges) + elif forward_set is not None: + logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0]) + elif back_set is not None: + logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0]) + connected_edges = connected_edge_cache.copy() + groups = copy.deepcopy(groups_cache) + groupKeys = list(groups.keys()) if world.mode[player] == 'standard': - random.shuffle(groups[2:]) # keep first 2 groups (Standard) first + subset = groupKeys[2:] + random.shuffle(subset) # keep first 2 groups (Standard) first + groupKeys[2:] = subset else: - random.shuffle(groups) + random.shuffle(groupKeys) - for (forward_edge_sets, back_edge_sets) in groups: - assert len(forward_edge_sets) == len(back_edge_sets) + for key in groupKeys: + (mode, wrld, dir, terrain, parallel, count, _) = key + (forward_edge_sets, back_edge_sets) = groups[key] + remove_connected(forward_edge_sets, back_edge_sets) random.shuffle(forward_edge_sets) random.shuffle(back_edge_sets) - if len(forward_edge_sets) > 0: - f = 0 - b = 0 - while f < len(forward_edge_sets) and b < len(back_edge_sets): - forward_set = forward_edge_sets[f] - back_set = back_edge_sets[b] - while forward_set[0] in connected_edges: - f += 1 - if f < len(forward_edge_sets): - forward_set = forward_edge_sets[f] + + if wrld is None and len(force_crossed) + len(force_noncrossed) > 0: + # divide forward/back sets into LW/DW + forward_lw_sets, forward_dw_sets = [], [] + back_lw_sets, back_dw_sets = [], [] + forward_parallel_lw_sets, forward_parallel_dw_sets = [], [] + back_parallel_lw_sets, back_parallel_dw_sets = [], [] + + def add_edgeset_to_worldsets(edge_set, sets, parallel_sets): + sets.append(edge_set) + if parallel == IsParallel.Yes: + parallel_sets.append([parallel_links_new[e] for e in edge_set]) + for edge_set in forward_edge_sets: + if world.get_owedge(edge_set[0], player).is_lw(world): + add_edgeset_to_worldsets(edge_set, forward_lw_sets, forward_parallel_lw_sets) + else: + add_edgeset_to_worldsets(edge_set, forward_dw_sets, forward_parallel_dw_sets) + for edge_set in back_edge_sets: + if world.get_owedge(edge_set[0], player).is_lw(world): + add_edgeset_to_worldsets(edge_set, back_lw_sets, back_parallel_lw_sets) + else: + add_edgeset_to_worldsets(edge_set, back_dw_sets, back_parallel_dw_sets) + + crossed_sets = [] + noncrossed_sets = [] + def add_to_crossed_sets(sets, parallel_sets): + for i in range(0, len(sets)): + affected_edges = set(sets[i]+(parallel_sets[i] if parallel == IsParallel.Yes else [])) + if sets[i] not in crossed_sets and len(set.intersection(set(force_crossed), affected_edges)) > 0: + crossed_sets.append(sets[i]) + if sets[i] not in noncrossed_sets and len(set.intersection(set(force_noncrossed), affected_edges)) > 0: + noncrossed_sets.append(sets[i]) + if sets[i] in crossed_sets and sets[i] in noncrossed_sets: + raise GenerationException('Conflict in force crossed/non-crossed definition') + add_to_crossed_sets(forward_lw_sets, forward_parallel_lw_sets) + add_to_crossed_sets(forward_dw_sets, forward_parallel_dw_sets) + add_to_crossed_sets(back_lw_sets, back_parallel_lw_sets) + add_to_crossed_sets(back_dw_sets, back_parallel_dw_sets) + + # random connect forced crossed/noncrossed + def connect_forced(forced_sets, is_crossed, opposite_sets=[]): + c = 0 + while c < len(forced_sets): + if forced_sets[c] in forward_edge_sets: + forward_set = forced_sets[c] + if (forward_set in forward_lw_sets) != is_crossed: + back_set = next(s for s in back_lw_sets if s in back_edge_sets and s not in opposite_sets) + else: + back_set = next(s for s in back_dw_sets if s in back_edge_sets and s not in opposite_sets) + elif forced_sets[c] in back_edge_sets: + back_set = forced_sets[c] + if (back_set in back_lw_sets) != is_crossed: + forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets and s not in opposite_sets) + else: + forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets and s not in opposite_sets) else: - forward_set = None - break - f += 1 - while back_set[0] in connected_edges: - b += 1 - if b < len(back_edge_sets): - back_set = back_edge_sets[b] - else: - back_set = None - break - b += 1 - if forward_set is not None and back_set is not None: - assert len(forward_set) == len(back_set) - for (forward_edge, back_edge) in zip(forward_set, back_set): - connect_two_way(world, forward_edge, back_edge, player, connected_edges) - elif forward_set is not None: - logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0]) - elif back_set is not None: - logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0]) + c = c + 1 + continue + connect_set(forward_set, back_set, connected_edges) + remove_connected(forward_edge_sets, back_edge_sets) + c = c + 1 + connect_forced(noncrossed_sets, False, crossed_sets) + connect_forced(crossed_sets, True) + + while len(forward_edge_sets) > 0 and len(back_edge_sets) > 0: + connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges) + remove_connected(forward_edge_sets, back_edge_sets) assert len(connected_edges) == len(default_connections) * 2, connected_edges world.owsectors[player] = build_sectors(world, player) @@ -564,32 +724,129 @@ def link_overworld(world, player): s[0x3a],s[0x3b],s[0x3c], s[0x3f]) world.spoiler.set_map('flute', text_output, new_spots, player) -def connect_custom(world, connected_edges, player): - if hasattr(world, 'custom_overworld') and world.custom_overworld[player]: - for edgename1, edgename2 in world.custom_overworld[player]: - if edgename1 in connected_edges or edgename2 in connected_edges: - owedge1 = world.check_for_owedge(edgename1, player) - owedge2 = world.check_for_owedge(edgename2, player) - if owedge1.dest is not None and owedge1.dest.name == owedge2.name: - continue # if attempting to connect a pair that was already connected earlier, allow it to continue - raise RuntimeError('Invalid plando connection: rule violation based on current settings') - connect_two_way(world, edgename1, edgename2, player, connected_edges) - if world.owKeepSimilar[player]: #TODO: If connecting an edge that belongs to a similar pair, the remaining edges need to get connected automatically +def connect_custom(world, connected_edges, groups, forced, player): + forced_crossed, forced_noncrossed = forced + def remove_pair_from_pool(edgename1, edgename2, is_crossed): + def add_to_unresolved(forward_set, back_set): + if len(forward_set) > 1: + if edgename1 in forward_set: + forward_set.remove(edgename1) + back_set.remove(edgename2) + else: + back_set.remove(edgename1) + forward_set.remove(edgename2) + unresolved_similars.append(tuple((forward_set, back_set, is_crossed))) + for forward_pool, back_pool in groups.values(): + if not len(forward_pool): continue + if len(forward_pool[0]) == 1: + if [edgename1] in forward_pool: + if [edgename2] in back_pool: + forward_pool.remove([edgename1]) + back_pool.remove([edgename2]) + return + else: + break + elif [edgename1] in back_pool: + if [edgename2] in forward_pool: + back_pool.remove([edgename1]) + forward_pool.remove([edgename2]) + return + else: + break + else: + forward_similar = next((x for x in forward_pool if edgename1 in x), None) + if forward_similar: + back_similar = next((x for x in back_pool if edgename2 in x), None) + if back_similar: + forward_pool.remove(forward_similar) + back_pool.remove(back_similar) + add_to_unresolved(forward_similar, back_similar) + return + else: + break + else: + back_similar = next((x for x in back_pool if edgename1 in x), None) + if back_similar: + forward_similar = next((x for x in forward_pool if edgename2 in x), None) + if forward_similar: + back_pool.remove(forward_similar) + forward_pool.remove(back_similar) + add_to_unresolved(forward_similar, back_similar) + return + else: + break + for pair in unresolved_similars: + forward_set, back_set, _ = pair + if edgename1 in forward_set: + if edgename2 in back_set: + unresolved_similars.remove(pair) + add_to_unresolved(forward_set, back_set) + return + else: + break + else: + if edgename1 in back_set: + if edgename2 in forward_set: + unresolved_similars.remove(pair) + add_to_unresolved(forward_set, back_set) + return + else: + break + raise GenerationException('Could not find both OW edges in same pool: \'%s\' <-> \'%s\'', edgename1, edgename2) -def connect_simple(world, exitname, regionname, player): - world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) + if world.customizer: + custom_edges = world.customizer.get_owedges() + if custom_edges and player in custom_edges: + custom_edges = custom_edges[player] + if 'two-way' in custom_edges: + unresolved_similars = [] + def validate_crossed_allowed(edge1, edge2, is_crossed): + return not ((not is_crossed and (edge1 in forced_crossed or edge2 in forced_crossed)) + or (is_crossed and (edge1 in forced_noncrossed or edge2 in forced_noncrossed))) + for edgename1, edgename2 in custom_edges['two-way'].items(): + edge1 = world.get_owedge(edgename1, player) + edge2 = world.get_owedge(edgename2, player) + is_crossed = edge1.is_lw(world) != edge2.is_lw(world) + if not validate_crossed_allowed(edge1.name, edge2.name, is_crossed): + if edgename2[-1] == '*': + edge2 = world.get_owedge(edge2.name + '*', player) + is_crossed = not is_crossed + else: + raise GenerationException('Violation of force crossed rules: \'%s\' <-> \'%s\'', edgename1, edgename2) + if edge1.name not in connected_edges and edge2.name not in connected_edges: + # attempt connection + remove_pair_from_pool(edge1.name, edge2.name, is_crossed) + connect_two_way(world, edge1.name, edge2.name, player, connected_edges) + # resolve parallel + if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new: + parallel_forward_edge = parallel_links_new[edge1.name] + parallel_back_edge = parallel_links_new[edge2.name] + if validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed): + remove_pair_from_pool(parallel_forward_edge, parallel_back_edge, is_crossed) + else: + raise GenerationException('Violation of force crossed rules on parallel connection: \'%s\' <-> \'%s\'', edgename1, edgename2) + elif not edge1.dest or not edge2.dest or edge1.dest.name != edge2.name or edge2.dest.name != edge1.name: + raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2) + # connect leftover similars + for forward_pool, back_pool, is_crossed in unresolved_similars: + for (forward_edge, back_edge) in zip(forward_pool, back_pool): + if validate_crossed_allowed(forward_edge, back_edge, is_crossed): + connect_two_way(world, forward_edge, back_edge, player, connected_edges) + else: + raise GenerationException('Violation of force crossed rules on unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge) + if world.owShuffle[player] == 'parallel' and forward_edge in parallel_links_new: + parallel_forward_edge = parallel_links_new[forward_edge] + parallel_back_edge = parallel_links_new[back_edge] + if not validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed): + raise GenerationException('Violation of force crossed rules on parallel unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge) def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): edge1 = world.get_entrance(edgename1, player) edge2 = world.get_entrance(edgename2, player) - x = world.check_for_owedge(edgename1, player) - y = world.check_for_owedge(edgename2, player) + x = world.get_owedge(edgename1, player) + y = world.get_owedge(edgename2, player) - if x is None: - raise Exception('%s is not a valid edge.', edgename1) - elif y is None: - raise Exception('%s is not a valid edge.', edgename2) if connected_edges is not None: if edgename1 in connected_edges or edgename2 in connected_edges: if (x.dest and x.dest.name == edgename2) and (y.dest and y.dest.name == edgename1): @@ -617,16 +874,110 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): # connecting parallel connections if world.owShuffle[player] in ['vanilla', 'parallel']: - if (edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys()): + if edgename1 in parallel_links_new: try: - parallel_forward_edge = parallel_links[edgename1] if edgename1 in parallel_links.keys() else parallel_links.inverse[edgename1][0] - parallel_back_edge = parallel_links[edgename2] if edgename2 in parallel_links.keys() else parallel_links.inverse[edgename2][0] + parallel_forward_edge = parallel_links_new[edgename1] + parallel_back_edge = parallel_links_new[edgename2] if not (parallel_forward_edge in connected_edges) and not (parallel_back_edge in connected_edges): connect_two_way(world, parallel_forward_edge, parallel_back_edge, player, connected_edges) except KeyError: raise KeyError('No parallel edge for edge %s' % edgename2) -def shuffle_tiles(world, groups, result_list, do_grouped, player): +def determine_forced_flips(world, tile_ow_groups, do_grouped, player): + undefined_chance = 50 + flipped_groups = list() + nonflipped_groups = list() + merged_owids = list() + if world.customizer: + if do_grouped: + custom_flips = world.customizer.get_owcrossed() + else: + custom_flips = world.customizer.get_owtileflips() + if custom_flips and player in custom_flips: + custom_flips = custom_flips[player] + forced_flips = list() + forced_nonflips = list() + if 'undefined_chance' in custom_flips: + undefined_chance = custom_flips['undefined_chance'] + if 'force_flip' in custom_flips: + forced_flips = custom_flips['force_flip'] + if 'force_no_flip' in custom_flips: + forced_nonflips = custom_flips['force_no_flip'] + if 'force_together' in custom_flips: + merged_owids = list(custom_flips['force_together'].values()) + + for group in tile_ow_groups: + if any(owid in group for owid in forced_nonflips): + nonflipped_groups.append(group) + if any(owid in group for owid in forced_flips): + flipped_groups.append(group) + + if undefined_chance == 0: + nonflipped_groups.extend([g for g in tile_ow_groups if g not in flipped_groups + nonflipped_groups]) + + # ensure any customized connections don't end up crossworld + if world.owCrossed[player] == 'none': + def should_merge_group(s1_owid, s2_owid): + flip_together = (s1_owid & 0x40) == (s2_owid & 0x40) + s1_nonflipped = any(g for g in nonflipped_groups if s1_owid in g) + s1_flipped = any(g for g in flipped_groups if s1_owid in g) + if s1_nonflipped or s1_flipped: + group = next(g for g in tile_ow_groups if s2_owid in g) + if s1_nonflipped == flip_together: + nonflipped_groups.append(group) + else: + flipped_groups.append(group) + else: + s2_nonflipped = any(g for g in nonflipped_groups if s2_owid in g) + s2_flipped = any(g for g in flipped_groups if s2_owid in g) + if s2_nonflipped or s2_flipped: + group = next(g for g in tile_ow_groups if s1_owid in g) + if s2_nonflipped == flip_together: + nonflipped_groups.append(group) + else: + flipped_groups.append(group) + else: + s1_group = next(g for g in tile_ow_groups if s1_owid in g) + s2_group = next(g for g in tile_ow_groups if s2_owid in g) + if not flip_together: + if random.randint(0, 1) > 0: + nonflipped_groups.append(s1_group) + flipped_groups.append(s2_group) + else: + flipped_groups.append(s1_group) + nonflipped_groups.append(s2_group) + else: + return True + return False + if world.owWhirlpoolShuffle[player]: + custom_whirlpools = world.customizer.get_whirlpools() + if custom_whirlpools and player in custom_whirlpools: + custom_whirlpools = custom_whirlpools[player] + if 'two-way' in custom_whirlpools and len(custom_whirlpools['two-way']) > 0: + custom_whirlpools = custom_whirlpools['two-way'] + whirlpool_map = {name:owid for wc in default_whirlpool_connections for (owid, name, _) in wc} + for whirl1, whirl2 in custom_whirlpools.items(): + if [whirlpool_map[whirl1], whirlpool_map[whirl2]] not in merged_owids and should_merge_group(whirlpool_map[whirl1], whirlpool_map[whirl2]): + merged_owids.append([whirlpool_map[whirl1], whirlpool_map[whirl2]]) + if world.owShuffle[player] != 'vanilla': + custom_edges = world.customizer.get_owedges() + if custom_edges and player in custom_edges: + custom_edges = custom_edges[player] + if 'two-way' in custom_edges and len(custom_edges['two-way']) > 0: + custom_edges = custom_edges['two-way'] + for edgename1, edgename2 in custom_edges.items(): + if edgename1[-1] != '*' and edgename2[-1] != '*': + edge1 = world.get_owedge(edgename1, player) + edge2 = world.get_owedge(edgename2, player) + if [edge1.owIndex, edge2.owIndex] not in merged_owids and should_merge_group(edge1.owIndex, edge2.owIndex): + merged_owids.append([edge1.owIndex, edge2.owIndex]) + # Check if there are any groups that appear in both sets + if any(group in flipped_groups for group in nonflipped_groups): + raise GenerationException('Conflict found when flipping tiles') + return flipped_groups, nonflipped_groups, undefined_chance, merged_owids + +def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player): + (flipped_groups, nonflipped_groups, undefined_chance) = forced_flips swapped_edges = list() group_parity = {} for group_data in groups: @@ -683,55 +1034,20 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): parity[5] -= 1 group_parity[group[0]] = parity - # customizer adjustments - undefined_chance = 50 - flipped_groups = list() - always_removed = list() - if world.customizer: - if not do_grouped: - custom_flips = world.customizer.get_owtileflips() - if custom_flips and player in custom_flips: - custom_flips = custom_flips[player] - nonflipped_groups = list() - forced_flips = list() - forced_nonflips = list() - if 'undefined_chance' in custom_flips: - undefined_chance = custom_flips['undefined_chance'] - if 'force_flip' in custom_flips: - forced_flips = custom_flips['force_flip'] - if 'force_no_flip' in custom_flips: - forced_nonflips = custom_flips['force_no_flip'] - - for group in groups: - if any(owid in group[0] for owid in forced_nonflips): - nonflipped_groups.append(group) - if any(owid in group[0] for owid in forced_flips): - flipped_groups.append(group) - - # Check if there are any groups that appear in both sets - if any(group in flipped_groups for group in nonflipped_groups): - raise GenerationException('Conflict found when flipping tiles') - - for g in nonflipped_groups: - always_removed.append(g) - if undefined_chance == 0: - for g in [g for g in groups if g not in flipped_groups + always_removed]: - always_removed.append(g) - attempts = 1 if 0 < undefined_chance < 100: # do roughly 1000 attempts at a full list - attempts = len(groups) - len(always_removed) + attempts = len(groups) - len(nonflipped_groups) attempts = (attempts ** 1.9) + (attempts * 10) + 1 while True: if attempts == 0: # expected to only occur with custom flips raise GenerationException('Could not find valid tile flips') # tile shuffle happens here - removed = copy.deepcopy(always_removed) + removed = [] if 0 < undefined_chance < 100: - for group in [g for g in groups if g not in always_removed]: - if group not in flipped_groups and random.randint(1, 100) > undefined_chance: + for group in groups: + if group[0] in nonflipped_groups or (group[0] not in flipped_groups and random.randint(1, 100) > undefined_chance): removed.append(group) # save shuffled tiles to list @@ -756,7 +1072,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): attempts -= 1 continue # ensure sanc can be placed in LW in certain modes - if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'swapped', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): + if not do_grouped and world.shuffle[player] in ['simple', 'restricted', 'full', 'district'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): free_dw_drops = parity[5] + (1 if world.shuffle_ganon[player] else 0) free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon[player] else 0) if free_dw_drops == free_drops: @@ -770,7 +1086,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): exist_dw_regions.extend(new_results[2]) # replace LW edges with DW - if world.owCrossed[player] not in ['polar', 'grouped', 'chaos'] or do_grouped: + if world.owCrossed[player] == 'none' or do_grouped: # in polar, the actual edge connections remain vanilla def getSwappedEdges(world, lst, player): for regionname in lst: @@ -784,7 +1100,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): return swapped_edges -def define_tile_groups(world, player, do_grouped): +def define_tile_groups(world, do_grouped, player): groups = [[i, i + 0x40] for i in range(0x40)] def get_group(id): @@ -808,7 +1124,7 @@ def define_tile_groups(world, player, do_grouped): return False # sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen - if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \ + if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \ and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \ or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): return False @@ -822,27 +1138,39 @@ def define_tile_groups(world, player, do_grouped): groups.append([0x80]) groups.append([0x81]) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple']: - merge_groups([[0x03, 0x0a], [0x28, 0x29]]) - - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']: - merge_groups([[0x13, 0x14]]) - - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted']: - merge_groups([[0x05, 0x07]]) - - if world.shuffle[player] == 'vanilla' or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']): + # hyrule castle and sanctuary connector + if world.shuffle[player] in ['vanilla', 'district'] or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']): merge_groups([[0x13, 0x14, 0x1b]]) + # sanctuary and grave connector + if world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']: + merge_groups([[0x13, 0x14]]) + + # cross-screen connector + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'district']: + merge_groups([[0x03, 0x0a], [0x28, 0x29]]) + + # turtle rock connector + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']: + merge_groups([[0x05, 0x07]]) + + # all non-parallel screens if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]]) + # special case: non-parallel keep similar if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x28, 0x29]]) + # whirlpool screens if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]]) + # customizer adjustments + flipped_groups, nonflipped_groups, undefined_chance, merged_owids = determine_forced_flips(world, groups, do_grouped, player) + for owids in merged_owids: + merge_groups([owids]) + tile_groups = [] for group in groups: if can_shuffle_group(group): @@ -852,7 +1180,8 @@ def define_tile_groups(world, player, do_grouped): (lw_regions if id < 0x40 or id >= 0x80 else dw_regions).extend(OWTileRegions.inverse[id]) tile_groups.append((group, lw_regions, dw_regions)) - return tile_groups + random.shuffle(tile_groups) + return tile_groups, flipped_groups, nonflipped_groups, undefined_chance def remove_reserved(world, groupedlist, connected_edges, player): new_grouping = {} @@ -915,24 +1244,50 @@ def reorganize_groups(world, groups, player): def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): groups = defaultdict(lambda: ([],[])) + limited_crossed = False + custom_groups = dict() + if world.customizer: + custom_crossed = world.customizer.get_owcrossed() + limited_crossed = custom_crossed and (player in custom_crossed) and ('limit_crossed' in custom_crossed[player]) + limited_crossed = limited_crossed and world.owCrossed[player] == 'unrestricted' + custom_edge_groups = world.customizer.get_owedges() + if custom_edge_groups and player in custom_edge_groups: + custom_edge_groups = custom_edge_groups[player] + if 'groups' in custom_edge_groups: + custom_groups = dict(custom_edge_groups['groups']) + for name, edges in custom_groups.items(): + custom_groups[name] = [world.get_owedge(e, player).name if e[-1] == '*' else e for e in edges] for (key, group) in trimmed_groups.items(): - (mode, wrld, dir, terrain, parallel, count) = key + (mode, wrld, dir, terrain, parallel, count, custom) = key if mode == OpenStd.Standard: groups[key] = group else: - if world.owCrossed[player] == 'chaos': - groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0]) - groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1]) + if world.owCrossed[player] == 'unrestricted' and not limited_crossed: + groups[(mode, None, dir, terrain, parallel, count, custom)][0].extend(group[0]) + groups[(mode, None, dir, terrain, parallel, count, custom)][1].extend(group[1]) else: for i in range(2): for edge_set in group[i]: new_world = int(wrld) if edge_set[0] in edges_to_swap: new_world += 1 - groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count)][i].append(edge_set) - return list(groups.values()) + groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count, custom)][i].append(edge_set) + for (key, group) in groups.copy().items(): + (mode, wrld, dir, terrain, parallel, count, custom) = key + if mode != OpenStd.Standard: + for group_name, edges in custom_groups.items(): + for i in range(0, 2): + matches = [s for s in groups[key][i] if any(e in s for e in edges)] + if len(matches) > 0: + for m in matches: + groups[key][i].remove(m) + groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches) + return groups def create_flute_exits(world, player): + flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) + if not flute_in_pool: + return for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']): if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): exitname = 'Flute From ' + region.name @@ -1024,14 +1379,14 @@ def can_reach_smith(world, player): for exit in region.exits: if not found and exit.connected_region is not None: if exit.spot_type == 'Flute': - if any(map(lambda i: i.name == 'Ocarina (Activated)', world.precollected_items)): + if any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, world.precollected_items)): for flutespot in exit.connected_region.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: explore_region(flutespot.connected_region.name, flutespot.connected_region) elif exit.connected_region.name == 'Blacksmiths Hut' and exit.access_rule(blank_state): found = True return - elif exit.connected_region.name not in explored_regions: + elif exit.connected_region.name not in explored_regions and exit.name != "Dig Game To Ledge Drop": if (region.type == RegionType.Dungeon and exit.connected_region.name.endswith(' Portal')) \ or (exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ and exit.access_rule(blank_state)): @@ -1112,18 +1467,6 @@ def build_sectors(world, player): else: sectors2.append(explored_regions) sectors[s] = sectors2 - - #TODO: Keep largest LW sector for Links House consideration, keep sector containing WDM for Old Man consideration - # sector_entrances = list() - # for sector in sectors: - # entrances = list() - # for s2 in sector: - # for region_name in s2: - # region = world.get_region(region_name, player) - # for exit in region.exits: - # if exit.spot_type == 'Entrance' and exit.name in entrance_pool: - # entrances.append(exit.name) - # sector_entrances.append(entrances) return sectors @@ -1142,7 +1485,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F region = base_world.get_region(region_name, player) for exit in region.exits: if exit.connected_region is not None: - if any(map(lambda i: i.name == 'Ocarina (Activated)', base_world.precollected_items)) and exit.spot_type == 'Flute': + if any(map(lambda i: i.name == 'Ocarina (Activated)' and i.player == player, base_world.precollected_items)) and exit.spot_type == 'Flute': fluteregion = exit.connected_region for flutespot in fluteregion.exits: if flutespot.connected_region and flutespot.connected_region.name not in explored_regions: @@ -1150,7 +1493,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F elif exit.connected_region.name not in explored_regions \ and (exit.connected_region.type == region.type or exit.name in OWExitTypes['OWEdge'] or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \ - and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge']): + and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge', 'OWG']): explore_region(exit.connected_region.name, exit.connected_region) if build_copy_world: @@ -1208,7 +1551,8 @@ def validate_layout(world, player): item_entrances = list(zip(*default_item_connections))[0] shop_entrances = list(zip(*default_shop_connections))[0] drop_entrances = list(zip(*default_drop_connections + default_dropexit_connections))[0] - + flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0) + def explore_region(region_name, region=None): if stack_size3a() > 500: raise GenerationException(f'Infinite loop detected for "{region_name}" located at \'validate_layout\'') @@ -1225,7 +1569,7 @@ def validate_layout(world, player): for dest_region in entrance_connectors[region_name]: if dest_region not in explored_regions: explore_region(dest_region) - if world.shuffle[player] not in ['insanity'] and region_name in sane_connectors: + if world.shuffle[player] not in ['district', 'insanity'] and region_name in sane_connectors: for dest_region in sane_connectors[region_name]: if dest_region not in explored_regions: explore_region(dest_region) @@ -1246,11 +1590,12 @@ def validate_layout(world, player): start_region = 'Dark Chapel Area' explore_region(start_region) - if not world.is_tile_swapped(0x30, player): - start_region = 'Desert Teleporter Ledge' - else: - start_region = 'Mire Teleporter Ledge' - explore_region(start_region) + if flute_in_pool: + if not world.is_tile_swapped(0x30, player): + start_region = 'Desert Teleporter Ledge' + else: + start_region = 'Mire Teleporter Ledge' + explore_region(start_region) if not world.is_tile_swapped(0x1b, player): start_region = 'Pyramid Area' @@ -1272,21 +1617,23 @@ def validate_layout(world, player): unreachable_count = len(unreachable_regions) for region_name in reversed(unreachable_regions): # check if can be accessed flute - if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): + if flute_in_pool and unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld): owid = OWTileRegions[region_name] - if owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid][0]: + if owid < 0x80 and owid % 0x40 in flute_data and region_name in flute_data[owid % 0x40][0]: if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections: unreachable_regions.pop(region_name) explore_region(region_name) break # check if entrances in region could be used to access region if world.shuffle[player] != 'vanilla': + # TODO: For District ER, we need to check if there is a dropdown or connector that is able to connect for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']: - if (entrance.name == 'Links House' and (world.mode[player] == 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ - or (entrance.name == 'Big Bomb Shop' and (world.mode[player] != 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ - or (entrance.name == 'Ganons Tower' and (world.mode[player] != 'inverted' and not world.shuffle_ganon[player])) \ - or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['insanity']) \ - or entrance.name == 'Tavern North': + if (entrance.name == 'Links House' and ((not world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ + or (entrance.name == 'Big Bomb Shop' and ((world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \ + or (entrance.name == 'Ganons Tower' and (not world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \ + or (entrance.name == 'Agahnims Tower' and (world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \ + or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['district', 'insanity']) \ + or (entrance.name == 'Tavern North' and not world.shuffletavern[player]): continue # these are fixed entrances and cannot be used for gaining access to region if entrance.name not in drop_entrances \ and ((entrance.name in dungeon_entrances and world.shuffle[player] not in ['dungeonssimple', 'simple', 'restricted']) \ @@ -1297,7 +1644,11 @@ def validate_layout(world, player): break if unreachable_count != len(unreachable_regions): break - + + if not flute_in_pool: + unreachable_regions.pop('Desert Teleporter Ledge') + unreachable_regions.pop('Mire Teleporter Ledge') + if len(unreachable_regions): return False @@ -1967,7 +2318,8 @@ mirror_connections = { } parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'), - ('Dig Game EC', 'Frog WC') + ('Dig Game EC', 'Frog WC'), + ('Dig Game ES', 'Frog WS') ] # non shuffled overworld @@ -2181,7 +2533,7 @@ isolated_regions = [ ] flute_data = { - #Slot LW Region DW Region OWID VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX + #OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX 0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290), 0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550), 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), diff --git a/README.md b/README.md index e757958a..88b27139 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,9 @@ With OW Layout Shuffle, this allows land and water edges to be connected. This allows OW connections to be shuffled cross-world. There are 2 main methodologies of Crossed OWR: -- Grouped and Polar both are guaranteed to result in two separated planes of tiles, similar to that of vanilla. This means you cannot simply walk around and be able to visit all the tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a tile that was previously unreachable +- `Grouped` and `Polar` both are guaranteed to result in two separated planes of tiles, similar to that of vanilla. This means you cannot simply walk around and be able to visit all the tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a tile that was previously unreachable -- Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. +- `Unrestricted` is not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. See each option to get more details on the differences. @@ -171,31 +171,23 @@ Transitions will remain same-world. ### Grouped -This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR. +This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike `Unrestricted`). This is considered the simplest way to play Crossed OWR. ### Polar Only effective if Tile Flip (Mixed) is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are flipped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Tile Flip works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Flip / Mixed` section for more details) -### Limited +### Unrestricted -Every transition is independently a candidate to be chosen as a cross-world connection, however only 9 total transitions become crossed (to/from each world). This option abides by the `Keep Similar Edges Together` option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. +Every transition is independently a candidate to be chosen as a cross-world connection. This option abides by the `Keep Similar Edges Together` option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. Each transition has an equal 50/50 chance of being a crossed connection. -Note: Only parallel connections (a connection that also exists in the opposite world) are considered for cross-world connections, which means that the same connection in the opposite world will also connect cross-world. - -Note: If Whirlpool Shuffle is enabled, those connections can be cross-world but do not count towards the 9 transitions that are crossed. - -Motive: Why 9 connections? To imitate the effect of the 9 existing standard portals. - -### Chaos - -Same as Limited, except that there is no limit to the number of cross-world connections that are made. Each transition has an equal 50/50 chance of being a crossed connection. +Note: If Whirlpool Shuffle is enabled, those connections can be cross-world. ## Keep Similar Edges Together (--ow_keepsimilar) This keeps similar edge transitions together. ie. The 2 west land edges of Potion Shop will be paired to another set of two similar edges, unless Free Terrain is also enabled, in which case these 2 edges together with the west water edge form a group of 3 similar edges. See `Terminology` section above for a more detailed explanation of Similar Edges. -Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW. +Note: This affects OW Layout Shuffle mostly, but also affects `Unrestricted` mode in Crossed OW. ## Tile Flip / Mixed Overworld (--ow_mixed) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 336c604e..cad049a6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -109,6 +109,31 @@ These are now independent of retro mode and have three options: None, Random, an # Bug Fixes and Notes +* 1.4.0.1v + * Key logic: Vanilla key logic fixes. Statically set some HC logic and PoD front door +* 1.3.0.9v + * Ganonhunt: playthrough no longer collects crystals + * Vanilla Fill: Uncle weapon is always a sword, medallions for Mire/TR will be vanilla + * Customizer: support shufflebosses/shuffleenemies as well as boss_shuffle/enemy_shuffle +* 1.3.0.8v + * No Logic Standard ER: Rain doors aren't blocked if no logic is enabled. + * MW Progression Balancing: Change to be percentage based instead of raw count. (80% threshold) + * Take anys: Good Bee cave chosen as take any should no longer prevent generation + * Money balancing: Fixed generation issue + 1.2.0.23u + * Generation: fix for bunny walk logic taking up too much memory + * Key Logic: Partial is now the new default +* 1.2.0.22u + * Flute can't be activated in rain state (except glitched modes) (Thanks codemann!) + * ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used) + * Logic issues: + * Self-locking key not allowed in Sanctuary in standard (typo fixed) + * More advanced bunny-walking logic in dungeons (multiple paths considred) + * MSU: GTBK song fix for DR (Thanks codemann!) +* 1.2.0.21u + * Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory + * Small fix for Tavern Shuffle (thanks Catobat) + * Several small generation fixes * 1.2.0.20u * New generation feature that allows Spiral Stair to link to themselves (thank Catobat) * Added logic for trap doors that could be opened using existing room triggers diff --git a/Regions.py b/Regions.py index 7dd3617a..f9152e08 100644 --- a/Regions.py +++ b/Regions.py @@ -1287,32 +1287,32 @@ bonk_prize_table = { 'Forgotten Forest Southwest Tree': (0x0e, 0x10, False, '', 'Forgotten Forest Area', 'in a tree'), 'Forgotten Forest Central Tree': (0x0f, 0x08, False, '', 'Forgotten Forest Area', 'in a tree'), #'Forgotten Forest Southeast Tree': (0x10, 0x04, False, '', 'Forgotten Forest Area', 'in a tree'), - 'Hyrule Castle Tree': (0x11, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'), - 'Wooden Bridge Tree': (0x12, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'), - 'Eastern Palace Tree': (0x13, 0x10, True, '', 'Eastern Palace Area', 'in a tree'), - 'Flute Boy South Tree': (0x14, 0x10, True, '', 'Flute Boy Area', 'in a tree'), - 'Flute Boy East Tree': (0x15, 0x08, True, '', 'Flute Boy Area', 'in a tree'), - 'Central Bonk Rocks Tree': (0x16, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'), - 'Tree Line Tree 2': (0x17, 0x10, True, '', 'Tree Line Area', 'in a tree'), - 'Tree Line Tree 4': (0x18, 0x08, True, '', 'Tree Line Area', 'in a tree'), - 'Flute Boy Approach South Tree': (0x19, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'), - 'Flute Boy Approach North Tree': (0x1a, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'), - 'Dark Lumberjack Tree': (0x1b, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'), - 'Dark Fortune Bonk Rocks (Drop 1)': (0x1c, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'), - 'Dark Fortune Bonk Rocks (Drop 2)': (0x1d, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'), - 'Dark Graveyard West Bonk Rocks': (0x1e, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'), - 'Dark Graveyard North Bonk Rocks': (0x1f, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'), - 'Dark Graveyard Tomb Bonk Rocks': (0x20, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'), - 'Qirn Jump West Tree': (0x21, 0x10, False, '', 'Qirn Jump Area', 'in a tree'), - 'Qirn Jump East Tree': (0x22, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'), - 'Dark Witch Tree': (0x23, 0x10, False, '', 'Dark Witch Area', 'in a tree'), - 'Pyramid Tree': (0x24, 0x10, False, '', 'Pyramid Area', 'in a tree'), - 'Palace of Darkness Tree': (0x25, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'), - 'Dark Tree Line Tree 2': (0x26, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Dark Tree Line Tree 3': (0x27, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Dark Tree Line Tree 4': (0x28, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), - 'Hype Cave Statue': (0x29, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), - 'Cold Fairy Statue': (0x2a, 0x02, False, '', 'Good Bee Cave', 'encased in stone') + 'Hyrule Castle Tree': (0x10, 0x10, False, '', 'Hyrule Castle Courtyard', 'in a tree'), + 'Wooden Bridge Tree': (0x11, 0x10, False, '', 'Wooden Bridge Area', 'in a tree'), + 'Eastern Palace Tree': (0x12, 0x10, True, '', 'Eastern Palace Area', 'in a tree'), + 'Flute Boy South Tree': (0x13, 0x10, True, '', 'Flute Boy Area', 'in a tree'), + 'Flute Boy East Tree': (0x14, 0x08, True, '', 'Flute Boy Area', 'in a tree'), + 'Central Bonk Rocks Tree': (0x15, 0x10, False, '', 'Central Bonk Rocks Area', 'in a tree'), + 'Tree Line Tree 2': (0x16, 0x10, True, '', 'Tree Line Area', 'in a tree'), + 'Tree Line Tree 4': (0x17, 0x08, True, '', 'Tree Line Area', 'in a tree'), + 'Flute Boy Approach South Tree': (0x18, 0x10, False, '', 'Flute Boy Approach Area', 'in a tree'), + 'Flute Boy Approach North Tree': (0x19, 0x08, False, '', 'Flute Boy Approach Area', 'in a tree'), + 'Dark Lumberjack Tree': (0x1a, 0x10, False, '', 'Dark Lumberjack Area', 'in a tree'), + 'Dark Fortune Bonk Rocks (Drop 1)': (0x1b, 0x10, False, '', 'Dark Fortune Area', 'encased in stone'), + 'Dark Fortune Bonk Rocks (Drop 2)': (0x1c, 0x08, False, '', 'Dark Fortune Area', 'encased in stone'), + 'Dark Graveyard West Bonk Rocks': (0x1d, 0x10, False, '', 'Dark Graveyard Area', 'encased in stone'), + 'Dark Graveyard North Bonk Rocks': (0x1e, 0x08, False, '', 'Dark Graveyard North', 'encased in stone'), + 'Dark Graveyard Tomb Bonk Rocks': (0x1f, 0x04, False, '', 'Dark Graveyard North', 'encased in stone'), + 'Qirn Jump West Tree': (0x20, 0x10, False, '', 'Qirn Jump Area', 'in a tree'), + 'Qirn Jump East Tree': (0x21, 0x08, False, '', 'Qirn Jump East Bank', 'in a tree'), + 'Dark Witch Tree': (0x22, 0x10, False, '', 'Dark Witch Area', 'in a tree'), + 'Pyramid Tree': (0x23, 0x10, False, '', 'Pyramid Area', 'in a tree'), + 'Palace of Darkness Tree': (0x24, 0x10, False, '', 'Palace of Darkness Area', 'in a tree'), + 'Dark Tree Line Tree 2': (0x25, 0x10, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Dark Tree Line Tree 3': (0x26, 0x08, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Dark Tree Line Tree 4': (0x27, 0x04, False, '', 'Dark Tree Line Area', 'in a tree'), + 'Hype Cave Statue': (0x28, 0x10, False, '', 'Hype Cave Area', 'encased in stone'), + 'Cold Fairy Statue': (0x29, 0x02, False, '', 'Good Bee Cave', 'encased in stone') } bonk_table_by_location_id = {0x2ABB00+(data[0]*6)+3: name for name, data in bonk_prize_table.items()} diff --git a/Rom.py b/Rom.py index 9108950e..4df96de0 100644 --- a/Rom.py +++ b/Rom.py @@ -32,13 +32,13 @@ from EntranceShuffle import door_addresses, exit_ids, ow_prize_table from OverworldShuffle import default_flute_connections, flute_data from InitialSram import InitialSram -from source.classes.SFX import randomize_sfx +from source.classes.SFX import randomize_sfx, randomize_sfxinstruments, randomize_songinstruments from source.item.FillUtil import valid_pot_items from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'ee45fd99b433e6f3e46e4f98a35ec544' +RANDOMIZERBASEHASH = '7365fcc38493f960db07a5478135f850' class JsonRom(object): @@ -109,6 +109,9 @@ class LocalRom(object): self.patch_base_rom() self.orig_buffer = self.buffer.copy() + def read_byte(self, address): + return self.buffer[address] + def write_byte(self, address, value): self.buffer[address] = value @@ -709,7 +712,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): offset = 0 data = flute_data[owid] - if world.is_tile_swapped(data[1], player): + if world.is_tile_swapped(owid, player): offset = 0x40 write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid @@ -755,7 +758,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif world.owShuffle[player] == 'full': owMode = 2 - if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] in ['limited', 'chaos']): + if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'): owMode |= 0x0100 if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]): owMode |= 0x0200 @@ -811,13 +814,13 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): owFlags |= 0x0200 - # setting spriteID to D8, a placeholder sprite we use to inform ROM to spawn a dynamic item + # setting spriteID to D9, a placeholder sprite we use to inform ROM to spawn a dynamic item #for address in bonk_addresses: for address in [b for b in bonk_addresses if b != 0x4D0AE]: # temp fix for screen 1A murahdahla sprite replacement - rom.write_byte(address, 0xD8) + rom.write_byte(address, 0xD9) # temporary fix for screen 1A - rom.write_byte(snes_to_pc(0x09AE32), 0xD8) - rom.write_byte(snes_to_pc(0x09AE35), 0xD8) + rom.write_byte(snes_to_pc(0x09AE32), 0xD9) + rom.write_byte(snes_to_pc(0x09AE35), 0xD9) rom.write_byte(snes_to_pc(0x06918E), 0x80) # skip good bee bottle check @@ -1547,9 +1550,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180211, gametype) # Game type warningflags = 0x00 # none - if world.logic[player] in ['owglitches', 'nologic']: + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: warningflags |= 0x20 - if world.logic[player] in ['minorglitches', 'owglitches', 'nologic']: + if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic']: warningflags |= 0x40 rom.write_byte(0x180212, warningflags) # Warning flags @@ -1577,7 +1580,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable - rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness + rom.write_byte(0x180086, 0x00 if world.aga_randomness[player] else 0x01) # set blue ball and ganon warp randomness rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) @@ -1591,7 +1594,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x180035, 30) # starting max arrows rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode - rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier + #rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier + rom.write_byte(0x02AF79, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) rom.write_byte(0x03A943, 0xD0 if world.mode[player] != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) rom.write_byte(0x03A96D, 0xF0 if world.mode[player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) rom.write_byte(0x03A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) @@ -1639,10 +1643,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x18002E, ganon_item_byte[world.ganon_item[player]]) # block HC upstairs doors in rain state in standard mode - prevent_rain = world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' + prevent_rain = world.mode[player] == 'standard' and world.shuffle[player] != 'vanilla' and world.logic[player] != 'nologic' rom.write_byte(0x18008A, 0x01 if prevent_rain else 0x00) # block sanc door in rain state and the dungeon is not vanilla - rom.write_byte(0x13f0fa, 0x01 if world.mode[player] == "standard" and world.doorShuffle[player] != 'vanilla' else 0x00) + block_sanc = world.mode[player] == 'standard' and world.doorShuffle[player] != 'vanilla' and world.logic[player] != 'nologic' + rom.write_byte(0x13f0fa, 0x01 if block_sanc else 0x00) if prevent_rain: portals = [world.get_portal('Hyrule Castle East', player), world.get_portal('Hyrule Castle West', player)] @@ -1758,7 +1763,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # b - Big Key # a - Small Key # - enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'none' + enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla' rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] == 'wild' else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00) @@ -1808,7 +1813,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill - rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'nologic']) else 0x00) + rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']) else 0x00) # remove shield from uncle rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) @@ -1876,7 +1881,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # rom.write_byte(snes_to_pc(0x0DB730), 0x08) # allows chickens to travel across water # allow smith into multi-entrance caves in appropriate shuffles - if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): + if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -2039,7 +2044,8 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, - ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, msu_resume): + ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, + shuffle_sfxinstruments, shuffle_songinstruments, msu_resume): if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer: dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380], @@ -2134,6 +2140,10 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr if shuffle_sfx: randomize_sfx(rom) + if shuffle_sfxinstruments: + randomize_sfxinstruments(rom) + if shuffle_songinstruments: + randomize_songinstruments(rom) if isinstance(rom, LocalRom): rom.write_crc() @@ -2338,7 +2348,7 @@ def write_strings(rom, world, player, team): tt.removeUnwantedText() # Let's keep this guy's text accurate to the shuffle setting. - if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']: + if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'lite', 'lean']: tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' @@ -2389,9 +2399,9 @@ def write_strings(rom, world, player, team): # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. if world.shuffle[player] not in ['lite', 'lean']: entrances_to_hint.update(InconvenientOtherEntrances) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'swapped']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped']: hint_count = 0 - elif world.shuffle[player] in ['simple', 'restricted']: + elif world.shuffle[player] in ['simple', 'restricted', 'lite', 'lean']: hint_count = 2 else: hint_count = 4 @@ -2417,7 +2427,7 @@ def write_strings(rom, world, player, team): entrances_to_hint.update(OtherEntrances) if world.mode[player] != 'inverted': entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'}) - if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean']: + if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']: if world.shufflelinks[player]: entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'}) entrances_to_hint.update({'Links House': 'The hero\'s old residence'}) @@ -2434,17 +2444,17 @@ def write_strings(rom, world, player, team): entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) - hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 0 + hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 0 hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0 for entrance in all_entrances: - if entrance.name in entrances_to_hint: - if hint_count > 0: + if hint_count > 0: + if entrance.name in entrances_to_hint: this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(entrance.connected_region) + '.' tt[hint_locations.pop(0)] = this_hint entrances_to_hint.pop(entrance.name) hint_count -= 1 - else: - break + else: + break # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() @@ -2453,7 +2463,7 @@ def write_strings(rom, world, player, team): if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: locations_to_hint.extend(InconvenientVanillaLocations) random.shuffle(locations_to_hint) - hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 5 + hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 5 hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 del locations_to_hint[hint_count:] for location in locations_to_hint: @@ -2528,7 +2538,7 @@ def write_strings(rom, world, player, team): if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) random.shuffle(items_to_hint) - hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 8 + hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8 hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0 while hint_count > 0 and len(items_to_hint) > 0: @@ -2572,11 +2582,11 @@ def write_strings(rom, world, player, team): choices.clear() choices.append(location_item) if hint_type == 'foolish': - if district.dungeons and world.shuffle[player] != 'vanilla': + if district.dungeons and world.shuffle[player] not in ['vanilla', 'district']: choices.extend(district.dungeons) hint_type = 'dungeon_path' elif district.access_points and world.shuffle[player] not in ['vanilla', 'dungeonssimple', - 'dungeonsfull']: + 'dungeonsfull', 'district']: choices.extend([x.hint_text for x in district.access_points]) hint_type = 'connector' if hint_type == 'foolish': @@ -2771,9 +2781,15 @@ def write_strings(rom, world, player, team): rom.write_byte(0x04a52e, 0x06) # follower set to blind maiden # inverted spawn menu changes + lh_text = "House" + if world.is_tile_swapped(0x2c, player): + lh_text = "Bomb Shop" + sanc_text = "Sanctuary" + if world.mode[player] == 'inverted': + sanc_text = "Dark Chapel" + tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n{CHOICE3}" + tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s " + lh_text + "\n " + sanc_text + "\n Mountain Cave\n{CHOICE2}" if world.mode[player] == 'inverted': - tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s House\n Dark Chapel\n{CHOICE3}" - tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s House\n Dark Chapel\n Mountain Cave\n{CHOICE2}" tt['intro_main'] = CompressedTextMapper.convert( "{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n" + "{PAUSE3}\nInverted\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n" @@ -2960,10 +2976,6 @@ def set_inverted_mode(world, player, rom, inverted_buffer): write_int16s(rom, snes_to_pc(0x1BB810), [0x00BE, 0x00C0, 0x013E]) # update pyramid hole entrance write_int16s(rom, snes_to_pc(0x1BB836), [0x001B, 0x001B, 0x001B]) - write_int16(rom, snes_to_pc(0x308300), 0x0140) # add extra pyramid hole - write_int16(rom, snes_to_pc(0x308320), 0x001B) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - rom.write_byte(snes_to_pc(0x308340), 0x7B) rom.write_byte(snes_to_pc(0x00DB9D), 0x1A) # make retreat bat gfx available in HC area rom.write_byte(snes_to_pc(0x00DC09), 0x1A) diff --git a/Rules.py b/Rules.py index e593d14f..f90e5892 100644 --- a/Rules.py +++ b/Rules.py @@ -9,6 +9,7 @@ from Dungeons import dungeon_table from RoomData import DoorKind from OWEdges import OWExitTypes from OverworldGlitchRules import overworld_glitches_rules +from UnderworldGlitchRules import underworld_glitches_rules def set_rules(world, player): @@ -38,7 +39,7 @@ def set_rules(world, player): logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') no_glitches_rules(world, player) fake_flipper_rules(world, player) - elif world.logic[player] == 'owglitches': + elif world.logic[player] in ['owglitches', 'hybridglitches']: logging.getLogger('').info('There is a chance OWG has bugged edge case rulesets, especially in inverted. Definitely file a report on GitHub if you see anything strange.') # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. @@ -90,6 +91,9 @@ def set_rules(world, player): if not world.is_copied_world: set_bunny_rules(world, player, world.mode[player] == 'inverted') + # These rules go here because they overwrite/add to some of the above rules + if world.logic[player] == 'hybridglitches': + underworld_glitches_rules(world, player) def mirrorless_path_to_location(world, startName, targetName, player): # If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch. @@ -134,9 +138,11 @@ def mirrorless_path_to_castle_courtyard(world, player): queue.append((entrance.connected_region, new_path)) seen.add(entrance.connected_region) + def set_rule(spot, rule): spot.access_rule = rule + def set_defeat_dungeon_boss_rule(location): # Lambda required to defer evaluation of dungeon.boss since it will change later if boos shuffle is used set_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state)) @@ -145,6 +151,7 @@ def set_defeat_dungeon_boss_rule(location): def set_always_allow(spot, rule): spot.always_allow = rule + def add_rule(spot, rule, combine='and'): old_rule = spot.access_rule if combine == 'or': @@ -173,22 +180,26 @@ def forbid_item(location, item, player): old_rule = location.item_rule location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i) + def add_item_rule(location, rule): old_rule = location.item_rule location.item_rule = lambda item: rule(item) and old_rule(item) + def item_in_locations(state, item, player, locations): for location in locations: if item_name(state, location[0], location[1]) == (item, player): return True return False + def item_name(state, location, player): location = state.world.get_location(location, player) if location.item is None: return None return (location.item.name, location.item.player) + def global_rules(world, player): # ganon can only carry triforce add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) @@ -604,10 +615,10 @@ def global_rules(world, player): set_rule(location, lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Final Abyss Ledge Path', player), lambda state: state.has('Cane of Somaria', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) - set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.can_avoid_lasers(player)) + set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.can_avoid_lasers(player)) set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Turtle Rock - Prize', player)) @@ -810,9 +821,9 @@ def global_rules(world, player): set_rule(world.get_entrance('TR Crystaroller Bottom to Ranged Crystal', player), lambda state: state.can_shoot_arrows(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Bottom', player), player))) # or state.has_sword(player, 2) set_rule(world.get_entrance('TR Crystaroller Middle to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or (state.has('Hookshot', player) and state.can_reach_orange(world.get_region('TR Crystaroller Middle', player), player))) # or state.has_sword(player, 2) set_rule(world.get_entrance('TR Crystaroller Middle to Bottom Bypass', player), lambda state: state.can_use_bombs(player) or state.has('Blue Boomerang', player)) - set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes. - set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # Beam sword does NOT work - set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True) # Can always grab a pot from the interior and walk it to the start region and throw it there + set_rule(world.get_entrance('TR Crystal Maze End to Ranged Crystal', player), lambda state: state.has('Cane of Somaria', player)) # or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) // These work by clipping the rang through the two stone blocks, which works sometimes. + set_rule(world.get_entrance('TR Crystal Maze Interior to End Bypass', player), lambda state: state.can_use_bombs(player) or state.can_shoot_arrows(player) or state.has('Red Boomerang', player) or state.has('Blue Boomerang', player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player)) # Beam sword does NOT work + set_rule(world.get_entrance('TR Crystal Maze Interior to Start Bypass', player), lambda state: True) # Can always grab a pot from the interior and walk it to the start region and throw it there set_rule(world.get_entrance('GT Hookshot Platform Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Platform', player), player)) set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player)) @@ -845,6 +856,9 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Crystal Circles to Ranged Crystal', player), lambda state: state.can_hit_crystal_through_barrier(player) or state.has_blunt_weapon(player) or state.has('Cane of Byrna', player)) # or state.has_sword(player, 2) add_key_logic_rules(world, player) + + if world.logic[player] == 'hybridglitches': + add_hmg_key_logic_rules(world, player) # End of door rando rules. if world.restrict_boss_items[player] != 'none': @@ -876,6 +890,9 @@ def global_rules(world, player): (state.has('Lamp', player) or state.can_extend_magic(player, 12)))) # need to light torch a sufficient amount of times + if world.goal[player] != 'ganonhunt': + add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) + set_rule( world.get_entrance('Ganon Drop', player), lambda state: state.has_real_sword(player, 2) or state.has_special_weapon_level(player, 3)) @@ -941,7 +958,7 @@ def bomb_rules(world, player): ('TR Tongue Pull WS', True), ('TR Twin Pokeys NW', False), ] - for killdoor,bombable in easy_kill_rooms: + for killdoor, bombable in easy_kill_rooms: if bombable: add_rule(world.get_entrance(killdoor, player), lambda state: (state.can_kill_with_bombs(player) or state.can_kill_most_things(player))) else: @@ -958,25 +975,25 @@ def bomb_rules(world, player): if world.get_entrance('Mire Cross SW', player).door.trapped: add_rule(world.get_entrance('Mire Cross SW', player), lambda state: state.can_kill_most_things(player)) - enemy_kill_drops = [ # Location, bool-bombable + enemy_kill_drops = [ # Location, bool-bombable ('Hyrule Castle - Map Guard Key Drop', True), ('Hyrule Castle - Boomerang Guard Key Drop', True), ('Hyrule Castle - Key Rat Key Drop', True), -# ('Hyrule Castle - Big Key Drop', True), # Pots are available -# ('Eastern Palace - Dark Eyegore Key Drop', True), # Pots are available + # ('Hyrule Castle - Big Key Drop', True), # Pots are available + # ('Eastern Palace - Dark Eyegore Key Drop', True), # Pots are available ('Castle Tower - Dark Archer Key Drop', True), -# ('Castle Tower - Circle of Pots Key Drop', True), # Pots are available -# ('Skull Woods - Spike Corner Key Drop', True), # Pots are available + # ('Castle Tower - Circle of Pots Key Drop', True), # Pots are available + # ('Skull Woods - Spike Corner Key Drop', True), # Pots are available ('Ice Palace - Jelly Key Drop', True), ('Ice Palace - Conveyor Key Drop', True), ('Misery Mire - Conveyor Crystal Key Drop', True), ('Turtle Rock - Pokey 1 Key Drop', True), ('Turtle Rock - Pokey 2 Key Drop', True), -# ('Ganons Tower - Mini Helmasaur Key Drop', True) # Pots are available - ('Castle Tower - Room 03', True), # Two spring soliders - ('Ice Palace - Compass Chest', True) # Pengators + # ('Ganons Tower - Mini Helmasaur Key Drop', True) # Pots are available + ('Castle Tower - Room 03', True), # Two spring soliders + ('Ice Palace - Compass Chest', True) # Pengators ] - for location,bombable in enemy_kill_drops: + for location, bombable in enemy_kill_drops: if bombable: add_rule(world.get_location(location, player), lambda state: state.can_kill_with_bombs(player) or state.can_kill_most_things(player)) else: @@ -988,17 +1005,17 @@ def bomb_rules(world, player): add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player)) if world.doorShuffle[player] == 'vanilla': - add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. - add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? + add_rule(world.get_entrance('TR Lazy Eyes SE', player), lambda state: state.can_use_bombs(player)) # ToDo: Add always true for inverted, cross-entrance, and door-variants and so on. + add_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: state.can_use_bombs(player)) # Is this the same as above? dungeon_bonkable = ['Sewers Rat Path WS', 'Sewers Rat Path WN', 'PoD Warp Hint SE', 'PoD Jelly Hall NW', 'PoD Jelly Hall NE', 'PoD Mimics 1 SW', 'Thieves Ambush E', 'Thieves Rail Ledge W', 'TR Dash Room NW', 'TR Crystaroller SW', 'TR Dash Room ES', - 'GT Four Torches NW','GT Fairy Abyss SW' + 'GT Four Torches NW', 'GT Fairy Abyss SW' ] dungeon_bombable = ['PoD Map Balcony WS', 'PoD Arena Ledge ES', 'PoD Dark Maze E', 'PoD Big Chest Balcony W', - 'Swamp Pot Row WN','Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S', + 'Swamp Pot Row WN', 'Swamp Map Ledge EN', 'Swamp Hammer Switch WN', 'Swamp Hub Dead Ledge EN', 'Swamp Waterway N', 'Swamp I S', 'Skull Pot Circle WN', 'Skull Pull Switch EN', 'Skull Big Key EN', 'Skull Lone Pot WN', 'Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW', 'Ice Bomb Drop Hole', 'Ice Freezors Bomb Hole', @@ -1072,7 +1089,7 @@ def ow_inverted_rules(world, player): else: set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_sword(player, 2) or state.has_beaten_aga(player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('GT Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) - set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity')) + set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity')) if world.is_tile_swapped(0x03, player): set_rule(world.get_entrance('Spectacle Rock Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'] and state.has_Pearl(player)) @@ -1168,12 +1185,12 @@ def ow_bunny_rules(world, player): add_bunny_rule(world.get_entrance('20 Rupee Cave', player), player) add_bunny_rule(world.get_entrance('50 Rupee Cave', player), player) - add_bunny_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), player) # bunny cannot lift bush - add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player) # bunny cannot lift bush - add_bunny_rule(world.get_entrance('Skull Woods Final Section', player), player) # bunny cannot use fire rod + add_bunny_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), player) # bunny cannot lift bush + add_bunny_rule(world.get_entrance('Skull Woods Second Section Hole', player), player) # bunny cannot lift bush + add_bunny_rule(world.get_entrance('Skull Woods Final Section', player), player) # bunny cannot use fire rod add_bunny_rule(world.get_entrance('Hookshot Cave', player), player) - add_bunny_rule(world.get_entrance('Thieves Town', player), player) # bunny cannot pull - add_bunny_rule(world.get_entrance('Palace of Darkness', player), player) # kiki needs pearl + add_bunny_rule(world.get_entrance('Thieves Town', player), player) # bunny cannot pull + add_bunny_rule(world.get_entrance('Palace of Darkness', player), player) # kiki needs pearl add_bunny_rule(world.get_entrance('Hammer Peg Cave', player), player) add_bunny_rule(world.get_entrance('Bonk Fairy (Dark)', player), player) add_bunny_rule(world.get_entrance('Misery Mire', player), player) @@ -1400,6 +1417,7 @@ def forbid_bomb_jump_requirements(world, player): set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) set_rule(world.get_entrance('Ice Lake Iceberg Bomb Jump', player), lambda state: False) + def add_conditional_lamps(world, player): def add_conditional_lamp(spot, spottype='Location'): if spottype == 'Location': @@ -1530,33 +1548,33 @@ def pseudo_sword_mode_rules(world, player): # todo: new traps std_kill_rooms = { - 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard - 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard - 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots - 'Desert Compass Room': ['Desert Compass NE'], # Three popos - 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos + 'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES'], # One green guard + 'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'], # One blue guard + 'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'], # Can use pots + 'Desert Compass Room': ['Desert Compass NE'], # Three popos + 'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'], # Four popos 'Hera Beetles': ['Hera Beetles WS'], # Three blue beetles and only two pots, and bombs don't work. - 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain + 'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'], # Two ball and chain 'Tower Dark Archers': ['Tower Dark Archers WN'], # Not a kill room - 'Tower Red Spears': ['Tower Red Spears WN'], # Two spear soldiers - 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts - 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. + 'Tower Red Spears': ['Tower Red Spears WN'], # Two spear soldiers + 'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'], # Two usain bolts + 'Tower Circle of Pots': ['Tower Circle of Pots NW'], # Two spear soldiers. Plenty of pots. 'PoD Turtle Party': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], # Lots of turtles. - 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. - 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights - 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? - 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. - 'Mire Cross': ['Mire Cross ES'], # 4 Sluggulas. Bombs don't work - 'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], # Two pokeys - 'GT Petting Zoo': ['GT Petting Zoo SE'], # Dont make anyone do this room with bombs. - 'GT DMs Room': ['GT DMs Room SW'], # Four red stalfos - 'GT Gauntlet 1': ['GT Gauntlet 1 WN'], # Stalfos/zazaks - 'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], # Red stalfos - 'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], # Blue zazaks - 'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], # Red zazaks - 'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], # Stalfos and zazak - 'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'], # Wizzrobes. Bombs don't work - 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work + 'Thieves Basement Block': ['Thieves Basement Block WN'], # One blue and one red zazak and one Stalfos. Two pots. Need weapon. + 'Ice Stalfos Hint': ['Ice Stalfos Hint SE'], # Need bombs for big stalfos knights + 'Ice Pengator Trap': ['Ice Pengator Trap NE'], # Five pengators. Bomb-doable? + 'Mire 2': ['Mire 2 NE'], # Wizzrobes. Bombs dont work. + 'Mire Cross': ['Mire Cross ES'], # 4 Sluggulas. Bombs don't work + 'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'], # Two pokeys + 'GT Petting Zoo': ['GT Petting Zoo SE'], # Dont make anyone do this room with bombs. + 'GT DMs Room': ['GT DMs Room SW'], # Four red stalfos + 'GT Gauntlet 1': ['GT Gauntlet 1 WN'], # Stalfos/zazaks + 'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'], # Red stalfos + 'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'], # Blue zazaks + 'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'], # Red zazaks + 'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'], # Stalfos and zazak + 'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'], # Wizzrobes. Bombs don't work + 'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE'] # Wizzrobes. Bombs don't work } # all trap rooms? std_kill_doors_if_trapped = { @@ -1571,6 +1589,7 @@ std_kill_doors_if_trapped = { # 'Ice Lobby S' # can melt rule is sufficient } + def add_connection(parent_name, target_name, entrance_name, world, player): parent = world.get_region(parent_name, player) target = world.get_region(target_name, player) @@ -1584,6 +1603,7 @@ def standard_rules(world, player): world.get_entrance('Uncle S&Q', player).hide_path = True set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.has('Zelda Delivered', player)) + add_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.has('Zelda Delivered', player)) # these are because of rails if world.shuffle[player] != 'vanilla': # where ever these happen to be @@ -1629,13 +1649,16 @@ def standard_rules(world, player): add_rule(ent, lambda state: standard_escape_rule(state)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) - set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player)) set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player)) def check_rule_list(state, r_list): return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) + rule_list, debug_path = find_rules_for_zelda_delivery(world, player) - set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) + set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), + lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) + set_rule(world.get_location('Zelda Drop Off', player), + lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) for entrance in ['Links House SC', 'Links House ES', 'Central Bonk Rocks SW', 'Hyrule Castle WN', 'Hyrule Castle ES', 'Bonk Fairy (Light)', 'Hyrule Castle Main Gate (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Ledge Drop']: @@ -1669,18 +1692,19 @@ def find_rules_for_zelda_delivery(world, player): if not rule(blank_state): rule_list.append(rule) next_path.append(ext.name) - if connect.name == 'Sanctuary': + if connect.name == 'Hyrule Castle Throne Room': return rule_list, next_path else: visited.add(connect) queue.append((connect, rule_list, next_path)) - raise Exception('No path to Sanctuary found') + raise Exception('No path to Throne Room found') 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. + all_single_exit_dungeons = ['Eastern Palace', 'Tower of Hera', 'Castle Tower', 'Palace of Darkness', 'Swamp Palace', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Ganons Tower'] + hmg_single_exit_dungeons = [d for d in all_single_exit_dungeons if d not in ['Tower of Hera', 'Misery Mire', 'Thieves Town']] bunny_impassable_caves = ['Bumper Cave (top)', 'Bumper Cave (bottom)', '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', @@ -1705,6 +1729,9 @@ def set_bunny_rules(world, player, inverted): 'Take - Any # 3 Item 1', 'Take - Any # 3 Item 2', 'Take - Any # 4 Item 1', 'Take - Any # 4 Item 2', ] + bunny_pocket_entrances = ['Skull Woods Final Section', 'Bush Yard Pegs (Inner)', 'Bush Yard Pegs (Outer)', + 'DM Hammer Bridge (West)', 'DM Hammer Bridge (East)', 'Blacksmith Ledge Peg (West)' + ] def path_to_access_rule(path, entrance): return lambda state: state.can_reach(entrance) and all(rule_func(state) for rule_func in path) @@ -1718,18 +1745,38 @@ def set_bunny_rules(world, player, inverted): return region.is_light_world else: return region.is_dark_world + def is_link(region): if inverted: return region.is_dark_world else: return region.is_light_world + + # Is it possible to do bunny pocket here + def can_bunny_pocket_to(world, entrance_name, player): + def can_activate_bunny_pocket(region): + explored_regions.append(region.name) + for ent in region.entrances: + if (is_link(ent.parent_region) or + (ent.parent_region.type == RegionType.Dungeon and ent.parent_region.name in bunny_revivable_entrances)): + return True + for ent in region.entrances: + if ent.spot_type in ['OWEdge', 'Ledge', 'OpenTerrain'] and ent.parent_region.name not in explored_regions: + if can_activate_bunny_pocket(ent.parent_region): + return True + return False - def get_rule_to_add(region, location = None, connecting_entrance = None): + entrance = world.get_entrance(entrance_name, player) + explored_regions = [] + return can_activate_bunny_pocket(entrance.parent_region) + + + def get_rule_to_add(region, location=None, connecting_entrance=None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: if region.type != RegionType.Dungeon \ - and (location is None or location.name not in OverworldGlitchRules.get_superbunny_accessible_locations()) \ + and (location is None or location.name not in OverworldGlitchRules.superbunny_accessible_locations) \ and not is_link(region): return lambda state: state.has_Pearl(player) else: @@ -1747,25 +1794,33 @@ def set_bunny_rules(world, player, inverted): # for each such entrance a new option is added that consist of: # a) being able to reach it, and # b) being able to access all entrances from there to `region` - seen = {region} - queue = deque([(region, [])]) + queue = deque([(region, [], {region}, [region])]) + seen_sets = set([frozenset({region})]) while queue: - (current, path) = queue.popleft() + (current, path, seen, region_path) = queue.popleft() for entrance in current.entrances: + if entrance.door and entrance.door.blocked: + continue new_region = entrance.parent_region - if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_region in seen: + new_seen = seen.union({new_region}) + if new_region.type in (RegionType.Cave, RegionType.Dungeon) and new_seen in seen_sets: continue new_path = path + [entrance.access_rule] - seen.add(new_region) + new_region_path = region_path + [new_region] + seen_sets.add(frozenset(new_seen)) if not is_link(new_region): - if world.logic[player] == 'owglitches': + if world.logic[player] in ['owglitches', 'hybridglitches']: + # Is this a bunny pocketable entrance? if region.type == RegionType.Dungeon and new_region.type != RegionType.Dungeon: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: + continue + if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player): continue if entrance.name in drop_dungeon_entrances: lobby = entrance.connected_region else: - lobby = next(exit.connected_region for exit in current.exits if exit.connected_region.type == RegionType.Dungeon) + portal_regions = [world.get_region(reg, player) for reg in region.dungeon.regions if reg.endswith('Portal')] + lobby = next(reg.connected_region for portal_reg in portal_regions for reg in portal_reg.exits if reg.name.startswith('Enter ')) if lobby.name in bunny_revivable_entrances: possible_options.append(path_to_access_rule(new_path, entrance)) elif lobby.name in superbunny_revivable_entrances: @@ -1774,26 +1829,29 @@ def set_bunny_rules(world, player, inverted): possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) continue elif region.type == RegionType.Cave and new_region.type != RegionType.Cave: - if entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if entrance.name in OverworldGlitchRules.invalid_mirror_bunny_entrances: continue - if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): + if entrance.name in bunny_pocket_entrances and not can_bunny_pocket_to(world, entrance.name, player): + continue + if region.name in OverworldGlitchRules.sword_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_sword(player)], entrance)) - elif region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions(): + elif region.name in OverworldGlitchRules.boots_required_superbunny_mirror_regions: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) - elif location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): - if location.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_locations(): + elif location and location.name in OverworldGlitchRules.superbunny_accessible_locations: + if location.name in OverworldGlitchRules.boots_required_superbunny_mirror_locations: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player) and state.has_Boots(player)], entrance)) elif region.name == 'Kakariko Well (top)': possible_options.append(path_to_access_rule(new_path, entrance)) else: possible_options.append(path_to_access_rule(new_path + [lambda state: state.has_Mirror(player)], entrance)) continue - elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.get_superbunny_accessible_locations(): + elif region.name == 'Superbunny Cave (Top)' and new_region.name == 'Superbunny Cave (Bottom)' and location and location.name in OverworldGlitchRules.superbunny_accessible_locations: possible_options.append(path_to_access_rule(new_path, entrance)) else: continue if is_bunny(new_region): - queue.append((new_region, new_path)) + # todo: if not owg or hmg and entrance is in bunny_impassible_doors, then skip this nonsense? + queue.append((new_region, new_path, new_seen, new_region_path)) else: # we have reached pure light world, so we have a new possible option possible_options.append(path_to_access_rule(new_path, entrance)) @@ -1801,7 +1859,6 @@ def set_bunny_rules(world, player, inverted): # Add requirements for bunny-impassible caves if they occur in the light world for region in [world.get_region(name, player) for name in bunny_impassable_caves]: - if not is_bunny(region): continue rule = get_rule_to_add(region) @@ -1836,6 +1893,12 @@ def set_bunny_rules(world, player, inverted): continue add_rule(location, get_rule_to_add(region, location)) + if world.logic[player] in ['owglitches', 'hybridglitches']: + for ent_name in bunny_pocket_entrances: + bunny_exit = world.get_entrance(ent_name, player) + if bunny_exit.connected_region and is_bunny(bunny_exit.parent_region) and not can_bunny_pocket_to(world, ent_name, player): + add_rule(bunny_exit, lambda state: state.has_Pearl(player)) + drop_dungeon_entrances = { "Sewer Drop", @@ -1845,7 +1908,6 @@ drop_dungeon_entrances = { "Skull Back Drop" } - bunny_revivable_entrances = { "Sewers Pull Switch", "TR Dash Room", "Swamp Boss", "Hera Boss", "Tower Agahnim 1", "Ice Lobby", "Sewers Rat Path", "PoD Falling Bridge", @@ -1947,7 +2009,7 @@ bunny_impassible_doors = { 'GT Double Switch Exit to Blue Barrier', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS', 'GT Mimics 2 NE', 'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN', - 'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', + 'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', 'GT Gauntlet 5 NW', 'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW', 'GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap', 'GT Validation Block Path' @@ -1964,6 +2026,11 @@ bunny_impassible_if_trapped = { 'GT Speed Torch WN', 'Ice Lobby SE' } +def add_hmg_key_logic_rules(world, player): + for toh_loc in world.key_logic[player]['Tower of Hera'].bk_restricted: + set_always_allow(world.get_location(toh_loc.name, player), allow_big_key_in_big_chest('Big Key (Tower of Hera)', player)) + set_always_allow(world.get_location('Swamp Palace - Entrance', player), allow_big_key_in_big_chest('Big Key (Swamp Palace)', player)) + def add_key_logic_rules(world, player): key_logic = world.key_logic[player] @@ -2097,7 +2164,7 @@ def create_key_rule(small_key_name, player, keys): def create_key_rule_allow_small(small_key_name, player, keys, location): loc = location.name - return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) + return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs): @@ -2108,7 +2175,7 @@ def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_ def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs): loc = location.name chest_names = [x.name for x in bk_locs] - return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys)) + return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys - 1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys)) def create_advanced_key_rule(key_logic, player, rule): diff --git a/TestSuite.py b/TestSuite.py index 9c2f29d0..a1b18ab3 100644 --- a/TestSuite.py +++ b/TestSuite.py @@ -51,6 +51,7 @@ def main(args=None): test("Full ", "--shuffle full") test("Lite ", "--shuffle lite") test("Lean ", "--shuffle lean") + test("District ", "--shuffle district") test("Swapped ", "--shuffle swapped") test("Crossed ", "--shuffle crossed") test("Insanity ", "--shuffle insanity") diff --git a/TestSuiteStat.py b/TestSuiteStat.py index ecde323d..574e3fb4 100644 --- a/TestSuiteStat.py +++ b/TestSuiteStat.py @@ -14,7 +14,7 @@ ALL_SETTINGS = { 'mode': ['open', 'standard', 'inverted'], 'goal': ['ganon', 'pedestal', 'triforcehunt', 'trinity', 'crystals', 'dungeons'], 'swords': ['random', 'swordless', 'assured'], - 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','swapped','crossed','insanity'], + 'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','district','swapped','crossed','insanity'], 'shufflelinks': [True, False], 'shuffleganon': [True, False], 'door_shuffle': ['vanilla', 'basic', 'crossed'], @@ -39,7 +39,7 @@ SETTINGS = { 'goal': ['ganon'], 'swords': ['random'], 'shuffle': ['vanilla', - 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity' + 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity' ], 'shufflelinks': [True, False], 'shuffleganon': [True, False], @@ -49,7 +49,7 @@ SETTINGS = { 'ow_fluteshuffle': ['balanced'], 'ow_keepsimilar': [True, False], 'ow_mixed': [True, False], - 'ow_crossed': ['none', 'polar', 'grouped', 'limited'], + 'ow_crossed': ['none', 'polar', 'grouped', 'unrestricted'], 'accessibility': [True], 'difficulty': [False], 'shufflepots': [False], diff --git a/UnderworldGlitchRules.py b/UnderworldGlitchRules.py new file mode 100644 index 00000000..c01e242f --- /dev/null +++ b/UnderworldGlitchRules.py @@ -0,0 +1,278 @@ +from BaseClasses import Entrance +import Rules +from OverworldGlitchRules import create_no_logic_connections + +kikiskip_spots = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")] + +mireheraswamp_spots = [ + ("Mire to Hera Clip", "Mire Torches Top", "Hera Portal"), + ("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal"), +] + +icepalace_spots = [("Ice Lobby Clip", "Ice Portal", "Ice Bomb Drop")] + +thievesdesert_spots = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert West Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert South Portal"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert East Portal"), +] + +specrock_spots = [("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)")] + +paradox_spots = [("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area")] + + +# We need to make connectors at a separate time from the connections, because of how dungeons are linked to regions +kikiskip_connectors = [("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Exit")] + + +mireheraswamp_connectors = [ + ("Mire to Hera Clip", "Mire Torches Top", "Tower of Hera Exit"), + ("Mire to Hera Clip", "Mire Torches Top", "Swamp Palace Exit"), +] + + +thievesdesert_connectors = [ + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (West)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (South)"), + ("Thieves to Desert Clip", "Thieves Attic", "Desert Palace Exit (East)"), +] + +specrock_connectors = [ + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit (Top)"), + ("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave Exit"), +] + + +# Create connections between dungeons/locations +def create_hybridmajor_connections(world, player): + for spots in [ + kikiskip_spots, + mireheraswamp_spots, + icepalace_spots, + thievesdesert_spots, + specrock_spots, + paradox_spots, + ]: + create_no_logic_connections(player, world, spots) + + +# Turn dungeons into connectors +def create_hybridmajor_connectors(world, player): + for connectors in [ + kikiskip_connectors, + mireheraswamp_connectors, + thievesdesert_connectors, + specrock_connectors, + ]: + new_connectors = [(connector[0], connector[1], world.get_entrance(connector[2], player).connected_region) for connector in connectors] + create_no_logic_connections(player, world, new_connectors) + + +# For some entrances, we need to fake having pearl, because we're in fake DW/LW. +# This creates a copy of the input state that has Moon Pearl. +def fake_pearl_state(state, player): + if state.has("Moon Pearl", player): + return state + fake_state = state.copy() + fake_state.prog_items["Moon Pearl", player] += 1 + return fake_state + + +# Sets the rules on where we can actually go using this clip. +# Behavior differs based on what type of ER shuffle we're playing. +def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str): + fix_dungeon_exits = world.fix_palaceofdarkness_exit[player] + fix_fake_worlds = world.fix_fake_world[player] + + dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0] + if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix + # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. + + # entrance doesn't exist until you fire rod it from the other side + if dungeon_entrance.name == "Skull Woods Final Section": + Rules.set_rule(clip, lambda state: False) + + elif dungeon_entrance.name == "Misery Mire": + if world.swords[player] == "swordless": + Rules.add_rule(clip, lambda state: state.has_misery_mire_medallion(player)) + else: + Rules.add_rule(clip, lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) + + elif dungeon_entrance.name == "Agahnims Tower": + Rules.add_rule( + clip, + lambda state: state.has("Cape", player) + or state.has_beam_sword(player) + or state.has("Beat Agahnim 1", player), + ) + + # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + elif not fix_fake_worlds: # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix + # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. + Rules.add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) + # exiting restriction + Rules.add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) + + # Otherwise, the shuffle type is lean, lite, crossed, or insanity; all of these do not need additional rules on where we can go, + # since the clip links directly to the exterior region. + + +def underworld_glitches_rules(world, player): + # Ice Palace Entrance Clip, needs bombs or cane of somaria to exit bomb drop room + Rules.add_rule( + world.get_entrance("Ice Bomb Drop SE", player), + lambda state: state.can_dash_clip(world.get_region("Ice Lobby", player), player) + and (state.can_use_bombs(player) or state.has("Cane of Somaria", player)), + combine="or", + ) + + # Kiki Skip + kks = world.get_entrance("Kiki Skip", player) + Rules.set_rule(kks, lambda state: state.can_bomb_clip(kks.parent_region, player)) + dungeon_reentry_rules(world, player, kks, "Palace of Darkness Portal", "Palace of Darkness Exit") + + # Mire -> Hera -> Swamp + def mire_clip(state): + return state.can_reach("Mire Torches Top", "Region", player) and state.can_dash_clip( + world.get_region("Mire Torches Top", player), player + ) + + def hera_clip(state): + return state.can_reach("Hera 4F", "Region", player) and state.can_dash_clip( + world.get_region("Hera 4F", player), player + ) + + Rules.add_rule( + world.get_entrance("Hera Startile Corner NW", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + Rules.add_rule( + world.get_location("Tower of Hera - Big Chest", player), + lambda state: mire_clip(state) and state.has("Big Key (Misery Mire)", player), + combine="or", + ) + + mire_to_hera = world.get_entrance("Mire to Hera Clip", player) + mire_to_swamp = world.get_entrance("Hera to Swamp Clip", player) + Rules.set_rule(mire_to_hera, mire_clip) + Rules.set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has("Flippers", player)) + + # Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys + dungeon_reentry_rules(world, player, mire_to_hera, "Hera Lobby", "Tower of Hera Exit") + dungeon_reentry_rules(world, player, mire_to_swamp, "Swamp Lobby", "Swamp Palace Exit") + # We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots + # Flippers required for all of these doors to prevent locks when flooding + for door in [ + "Swamp Trench 1 Approach ES", + "Swamp Hammer Switch SW", + "Swamp Entrance Down Stairs", + "Swamp Pot Row WS", + "Swamp Trench 1 Key Ledge NW", + "Swamp Hub WN", + "Swamp Hub North Ledge N", + "Swamp Crystal Switch EN", + "Swamp Push Statue S", + "Swamp Waterway NW", + "Swamp T SW", + ]: + Rules.add_rule( + world.get_entrance(door, player), + lambda state: mire_clip(state) + and state.has("Small Key (Misery Mire)", player, count=6) + and state.has("Flippers", player), + combine="or", + ) + # Rules.add_rule(world.get_entrance(door, player), lambda state: mire_clip(state) and state.has('Flippers', player), combine="or") + + Rules.add_rule( + world.get_location("Trench 1 Switch", player), lambda state: mire_clip(state) or hera_clip(state), combine="or" + ) + + # Build the rule for SP moat. + # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. + # First we require a certain type of entrance shuffle, then build the rule from its pieces. + if not world.swamp_patch_required[player]: + if world.shuffle[player] in [ + "vanilla", + "dungeonssimple", + "dungeonsfull", + "dungeonscrossed", + ]: + rule_map = { + "Mire Portal": (lambda state: state.can_reach("Mire Torches Top", "Entrance", player)), + "Hera Portal": (lambda state: state.can_reach("Hera Startile Corner NW", "Entrance", player)), + } + inverted = world.mode[player] == "inverted" + + def hera_rule(state): + return (state.has("Moon Pearl", player) or not inverted) and rule_map.get( + world.get_entrance("Tower of Hera", player).connected_region.name, lambda state: False + )(state) + + def gt_rule(state): + return (state.has("Moon Pearl", player) or inverted) and rule_map.get( + world.get_entrance(("Ganons Tower"), player).connected_region.name, lambda state: False + )(state) + + def mirrorless_moat_rule(state): + return ( + state.can_reach("Old Man S&Q", "Entrance", player) + and state.has("Flippers", player) + and mire_clip(state) + and (hera_rule(state) or gt_rule(state)) + ) + + Rules.add_rule( + world.get_entrance("Swamp Lobby Moat", player), lambda state: mirrorless_moat_rule(state), combine="or" + ) + + # Thieves -> Desert + Rules.add_rule( + world.get_entrance("Thieves to Desert Clip", player), + lambda state: state.can_dash_clip(world.get_region("Thieves Attic", player), player), + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert West Portal", + "Desert Palace Exit (West)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert South Portal", + "Desert Palace Exit (South)", + ) + dungeon_reentry_rules( + world, + player, + world.get_entrance("Thieves to Desert Clip", player), + "Desert East Portal", + "Desert Palace Exit (East)", + ) + + # Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up + paradox_left_chests = ["Paradox Cave Lower - Far Left", "Paradox Cave Lower - Left", "Paradox Cave Lower - Middle"] + for location in paradox_left_chests: + Rules.add_rule( + world.get_location(location, player), + lambda state: state.can_dash_clip(world.get_location(location, player).parent_region, player), + "or", + ) + + # Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch + paradox_right_chests = ["Paradox Cave Lower - Right", "Paradox Cave Lower - Far Right"] + for location in paradox_right_chests: + Rules.add_rule( + world.get_location(location, player), + lambda state: ( + state.can_dash_clip(world.get_location(location, player).parent_region, player) + and state.can_hit_crystal(player) + ), + "or", + ) diff --git a/asm/owrando.asm b/asm/owrando.asm index d74718dd..bb9856b4 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -29,6 +29,9 @@ BCS OWDetectTransitionReturn org $02a999 jsl OWEdgeTransition : nop #4 ;LDA $02A4E3,X : ORA $7EF3CA +org $02aa07 +JSL OWMarkVisited : NOP + org $04e8ae JSL OWDetectSpecialTransition RTL : NOP @@ -56,16 +59,17 @@ Link_ResetSwimmingState: ; mirror hooks -org $02FBAB -JSL OWMirrorSpriteRestore : NOP +org $0283DC ; override world check when spawning mirror portal sprite in Crossed OWR +jsl.l OWLightWorldOrCrossed org $05AF75 Sprite_6C_MirrorPortal: -jsl OWPreserveMirrorSprite : nop #2 ; LDA $7EF3CA : BNE $05AFDF +jsl OWMirrorSpriteDisable ; LDA $7EF3CA +org $05AF88 +jsl OWMirrorSpriteSkipDraw : NOP ; LDA.w $0FC6 : CMP.b #$03 org $05AFDF Sprite_6C_MirrorPortal_missing_mirror: -JML OWMirrorSpriteDelete : NOP ; STZ $0DD0,X : BRA $05AFF1 -org $0ABFBF -JSL OWMirrorSpriteOnMap : BRA + : NOP #6 : + +org $0ABFB6 +jsl OWMirrorSpriteOnMap : NOP ; LDA.w $008A : CMP.b #$40 ; whirlpool shuffle cross world change org $02b3bd @@ -100,10 +104,6 @@ jsl OWOldManSpeed ;org $09c957 ; <- 4c957 ;dw #$cb5f ; matches value on Central Bonk Rocks screen -; override world check when spawning mirror portal sprite in Crossed OWR -org $0283dc -jsl.l OWLightWorldOrCrossed - ; override world check when viewing overworld (incl. title screen portion) org $0aba6c ; < ? - Bank0a.asm:474 () jsl.l OWMapWorldCheck16 : nop @@ -161,15 +161,22 @@ jsl.l OWWorldCheck16 : nop org $02b16e ; AND #$3F : ORA 7EF3CA and #$7f : eor #$40 : nop #2 -org $06AD4C -jsl.l OWBonkDrops : nop #4 -org $1EDE6F -jsl.l OWBonkGoodBeeDrop : bra + +org $09C3C4 +jsl.l OWBonkDropPrepSprite : nop #2 +org $09C801 +jsl.l OWBonkDropPrepSprite : nop #2 +org $06D052 +jsl.l OWBonkDropSparkle +org $06AD49 +jsl.l OWBonkDropsOverworld : nop +org $1EDE6A +jsl.l OWBonkDropSparkle : BNE GoldBee_Dormant_exit +jsl.l OWBonkDropsUnderworld : bra + GoldBee_SpawnSelf_SetProperties: phb : lda.b #$1E : pha : plb ; switch to bank 1E jsr GoldBee_SpawnSelf+12 plb : rtl -nop #3 +nop #2 + ;Code @@ -257,66 +264,42 @@ OWDestroyItemSprites: DEX : BPL .nextSprite PLX : RTL } + OWMirrorSpriteOnMap: { - lda.w $1ac0,x : bit.b #$f0 : beq .continue - lda.b #$00 : rtl - .continue - ora.w $1ab0,x - ora.w $1ad0,x - ora.w $1ae0,x - rtl + JSL OWWorldCheck + CMP.b #$40 ; part of what we wrote over + RTL } -OWPreserveMirrorSprite: +OWMirrorSpriteDisable: { - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla ; if OW Crossed, skip world check and continue - lda.b $10 : cmp.b #$0f : beq .vanilla ; if performing mirror superbunny - rtl + LDA.b $10 : CMP.b #$0F : BNE + ; avoid rare freeze during mirror superbunny + PLA : PLA : PLA : JML Sprite_6C_MirrorPortal_missing_mirror + + + + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla + lda.l InvertedMode : beq + + lda.b #$40 + + rtl .vanilla - lda.l InvertedMode : beq + - lda.l CurrentWorld : beq .deleteMirror - rtl - + lda.l CurrentWorld : bne .deleteMirror - rtl - - .deleteMirror - lda.b $10 : cmp.b #$0f : bne + - jsr.w OWMirrorSpriteMove ; if performing mirror superbunny - + pla : pla : pla : jml Sprite_6C_MirrorPortal_missing_mirror -} -OWMirrorSpriteMove: -{ - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq + - lda.w $1acf : ora.b #$40 : sta.w $1acf - + rts -} -OWMirrorSpriteBonk: -{ - jsr.w OWMirrorSpriteMove - lda.b #$2c : jml SetGameModeLikeMirror ; what we wrote over -} -OWMirrorSpriteDelete: -{ - stz.w $0dd0,x ; what we wrote over - jsr.w OWMirrorSpriteMove - jml Sprite_6C_MirrorPortal_dont_do_warp -} -OWMirrorSpriteRestore: -{ - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return - lda.l InvertedMode : beq + - lda.l CurrentWorld : beq .return - bra .restorePortal - + lda.l CurrentWorld : bne .return - - .restorePortal - lda.w $1acf : and.b #$0f : sta.w $1acf - - .return - rep #$30 : lda.w $04AC ; what we wrote over + lda.l CurrentWorld ; what we wrote over rtl } +OWMirrorSpriteSkipDraw: +{ + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla + lda.l InvertedMode : beq + + lda.l CurrentWorld : eor.b #$40 + bra ++ + + lda.l CurrentWorld : ++ beq .vanilla + stz.w $0D90,x ; disables collision + sec : rtl + + .vanilla + LDA.w $0FC6 : CMP.b #$03 ; what we wrote over + RTL +} OWLightWorldOrCrossed: { lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq ++ @@ -367,6 +350,16 @@ OWOldManSpeed: lda #$0c : sta $5e ; what we wrote over rtl } +OWMarkVisited: +{ + LDX.b $8A : STZ.w $0412 ; what we wrote over + LDA.b $10 : CMP.b #$14 : BCS .return + LDA.l OverworldEventDataWRAM,X + ORA.b #$80 : STA.l OverworldEventDataWRAM,X + + .return + RTL +} LoadMapDarkOrMixed: { @@ -422,7 +415,66 @@ LoadMapDarkOrMixed: dw $0400+$0210 ; bottom right } -OWBonkGoodBeeDrop: +OWBonkDropPrepSprite: +{ + LDA.b $1B : BEQ + + LDA.w $0FB5 ; what we wrote over + PHA + BRA .continue + + + STZ.w $0F20,X : STZ.w $0E30,X ; what we wrote over + PHA + + .continue + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BEQ .return + + LDA.w $0E20,X : CMP.b #$D9 : BNE + + LDA.b #$03 : STA.w $0F20,X + BRA .prep + + CMP.b #$B2 : BEQ .prep + PLA : RTL + + .prep + STZ.w !SPRITE_REDRAW,X + PHB : PHK : PLB : PHY + TXY : JSR OWBonkDropLookup : BCC .done + ; found match ; X = rec + 1 + INX : LDA.w OWBonkPrizeData,X : PHA + JSR OWBonkDropCollected : PLA : BCC .done + TYX : LDA.b #$01 : STA.w !SPRITE_REDRAW,X + .done + TYX : PLY : PLB + + .return + PLA : RTL +} + +OWBonkDropSparkle: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BEQ .nosparkle + LDA.w $0E90,X : BEQ .nosparkle + LDA.w !SPRITE_REDRAW,X : BNE .nosparkle + JSL Sprite_SpawnSparkleGarnish + ; move sparkle down 1 tile + PHX : TYX : PLY + LDA.l $7FF81E,X : CLC : ADC.b #$10 : STA.l $7FF81E,X + LDA.l $7FF85A,X : ADC.b #$00 : STA.l $7FF85A,X + PHY : TXY : PLX + + .nosparkle + LDA $0E20,X : CMP.b #$D9 : BEQ .greenrupee + CMP.b #$B2 : BEQ .goodbee + RTL + + .goodbee + LDA $0E90,X ; what we wrote over + RTL + + .greenrupee + JSL Sprite_DrawRippleIfInWater ; what we wrote over + RTL +} + +OWBonkDropsUnderworld: { LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled .vanilla ; what we wrote over @@ -430,20 +482,63 @@ OWBonkGoodBeeDrop: LDA.l BottleContentsOne : ORA.l BottleContentsTwo ORA.l BottleContentsThree : ORA.l BottleContentsFour RTL + .shuffled LDA.w $0DD0,X : BNE + - JMP .return+1 + BRA .return+1 + PHY : TXY - LDA.l RoomDataWRAM[$0120].high : AND.b #$02 : PHA : BNE + ; check if collected - LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - + - LDA.l OWBonkPrizeTable[42].mw_player : BEQ + ; multiworld item - LDA.l OWBonkPrizeTable[42].loot - JMP .spawn_item - + + JSL OWBonkDrops - .determine_type ; S = Collected - LDA.l OWBonkPrizeTable[42].loot ; A = item id + .return + PLY + LDA #$08 ; makes original good bee not spawn + RTL +} + +OWBonkDropsOverworld: +{ + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE .shuffled + BRA .vanilla + + .shuffled + LDA.w $0DD0,Y : BNE + + BRA .vanilla + + LDA.w $0E20,Y : CMP.b #$D9 : BEQ + + BRA .vanilla+3 + + + LDA.b #$00 : STA.w $0F20,Y ; restore proper layer + JSL OWBonkDrops + + .vanilla + LDA.w $0E20,Y : CMP.b #$D8 ; what we wrote over + RTL +} + +OWBonkDrops: +{ + PHB : PHK : PLB + LDA.b $1B : BEQ + + LDX.b #((UWBonkPrizeData-OWBonkPrizeData)+1) + BRA .found_match + + + JSR OWBonkDropLookup : BCS .found_match + JMP .return+2 + + .found_match + INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) + JSR OWBonkDropCollected : PHA : BCS .load_item_and_mw ; S = Collected, FlagBitmask, X (row + 2) + LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx + ; JSLSpriteSFX_QueueSFX3WithPan + + .load_item_and_mw + LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X + PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + + ; multiworld item + DEX : PLA ; A = item id; X = row + 3 + JMP .spawn_item + + DEX : PLA ; A = item id; X = row + 3 + + .determine_type ; A = item id; X = row + 3; S = Collected, FlagBitmask, X (row + 2) CMP.b #$B0 : BNE + LDA.b #$79 : JMP .sprite_transform ; transform to bees + CMP.b #$42 : BNE + @@ -475,140 +570,12 @@ OWBonkGoodBeeDrop: LDA.b #$AC : BRA .sprite_transform ; transform to apples + CMP.b #$B2 : BNE + LDA.b #$E3 : BRA .sprite_transform ; transform to fairy - + CMP.b #$B3 : BNE .spawn_item - INX : INX : LDA.l OWBonkPrizeTable[42].vert_offset - CLC : ADC.b #$08 : PHA - LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y - LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX - LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken - - .sprite_transform - JSL.l OWBonkSpritePrep - - .mark_collected ; S = Collected - PLA : BNE + - LDA.l RoomDataWRAM[$0120].high : ORA.b #$02 : STA.l RoomDataWRAM[$0120].high - - REP #$20 - LDA.l TotalItemCounter : INC : STA.l TotalItemCounter - SEP #$20 - + BRA .return - - ; spawn itemget item - .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected - PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return - + PHA - - LDA.b #$01 : STA !FORCE_HEART_SPAWN - - LDA.b #$EB : STA.l $7FFE00 - JSL Sprite_SpawnDynamically+15 ; +15 to skip finding a new slot, use existing sprite - - LDA.b #$01 : STA.w !SPRITE_REDRAW,Y - - PLA : STA.w $0E80,Y - - ; affects the rate the item moves in the Y/X direction - LDA.b #$00 : STA.w $0D40,Y - LDA.b #$0A : STA.w $0D50,Y - - LDA.b #$1A : STA.w $0F80,Y ; amount of force (gives height to the arch) - LDA.b #$FF : STA.w $0B58,Y ; stun timer - LDA.b #$30 : STA.w $0F10,Y ; aux delay timer 4 ?? dunno what that means - - LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on - - ; sets the tile type that is underneath the sprite, water - TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this - - ; sets OW event bitmask flag, uses free RAM - LDA.l OWBonkPrizeTable[42].flag : STA.w $0ED0,Y - - ; determines the initial spawn point of item - LDA.w $0D00,Y : SEC : SBC.l OWBonkPrizeTable[42].vert_offset : STA.w $0D00,Y - LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - - .return - PLY - LDA #$08 ; makes original good bee not spawn - RTL -} - -; Y = sprite slot index of bonk sprite -OWBonkDrops: -{ - CMP.b #$D8 : BEQ + - RTL - + LDA.l OWFlags+1 : AND.b #!FLAG_OW_BONKDROP : BNE + - JSL.l Sprite_TransmuteToBomb : RTL - + LDA.w $0DD0,Y : BNE + - RTL - + - - ; loop thru rando bonk table to find match - PHB : PHK : PLB - LDA.b $8A - LDX.b #(41*6) ; 41 bonk items, 6 bytes each - - CMP.w OWBonkPrizeData,X : BNE + - INX - LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A - EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1 - BRA .found_match - ++ DEX : LDA.b $8A - + CPX.b #$00 : BNE + - PLB : RTL - + DEX : DEX : DEX : DEX : DEX : DEX : BRA - - - .found_match - INX : LDA.w OWBonkPrizeData,X : PHX : PHA ; S = FlagBitmask, X (row + 2) - LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 1,S : PHA : BNE + ; S = Collected, FlagBitmask, X (row + 2) - LDA.b #$1B : STA $12F ; JSL Sound_SetSfx3PanLong ; seems that when you bonk, there is a pending bonk sfx, so we clear that out and replace with reveal secret sfx - + - LDA 3,S : TAX : INX : LDA.w OWBonkPrizeData,X - PHA : INX : LDA.w OWBonkPrizeData,X : BEQ + - ; multiworld item - DEX : PLA ; X = row + 3 - JMP .spawn_item - + DEX : PLA ; X = row + 3 - - .determine_type ; A = item id ; S = Collected, FlagBitmask, X (row + 2) - CMP.b #$B0 : BNE + - LDA.b #$79 : JMP .sprite_transform ; transform to bees - + CMP.b #$42 : BNE + - JSL.l Sprite_TransmuteToBomb ; transform a heart to bomb, vanilla behavior - JMP .mark_collected - + CMP.b #$34 : BNE + - LDA.b #$D9 : CLC : JMP .sprite_transform ; transform to single rupee - + CMP.b #$35 : BNE + - LDA.b #$DA : CLC : JMP .sprite_transform ; transform to blue rupee - + CMP.b #$36 : BNE + - LDA.b #$DB : CLC : BRA .sprite_transform ; transform to red rupee - + CMP.b #$27 : BNE + - LDA.b #$DC : CLC : BRA .sprite_transform ; transform to 1 bomb - + CMP.b #$28 : BNE + - LDA.b #$DD : CLC : BRA .sprite_transform ; transform to 4 bombs - + CMP.b #$31 : BNE + - LDA.b #$DE : CLC : BRA .sprite_transform ; transform to 8 bombs - + CMP.b #$45 : BNE + - LDA.b #$DF : CLC : BRA .sprite_transform ; transform to small magic - + CMP.b #$B4 : BNE + - LDA.b #$E0 : CLC : BRA .sprite_transform ; transform to big magic - + CMP.b #$B5 : BNE + - LDA.b #$79 : JSL.l OWBonkSpritePrep - JSL.l GoldBee_SpawnSelf_SetProperties ; transform to good bee - BRA .mark_collected - + CMP.b #$44 : BNE + - LDA.b #$E2 : CLC : BRA .sprite_transform ; transform to 10 arrows - + CMP.b #$B1 : BNE + - LDA.b #$AC : BRA .sprite_transform ; transform to apples - + CMP.b #$B2 : BNE + - LDA.b #$E3 : BRA .sprite_transform ; transform to fairy + CMP.b #$B3 : BNE .spawn_item INX : INX : LDA.w OWBonkPrizeData,X ; X = row + 5 CLC : ADC.b #$08 : PHA LDA.w $0D00,Y : SEC : SBC.b 1,S : STA.w $0D00,Y LDA.w $0D20,Y : SBC.b #$00 : STA.w $0D20,Y : PLX - LDA.b #$0B : SEC ; BRA .sprite_transform ; transform to chicken + LDA.b #$0B ; BRA .sprite_transform ; transform to chicken .sprite_transform JSL.l OWBonkSpritePrep @@ -616,16 +583,22 @@ OWBonkDrops: .mark_collected ; S = Collected, FlagBitmask, X (row + 2) PLA : BNE + ; S = FlagBitmask, X (row + 2) TYX : JSL Sprite_IsOnscreen : BCC + + LDA.b $1B : BEQ ++ + LDA.l RoomDataWRAM[$0120].high : ORA 1,S : STA.l RoomDataWRAM[$0120].high + LDA.w $0400 : ORA 1,S : STA.w $0400 + BRA .increment_collection + ++ LDX.b $8A : LDA.l OverworldEventDataWRAM,X : ORA 1,S : STA.l OverworldEventDataWRAM,X + .increment_collection REP #$20 LDA.l TotalItemCounter : INC : STA.l TotalItemCounter SEP #$20 - + JMP .return + + BRA .return ; spawn itemget item - .spawn_item ; A = item id ; Y = tree sprite slot ; S = Collected, FlagBitmask, X (row + 2) - PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : JMP .return ; S = FlagBitmask, X (row + 2) + .spawn_item ; A = item id ; Y = bonk sprite slot ; S = Collected, FlagBitmask, X (row + 2) + PLX : BEQ + : LDA.b #$00 : STA.w $0DD0,Y : BRA .return ; S = FlagBitmask, X (row + 2) + PHA LDA.b #$01 : STA !FORCE_HEART_SPAWN @@ -647,18 +620,57 @@ OWBonkDrops: LDA.b #$00 : STA.w $0F20,Y ; layer the sprite is on - ; sets OW event bitmask flag, uses free RAM + LDA.b $1B : BEQ + + ; sets the tile type that is underneath the sprite, water + TYX : LDA.b #$09 : STA.l $7FF9C2,X ; TODO: Figure out how to get the game to set this + + + + ; sets bitmask flag, uses free RAM PLA : STA.w $0ED0,Y ; S = X (row + 2) ; determines the initial spawn point of item PLX : INX : INX : INX LDA.w $0D00,Y : SEC : SBC.w OWBonkPrizeData,X : STA.w $0D00,Y LDA.w $0D20,Y : SBC #$00 : STA.w $0D20,Y - - PLB : RTL + + BRA .return+2 .return - PLA : PLA : PLB : RTL + PLA : PLA : PLB + RTL +} + +; Y = sprite slot; returns X = row + 1 +OWBonkDropLookup: +{ + ; loop thru rando bonk table to find match + LDA.b $8A + LDX.b #((UWBonkPrizeData-OWBonkPrizeData)-sizeof(OWBonkPrizeTable)) ; 41 bonk items, 6 bytes each + - CMP.w OWBonkPrizeData,X : BNE + + INX + LDA.w $0D10,Y : LSR A : LSR A : LSR A : LSR A + EOR.w $0D00,Y : CMP.w OWBonkPrizeData,X : BNE ++ ; X = row + 1 + SEC : RTS + ++ DEX : LDA.b $8A + + CPX.b #$00 : BNE + + CLC : RTS + + DEX : DEX : DEX : DEX : DEX : DEX : BRA - +} + +; S = FlagBitmask ; returns SEC if collected +OWBonkDropCollected: +{ + ; check if collected + CLC + LDA.b $1B : BEQ + + LDA.l RoomDataWRAM[$0120].high : AND.b 3,S : BEQ .return ; S = Collected, FlagBitmask, X (row + 2) + SEC : RTS + + + LDX.b $8A : LDA.l OverworldEventDataWRAM,X : AND 3,S : BEQ .return ; S = Collected, FlagBitmask, X (row + 2) + SEC : RTS + + .return + RTS } ; A = SpriteID, Y = Sprite Slot Index, X = free/overwritten @@ -1731,7 +1743,7 @@ db $18, $a8, $10, $b2, $00, $20 db $18, $36, $08, $35, $00, $20 db $1a, $8a, $10, $42, $00, $20 db $1a, $1d, $08, $b2, $00, $20 -db $ff, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree +;db $1a, $77, $04, $35, $00, $20 ; pre aga ONLY ; hijacked murahdahla bonk tree db $1b, $46, $10, $b1, $00, $10 db $1d, $6b, $10, $b1, $00, $20 db $1e, $72, $10, $b2, $00, $20 @@ -1742,6 +1754,7 @@ db $2e, $9c, $10, $b2, $00, $20 db $2e, $b4, $08, $b0, $00, $20 db $32, $29, $10, $42, $00, $20 db $32, $9a, $08, $b2, $00, $20 +;db $34, $xx, $10, $xx, $00, $1c ; pre aga ONLY db $42, $66, $10, $b2, $00, $20 db $51, $08, $10, $b2, $00, $04 db $51, $09, $08, $b2, $00, $04 @@ -1757,6 +1770,7 @@ db $6e, $8c, $10, $35, $00, $10 db $6e, $90, $08, $b0, $00, $10 db $6e, $a4, $04, $b1, $00, $10 db $74, $4e, $10, $b1, $00, $1c +UWBonkPrizeData: db $ff, $00, $02, $b5, $00, $08 ; temporary fix - murahdahla replaces one of the bonk tree prizes diff --git a/data/base2current.bps b/data/base2current.bps index 6c05ff5f..3c92ef74 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/docs/Customizer.md b/docs/Customizer.md index 68687c23..c881119f 100644 --- a/docs/Customizer.md +++ b/docs/Customizer.md @@ -6,6 +6,10 @@ This can also be used to roll a mystery or mutli-mystery seed via the GUI. [Exam The cli includes a couple arguments to help: +`--print_template_yaml` will create a yaml file based on the settings used. This does not contain any seed specific information. + +Present on the GUI as `Export Yaml` on the bottom. + `--print_custom_yaml` will create a yaml file based on the seed rolled. Treat it like a spoiler. `--customizer` takes a file as an argument. @@ -44,7 +48,7 @@ Start inventory is not supported here. It has a separate section. ###### Not Yet Implemented -Rom/Adjust flags like sprite, quickswap are not outputing with the print_custom_yaml settings +Rom/Adjust flags like sprite, quickswap are not outputing with the print_template_yaml or print_custom_yaml settings ### item_pool @@ -87,9 +91,62 @@ You may define an item, and a list of locations. The locations may be weighted i You may define an item and a list of locations that an item should not be placed at. This will apply to all items of that type. The logic is considered for this. If it is otherwise impossible, the item will be considered for the listed locations. This is important for small key layouts mostly, but it will try other locations first. +### ow-edges + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`. + +#### two-way + +`two-way` should be used for defining overworld edge transition connections. An asterisk `*` at the end of an edge name can be used on any parallel edge (an edge that exists in the same place in the opposite world), this will swap the defined edge with its parallel edge if the tile is flipped by Tile Flip. + +`Links House ES*: Stone Bridge WS*` The edge east of Links House will be vanilla, but if Links House screen gets flipped by Tile Flip, then Big Bomb Shop ES will connect to Stone Bridge. + +#### groups + +`groups` should be used for defining new pool divisions of overworld edge transitions. Each group must have some unique name with all the edges listed that are desired to exist in the pool. The name of a group can be anything as long as it is valid yaml syntax. These defined groups cannot break up edges that conflict with mode settings, like `Keep Similar Edges Together`. The asterisk `*` notation, described in the `ow-edges/two-way` section, can be used here. + +This example puts these 2 edges in their own pool, while the rest of the edges remain in their existing pools: +``` +someDescription: + - Links House ES* + - Stone Bridge WS* +``` + +### ow-crossed + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_crossed`, `force_noncrossed`, `limit_crossed`, and `undefined_chance`. There are also + +#### force_crossed / force_noncrossed + +`force_crossed` and `force_noncrossed` should be used to define specific overworld edge transitions you wish to be cross-world connected without needing to specify an exact destination. These sections are optional but must contain a list of edge names. The asterisk `*` notation, described in the `ow-edges/two-way` section, can be used here. + +#### limit_crossed + +`limit_crossed` should be used to limit how many overworld edge transitions end up connecting cross-world. This value can be set to any non-negative integer. A value of 0 means no edges will be cross-world, except for edges that are forced cross-world (either by the previous step or a result of some combination of OWR settings). This option only takes effect in `Unrestricted` Crossed setting. + +#### undefined_chance + +`undefined_chance` should be used to determine how to handle all the remaining overworld edge transitions that aren't explicitly defined in the earlier steps. This represents the percent chance an edge will be cross-world. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be cross-world. This option only takes effect under two mode combinations: +- 1: `Unrestricted` Crossed with a `Vanilla` OW Layout +- 2: `Grouped` Crossed + +#### (Grouped specific options) + +All four options available in the `ow-tileflips` section below are also available for `Grouped`, but must be defined under the `ow-crossed` section. In addition to the previous `undefined_chance`, the three other subsections are: `force_flip`, `force_no_flip`, and `force_together`. For more information about these sections, see the `ow-tileflips` section below. + +### ow-whirlpools + +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_whirlpool: true` in the `settings` section in order for any values here to take effect. This section has one primary subsection: `two-way`. + +#### two-way + +`two-way` should be used for defining whirlpool connections. + +`River Bend Whirlpool: Lake Hylia Whirlpool` The whirlpool west of Potion Shop will be connected to the whirlpool at Lake Hylia. + ### ow-tileflips -This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined_chance`. +This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_flip`, `force_no_flip`, `force_together`, and `undefined_chance`. #### force_flip / force_no_flip @@ -106,6 +163,17 @@ force_no_flip: - 0x13 ``` +#### force_together + +`force_together` should be used for defining tiles you want to force to flip as a group. Each group must have some unique name with all the OW Screen IDs listed that are desired to be grouped together. The name of a group can be anything as long as it is valid yaml syntax. + +Here is an example which forces Links House and Sanctuary screens to flip together: +``` +someGroup: + - 0x2c + - 0x13 +``` + #### undefined_chance `undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped. diff --git a/docs/customizer_example.yaml b/docs/customizer_example.yaml index ecf209b1..75c997bc 100644 --- a/docs/customizer_example.yaml +++ b/docs/customizer_example.yaml @@ -69,6 +69,36 @@ placements: Palace of Darkness - Big Chest: Hammer Capacity Upgrade - Left: Moon Pearl Turtle Rock - Pokey 2 Key Drop: Ice Rod +ow-edges: + 1: + two-way: + Kakariko Fortune ES*: Sanctuary WN* + Central Bonk Rocks EC: Potion Shop WN + Central Bonk Rocks ES: Potion Shop WC + groups: + someDescription: + - Tree Line SC* + - Lake Hylia NC* + - Dark Tree Line SC* + - Ice Lake NC* + 1234: + - Lake Hylia EC* + - Octoballoon WC* + - Ice Lake EC* + - Bomber Corner WC* +ow-crossed: + 1: + force_crossed: + - Links House ES* + - Kakariko Fortune ES* + force_noncrossed: + - Links House NE + limit_crossed: 9 # emulates Limited Crossed + undefined_chance: 25 +ow-whirlpools: + 1: + two-way: + River Bend Whirlpool: Lake Hylia Whirlpool ow-tileflips: 1: force_flip: @@ -76,6 +106,10 @@ ow-tileflips: force_no_flip: - 0x2c - 0x18 + force_together: + someDescription: + - 0x16 + - 0x2b undefined_chance: 50 ow-flutespots: 1: diff --git a/docs/presets/Swapkeys.yaml b/docs/presets/Swapkeys.yaml deleted file mode 100644 index 20b7e88b..00000000 --- a/docs/presets/Swapkeys.yaml +++ /dev/null @@ -1,22 +0,0 @@ -meta: - branch: OWR - seed_name: Swapkeys - seed_notes: Crosskeys but Swapped ER -settings: - 1: - mode: open - logic: noglitches - goal: crystals - crystals_gt: "7" - crystals_ganon: "7" - accessibility: locations - mapshuffle: 1 - compassshuffle: 1 - keyshuffle: wild - bigkeyshuffle: 1 - shuffle: swapped - shuffleganon: 1 - shufflelinks: 0 - shuffletavern: 1 - experimental: 0 - hints: 0 diff --git a/docs/presets/swapkeys.yml b/docs/presets/swapkeys.yml deleted file mode 100644 index e71e4521..00000000 --- a/docs/presets/swapkeys.yml +++ /dev/null @@ -1,17 +0,0 @@ -settings: - 1: - description: Swapkeys - glitches_required: none - mode: open - goal: crystals - crystals_gt: "7" - crystals_ganon: "7" - weapons: randomized - accessibility: locations - entrance_shuffle: swapped - shufflelinks: off - shuffletavern: on - mapshuffle: on - compassshuffle: on - keyshuffle: wild - bigkeyshuffle: on diff --git a/docs/vanilla_multi_lobbies.yaml b/docs/vanilla_multi_lobbies.yaml deleted file mode 100644 index 06eaf835..00000000 --- a/docs/vanilla_multi_lobbies.yaml +++ /dev/null @@ -1,28 +0,0 @@ -doors: - 1: - lobbies: - #Agahnims Tower: Tower Lobby S - Desert Back: Desert Back Lobby S - Desert East: Desert East Lobby S - Desert South: Desert Main Lobby S - Desert West: Desert West S - #Eastern: Eastern Lobby S - #Ganons Tower: GT Lobby S - #Hera: Hera Lobby S - Hyrule Castle East: Hyrule Castle East Lobby S - Hyrule Castle South: Hyrule Castle Lobby S - Hyrule Castle West: Hyrule Castle West Lobby S - #Ice: Ice Lobby SE - #Mire: Mire Lobby S - #Palace of Darkness: PoD Lobby S - #Sanctuary: Sanctuary S - #Skull 1: Skull 1 Lobby S - #Skull 2 East: Skull 2 East Lobby SW - #Skull 2 West: Skull 2 West Lobby S - #Skull 3: Skull 3 Lobby SW - #Swamp: Swamp Lobby S - #Thieves Town: Thieves Lobby S - Turtle Rock Chest: TR Big Chest Entrance SE - Turtle Rock Eye Bridge: TR Eye Bridge SW - Turtle Rock Lazy Eyes: TR Lazy Eyes SE - Turtle Rock Main: TR Main Lobby SE diff --git a/mystery_example.yml b/mystery_example.yml index f37bc7c3..113c67bd 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -10,8 +10,7 @@ none: 4 polar: 1 grouped: 1 - limited: 1 - chaos: 1 + unrestricted: 1 overworld_keepsimilar: on: 1 off: 1 @@ -274,3 +273,9 @@ shuffle_sfx: on: 1 off: 1 + shuffle_sfxinstruments: + on: 1 + off: 1 + shuffle_songinstruments: + on: 1 + off: 1 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index d2355f4f..7a2ad91c 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -199,3 +199,9 @@ rom: shuffle_sfx: on: 1 off: 1 + shuffle_sfxinstruments: + on: 1 + off: 1 + shuffle_songinstruments: + on: 1 + off: 1 diff --git a/presets/world/owr_crossed-limited.yaml b/presets/world/owr_crossed-limited.yaml new file mode 100644 index 00000000..54d07a32 --- /dev/null +++ b/presets/world/owr_crossed-limited.yaml @@ -0,0 +1,6 @@ +settings: + 1: + ow_crossed: unrestricted +ow-crossed: + 1: + limit_crossed: 9 \ No newline at end of file diff --git a/presets/world/owr_crossed-max.yaml b/presets/world/owr_crossed-max.yaml new file mode 100644 index 00000000..63409850 --- /dev/null +++ b/presets/world/owr_crossed-max.yaml @@ -0,0 +1,7 @@ +settings: + 1: + ow_crossed: unrestricted +ow-crossed: + 1: + limit_crossed: 9999 + undefined_chance: 100 \ No newline at end of file diff --git a/presets/world/owr_districtshuffle-full.yaml b/presets/world/owr_districtshuffle-full.yaml new file mode 100644 index 00000000..e9e62916 --- /dev/null +++ b/presets/world/owr_districtshuffle-full.yaml @@ -0,0 +1,284 @@ +settings: + 1: + ow_shuffle: full + ow_keepsimilar: false +ow-edges: + 1: + groups: + NorthwestHyrule: + - Lost Woods NW + - Master Sword Meadow SC + - Lost Woods EN* + - Lumberjack WN* + - Lost Woods SW* + - Lost Woods Pass NW* + - Lost Woods SC* + - Lost Woods Pass NE* + - Lost Woods SE* + - Kakariko Fortune NE* + - Lumberjack SW* + - Mountain Pass NW* + - Mountain Pass SE* + - Kakariko Pond NE* + - Kakariko Fortune EN* + - Kakariko Pond WN* + - Kakariko Fortune ES* + - Kakariko Pond WS* + - Kakariko Pond EN* + - Sanctuary WN* + - Kakariko Pond ES* + - Sanctuary WS* + - Kakariko Pond SW* + - Forgotten Forest NW* + - Kakariko Pond SE* + - Forgotten Forest NE* + - Sanctuary EC* + - Graveyard WC* + - Graveyard EC* + - River Bend WC* + DeathMountain: + - West Death Mountain EN* + - East Death Mountain WN* + - West Death Mountain ES* + - East Death Mountain WS* + - East Death Mountain EN* + - Death Mountain TR Pegs WN* + EasternHyrule: + - Zora Waterfall NE + - Zoras Domain SW + - Zora Waterfall SE* + - Zora Approach NE* + - River Bend EN* + - Potion Shop WN* + - River Bend EC* + - Potion Shop WC* + - River Bend ES* + - Potion Shop WS* + - River Bend SC* + - Wooden Bridge NC* + - River Bend SE* + - Wooden Bridge NE* + - Potion Shop EN* + - Zora Approach WN* + - Potion Shop EC* + - Zora Approach WC* + - Wooden Bridge SW* + - Sand Dunes NW* + - Eastern Palace SW* + - Tree Line NW* + - Eastern Palace SE* + - Eastern Nook NE* + - Sand Dunes SC* + - Stone Bridge NC* + - Stone Bridge EN* + - Tree Line WN* + Kakariko: + - Kakariko ES* + - Blacksmith WS* + - Kakariko SE* + - Kakariko Suburb NE* + - Maze Race ES* + - Kakariko Suburb WS* + CentralHyrule: + - Hyrule Castle SW* + - Central Bonk Rocks NW* + - Hyrule Castle SE* + - Links House NE* + - Flute Boy SW* + - Flute Boy Approach NW* + - Flute Boy SC* + - Flute Boy Approach NC* + - Central Bonk Rocks EN* + - Links House WN* + - Central Bonk Rocks EC* + - Links House WC* + - Central Bonk Rocks ES* + - Links House WS* + - Central Bonk Rocks SW* + - C Whirlpool NW* + - Links House SC* + - Statues NC* + - Flute Boy Approach EC* + - C Whirlpool WC* + - C Whirlpool EN* + - Statues WN* + - C Whirlpool EC* + - Statues WC* + - C Whirlpool ES* + - Statues WS* + - C Whirlpool SC* + - Dam NC* + - Statues SC* + - South Pass NC* + - Dam EC* + - South Pass WC* + LakeHylia: + - Stone Bridge WC + - Hobo EC + - Stone Bridge EC* + - Tree Line WC* + - Stone Bridge SC* + - Lake Hylia NW* + - Tree Line SC* + - Lake Hylia NC* + - Lake Hylia EC* + - Octoballoon WC* + - Lake Hylia ES* + - Octoballoon WS* + - Ice Cave SW* + - Octoballoon NW* + - Ice Cave SE* + - Octoballoon NE* + Desert: + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + NorthwestDarkWorld: + - Skull Woods EN* + - Dark Lumberjack WN* + - Skull Woods SW* + - Skull Woods Pass NW* + - Skull Woods SC* + - Skull Woods Pass NE* + - Skull Woods SE* + - Dark Fortune NE* + - Dark Lumberjack SW* + - Bumper Cave NW* + - Bumper Cave SE* + - Outcast Pond NE* + - Skull Woods Pass SW* + - Village of Outcasts NW* + - Skull Woods Pass SE* + - Village of Outcasts NC* + - Dark Fortune EN* + - Outcast Pond WN* + - Dark Fortune ES* + - Outcast Pond WS* + - Dark Fortune SC* + - Village of Outcasts NE* + - Outcast Pond EN* + - Dark Chapel WN* + - Outcast Pond ES* + - Dark Chapel WS* + - Outcast Pond SW* + - Shield Shop NW* + - Outcast Pond SE* + - Shield Shop NE* + - Dark Chapel EC* + - Dark Graveyard WC* + - Dark Graveyard EC* + - Qirn Jump WC* + - Village of Outcasts ES* + - Hammer Pegs WS* + DarkDeathMountain: + - West Dark Death Mountain EN* + - East Dark Death Mountain WN* + - West Dark Death Mountain ES* + - East Dark Death Mountain WS* + - East Dark Death Mountain EN* + - Turtle Rock WN* + EastDarkWorld: + - Catfish SE* + - Catfish Approach NE* + - Dark Witch EN* + - Catfish Approach WN* + - Dark Witch EC* + - Catfish Approach WC* + - Qirn Jump EN* + - Dark Witch WN* + - Qirn Jump EC* + - Dark Witch WC* + - Qirn Jump ES* + - Dark Witch WS* + - Qirn Jump SC* + - Broken Bridge NC* + - Qirn Jump SE* + - Broken Bridge NE* + - Broken Bridge SW* + - Dark Dunes NW* + - Pyramid ES* + - Dark Dunes WN* + - Dark Dunes SC* + - Hammer Bridge NC* + - Palace of Darkness SW* + - Dark Tree Line NW* + - Palace of Darkness SE* + - Palace of Darkness Nook NE* + - Hammer Bridge EN* + - Dark Tree Line WN* + SouthDarkWorld: + - Dig Game EC + - Frog WC + - Dig Game ES* + - Frog WS* + - Frog ES* + - Stumpy WS* + - Stumpy SW* + - Stumpy Approach NW* + - Stumpy SC* + - Stumpy Approach NC* + - Dark Bonk Rocks EN* + - Big Bomb Shop WN* + - Dark Bonk Rocks EC* + - Big Bomb Shop WC* + - Dark Bonk Rocks ES* + - Big Bomb Shop WS* + - Dark Bonk Rocks SW* + - Dark C Whirlpool NW* + - Big Bomb Shop SC* + - Hype Cave NC* + - Big Bomb Shop ES* + - Hammer Bridge WS* + - Hammer Bridge EC* + - Dark Tree Line WC* + - Hammer Bridge SC* + - Ice Lake NW* + - Dark Tree Line SC* + - Ice Lake NC* + - Stumpy Approach EC* + - Dark C Whirlpool WC* + - Dark C Whirlpool EN* + - Hype Cave WN* + - Dark C Whirlpool EC* + - Hype Cave WC* + - Dark C Whirlpool ES* + - Hype Cave WS* + - Dark C Whirlpool SC* + - Swamp NC* + - Hype Cave SC* + - Dark South Pass NC* + - Ice Lake WS* + - Dark South Pass ES* + - Ice Lake EC* + - Bomber Corner WC* + - Ice Lake ES* + - Bomber Corner WS* + - Shopping Mall SW* + - Bomber Corner NW* + - Shopping Mall SE* + - Bomber Corner NE* + - Swamp Nook EC* + - Swamp WC* + - Swamp Nook ES* + - Swamp WS* + - Swamp EC* + - Dark South Pass WC* +ow-tileflips: + 1: + force_no_flips: + - 0x30 + - 0x3a + force_together: + LostWoodsPedestal: + - 0x00 + - 0x80 + ZoraWaterfall: + - 0x0f + - 0x81 + MazeRaceSuburb: + - 0x28 + - 0x29 + HyruleCastleWest: + - 0x1a + - 0x1b \ No newline at end of file diff --git a/presets/world/owr_districtshuffle-pangea.yaml b/presets/world/owr_districtshuffle-pangea.yaml new file mode 100644 index 00000000..e4d1ff3c --- /dev/null +++ b/presets/world/owr_districtshuffle-pangea.yaml @@ -0,0 +1,151 @@ +settings: + 1: + ow_shuffle: full + ow_keepsimilar: false +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Mountain Pass SE*: Kakariko Pond NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Sanctuary EC*: Graveyard WC* + Graveyard EC*: River Bend WC* + + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Stone Bridge EN*: Tree Line WN* + + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Maze Race ES*: Kakariko Suburb WS* + + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Links House SC*: Statues NC* + Flute Boy Approach EC*: C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Statues SC*: South Pass NC* + Dam EC*: South Pass WC* + + Stone Bridge WC: Hobo EC + Stone Bridge EC*: Tree Line WC* + Stone Bridge SC*: Lake Hylia NW* + Tree Line SC*: Lake Hylia NC* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Dark Lumberjack SW*: Bumper Cave NW* + Bumper Cave SE*: Outcast Pond NE* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Dark Fortune SC*: Village of Outcasts NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Dark Chapel EC*: Dark Graveyard WC* + Dark Graveyard EC*: Qirn Jump WC* + Village of Outcasts ES*: Hammer Pegs WS* + + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Dark Death Mountain EN*: Turtle Rock WN* + + Catfish SE*: Catfish Approach NE* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Broken Bridge SW*: Dark Dunes NW* + Pyramid ES*: Dark Dunes WN* + Dark Dunes SC*: Hammer Bridge NC* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Hammer Bridge EN*: Dark Tree Line WN* + + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Frog ES*: Stumpy WS* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Hammer Bridge EC*: Dark Tree Line WC* + Hammer Bridge SC*: Ice Lake NW* + Dark Tree Line SC*: Ice Lake NC* + Stumpy Approach EC*: Dark C Whirlpool WC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Hype Cave SC*: Dark South Pass NC* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Swamp EC*: Dark South Pass WC* +ow-tileflips: + 1: + force_no_flips: + - 0x30 + - 0x3a + force_together: + HyruleCastleWest: + - 0x1a + - 0x1b \ No newline at end of file diff --git a/presets/world/owr_districtshuffle-vanillaborders.yaml b/presets/world/owr_districtshuffle-vanillaborders.yaml new file mode 100644 index 00000000..016dd9da --- /dev/null +++ b/presets/world/owr_districtshuffle-vanillaborders.yaml @@ -0,0 +1,298 @@ +settings: + 1: + ow_shuffle: full + ow_keepsimilar: false +ow-edges: + 1: + two-way: + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Kakariko Fortune SC*: Kakariko NE* + River Bend SW*: Wooden Bridge NW* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Kakariko Suburb ES*: Flute Boy WS* + Links House ES*: Stone Bridge WS* + Tree Line SE*: Lake Hylia NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Qirn Jump SW*: Broken Bridge NW* + Village of Outcasts SE*: Frog NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Dark Tree Line SE*: Ice Lake NE* + groups: + NorthwestHyrule: + - Lost Woods NW + - Master Sword Meadow SC + - Lost Woods EN* + - Lumberjack WN* + - Lost Woods SW* + - Lost Woods Pass NW* + - Lost Woods SC* + - Lost Woods Pass NE* + - Lost Woods SE* + - Kakariko Fortune NE* + - Lumberjack SW* + - Mountain Pass NW* + - Mountain Pass SE* + - Kakariko Pond NE* + - Kakariko Fortune EN* + - Kakariko Pond WN* + - Kakariko Fortune ES* + - Kakariko Pond WS* + - Kakariko Pond EN* + - Sanctuary WN* + - Kakariko Pond ES* + - Sanctuary WS* + - Kakariko Pond SW* + - Forgotten Forest NW* + - Kakariko Pond SE* + - Forgotten Forest NE* + - Sanctuary EC* + - Graveyard WC* + - Graveyard EC* + - River Bend WC* + DeathMountain: + - West Death Mountain EN* + - East Death Mountain WN* + - West Death Mountain ES* + - East Death Mountain WS* + - East Death Mountain EN* + - Death Mountain TR Pegs WN* + EasternHyrule: + - Zora Waterfall NE + - Zoras Domain SW + - Zora Waterfall SE* + - Zora Approach NE* + - River Bend EN* + - Potion Shop WN* + - River Bend EC* + - Potion Shop WC* + - River Bend ES* + - Potion Shop WS* + - River Bend SC* + - Wooden Bridge NC* + - River Bend SE* + - Wooden Bridge NE* + - Potion Shop EN* + - Zora Approach WN* + - Potion Shop EC* + - Zora Approach WC* + - Wooden Bridge SW* + - Sand Dunes NW* + - Eastern Palace SW* + - Tree Line NW* + - Eastern Palace SE* + - Eastern Nook NE* + - Sand Dunes SC* + - Stone Bridge NC* + - Stone Bridge EN* + - Tree Line WN* + Kakariko: + - Kakariko ES* + - Blacksmith WS* + - Kakariko SE* + - Kakariko Suburb NE* + - Maze Race ES* + - Kakariko Suburb WS* + CentralHyrule: + - Hyrule Castle SW* + - Central Bonk Rocks NW* + - Hyrule Castle SE* + - Links House NE* + - Flute Boy SW* + - Flute Boy Approach NW* + - Flute Boy SC* + - Flute Boy Approach NC* + - Central Bonk Rocks EN* + - Links House WN* + - Central Bonk Rocks EC* + - Links House WC* + - Central Bonk Rocks ES* + - Links House WS* + - Central Bonk Rocks SW* + - C Whirlpool NW* + - Links House SC* + - Statues NC* + - Flute Boy Approach EC* + - C Whirlpool WC* + - C Whirlpool EN* + - Statues WN* + - C Whirlpool EC* + - Statues WC* + - C Whirlpool ES* + - Statues WS* + - C Whirlpool SC* + - Dam NC* + - Statues SC* + - South Pass NC* + - Dam EC* + - South Pass WC* + LakeHylia: + - Stone Bridge WC + - Hobo EC + - Stone Bridge EC* + - Tree Line WC* + - Stone Bridge SC* + - Lake Hylia NW* + - Tree Line SC* + - Lake Hylia NC* + - Lake Hylia EC* + - Octoballoon WC* + - Lake Hylia ES* + - Octoballoon WS* + - Ice Cave SW* + - Octoballoon NW* + - Ice Cave SE* + - Octoballoon NE* + Desert: + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + NorthwestDarkWorld: + - Skull Woods EN* + - Dark Lumberjack WN* + - Skull Woods SW* + - Skull Woods Pass NW* + - Skull Woods SC* + - Skull Woods Pass NE* + - Skull Woods SE* + - Dark Fortune NE* + - Dark Lumberjack SW* + - Bumper Cave NW* + - Bumper Cave SE* + - Outcast Pond NE* + - Skull Woods Pass SW* + - Village of Outcasts NW* + - Skull Woods Pass SE* + - Village of Outcasts NC* + - Dark Fortune EN* + - Outcast Pond WN* + - Dark Fortune ES* + - Outcast Pond WS* + - Dark Fortune SC* + - Village of Outcasts NE* + - Outcast Pond EN* + - Dark Chapel WN* + - Outcast Pond ES* + - Dark Chapel WS* + - Outcast Pond SW* + - Shield Shop NW* + - Outcast Pond SE* + - Shield Shop NE* + - Dark Chapel EC* + - Dark Graveyard WC* + - Dark Graveyard EC* + - Qirn Jump WC* + - Village of Outcasts ES* + - Hammer Pegs WS* + DarkDeathMountain: + - West Dark Death Mountain EN* + - East Dark Death Mountain WN* + - West Dark Death Mountain ES* + - East Dark Death Mountain WS* + - East Dark Death Mountain EN* + - Turtle Rock WN* + EastDarkWorld: + - Catfish SE* + - Catfish Approach NE* + - Dark Witch EN* + - Catfish Approach WN* + - Dark Witch EC* + - Catfish Approach WC* + - Qirn Jump EN* + - Dark Witch WN* + - Qirn Jump EC* + - Dark Witch WC* + - Qirn Jump ES* + - Dark Witch WS* + - Qirn Jump SC* + - Broken Bridge NC* + - Qirn Jump SE* + - Broken Bridge NE* + - Broken Bridge SW* + - Dark Dunes NW* + - Pyramid ES* + - Dark Dunes WN* + - Dark Dunes SC* + - Hammer Bridge NC* + - Palace of Darkness SW* + - Dark Tree Line NW* + - Palace of Darkness SE* + - Palace of Darkness Nook NE* + - Hammer Bridge EN* + - Dark Tree Line WN* + SouthDarkWorld: + - Dig Game EC + - Frog WC + - Dig Game ES* + - Frog WS* + - Frog ES* + - Stumpy WS* + - Stumpy SW* + - Stumpy Approach NW* + - Stumpy SC* + - Stumpy Approach NC* + - Dark Bonk Rocks EN* + - Big Bomb Shop WN* + - Dark Bonk Rocks EC* + - Big Bomb Shop WC* + - Dark Bonk Rocks ES* + - Big Bomb Shop WS* + - Dark Bonk Rocks SW* + - Dark C Whirlpool NW* + - Big Bomb Shop SC* + - Hype Cave NC* + - Big Bomb Shop ES* + - Hammer Bridge WS* + - Hammer Bridge EC* + - Dark Tree Line WC* + - Hammer Bridge SC* + - Ice Lake NW* + - Dark Tree Line SC* + - Ice Lake NC* + - Stumpy Approach EC* + - Dark C Whirlpool WC* + - Dark C Whirlpool EN* + - Hype Cave WN* + - Dark C Whirlpool EC* + - Hype Cave WC* + - Dark C Whirlpool ES* + - Hype Cave WS* + - Dark C Whirlpool SC* + - Swamp NC* + - Hype Cave SC* + - Dark South Pass NC* + - Ice Lake WS* + - Dark South Pass ES* + - Ice Lake EC* + - Bomber Corner WC* + - Ice Lake ES* + - Bomber Corner WS* + - Shopping Mall SW* + - Bomber Corner NW* + - Shopping Mall SE* + - Bomber Corner NE* + - Swamp Nook EC* + - Swamp WC* + - Swamp Nook ES* + - Swamp WS* + - Swamp EC* + - Dark South Pass WC* +ow-tileflips: + 1: + force_no_flips: + - 0x30 + - 0x3a + force_together: + LostWoodsPedestal: + - 0x00 + - 0x80 + ZoraWaterfall: + - 0x0f + - 0x81 + MazeRaceSuburb: + - 0x28 + - 0x29 \ No newline at end of file diff --git a/presets/world/owr_flute-nearvanilla.yaml b/presets/world/owr_flute-nearvanilla.yaml new file mode 100644 index 00000000..5941d344 --- /dev/null +++ b/presets/world/owr_flute-nearvanilla.yaml @@ -0,0 +1,31 @@ +settings: + 1: + ow_fluteshuffle: balanced +ow-flutespots: + 1: + forbid: + - 0x00 # Lost Woods + - 0x02 # Lumberjack + - 0x07 # Death Mountain TR Pegs + - 0x0A # Mountain Pass + - 0x0F # Zora Waterfall + - 0x10 # Lost Woods Pass + - 0x12 # Kakariko Pond + - 0x13 # Sanctuary + - 0x14 # Graveyard + - 0x17 # Zora Approach + - 0x1A # Forgotten Forest + - 0x1D # Wooden Bridge + - 0x22 # Blacksmith + - 0x25 # Sand Dunes + - 0x28 # Maze Race + - 0x29 # Kakariko Suburb + - 0x2A # Flute Boy + - 0x2B # Central Bonk Rocks + - 0x2D # Stone Bridge + - 0x2E # Tree Line + - 0x32 # Flute Boy Approach + - 0x34 # Statues + - 0x37 # Ice Cave + - 0x3A # Desert Pass + - 0x3C # South Pass diff --git a/presets/world/owr_quadrantshuffle-diagonal.yaml b/presets/world/owr_quadrantshuffle-diagonal.yaml new file mode 100644 index 00000000..010d9e3c --- /dev/null +++ b/presets/world/owr_quadrantshuffle-diagonal.yaml @@ -0,0 +1,308 @@ +ow-edges: + 1: + groups: + north: + - Lost Woods NW + - Lost Woods EN + - Skull Woods EN + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Kakariko Pond NE + - Kakariko Pond EN + - Kakariko Pond ES + - Outcast Pond NE + - Outcast Pond EN + - Outcast Pond ES + - Sanctuary WN + - Sanctuary WS + - Sanctuary EC + - Dark Chapel WN + - Dark Chapel WS + - Dark Chapel EC + - Graveyard WC + - Graveyard EC + - Dark Graveyard WC + - Dark Graveyard EC + - River Bend WC + - Qirn Jump WC + - Master Sword Meadow SC + east: + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Hyrule Castle ES + - Pyramid ES + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Wooden Bridge SW + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Broken Bridge SW + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes NW + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes NW + - Dark Dunes WN + - Dark Dunes SC + - Stone Bridge NC + - Stone Bridge EN + - Stone Bridge EC + - Hammer Bridge NC + - Hammer Bridge EN + - Hammer Bridge EC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Lake Hylia NC + - Lake Hylia NE + - Ice Lake NC + - Ice Lake NE + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - Octoballoon NW + - Octoballoon NE + - Bomber Corner NW + - Bomber Corner NE + - Zoras Domain SW + south: + - Hyrule Castle SW + - Hyrule Castle SE + - Pyramid SW + - Pyramid SE + - Flute Boy SW + - Flute Boy SC + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Dark Bonk Rocks SW + - Links House NE + - Links House WN + - Links House WC + - Links House WS + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge SC + - Hammer Bridge WS + - Hammer Bridge SC + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Dark C Whirlpool SC + - Statues NC + - Statues WN + - Statues WC + - Statues WS + - Statues SC + - Hype Cave NC + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Dam EC + - Swamp NC + - Swamp WC + - Swamp WS + - Swamp EC + - South Pass NC + - South Pass WC + - South Pass ES + - Dark South Pass NC + - Dark South Pass WC + - Dark South Pass ES + - Octoballoon WC + - Octoballoon WS + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC + west: + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond SW + - Outcast Pond SE + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Kakariko ES + - Kakariko SE + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Village of Outcasts ES + - Village of Outcasts SE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Stumpy WS +ow-tileflips: + 1: + force_together: + PedHobo: + - 0x00 + - 0x2d + - 0x80 + Zora: + - 0x0f + - 0x81 + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_quadrantshuffle-full.yaml b/presets/world/owr_quadrantshuffle-full.yaml new file mode 100644 index 00000000..6dabad7c --- /dev/null +++ b/presets/world/owr_quadrantshuffle-full.yaml @@ -0,0 +1,291 @@ +ow-edges: + 1: + groups: + northwest: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Dark Chapel WN + - Dark Chapel WS + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Master Sword Meadow SC + northeast: + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Graveyard EC + - Dark Graveyard EC + - River Bend WC + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Zoras Domain SW + southwest: + - Kakariko ES + - Kakariko SE + - Village of Outcasts ES + - Village of Outcasts SE + - Hyrule Castle SW + - Pyramid SW + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Flute Boy SW + - Flute Boy SC + - Stumpy WS + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks SW + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool SC + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Swamp NC + - Swamp WC + - Swamp WS + southeast: + - Hyrule Castle ES + - Hyrule Castle SE + - Pyramid ES + - Pyramid SE + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes WN + - Dark Dunes SC + - Links House NE + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge NC + - Hammer Bridge WS + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Statues NC + - Statues SC + - Hype Cave NC + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia NC + - Lake Hylia NE + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake NC + - Ice Lake NE + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - South Pass NC + - South Pass ES + - Dark South Pass NC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC + borders: + - Sanctuary EC + - Dark Chapel EC + - Graveyard WC + - Dark Graveyard WC + - Wooden Bridge SW + - Broken Bridge SW + - Sand Dunes NW + - Dark Dunes NW + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - C Whirlpool + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Dam EC + - Swamp EC + - South Pass WC + - Dark South Pass WC \ No newline at end of file diff --git a/presets/world/owr_quadrantshuffle-vanillaborders.yaml b/presets/world/owr_quadrantshuffle-vanillaborders.yaml new file mode 100644 index 00000000..22f9e8fb --- /dev/null +++ b/presets/world/owr_quadrantshuffle-vanillaborders.yaml @@ -0,0 +1,275 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + groups: + northwest: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Lost Woods SE + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Skull Woods SE + - Lumberjack WN + - Lumberjack SW + - Dark Lumberjack WN + - Dark Lumberjack SW + - Mountain Pass NW + - Mountain Pass SE + - Bumper Cave NW + - Bumper Cave SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Kakariko Fortune NE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune EN + - Dark Fortune ES + - Dark Fortune SC + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Kakariko NW + - Kakariko NC + - Kakariko NE + - Dark Chapel WN + - Dark Chapel WS + - Village of Outcasts NW + - Village of Outcasts NC + - Village of Outcasts NE + - Forgotten Forest NW + - Forgotten Forest NE + - Forgotten Forest ES + - Shield Shop NW + - Shield Shop NE + - Hyrule Castle WN + - Master Sword Meadow SC + northeast: + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Graveyard EC + - Dark Graveyard EC + - River Bend WC + - River Bend EN + - River Bend EC + - River Bend ES + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Potion Shop EN + - Potion Shop EC + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Dark Witch EN + - Dark Witch EC + - Zora Approach NE + - Zora Approach WN + - Zora Approach WC + - Catfish Approach NE + - Catfish Approach WN + - Catfish Approach WC + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Zoras Domain SW + southwest: + - Kakariko ES + - Kakariko SE + - Village of Outcasts ES + - Village of Outcasts SE + - Hyrule Castle SW + - Pyramid SW + - Blacksmith WS + - Hammer Pegs WS + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb NE + - Kakariko Suburb WS + - Kakariko Suburb ES + - Frog NE + - Frog WC + - Frog WS + - Frog ES + - Flute Boy WS + - Flute Boy SW + - Flute Boy SC + - Stumpy WS + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks NW + - Central Bonk Rocks SW + - Dark Bonk Rocks NW + - Dark Bonk Rocks SW + - Desert EC + - Desert ES + - Flute Boy Approach NW + - Flute Boy Approach NC + - Flute Boy Approach EC + - Stumpy Approach NW + - Stumpy Approach NC + - Stumpy Approach EC + - C Whirlpool NW + - C Whirlpool WC + - C Whirlpool SC + - Dark C Whirlpool NW + - Dark C Whirlpool WC + - Dark C Whirlpool SC + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam NC + - Dam WC + - Dam WS + - Swamp NC + - Swamp WC + - Swamp WS + southeast: + - Hyrule Castle ES + - Hyrule Castle SE + - Pyramid ES + - Pyramid SE + - Eastern Palace SW + - Eastern Palace SE + - Palace of Darkness SW + - Palace of Darkness SE + - Sand Dunes WN + - Sand Dunes SC + - Dark Dunes WN + - Dark Dunes SC + - Links House NE + - Links House SC + - Links House ES + - Big Bomb Shop NE + - Big Bomb Shop SC + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge NC + - Hammer Bridge WS + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line NW + - Tree Line WN + - Tree Line WC + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line WN + - Dark Tree Line WC + - Dark Tree Line SC + - Dark Tree Line SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Statues NC + - Statues SC + - Hype Cave NC + - Hype Cave SC + - Lake Hylia NW + - Lake Hylia NC + - Lake Hylia NE + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake NW + - Ice Lake NC + - Ice Lake NE + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - South Pass NC + - South Pass ES + - Dark South Pass NC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Hobo EC \ No newline at end of file diff --git a/presets/world/owr_ringshuffle-borders.yaml b/presets/world/owr_ringshuffle-borders.yaml new file mode 100644 index 00000000..3cbdb28f --- /dev/null +++ b/presets/world/owr_ringshuffle-borders.yaml @@ -0,0 +1,214 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + West Death Mountain EN*: East Death Mountain WN* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods SE*: Dark Fortune NE* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Eastern Palace SW*: Tree Line NW* + Palace of Darkness SW*: Dark Tree Line NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + groups: + border12_nw: + - Potion Shop EN + - Potion Shop EC + - Dark Witch EN + - Dark Witch EC + - Zora Approach WN + - Zora Approach WC + - Catfish Approach WN + - Catfish Approach WC + - C Whirlpool SC + - Dark C Whirlpool SC + - Statues SC + - Hype Cave SC + - Dam NC + - Swamp NC + - South Pass NC + - Dark South Pass NC + border12_es: + - Lumberjack SW + - Dark Lumberjack SW + - Mountain Pass NW + - Bumper Cave NW + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb WS + - Frog WC + - Frog WS + border23_nw: + - River Bend EN + - River Bend EC + - River Bend ES + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Flute Boy SW + - Flute Boy SC + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks SW + - Dark Bonk Rocks SW + - Links House SC + - Big Bomb Shop SC + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line WN + - Tree Line WC + - Dark Tree Line WN + - Dark Tree Line WC + - Flute Boy Approach NW + - Flute Boy Approach NC + - Stumpy Approach NW + - Stumpy Approach NC + - C Whirlpool NW + - Dark C Whirlpool NW + - Statues NC + - Hype Cave NC + - Lake Hylia NW + - Ice Lake NW + border23_es: + - Mountain Pass SE + - Bumper Cave SE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Dark Fortune EN + - Dark Fortune ES + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Kakariko ES + - Village of Outcasts ES + - Blacksmith WS + - Hammer Pegs WS + - Kakariko Suburb ES + - Frog ES + - Flute Boy WS + - Stumpy WS + border34_nw: + - Hyrule Castle ES + - Hyrule Castle SW + - Hyrule Castle SE + - Pyramid ES + - Pyramid SW + - Pyramid SE + - Sand Dunes WN + - Dark Dunes WN + - Central Bonk Rocks NW + - Dark Bonk Rocks NW + - Links House NE + - Big Bomb Shop NE + border34_es: + - Forgotten Forest ES + - Hyrule Castle WN +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool +ow-tileflips: + 1: + force_together: + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 \ No newline at end of file diff --git a/presets/world/owr_ringshuffle-full.yaml b/presets/world/owr_ringshuffle-full.yaml new file mode 100644 index 00000000..c271befe --- /dev/null +++ b/presets/world/owr_ringshuffle-full.yaml @@ -0,0 +1,313 @@ +ow-edges: + 1: + groups: + ring1: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Lumberjack WN + - Dark Lumberjack WN + - West Death Mountain EN + - West Dark Death Mountain EN + - East Death Mountain WN + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Zora Approach NE + - Catfish Approach NE + - Kakariko NW + - Kakariko NC + - Village of Outcasts NW + - Village of Outcasts NC + - Eastern Palace SE + - Palace of Darkness SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Desert EC + - Desert ES + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam WC + - Dam WS + - Dam EC + - Swamp WC + - Swamp WS + - Swamp EC + - South Pass WC + - South Pass ES + - Dark South Pass WC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Master Sword Meadow SC + - Zoras Domain SW + ring2: + - Lost Woods SE + - Skull Woods SE + - West Death Mountain ES + - West Dark Death Mountain ES + - East Death Mountain WS + - East Dark Death Mountain WS + - Kakariko Fortune NE + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune SC + - Kakariko NE + - Kakariko SE + - Village of Outcasts NE + - Village of Outcasts SE + - Eastern Palace SW + - Palace of Darkness SW + - Kakariko Suburb NE + - Frog NE + - Tree Line NW + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line SC + - Dark Tree Line SE + - Flute Boy Approach EC + - Stumpy Approach EC + - C Whirlpool WC + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - Dark C Whirlpool WC + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Lake Hylia NC + - Lake Hylia NE + - Ice Lake NC + - Ice Lake NE + ring3: + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Sanctuary EC + - Dark Chapel WN + - Dark Chapel WS + - Dark Chapel EC + - Graveyard WC + - Graveyard EC + - Dark Graveyard WC + - Dark Graveyard EC + - River Bend WC + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Forgotten Forest NW + - Forgotten Forest NE + - Shield Shop NW + - Shield Shop NE + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Wooden Bridge SW + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Broken Bridge SW + - Sand Dunes NW + - Sand Dunes SC + - Dark Dunes NW + - Dark Dunes SC + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Links House ES + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Hammer Bridge NC + - Hammer Bridge WS + - Hobo EC + border12_nw: + - Potion Shop EN + - Potion Shop EC + - Dark Witch EN + - Dark Witch EC + - Zora Approach WN + - Zora Approach WC + - Catfish Approach WN + - Catfish Approach WC + - C Whirlpool SC + - Dark C Whirlpool SC + - Statues SC + - Hype Cave SC + - Dam NC + - Swamp NC + - South Pass NC + - Dark South Pass NC + border12_es: + - Lumberjack SW + - Dark Lumberjack SW + - Mountain Pass NW + - Bumper Cave NW + - Maze Race ES + - Dig Game EC + - Dig Game ES + - Kakariko Suburb WS + - Frog WC + - Frog WS + border23_nw: + - River Bend EN + - River Bend EC + - River Bend ES + - Qirn Jump EN + - Qirn Jump EC + - Qirn Jump ES + - Potion Shop WN + - Potion Shop WC + - Potion Shop WS + - Dark Witch WN + - Dark Witch WC + - Dark Witch WS + - Flute Boy SW + - Flute Boy SC + - Stumpy SW + - Stumpy SC + - Central Bonk Rocks SW + - Dark Bonk Rocks SW + - Links House SC + - Big Bomb Shop SC + - Stone Bridge EN + - Stone Bridge EC + - Stone Bridge SC + - Hammer Bridge EN + - Hammer Bridge EC + - Hammer Bridge SC + - Tree Line WN + - Tree Line WC + - Dark Tree Line WN + - Dark Tree Line WC + - Flute Boy Approach NW + - Flute Boy Approach NC + - Stumpy Approach NW + - Stumpy Approach NC + - C Whirlpool NW + - Dark C Whirlpool NW + - Statues NC + - Hype Cave NC + - Lake Hylia NW + - Ice Lake NW + border23_es: + - Mountain Pass SE + - Bumper Cave SE + - Kakariko Fortune EN + - Kakariko Fortune ES + - Dark Fortune EN + - Dark Fortune ES + - Kakariko Pond NE + - Kakariko Pond WN + - Kakariko Pond WS + - Outcast Pond NE + - Outcast Pond WN + - Outcast Pond WS + - Kakariko ES + - Village of Outcasts ES + - Blacksmith WS + - Hammer Pegs WS + - Kakariko Suburb ES + - Frog ES + - Flute Boy WS + - Stumpy WS + border34_nw: + - Hyrule Castle ES + - Hyrule Castle SW + - Hyrule Castle SE + - Pyramid ES + - Pyramid SW + - Pyramid SE + - Sand Dunes WN + - Dark Dunes WN + - Central Bonk Rocks NW + - Dark Bonk Rocks NW + - Links House NE + - Big Bomb Shop NE + border34_es: + - Forgotten Forest ES + - Hyrule Castle WN +ow-tileflips: + 1: + force_together: + PedHobo: + - 0x00 + - 0x2d + - 0x80 + Zora: + - 0x0f + - 0x81 + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_ringshuffle-interiors.yaml b/presets/world/owr_ringshuffle-interiors.yaml new file mode 100644 index 00000000..0dcfbaa7 --- /dev/null +++ b/presets/world/owr_ringshuffle-interiors.yaml @@ -0,0 +1,258 @@ +ow-edges: + 1: + two-way: + Lumberjack SW: Mountain Pass NW + Dark Lumberjack SW: Bumper Cave NW + Potion Shop EN: Zora Approach WN + Potion Shop EC: Zora Approach WC + Dark Witch EN: Catfish Approach WN + Dark Witch EC: Catfish Approach WC + Maze Race ES: Kakariko Suburb WS + Dig Game EC: Frog WC + Dig Game ES: Frog WS + C Whirlpool SC: Dam NC + Dark C Whirlpool SC: Swamp NC + Statues SC: South Pass NC + Hype Cave SC: Dark South Pass NC + Mountain Pass SE: Kakariko Pond NE + Bumper Cave SE: Outcast Pond NE + Kakariko Fortune EN: Kakariko Pond WN + Kakariko Fortune ES: Kakariko Pond WS + Dark Fortune EN: Outcast Pond WN + Dark Fortune ES: Outcast Pond WS + River Bend EN: Potion Shop WN + River Bend EC: Potion Shop WC + River Bend ES: Potion Shop WS + Qirn Jump EN: Dark Witch WN + Qirn Jump EC: Dark Witch WC + Qirn Jump ES: Dark Witch WS + Kakariko ES: Blacksmith WS + Village of Outcasts ES: Hammer Pegs WS + Kakariko Suburb ES: Flute Boy WS + Frog ES: Stumpy WS + Flute Boy SW: Flute Boy Approach NW + Flute Boy SC: Flute Boy Approach NC + Stumpy SW: Stumpy Approach NW + Stumpy SC: Stumpy Approach NC + Central Bonk Rocks SW: C Whirlpool NW + Dark Bonk Rocks SW: Dark C Whirlpool NW + Links House SC: Statues NC + Big Bomb Shop SC: Hype Cave NC + Stone Bridge EN: Tree Line WN + Stone Bridge EC: Tree Line WC + Stone Bridge SC: Lake Hylia NW + Hammer Bridge EN: Dark Tree Line WN + Hammer Bridge EC: Dark Tree Line WC + Hammer Bridge SC: Ice Lake NW + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES: Sand Dunes WN + Hyrule Castle SW: Central Bonk Rocks NW + Hyrule Castle SE: Links House NE + Pyramid ES: Dark Dunes WN + Pyramid SW: Dark Bonk Rocks NW + Pyramid SE: Big Bomb Shop NE + groups: + ring1: + - Lost Woods NW + - Lost Woods EN + - Lost Woods SW + - Lost Woods SC + - Skull Woods EN + - Skull Woods SW + - Skull Woods SC + - Lumberjack WN + - Dark Lumberjack WN + - West Death Mountain EN + - West Dark Death Mountain EN + - East Death Mountain WN + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN + - Zora Waterfall NE + - Zora Waterfall SE + - Catfish SE + - Lost Woods Pass NW + - Lost Woods Pass NE + - Lost Woods Pass SW + - Lost Woods Pass SE + - Skull Woods Pass NW + - Skull Woods Pass NE + - Skull Woods Pass SW + - Skull Woods Pass SE + - Zora Approach NE + - Catfish Approach NE + - Kakariko NW + - Kakariko NC + - Village of Outcasts NW + - Village of Outcasts NC + - Eastern Palace SE + - Palace of Darkness SE + - Eastern Nook NE + - Palace of Darkness Nook NE + - Desert EC + - Desert ES + - Lake Hylia WS + - Lake Hylia EC + - Lake Hylia ES + - Ice Lake WS + - Ice Lake EC + - Ice Lake ES + - Ice Cave SW + - Ice Cave SE + - Shopping Mall SW + - Shopping Mall SE + - Desert Pass WC + - Desert Pass WS + - Desert Pass EC + - Desert Pass ES + - Swamp Nook EC + - Swamp Nook ES + - Dam WC + - Dam WS + - Dam EC + - Swamp WC + - Swamp WS + - Swamp EC + - South Pass WC + - South Pass ES + - Dark South Pass WC + - Dark South Pass ES + - Octoballoon NW + - Octoballoon NE + - Octoballoon WC + - Octoballoon WS + - Bomber Corner NW + - Bomber Corner NE + - Bomber Corner WC + - Bomber Corner WS + - Master Sword Meadow SC + - Zoras Domain SW + ring2: + - Lost Woods SE + - Skull Woods SE + - West Death Mountain ES + - West Dark Death Mountain ES + - East Death Mountain WS + - East Dark Death Mountain WS + - Kakariko Fortune NE + - Kakariko Fortune SC + - Dark Fortune NE + - Dark Fortune SC + - Kakariko NE + - Kakariko SE + - Village of Outcasts NE + - Village of Outcasts SE + - Eastern Palace SW + - Palace of Darkness SW + - Kakariko Suburb NE + - Frog NE + - Tree Line NW + - Tree Line SC + - Tree Line SE + - Dark Tree Line NW + - Dark Tree Line SC + - Dark Tree Line SE + - Flute Boy Approach EC + - Stumpy Approach EC + - C Whirlpool WC + - C Whirlpool EN + - C Whirlpool EC + - C Whirlpool ES + - Dark C Whirlpool WC + - Dark C Whirlpool EN + - Dark C Whirlpool EC + - Dark C Whirlpool ES + - Statues WN + - Statues WC + - Statues WS + - Hype Cave WN + - Hype Cave WC + - Hype Cave WS + - Lake Hylia NC + - Lake Hylia NE + - Ice Lake NC + - Ice Lake NE + ring3: + - Kakariko Pond EN + - Kakariko Pond ES + - Kakariko Pond SW + - Kakariko Pond SE + - Outcast Pond EN + - Outcast Pond ES + - Outcast Pond SW + - Outcast Pond SE + - Sanctuary WN + - Sanctuary WS + - Sanctuary EC + - Dark Chapel WN + - Dark Chapel WS + - Dark Chapel EC + - Graveyard WC + - Graveyard EC + - Dark Graveyard WC + - Dark Graveyard EC + - River Bend WC + - River Bend SW + - River Bend SC + - River Bend SE + - Qirn Jump WC + - Qirn Jump SW + - Qirn Jump SC + - Qirn Jump SE + - Forgotten Forest NW + - Forgotten Forest NE + - Shield Shop NW + - Shield Shop NE + - Wooden Bridge NW + - Wooden Bridge NC + - Wooden Bridge NE + - Wooden Bridge SW + - Broken Bridge NW + - Broken Bridge NC + - Broken Bridge NE + - Broken Bridge SW + - Sand Dunes NW + - Sand Dunes SC + - Dark Dunes NW + - Dark Dunes SC + - Central Bonk Rocks EN + - Central Bonk Rocks EC + - Central Bonk Rocks ES + - Dark Bonk Rocks EN + - Dark Bonk Rocks EC + - Dark Bonk Rocks ES + - Links House WN + - Links House WC + - Links House WS + - Links House ES + - Big Bomb Shop WN + - Big Bomb Shop WC + - Big Bomb Shop WS + - Big Bomb Shop ES + - Stone Bridge NC + - Stone Bridge WC + - Stone Bridge WS + - Hammer Bridge NC + - Hammer Bridge WS + - Hobo EC +ow-whirlpools: + 1: + two-way: + Kakariko Pond Whirlpool: Octoballoon Whirlpool + Qirn Jump Whirlpool: Bomber Corner Whirlpool + River Bend Whirlpool: C Whirlpool +ow-tileflips: + 1: + force_together: + PedHobo: + - 0x00 + - 0x2d + - 0x80 + Zora: + - 0x0f + - 0x81 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_shuffle-dark.yaml b/presets/world/owr_shuffle-dark.yaml new file mode 100644 index 00000000..3b27e6d2 --- /dev/null +++ b/presets/world/owr_shuffle-dark.yaml @@ -0,0 +1,94 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Lumberjack SW*: Mountain Pass NW* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + Mountain Pass SE*: Kakariko Pond NE* + Zora Waterfall NE*: Zoras Domain SW* + Zora Waterfall SE*: Zora Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Kakariko Fortune SC*: Kakariko NE* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Sanctuary EC*: Graveyard WC* + Graveyard EC*: River Bend WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Wooden Bridge SW*: Sand Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Kakariko Suburb ES*: Flute Boy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Stone Bridge SC*: Lake Hylia NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC*: C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Statues SC*: South Pass NC* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Dam EC*: South Pass WC* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool + Kakariko Pond Whirlpool: Octoballoon Whirlpool + River Bend Whirlpool: C Whirlpool +ow-tileflips: + 1: + force_no_flip: + - 0x0f + - 0x12 + - 0x15 + - 0x33 + - 0x35 + - 0x3f \ No newline at end of file diff --git a/presets/world/owr_shuffle-horizontal.yaml b/presets/world/owr_shuffle-horizontal.yaml new file mode 100644 index 00000000..9eba3118 --- /dev/null +++ b/presets/world/owr_shuffle-horizontal.yaml @@ -0,0 +1,76 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Big Bomb Shop SC*: Hype Cave NC* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + +ow-tileflips: + 1: + force_together: + Hobo: + - 0x2d + - 0x80 \ No newline at end of file diff --git a/presets/world/owr_shuffle-horizontalbycolumns.yaml b/presets/world/owr_shuffle-horizontalbycolumns.yaml new file mode 100644 index 00000000..117b81d5 --- /dev/null +++ b/presets/world/owr_shuffle-horizontalbycolumns.yaml @@ -0,0 +1,233 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Big Bomb Shop SC*: Hype Cave NC* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + groups: + Column1: + - Maze Race ES + - Kakariko Suburb WS + - Dig Game EC + - Frog WC + - Dig Game ES + - Frog WS + Column2: + - Lost Woods EN + - Lumberjack WN + - Skull Woods EN + - Dark Lumberjack WN + - Kakariko Fortune EN + - Kakariko Pond WN + - Kakariko Fortune ES + - Kakariko Pond WS + - Dark Fortune EN + - Outcast Pond WN + - Dark Fortune ES + - Outcast Pond WS + - Kakariko ES + - Blacksmith WS + - Village of Outcasts ES + - Hammer Pegs WS + - Kakariko Suburb ES + - Flute Boy WS + - Frog ES + - Stumpy WS + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + Column3: + - Kakariko Pond EN + - Sanctuary WN + - Kakariko Pond ES + - Sanctuary WS + - Outcast Pond EN + - Dark Chapel WN + - Outcast Pond ES + - Dark Chapel WS + - Forgotten Forest ES + - Hyrule Castle WN + - Flute Boy Approach EC + - C Whirlpool WC + - Stumpy Approach EC + - Dark C Whirlpool WC + - Desert Pass EC + - Dam WC + - Desert Pass ES + - Dam WS + - Swamp Nook EC + - Swamp WC + - Swamp Nook ES + - Swamp WS + Column4: + - Sanctuary EC + - Graveyard WC + - Dark Chapel EC + - Dark Graveyard WC + - Central Bonk Rocks EN + - Links House WN + - Central Bonk Rocks EC + - Links House WC + - Central Bonk Rocks ES + - Links House WS + - Dark Bonk Rocks EN + - Big Bomb Shop WN + - Dark Bonk Rocks EC + - Big Bomb Shop WC + - Dark Bonk Rocks ES + - Big Bomb Shop WS + - C Whirlpool EN + - Statues WN + - C Whirlpool EC + - Statues WC + - C Whirlpool ES + - Statues WS + - Dark C Whirlpool EN + - Hype Cave WN + - Dark C Whirlpool EC + - Hype Cave WC + - Dark C Whirlpool ES + - Hype Cave WS + - Dam EC + - South Pass WC + - Swamp EC + - Dark South Pass WC + Column5: + - West Death Mountain EN + - East Death Mountain WN + - West Death Mountain ES + - East Death Mountain WS + - West Dark Death Mountain EN + - East Dark Death Mountain WN + - West Dark Death Mountain ES + - East Dark Death Mountain WS + - Graveyard EC + - River Bend WC + - Dark Graveyard EC + - Qirn Jump WC + - Hyrule Castle ES + - Sand Dunes WN + - Pyramid ES + - Dark Dunes WN + - Links House ES + - Stone Bridge WS + - Big Bomb Shop ES + - Hammer Bridge WS + - Lake Hylia WS + - South Pass ES + - Ice Lake WS + - Dark South Pass ES + Column6: + - River Bend EN + - Potion Shop WN + - River Bend EC + - Potion Shop WC + - River Bend ES + - Potion Shop WS + - Qirn Jump EN + - Dark Witch WN + - Qirn Jump EC + - Dark Witch WC + - Qirn Jump ES + - Dark Witch WS + - Stone Bridge EN + - Tree Line WN + - Stone Bridge EC + - Tree Line WC + - Hammer Bridge EN + - Dark Tree Line WN + - Hammer Bridge EC + - Dark Tree Line WC + - Stone Bridge WC + - Hobo EC + Column7: + - East Death Mountain EN + - Death Mountain TR Pegs WN + - East Dark Death Mountain EN + - Turtle Rock WN + - Potion Shop EN + - Zora Approach WN + - Potion Shop EC + - Zora Approach WC + - Dark Witch EN + - Catfish Approach WN + - Dark Witch EC + - Catfish Approach WC + - Lake Hylia EC + - Octoballoon WC + - Lake Hylia ES + - Octoballoon WS + - Ice Lake EC + - Bomber Corner WC + - Ice Lake ES + - Bomber Corner WS +ow-tileflips: + 1: + force_together: + Hobo: + - 0x2d + - 0x80 \ No newline at end of file diff --git a/presets/world/owr_shuffle-largescreens.yaml b/presets/world/owr_shuffle-largescreens.yaml new file mode 100644 index 00000000..47798cfa --- /dev/null +++ b/presets/world/owr_shuffle-largescreens.yaml @@ -0,0 +1,210 @@ +settings: + 1: + ow_whirlpool: false +ow-edges: + 1: + two-way: + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + groups: + 1: + - Lost Woods NW + - Master Sword Meadow SC + - Lost Woods EN + - Lumberjack WN + - Skull Woods EN + - Dark Lumberjack WN + - East Death Mountain EN + - Death Mountain TR Pegs WN + - East Dark Death Mountain EN + - Turtle Rock WN + - Lost Woods Pass SW + - Kakariko NW + - Lost Woods Pass SE + - Kakariko NC + - Skull Woods Pass SW + - Village of Outcasts NW + - Skull Woods Pass SE + - Village of Outcasts NC + - Kakariko Fortune SC + - Kakariko NE + - Dark Fortune SC + - Village of Outcasts NE + - Kakariko ES + - Blacksmith WS + - Village of Outcasts ES + - Hammer Pegs WS + - Hyrule Castle ES + - Sand Dunes WN + - Pyramid ES + - Dark Dunes WN + - Stone Bridge SC + - Lake Hylia NW + - Hammer Bridge SC + - Ice Lake NW + - Tree Line SC + - Lake Hylia NC + - Tree Line SE + - Lake Hylia NE + - Dark Tree Line SC + - Ice Lake NC + - Dark Tree Line SE + - Ice Lake NE + - Desert EC + - Desert Pass WC + - Desert ES + - Desert Pass WS + - Lake Hylia EC + - Octoballoon WC + - Lake Hylia ES + - Octoballoon WS + - Ice Lake EC + - Bomber Corner WC + - Ice Lake ES + - Bomber Corner WS + 2: + - Lost Woods SW + - Lost Woods Pass NW + - Lost Woods SC + - Lost Woods Pass NE + - Lost Woods SE + - Kakariko Fortune NE + - Skull Woods SW + - Skull Woods Pass NW + - Skull Woods SC + - Skull Woods Pass NE + - Skull Woods SE + - Dark Fortune NE + - Zora Waterfall NE + - Zoras Domain SW + - Kakariko SE + - Kakariko Suburb NE + - Village of Outcasts SE + - Frog NE + - Forgotten Forest ES + - Hyrule Castle WN + - Hyrule Castle SW + - Central Bonk Rocks NW + - Hyrule Castle SE + - Links House NE + - Pyramid SW + - Dark Bonk Rocks NW + - Pyramid SE + - Big Bomb Shop NE + - Eastern Palace SW + - Tree Line NW + - Eastern Palace SE + - Eastern Nook NE + - Palace of Darkness SW + - Dark Tree Line NW + - Palace of Darkness SE + - Palace of Darkness Nook NE + - Lake Hylia WS + - South Pass ES + - Ice Lake WS + - Dark South Pass ES + - West Death Mountain EN + - East Death Mountain WN + - West Death Mountain ES + - East Death Mountain WS + - West Dark Death Mountain EN + - East Dark Death Mountain WN + - West Dark Death Mountain ES + - East Dark Death Mountain WS +ow-tileflips: + 1: + force_together: + Pedestal: + - 0x00 + - 0x80 + Zora: + - 0x0f + - 0x81 \ No newline at end of file diff --git a/presets/world/owr_shuffle-light.yaml b/presets/world/owr_shuffle-light.yaml new file mode 100644 index 00000000..4feaa75e --- /dev/null +++ b/presets/world/owr_shuffle-light.yaml @@ -0,0 +1,84 @@ +ow-edges: + 1: + two-way: + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + Dark Lumberjack SW*: Bumper Cave NW* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Dark Death Mountain EN*: Turtle Rock WN* + Bumper Cave SE*: Outcast Pond NE* + Catfish SE*: Catfish Approach NE* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Dark Fortune SC*: Village of Outcasts NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Dark Chapel EC*: Dark Graveyard WC* + Dark Graveyard EC*: Qirn Jump WC* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Broken Bridge SW*: Dark Dunes NW* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Dark Dunes SC*: Hammer Bridge NC* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Frog ES*: Stumpy WS* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Hammer Bridge SC*: Ice Lake NW* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Stumpy Approach EC*: Dark C Whirlpool WC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Hype Cave SC*: Dark South Pass NC* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Swamp EC*: Dark South Pass WC* +ow-whirlpools: + 1: + two-way: + Qirn Jump Whirlpool: Bomber Corner Whirlpool +ow-tileflips: + 1: + force_no_flip: + - 0x0f + - 0x12 + - 0x15 + - 0x33 + - 0x35 + - 0x3f \ No newline at end of file diff --git a/presets/world/owr_shuffle-separatemountain.yaml b/presets/world/owr_shuffle-separatemountain.yaml new file mode 100644 index 00000000..02a53d9c --- /dev/null +++ b/presets/world/owr_shuffle-separatemountain.yaml @@ -0,0 +1,16 @@ +ow-edges: + 1: + groups: + mountain: + - West Death Mountain EN + - West Death Mountain ES + - West Dark Death Mountain EN + - West Dark Death Mountain ES + - East Death Mountain WN + - East Death Mountain WS + - East Death Mountain EN + - East Dark Death Mountain WN + - East Dark Death Mountain WS + - East Dark Death Mountain EN + - Death Mountain TR Pegs WN + - Turtle Rock WN diff --git a/presets/world/owr_shuffle-smallscreens.yaml b/presets/world/owr_shuffle-smallscreens.yaml new file mode 100644 index 00000000..a12ad95d --- /dev/null +++ b/presets/world/owr_shuffle-smallscreens.yaml @@ -0,0 +1,58 @@ +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Lost Woods SC*: Lost Woods Pass NE* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Skull Woods SC*: Skull Woods Pass NE* + Skull Woods SE*: Dark Fortune NE* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Zora Waterfall NE: Zoras Domain SW + Lost Woods Pass SW*: Kakariko NW* + Lost Woods Pass SE*: Kakariko NC* + Skull Woods Pass SW*: Village of Outcasts NW* + Skull Woods Pass SE*: Village of Outcasts NC* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NC* + Tree Line SE*: Lake Hylia NE* + Dark Tree Line SC*: Ice Lake NC* + Dark Tree Line SE*: Ice Lake NE* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool \ No newline at end of file diff --git a/presets/world/owr_shuffle-splitsimilar.yaml b/presets/world/owr_shuffle-splitsimilar.yaml new file mode 100644 index 00000000..bf1dde3d --- /dev/null +++ b/presets/world/owr_shuffle-splitsimilar.yaml @@ -0,0 +1,123 @@ +settings: + 1: + ow_terrain: false +ow-edges: + 1: + groups: + similar_2_left_top: + - Lost Woods SW + - Skull Woods SW + - Lost Woods Pass NW + - Lost Woods Pass SW + - Skull Woods Pass NW + - Skull Woods Pass SW + - Kakariko Fortune EN + - Dark Fortune EN + - Kakariko Pond WN + - Kakariko Pond EN + - Kakariko Pond SW + - Outcast Pond WN + - Outcast Pond EN + - Outcast Pond SW + - Sanctuary WN + - Dark Chapel WN + - River Bend EC + - River Bend SW + - Qirn Jump EC + - Qirn Jump SW + - Potion Shop WC + - Dark Witch WC + - Kakariko NW + - Village of Outcasts NW + - Forgotten Forest NW + - Shield Shop NW + - Wooden Bridge NW + - Broken Bridge NW + - Dig Game EC + - Frog WC + - Flute Boy SW + - Stumpy SW + - Desert EC + - Flute Boy Approach NW + - Stumpy Approach NW + - C Whirlpool EN + - Dark C Whirlpool EN + - Statues WN + - Hype Cave WN + - Desert Pass WC + - Desert Pass EC + - Swamp Nook EC + - Dam WC + - Swamp WC + similar_2_right_bottom: + - Lost Woods SC + - Skull Woods SC + - Lost Woods Pass NE + - Lost Woods Pass SE + - Skull Woods Pass NE + - Skull Woods Pass SE + - Kakariko Fortune ES + - Dark Fortune ES + - Kakariko Pond WS + - Kakariko Pond ES + - Kakariko Pond SE + - Outcast Pond WS + - Outcast Pond ES + - Outcast Pond SE + - Sanctuary WS + - Dark Chapel WS + - River Bend ES + - River Bend SE + - Qirn Jump ES + - Qirn Jump SE + - Potion Shop WS + - Dark Witch WS + - Kakariko NC + - Village of Outcasts NC + - Forgotten Forest NE + - Shield Shop NE + - Wooden Bridge NE + - Broken Bridge NE + - Dig Game ES + - Frog WS + - Flute Boy SC + - Stumpy SC + - Desert ES + - Flute Boy Approach NC + - Stumpy Approach NC + - C Whirlpool ES + - Dark C Whirlpool ES + - Statues WS + - Hype Cave WS + - Desert Pass WS + - Desert Pass ES + - Swamp Nook ES + - Dam WS + - Swamp WS + similar_3_left_top: + - Central Bonk Rocks EN + - Dark Bonk Rocks EN + - Links House WN + - Big Bomb Shop WN + similar_3_middle: + - Central Bonk Rocks EC + - Dark Bonk Rocks EC + - Links House WC + - Big Bomb Shop WC + similar_3_right_bottom: + - Central Bonk Rocks ES + - Dark Bonk Rocks ES + - Links House WS + - Big Bomb Shop WS +ow-tileflips: + 1: + force_together: + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_shuffle-splitsimilarterrain.yaml b/presets/world/owr_shuffle-splitsimilarterrain.yaml new file mode 100644 index 00000000..2ade18e4 --- /dev/null +++ b/presets/world/owr_shuffle-splitsimilarterrain.yaml @@ -0,0 +1,175 @@ +settings: + 1: + ow_terrain: true +ow-edges: + 1: + groups: + similar_2_left_top: + - Lost Woods SW + - Skull Woods SW + - Lost Woods Pass NW + - Lost Woods Pass SW + - Skull Woods Pass NW + - Skull Woods Pass SW + - Kakariko Fortune EN + - Dark Fortune EN + - Kakariko Pond WN + - Kakariko Pond EN + - Kakariko Pond SW + - Outcast Pond WN + - Outcast Pond EN + - Outcast Pond SW + - Sanctuary WN + - Dark Chapel WN + - Potion Shop EN + - Dark Witch EN + - Zora Approach WN + - Catfish Approach WN + - Kakariko NW + - Village of Outcasts NW + - Forgotten Forest NW + - Shield Shop NW + - Dig Game EC + - Frog WC + - Flute Boy SW + - Stumpy SW + - Stone Bridge EN + - Hammer Bridge EN + - Tree Line WN + - Tree Line SC + - Dark Tree Line WN + - Dark Tree Line SC + - Desert EC + - Flute Boy Approach NW + - Stumpy Approach NW + - Lake Hylia NC + - Lake Hylia EC + - Ice Lake NC + - Ice Lake EC + - Ice Cave SW + - Shopping Mall SW + - Desert Pass WC + - Desert Pass EC + - Swamp Nook EC + - Dam WC + - Swamp WC + - Octoballoon NW + - Octoballoon WC + - Bomber Corner NW + - Bomber Corner WC + similar_2_right_bottom: + - Lost Woods SC + - Skull Woods SC + - Lost Woods Pass NE + - Lost Woods Pass SE + - Skull Woods Pass NE + - Skull Woods Pass SE + - Kakariko Fortune ES + - Dark Fortune ES + - Kakariko Pond WS + - Kakariko Pond ES + - Kakariko Pond SE + - Outcast Pond WS + - Outcast Pond ES + - Outcast Pond SE + - Sanctuary WS + - Dark Chapel WS + - Potion Shop EC + - Dark Witch EC + - Zora Approach WC + - Catfish Approach WC + - Kakariko NC + - Village of Outcasts NC + - Forgotten Forest NE + - Shield Shop NE + - Dig Game ES + - Frog WS + - Flute Boy SC + - Stumpy SC + - Stone Bridge EC + - Hammer Bridge EC + - Tree Line WC + - Tree Line SE + - Dark Tree Line WC + - Dark Tree Line SE + - Desert ES + - Flute Boy Approach NC + - Stumpy Approach NC + - Lake Hylia NE + - Lake Hylia ES + - Ice Lake NE + - Ice Lake ES + - Ice Cave SE + - Shopping Mall SE + - Desert Pass WS + - Desert Pass ES + - Swamp Nook ES + - Dam WS + - Swamp WS + - Octoballoon NE + - Octoballoon WS + - Bomber Corner NE + - Bomber Corner WS + similar_3_left_top: + - River Bend EN + - River Bend SW + - Qirn Jump EN + - Qirn Jump SW + - Potion Shop WN + - Dark Witch WN + - Wooden Bridge NW + - Broken Bridge NW + - Central Bonk Rocks EN + - Dark Bonk Rocks EN + - Links House WN + - Big Bomb Shop WN + - C Whirlpool EN + - Dark C Whirlpool EN + - Statues WN + - Hype Cave WN + similar_3_middle: + - River Bend EC + - River Bend SC + - Qirn Jump EC + - Qirn Jump SC + - Potion Shop WC + - Dark Witch WC + - Wooden Bridge NC + - Broken Bridge NC + - Central Bonk Rocks EC + - Dark Bonk Rocks EC + - Links House WC + - Big Bomb Shop WC + - C Whirlpool EC + - Dark C Whirlpool EC + - Statues WC + - Hype Cave WC + similar_3_right_bottom: + - River Bend ES + - River Bend SE + - Qirn Jump ES + - Qirn Jump SE + - Potion Shop WS + - Dark Witch WS + - Wooden Bridge NE + - Broken Bridge NE + - Central Bonk Rocks ES + - Dark Bonk Rocks ES + - Links House WS + - Big Bomb Shop WS + - C Whirlpool ES + - Dark C Whirlpool ES + - Statues WS + - Hype Cave WS +ow-tileflips: + 1: + force_together: + Forest: + - 0x1a + - 0x1b + FrogDig: + - 0x28 + - 0x29 + Desert: + - 0x30 + - 0x3a \ No newline at end of file diff --git a/presets/world/owr_shuffle-vanillaloop.yaml b/presets/world/owr_shuffle-vanillaloop.yaml new file mode 100644 index 00000000..bc4223fb --- /dev/null +++ b/presets/world/owr_shuffle-vanillaloop.yaml @@ -0,0 +1,41 @@ +ow-edges: + 1: + two-way: + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NW* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NW* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Lost Woods Pass SW*: Kakariko NW* + Skull Woods Pass SW*: Village of Outcasts NW* + Kakariko Pond ES*: Sanctuary WS* + Outcast Pond ES*: Dark Chapel WS* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend SW*: Wooden Bridge NW* + Qirn Jump SW*: Broken Bridge NW* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts SE*: Frog NE* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Stumpy SW*: Stumpy Approach NW* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* diff --git a/presets/world/owr_shuffle-vertical.yaml b/presets/world/owr_shuffle-vertical.yaml new file mode 100644 index 00000000..d037f0c7 --- /dev/null +++ b/presets/world/owr_shuffle-vertical.yaml @@ -0,0 +1,87 @@ +ow-edges: + 1: + two-way: + Lost Woods EN*: Lumberjack WN* + Skull Woods EN*: Dark Lumberjack WN* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Kakariko ES*: Blacksmith WS* + Village of Outcasts ES*: Hammer Pegs WS* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Pyramid ES*: Dark Dunes WN* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* +ow-tileflips: + 1: + force_together: + Pedestal: + - 0x00 + - 0x80 + Zora: + - 0x0f + - 0x81 \ No newline at end of file diff --git a/presets/world/owr_shuffle-verticalbyrows.yaml b/presets/world/owr_shuffle-verticalbyrows.yaml new file mode 100644 index 00000000..25650d60 --- /dev/null +++ b/presets/world/owr_shuffle-verticalbyrows.yaml @@ -0,0 +1,227 @@ +ow-edges: + 1: + two-way: + Lost Woods EN*: Lumberjack WN* + Skull Woods EN*: Dark Lumberjack WN* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Kakariko ES*: Blacksmith WS* + Village of Outcasts ES*: Hammer Pegs WS* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Pyramid ES*: Dark Dunes WN* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Links House ES*: Stone Bridge WS* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Desert EC: Desert Pass WC + Desert ES: Desert Pass WS + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WC* + Lake Hylia ES*: Octoballoon WS* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WC* + Ice Lake ES*: Bomber Corner WS* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + groups: + Row1/Special: + - Lost Woods NW + - Master Sword Meadow SC + - Lumberjack SW + - Mountain Pass NW + - Dark Lumberjack SW + - Bumper Cave NW + - Zora Waterfall NE + - Zoras Domain SW + Row2: + - Lost Woods SW + - Lost Woods Pass NW + - Lost Woods SC + - Lost Woods Pass NE + - Lost Woods SE + - Kakariko Fortune NE + - Skull Woods SW + - Skull Woods Pass NW + - Skull Woods SC + - Skull Woods Pass NE + - Skull Woods SE + - Dark Fortune NE + - Mountain Pass SE + - Kakariko Pond NE + - Bumper Cave SE + - Outcast Pond NE + - Zora Waterfall SE + - Zora Approach NE + - Catfish SE + - Catfish Approach NE + Row3: + - Lost Woods Pass SW + - Kakariko NW + - Lost Woods Pass SE + - Kakariko NC + - Skull Woods Pass SW + - Village of Outcasts NW + - Skull Woods Pass SE + - Village of Outcasts NC + - Kakariko Fortune SC + - Kakariko NE + - Dark Fortune SC + - Village of Outcasts NE + - Kakariko Pond SW + - Forgotten Forest NW + - Kakariko Pond SE + - Forgotten Forest NE + - Outcast Pond SW + - Shield Shop NW + - Outcast Pond SE + - Shield Shop NE + - River Bend SW + - Wooden Bridge NW + - River Bend SC + - Wooden Bridge NC + - River Bend SE + - Wooden Bridge NE + - Qirn Jump SW + - Broken Bridge NW + - Qirn Jump SC + - Broken Bridge NC + - Qirn Jump SE + - Broken Bridge NE + Row4: + - Wooden Bridge SW + - Sand Dunes NW + - Broken Bridge SW + - Dark Dunes NW + Row5: + - Kakariko SE + - Kakariko Suburb NE + - Village of Outcasts SE + - Frog NE + - Hyrule Castle SW + - Central Bonk Rocks NW + - Hyrule Castle SE + - Links House NE + - Pyramid SW + - Dark Bonk Rocks NW + - Pyramid SE + - Big Bomb Shop NE + - Sand Dunes SC + - Stone Bridge NC + - Dark Dunes SC + - Hammer Bridge NC + - Eastern Palace SW + - Tree Line NW + - Eastern Palace SE + - Eastern Nook NE + - Palace of Darkness SW + - Dark Tree Line NW + - Palace of Darkness SE + - Palace of Darkness Nook NE + Row6: + - Flute Boy SW + - Flute Boy Approach NW + - Flute Boy SC + - Flute Boy Approach NC + - Stumpy SW + - Stumpy Approach NW + - Stumpy SC + - Stumpy Approach NC + - Central Bonk Rocks SW + - C Whirlpool NW + - Dark Bonk Rocks SW + - Dark C Whirlpool NW + - Links House SC + - Statues NC + - Big Bomb Shop SC + - Hype Cave NC + - Stone Bridge SC + - Lake Hylia NW + - Hammer Bridge SC + - Ice Lake NW + - Tree Line SC + - Lake Hylia NC + - Tree Line SE + - Lake Hylia NE + - Dark Tree Line SC + - Ice Lake NC + - Dark Tree Line SE + - Ice Lake NE + Row7: + - C Whirlpool SC + - Dam NC + - Dark C Whirlpool SC + - Swamp NC + - Statues SC + - South Pass NC + - Hype Cave SC + - Dark South Pass NC + - Ice Cave SW + - Octoballoon NW + - Ice Cave SE + - Octoballoon NE + - Shopping Mall SW + - Bomber Corner NW + - Shopping Mall SE + - Bomber Corner NE +ow-tileflips: + 1: + force_together: + Pedestal: + - 0x00 + - 0x80 + Zora: + - 0x0f + - 0x81 \ No newline at end of file diff --git a/presets/world/owr_vanilla-mirroredsimilar.yaml b/presets/world/owr_vanilla-mirroredsimilar.yaml new file mode 100644 index 00000000..f9411f08 --- /dev/null +++ b/presets/world/owr_vanilla-mirroredsimilar.yaml @@ -0,0 +1,148 @@ +settings: + 1: + ow_shuffle: full + ow_terrain: true +ow-edges: + 1: + two-way: + Lost Woods NW: Master Sword Meadow SC + Lost Woods EN*: Lumberjack WN* + Lost Woods SW*: Lost Woods Pass NE* + Lost Woods SC*: Lost Woods Pass NW* + Lost Woods SE*: Kakariko Fortune NE* + Skull Woods EN*: Dark Lumberjack WN* + Skull Woods SW*: Skull Woods Pass NE* + Skull Woods SC*: Skull Woods Pass NW* + Skull Woods SE*: Dark Fortune NE* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + West Death Mountain EN*: East Death Mountain WN* + West Death Mountain ES*: East Death Mountain WS* + West Dark Death Mountain EN*: East Dark Death Mountain WN* + West Dark Death Mountain ES*: East Dark Death Mountain WS* + East Death Mountain EN*: Death Mountain TR Pegs WN* + East Dark Death Mountain EN*: Turtle Rock WN* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall NE: Zoras Domain SW + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Lost Woods Pass SW*: Kakariko NC* + Lost Woods Pass SE*: Kakariko NW* + Skull Woods Pass SW*: Village of Outcasts NC* + Skull Woods Pass SE*: Village of Outcasts NW* + Kakariko Fortune EN*: Kakariko Pond WS* + Kakariko Fortune ES*: Kakariko Pond WN* + Kakariko Fortune SC*: Kakariko NE* + Dark Fortune EN*: Outcast Pond WS* + Dark Fortune ES*: Outcast Pond WN* + Dark Fortune SC*: Village of Outcasts NE* + Kakariko Pond EN*: Sanctuary WS* + Kakariko Pond ES*: Sanctuary WN* + Kakariko Pond SW*: Forgotten Forest NE* + Kakariko Pond SE*: Forgotten Forest NW* + Outcast Pond EN*: Dark Chapel WS* + Outcast Pond ES*: Dark Chapel WN* + Outcast Pond SW*: Shield Shop NE* + Outcast Pond SE*: Shield Shop NW* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WS* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WN* + River Bend SW*: Wooden Bridge NE* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NW* + Qirn Jump EN*: Dark Witch WS* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WN* + Qirn Jump SW*: Broken Bridge NE* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NW* + Potion Shop EN*: Zora Approach WC* + Potion Shop EC*: Zora Approach WN* + Dark Witch EN*: Catfish Approach WC* + Dark Witch EC*: Catfish Approach WN* + Kakariko ES*: Blacksmith WS* + Kakariko SE*: Kakariko Suburb NE* + Village of Outcasts ES*: Hammer Pegs WS* + Village of Outcasts SE*: Frog NE* + Forgotten Forest ES: Hyrule Castle WN + Hyrule Castle ES*: Sand Dunes WN* + Hyrule Castle SW*: Central Bonk Rocks NW* + Hyrule Castle SE*: Links House NE* + Pyramid ES*: Dark Dunes WN* + Pyramid SW*: Dark Bonk Rocks NW* + Pyramid SE*: Big Bomb Shop NE* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Eastern Palace SW*: Tree Line NW* + Eastern Palace SE*: Eastern Nook NE* + Palace of Darkness SW*: Dark Tree Line NW* + Palace of Darkness SE*: Palace of Darkness Nook NE* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WS + Dig Game ES*: Frog WC* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NC* + Flute Boy SC*: Flute Boy Approach NW* + Stumpy SW*: Stumpy Approach NC* + Stumpy SC*: Stumpy Approach NW* + Central Bonk Rocks EN*: Links House WS* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WN* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WS* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WN* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WC* + Stone Bridge EC*: Tree Line WN* + Stone Bridge SC*: Lake Hylia NW* + Hammer Bridge EN*: Dark Tree Line WC* + Hammer Bridge EC*: Dark Tree Line WN* + Hammer Bridge SC*: Ice Lake NW* + Tree Line SC*: Lake Hylia NE* + Tree Line SE*: Lake Hylia NC* + Dark Tree Line SC*: Ice Lake NE* + Dark Tree Line SE*: Ice Lake NC* + Desert EC: Desert Pass WS + Desert ES: Desert Pass WC + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WS* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WN* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WS* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WN* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Lake Hylia WS*: South Pass ES* + Lake Hylia EC*: Octoballoon WS* + Lake Hylia ES*: Octoballoon WC* + Ice Lake WS*: Dark South Pass ES* + Ice Lake EC*: Bomber Corner WS* + Ice Lake ES*: Bomber Corner WC* + Ice Cave SW*: Octoballoon NE* + Ice Cave SE*: Octoballoon NW* + Shopping Mall SW*: Bomber Corner NE* + Shopping Mall SE*: Bomber Corner NW* + Desert Pass EC*: Dam WS* + Desert Pass ES*: Dam WC* + Swamp Nook EC*: Swamp WS* + Swamp Nook ES*: Swamp WC* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC \ No newline at end of file diff --git a/presets/world/owr_vanilla.yaml b/presets/world/owr_vanilla.yaml new file mode 100644 index 00000000..d26bb8fa --- /dev/null +++ b/presets/world/owr_vanilla.yaml @@ -0,0 +1,190 @@ +ow-edges: + 1: + two-way: + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* + Lumberjack SW*: Mountain Pass NW* + Dark Lumberjack SW*: Bumper Cave NW* + Mountain Pass SE*: Kakariko Pond NE* + Bumper Cave SE*: Outcast Pond NE* + Zora Waterfall SE*: Zora Approach NE* + Catfish SE*: Catfish Approach NE* + Kakariko Fortune EN*: Kakariko Pond WN* + Kakariko Fortune ES*: Kakariko Pond WS* + Dark Fortune EN*: Outcast Pond WN* + Dark Fortune ES*: Outcast Pond WS* + Kakariko Pond EN*: Sanctuary WN* + Kakariko Pond ES*: Sanctuary WS* + Kakariko Pond SW*: Forgotten Forest NW* + Kakariko Pond SE*: Forgotten Forest NE* + Outcast Pond EN*: Dark Chapel WN* + Outcast Pond ES*: Dark Chapel WS* + Outcast Pond SW*: Shield Shop NW* + Outcast Pond SE*: Shield Shop NE* + Sanctuary EC*: Graveyard WC* + Dark Chapel EC*: Dark Graveyard WC* + Graveyard EC*: River Bend WC* + Dark Graveyard EC*: Qirn Jump WC* + River Bend EN*: Potion Shop WN* + River Bend EC*: Potion Shop WC* + River Bend ES*: Potion Shop WS* + River Bend SW*: Wooden Bridge NW* + River Bend SC*: Wooden Bridge NC* + River Bend SE*: Wooden Bridge NE* + Qirn Jump EN*: Dark Witch WN* + Qirn Jump EC*: Dark Witch WC* + Qirn Jump ES*: Dark Witch WS* + Qirn Jump SW*: Broken Bridge NW* + Qirn Jump SC*: Broken Bridge NC* + Qirn Jump SE*: Broken Bridge NE* + Potion Shop EN*: Zora Approach WN* + Potion Shop EC*: Zora Approach WC* + Dark Witch EN*: Catfish Approach WN* + Dark Witch EC*: Catfish Approach WC* + Wooden Bridge SW*: Sand Dunes NW* + Broken Bridge SW*: Dark Dunes NW* + Sand Dunes SC*: Stone Bridge NC* + Dark Dunes SC*: Hammer Bridge NC* + Maze Race ES*: Kakariko Suburb WS* + Dig Game EC: Frog WC + Dig Game ES*: Frog WS* + Kakariko Suburb ES*: Flute Boy WS* + Frog ES*: Stumpy WS* + Flute Boy SW*: Flute Boy Approach NW* + Flute Boy SC*: Flute Boy Approach NC* + Stumpy SW*: Stumpy Approach NW* + Stumpy SC*: Stumpy Approach NC* + Central Bonk Rocks EN*: Links House WN* + Central Bonk Rocks EC*: Links House WC* + Central Bonk Rocks ES*: Links House WS* + Central Bonk Rocks SW*: C Whirlpool NW* + Dark Bonk Rocks EN*: Big Bomb Shop WN* + Dark Bonk Rocks EC*: Big Bomb Shop WC* + Dark Bonk Rocks ES*: Big Bomb Shop WS* + Dark Bonk Rocks SW*: Dark C Whirlpool NW* + Links House SC*: Statues NC* + Links House ES*: Stone Bridge WS* + Big Bomb Shop SC*: Hype Cave NC* + Big Bomb Shop ES*: Hammer Bridge WS* + Stone Bridge WC: Hobo EC + Stone Bridge EN*: Tree Line WN* + Stone Bridge EC*: Tree Line WC* + Hammer Bridge EN*: Dark Tree Line WN* + Hammer Bridge EC*: Dark Tree Line WC* + Flute Boy Approach EC*: C Whirlpool WC* + Stumpy Approach EC*: Dark C Whirlpool WC* + C Whirlpool EN*: Statues WN* + C Whirlpool EC*: Statues WC* + C Whirlpool ES*: Statues WS* + C Whirlpool SC*: Dam NC* + Dark C Whirlpool EN*: Hype Cave WN* + Dark C Whirlpool EC*: Hype Cave WC* + Dark C Whirlpool ES*: Hype Cave WS* + Dark C Whirlpool SC*: Swamp NC* + Statues SC*: South Pass NC* + Hype Cave SC*: Dark South Pass NC* + Ice Cave SW*: Octoballoon NW* + Ice Cave SE*: Octoballoon NE* + Shopping Mall SW*: Bomber Corner NW* + Shopping Mall SE*: Bomber Corner NE* + Desert Pass EC*: Dam WC* + Desert Pass ES*: Dam WS* + Swamp Nook EC*: Swamp WC* + Swamp Nook ES*: Swamp WS* + Dam EC*: South Pass WC* + Swamp EC*: Dark South Pass WC* +ow-whirlpools: + 1: + two-way: + Zora Whirlpool: Lake Hylia Whirlpool + Kakariko Pond Whirlpool: Octoballoon Whirlpool + River Bend Whirlpool: C Whirlpool + Qirn Jump Whirlpool: Bomber Corner Whirlpool \ No newline at end of file diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index f5996573..bbc7070a 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -23,6 +23,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, @@ -184,8 +185,7 @@ "none", "grouped", "polar", - "limited", - "chaos" + "unrestricted" ] }, "ow_keepsimilar": { @@ -219,6 +219,7 @@ "full", "lite", "lean", + "district", "swapped", "crossed", "insanity", @@ -378,6 +379,14 @@ "action": "store_true", "type": "bool" }, + "shuffle_sfxinstruments": { + "action": "store_true", + "type": "bool" + }, + "shuffle_songinstruments": { + "action": "store_true", + "type": "bool" + }, "msu_resume": { "action": "store_true", "type": "bool" @@ -603,11 +612,15 @@ "action": "store_true", "type": "bool" }, - "saveonexit": { + "aga_randomness": { + "action": "store_false", + "type": "bool" + }, + "settingsonload": { "choices": [ - "ask", - "always", - "never" + "default", + "saved", + "lastused" ] }, "outputname": {}, diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 20dce34c..eec33d7e 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -2,7 +2,7 @@ "cli": { "yes": "Yes", "no": "No", - "app.title": "ALttP Door Randomizer Version %s : --seed %s --code %s", + "app.title": "ALttP Overworld Randomizer Version %s : --seed %s --code %s", "version": "Version", "seed": "Seed", "player": "Player", @@ -56,7 +56,8 @@ "building.collection.spheres": "Building up collection spheres", "building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.", "building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.", - "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s" + "old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s", + "hybridglitches.door.shuffle": "Hybrid Major Glitches is not currently compatible with Door Shuffle." }, "help": { "lang": [ "App Language, if available, defaults to English" ], @@ -64,11 +65,13 @@ "bps": [ "Output BPS patches instead of ROMs"], "logic": [ "Select Enforcement of Item Requirements. (default: %(default)s)", - "No Glitches: No Glitch knowledge required.", - "Minor Glitches: May require Fake Flippers, Bunny Revival", - " and Dark Room Navigation.", - "No Logic: Distribute items without regard for", - " item requirements." + "No Glitches: No Glitch knowledge required.", + "Minor Glitches: May require Fake Flippers, Bunny Revival", + " and Dark Room Navigation.", + "Overworld Glitches: May require overworld clips and teleports.", + "Hybrid Major Glitches: May require underworld clips.", + "No Logic: Distribute items without regard for", + " item requirements." ], "mode": [ "Select game mode. (default: %(default)s)", @@ -209,6 +212,7 @@ " item locations are shuffled in separate pools. Non-item", " locations remain vanilla. Connectors are same-world.", "Lean: Same as Lite, except connectors can travel cross worlds.", + "District: Entrances are shuffled within their overworld districts.", "Crossed: Mix cave and dungeon entrances freely while allowing", " caves to cross between worlds.", "Swapped: Same as Crossed, but entrances switch places in pairs.", @@ -239,9 +243,7 @@ " walk around and access the other plane version by walking.", "Polar: Only used when Tile Flip is enabled. This retains original", " connections even when overworld tiles are flipped.", - "Limited: Exactly nine transitions are randomly chosen as", - " cross-world connections (to emulate the nine portals).", - "Chaos: Every transition has a 50/50 chance to become a", + "Unrestricted: Every transition has a 50/50 chance to become a", " crossworld connection." ], "ow_keepsimilar": [ @@ -377,6 +379,7 @@ "Random: Take any caves can replace a random set of un-interesting caves. See documentation for full list", "Fixed: Take any caves will replace certain location. See documentation for full list" ], + "collection_rate": [ "Display collection rate (default: %(default)s)" ], "pseudoboots": [ " Start with pseudo boots that allow dashing but no item checks (default: %(default)s)"], "bombbag": ["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)" ], @@ -426,8 +429,9 @@ ], "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], + "shuffle_sfxinstruments": [ "Shuffle sound instruments (default: %(default)s)" ], + "shuffle_songinstruments": [ "Shuffle song instruments (default: %(default)s)" ], "msu_resume": [ "Enable MSU Resume (default: %(default)s)" ], - "collection_rate": [ "Display collection rate (default: %(default)s)" ], "create_rom": [ "Create an output rom file. (default: %(default)s)" ], "gui": [ "Launch the GUI. (default: %(default)s)" ], "jsonout": [ diff --git a/resources/app/gui/adjust/overview/widgets.json b/resources/app/gui/adjust/overview/widgets.json index a24bf7b4..bb4512c2 100644 --- a/resources/app/gui/adjust/overview/widgets.json +++ b/resources/app/gui/adjust/overview/widgets.json @@ -4,7 +4,9 @@ "msu_resume": { "type": "checkbox" }, "quickswap": { "type": "checkbox" }, "reduce_flashing": {"type": "checkbox" }, - "shuffle_sfx": {"type": "checkbox" } + "shuffle_sfx": {"type": "checkbox" }, + "shuffle_sfxinstruments": {"type": "checkbox" }, + "shuffle_songinstruments": {"type": "checkbox" } }, "leftAdjustFrame": { "heartcolor": { diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 3398dd58..81366e66 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -4,6 +4,8 @@ "adjust.quickswap": "L/R Quickswapping", "adjust.reduce_flashing": "Reduce Flashing", "adjust.shuffle_sfx": "Shuffle Sound Effects", + "adjust.shuffle_sfxinstruments": "Shuffle Sound Instruments", + "adjust.shuffle_songinstruments": "Shuffle Song Instruments", "adjust.msu_resume": "MSU Resume", "adjust.heartcolor": "Heart Color", @@ -147,8 +149,7 @@ "randomizer.overworld.crossed.none": "None", "randomizer.overworld.crossed.grouped": "Grouped", "randomizer.overworld.crossed.polar": "Polar", - "randomizer.overworld.crossed.limited": "Limited", - "randomizer.overworld.crossed.chaos": "Chaos", + "randomizer.overworld.crossed.unrestricted": "Unrestricted", "randomizer.overworld.keepsimilar": "Keep Similar Edges Together", @@ -180,6 +181,7 @@ "randomizer.entrance.entranceshuffle.restricted": "Restricted", "randomizer.entrance.entranceshuffle.full": "Full", "randomizer.entrance.entranceshuffle.lean": "Lean", + "randomizer.entrance.entranceshuffle.district": "District", "randomizer.entrance.entranceshuffle.swapped": "Swapped", "randomizer.entrance.entranceshuffle.crossed": "Crossed", "randomizer.entrance.entranceshuffle.insanity": "Insanity", @@ -190,8 +192,9 @@ "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", "randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects", + "randomizer.gameoptions.shuffle_sfxinstruments": "Shuffle Sound Instruments", + "randomizer.gameoptions.shuffle_songinstruments": "Shuffle Song Instruments", "randomizer.gameoptions.msu_resume": "MSU Resume", - "randomizer.gameoptions.collection_rate": "Display Collection Rate", "randomizer.gameoptions.heartcolor": "Heart Color", "randomizer.gameoptions.heartcolor.red": "Red", @@ -235,10 +238,10 @@ "randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.print_custom_yaml": "Print Customizer File", - "randomizer.generation.saveonexit": "Save Settings on Exit", - "randomizer.generation.saveonexit.ask": "Ask Me", - "randomizer.generation.saveonexit.always": "Always", - "randomizer.generation.saveonexit.never": "Never", + "randomizer.generation.settingsonload": "Settings On Load", + "randomizer.generation.settingsonload.default": "Default", + "randomizer.generation.settingsonload.saved": "Saved", + "randomizer.generation.settingsonload.lastused": "Last Used", "randomizer.generation.rom": "Base Rom: ", "randomizer.generation.rom.button": "Select Rom", @@ -249,6 +252,7 @@ "randomizer.item.race": "Generate \"Race\" ROM", "randomizer.item.retro": "Retro mode", "randomizer.item.pseudoboots": "Pseudoboots", + "randomizer.item.collection_rate": "Display Collection Rate", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", @@ -261,6 +265,7 @@ "randomizer.item.logiclevel.noglitches": "No Glitches", "randomizer.item.logiclevel.minorglitches": "Minor Glitches", "randomizer.item.logiclevel.owglitches": "Overworld Glitches", + "randomizer.item.logiclevel.hybridglitches": "Hybrid Major Glitches", "randomizer.item.logiclevel.nologic": "No Logic", "randomizer.item.goal": "Goal", @@ -408,11 +413,13 @@ "bottom.content.names": "Player names", "bottom.content.seed": "Seed #", "bottom.content.generationcount": "Count", - "bottom.content.go": "Generate Patched Rom", + "bottom.content.go": "Generate Game", + "bottom.content.exportyaml": "Export Yaml", "bottom.content.dialog.error": "Error while creating seed", "bottom.content.dialog.success": "Success", "bottom.content.dialog.success.message": "Rom created successfully.", - "bottom.content.outputdir": "Open Output Directory", + "bottom.content.savesettings": "Save Settings", + "bottom.content.outputdir": "Select Destination", "bottom.content.docs": "Open Documentation" } } diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index c325ea26..809dbac5 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -9,6 +9,7 @@ "full", "lite", "lean", + "district", "swapped", "crossed", "insanity", diff --git a/resources/app/gui/randomize/gameoptions/widgets.json b/resources/app/gui/randomize/gameoptions/widgets.json index 7b84b8a9..a35d1e4f 100644 --- a/resources/app/gui/randomize/gameoptions/widgets.json +++ b/resources/app/gui/randomize/gameoptions/widgets.json @@ -2,10 +2,11 @@ "checkboxes": { "nobgm": { "type": "checkbox" }, "msu_resume": { "type": "checkbox" }, - "collection_rate": {"type": "checkbox"}, "quickswap": { "type": "checkbox" }, "reduce_flashing": { "type": "checkbox" }, - "shuffle_sfx": { "type": "checkbox" } + "shuffle_sfx": { "type": "checkbox" }, + "shuffle_sfxinstruments": { "type": "checkbox" }, + "shuffle_songinstruments": { "type": "checkbox" } }, "leftRomOptionsFrame": { "heartcolor": { diff --git a/resources/app/gui/randomize/generation/widgets.json b/resources/app/gui/randomize/generation/widgets.json index 22f9decc..a410f227 100644 --- a/resources/app/gui/randomize/generation/widgets.json +++ b/resources/app/gui/randomize/generation/widgets.json @@ -1,11 +1,11 @@ { "widgets": { - "saveonexit": { + "settingsonload": { "type": "selectbox", "options": [ - "ask", - "always", - "never" + "default", + "saved", + "lastused" ] } } diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index b6c8ec67..7be74fdc 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -2,6 +2,7 @@ "checkboxes": { "hints": { "type": "checkbox" }, "pseudoboots": { "type": "checkbox" }, + "collection_rate": {"type": "checkbox"}, "race": { "type": "checkbox" } }, "leftItemFrame": { @@ -20,6 +21,7 @@ "noglitches", "minorglitches", "owglitches", + "hybridglitches", "nologic" ] }, diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index c9438deb..c3c49c94 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -17,8 +17,7 @@ "none", "grouped", "polar", - "limited", - "chaos" + "unrestricted" ] }, "mixed": { diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 7eb8a31c..c38ae024 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -48,11 +48,13 @@ class CustomSettings(object): meta = defaultdict(lambda: None, self.file_source['meta']) return meta['players'] - def adjust_args(self, args): + def adjust_args(self, args, resolve_weighted=True): def get_setting(value: Any, default): if value or value == 0: if isinstance(value, dict): - return random.choices(list(value.keys()), list(value.values()), k=1)[0] + if resolve_weighted: + return random.choices(list(value.keys()), list(value.values()), k=1)[0] + return None else: return value return default @@ -79,6 +81,10 @@ class CustomSettings(object): args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p]) args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p]) args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p]) + if args.ow_crossed[p] == 'chaos': + import logging + logging.getLogger('').info("Crossed OWR option 'chaos' is deprecated. Use 'unrestricted' instead.") + args.ow_crossed[p] = 'unrestricted' args.ow_keepsimilar[p] = get_setting(settings['ow_keepsimilar'], args.ow_keepsimilar[p]) args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p]) args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p]) @@ -143,8 +149,8 @@ class CustomSettings(object): args.mapshuffle[p] = True args.compassshuffle[p] = True - args.shufflebosses[p] = get_setting(settings['boss_shuffle'], args.shufflebosses[p]) - args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p]) + args.shufflebosses[p] = get_setting(settings['boss_shuffle'], get_setting(settings['shufflebosses'], args.shufflebosses[p])) + args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], get_setting(settings['shuffleenemies'], args.shuffleenemies[p])) args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p]) args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p]) args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p]) @@ -163,6 +169,7 @@ class CustomSettings(object): args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p]) args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p]) args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p]) + args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[p]) # mystery usage args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p]) @@ -179,6 +186,8 @@ class CustomSettings(object): args.ow_palettes[p] = get_setting(settings['ow_palettes'], args.ow_palettes[p]) args.uw_palettes[p] = get_setting(settings['uw_palettes'], args.uw_palettes[p]) args.shuffle_sfx[p] = get_setting(settings['shuffle_sfx'], args.shuffle_sfx[p]) + args.shuffle_sfxinstruments[p] = get_setting(settings['shuffle_sfxinstruments'], args.shuffle_sfxinstruments[p]) + args.shuffle_songinstruments[p] = get_setting(settings['shuffle_songinstruments'], args.shuffle_songinstruments[p]) args.msu_resume[p] = get_setting(settings['msu_resume'], args.msu_resume[p]) def get_item_pool(self): @@ -196,6 +205,21 @@ class CustomSettings(object): return self.file_source['advanced_placements'] return None + def get_owedges(self): + if 'ow-edges' in self.file_source: + return self.file_source['ow-edges'] + return None + + def get_owcrossed(self): + if 'ow-crossed' in self.file_source: + return self.file_source['ow-crossed'] + return None + + def get_whirlpools(self): + if 'ow-whirlpools' in self.file_source: + return self.file_source['ow-whirlpools'] + return None + def get_owtileflips(self): if 'ow-tileflips' in self.file_source: return self.file_source['ow-tileflips'] @@ -242,10 +266,11 @@ class CustomSettings(object): self.world_rep['meta'] = meta_dict meta_dict['players'] = world.players meta_dict['algorithm'] = world.algorithm - meta_dict['seed'] = world.seed meta_dict['race'] = settings.race meta_dict['user_notes'] = settings.notes self.world_rep['settings'] = settings_dict + if world.precollected_items: + self.world_rep['start_inventory'] = start_inv = {} for p in self.player_range: settings_dict[p] = {} settings_dict[p]['ow_shuffle'] = world.owShuffle[p] @@ -290,8 +315,8 @@ class CustomSettings(object): settings_dict[p]['keyshuffle'] = world.keyshuffle[p] settings_dict[p]['mapshuffle'] = world.mapshuffle[p] settings_dict[p]['compassshuffle'] = world.compassshuffle[p] - settings_dict[p]['shufflebosses'] = world.boss_shuffle[p] - settings_dict[p]['shuffleenemies'] = world.enemy_shuffle[p] + settings_dict[p]['boss_shuffle'] = world.boss_shuffle[p] + settings_dict[p]['enemy_shuffle'] = world.enemy_shuffle[p] settings_dict[p]['enemy_health'] = world.enemy_health[p] settings_dict[p]['enemy_damage'] = world.enemy_damage[p] settings_dict[p]['shufflepots'] = world.potshuffle[p] @@ -303,6 +328,11 @@ class CustomSettings(object): settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p] settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p] settings_dict[p]['beemizer'] = world.beemizer[p] + settings_dict[p]['aga_randomness'] = world.aga_randomness[p] + if world.precollected_items: + start_inv[p] = [] + for item in world.precollected_items: + start_inv[item.player].append(item.name) # rom adjust stuff # settings_dict[p]['sprite'] = world.sprite[p] @@ -315,34 +345,42 @@ class CustomSettings(object): # settings_dict[p]['ow_palettes'] = world.ow_palettes[p] # settings_dict[p]['uw_palettes'] = world.uw_palettes[p] # settings_dict[p]['shuffle_sfx'] = world.shuffle_sfx[p] + # settings_dict[p]['shuffle_songinstruments'] = world.shuffle_songinstruments[p] # more settings? def record_info(self, world): + self.world_rep['meta']['seed'] = world.seed self.world_rep['bosses'] = bosses = {} - self.world_rep['start_inventory'] = start_inv = {} + self.world_rep['medallions'] = medallions = {} for p in self.player_range: bosses[p] = {} - start_inv[p] = [] + medallions[p] = {} for dungeon in world.dungeons: for level, boss in dungeon.bosses.items(): location = dungeon.name if level is None else f'{dungeon.name} ({level})' if boss and 'Agahnim' not in boss.name: bosses[dungeon.player][location] = boss.name - for item in world.precollected_items: - start_inv[item.player].append(item.name) - - def record_item_pool(self, world): - self.world_rep['item_pool'] = item_pool = {} - self.world_rep['medallions'] = medallions = {} - for p in self.player_range: - item_pool[p] = defaultdict(int) - medallions[p] = {} - for item in world.itempool: - item_pool[item.player][item.name] += 1 for p, req_medals in world.required_medallions.items(): medallions[p]['Misery Mire'] = req_medals[0] medallions[p]['Turtle Rock'] = req_medals[1] + def record_item_pool(self, world, use_custom_pool=False): + if not use_custom_pool or world.custom: + self.world_rep['item_pool'] = item_pool = {} + for p in self.player_range: + if not use_custom_pool or p in world.customitemarray: + item_pool[p] = defaultdict(int) + if use_custom_pool and world.custom: + import source.classes.constants as CONST + for p in world.customitemarray: + for i, c in world.customitemarray[p].items(): + if c > 0: + item = CONST.CUSTOMITEMLABELS[CONST.CUSTOMITEMS.index(i)] + item_pool[p][item] += c + else: + for item in world.itempool: + item_pool[item.player][item.name] += 1 + def record_item_placements(self, world): self.world_rep['placements'] = placements = {} for p in self.player_range: @@ -355,21 +393,40 @@ class CustomSettings(object): placements[location.player][location.name] = location.item.name def record_overworld(self, world): + self.world_rep['ow-edges'] = edges = {} + self.world_rep['ow-whirlpools'] = whirlpools = {} self.world_rep['ow-tileflips'] = flips = {} - for p in self.player_range: - if p in world.owswaps and len(world.owswaps[p][0]) > 0: - flips[p] = {} - flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) - flips[p]['force_flip'].sort() - flips[p]['undefined_chance'] = 0 self.world_rep['ow-flutespots'] = flute = {} for p in self.player_range: + connections = edges[p] = {} + connections['two-way'] = {} + connections['one-way'] = {} + whirlconnects = whirlpools[p] = {} + whirlconnects['two-way'] = {} + whirlconnects['one-way'] = {} + # tile flips + if p in world.owswaps and len(world.owswaps[p][0]) > 0: + flips[p] = {} + flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f & 0x40 == 0) + flips[p]['force_flip'].sort() + flips[p]['undefined_chance'] = 0 + # flute spots flute[p] = {} if p in world.owflutespots: flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p])) else: flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections)) flute[p]['forbid'] = [] + for key, data in world.spoiler.overworlds.items(): + player = data['player'] if 'player' in data else 1 + connections = edges[player] + sub = 'two-way' if data['direction'] == 'both' else 'one-way' + connections[sub][data['entrance']] = data['exit'] + for key, data in world.spoiler.whirlpools.items(): + player = data['player'] if 'player' in data else 1 + whirlconnects = whirlconnects[player] + sub = 'two-way' if data['direction'] == 'both' else 'one-way' + whirlconnects[sub][data['entrance']] = data['exit'] def record_entrances(self, world): self.world_rep['entrances'] = entrances = {} diff --git a/source/classes/SFX.py b/source/classes/SFX.py index 750b284b..2998aacc 100644 --- a/source/classes/SFX.py +++ b/source/classes/SFX.py @@ -1,9 +1,9 @@ +from enum import IntEnum import random from Utils import int16_as_bytes, snes_to_pc class SFX(object): - def __init__(self, name, sfx_set, orig_id, addr, chain, accomp=False): self.name = name self.sfx_set = sfx_set @@ -16,6 +16,91 @@ class SFX(object): self.target_id = None self.target_chain = None +class SPCMusicType(IntEnum): + NONE = 0x00, + Ambient = 0x01, + Melody = 0x02, + Rhythm = 0x04, + Beat = 0x08 + +class SFXType(IntEnum): + NONE = 0x00, + Ambient = 0x01, + Melody = 0x02, + Beat = 0x04, + Short = 0x08, + Long = 0x10, + Soft = 0x20, + Hard = 0x40, + ALL = 0xFF + +class Instrument(object): + def __init__(self, name, id, srcn, adsr, gain, mult): + self.name = name + self.id = id + self.srcn = srcn + self.adsr = adsr + self.gain = gain + self.mult = mult + + self.primary_type = 0 + + self.target_id = None + self.replacements = [] + + def add_type(self, type, force_primary): + if force_primary or self.type == 0: + self.primary_type |= type + self.type |= type + return self + +class SFXInstrument(Instrument): + def __init__(self, name, id, srcn, adsr, gain, mult): + Instrument.__init__(self, name, id, srcn, adsr, gain, mult) + + self.type = SFXType.NONE + + def amb(self, force_primary=False): return self.add_type(SFXType.Ambient, force_primary) + def mel(self, force_primary=False): return self.add_type(SFXType.Melody, force_primary) + def beat(self, force_primary=False): return self.add_type(SFXType.Beat, force_primary) + def short(self, force_primary=False): return self.add_type(SFXType.Short, force_primary) + def long(self, force_primary=False): return self.add_type(SFXType.Long, force_primary) + def soft(self, force_primary=False): return self.add_type(SFXType.Soft, force_primary) + def hard(self, force_primary=False): return self.add_type(SFXType.Hard, force_primary) + +class SPCInstrument(Instrument): + def __init__(self, name, srcn, adsr, gain, mult, min=9999, max=0): + Instrument.__init__(self, name, srcn, srcn, adsr, gain, mult) + + self.type = SPCMusicType.NONE + + def amb(self, force_primary=False): return self.add_type(SPCMusicType.Ambient, force_primary) + def mel(self, force_primary=False): return self.add_type(SPCMusicType.Melody, force_primary) + def bass(self, force_primary=False): return self.add_type(SPCMusicType.Rhythm, force_primary) + def beat(self, force_primary=False): return self.add_type(SPCMusicType.Beat, force_primary) + +class InstrumentChange(object): + def __init__(self, orig_instrument, type, ban=[], inc=[]): + self.orig_instrument = orig_instrument + self.type = type + self.banned_list = ban + self.include_list = inc + self.target_instrument = None + +class SFXInstrumentChange(InstrumentChange): + def __init__(self, sfx_set, sfx_id, orig_instrument, addresses, type=SFXType.Ambient | SFXType.Melody | SFXType.Beat, ban=[], inc=[]): + InstrumentChange.__init__(self, orig_instrument, type, ban, inc) + self.sfx_set = sfx_set + self.sfx_id = sfx_id + self.addresses = addresses + +class SPCInstrumentChange(InstrumentChange): + def __init__(self, song_id, segment_id, tracks, orig_instrument, type=SPCMusicType.NONE, ban=[], inc=[]): + InstrumentChange.__init__(self, orig_instrument, type, ban, inc) + self.song_id = song_id + self.segment_id = segment_id + self.tracks = tracks + def init_sfx_data(): sfx_pool = [SFX('Slash1', 0x02, 0x01, 0x2614, []), SFX('Slash2', 0x02, 0x02, 0x2625, []), @@ -178,3 +263,878 @@ def randomize_sfx(rom): last = chained rom.write_byte(ac_base + last - 1, 0) + +def output_song_data(rom, filepath, outfilebase): + with open(filepath, 'w') as outfile: + last_song = 0 + last_segment = -1 + outfile.write(f'{outfilebase}\n') + if "SFX" in filepath: + for change in sfx_instrument_changes: + outfile.write(f'\nSFX{change.sfx_set:1d}.{change.sfx_id:02X} = {rom.read_byte(snes_to_pc(change.addresses[0])):02X}') + else: + for change in spc_instrument_changes: + if last_song != change.song_id: + last_song = change.song_id + last_segment = change.segment_id + outfile.write(f'\nSong{change.song_id:02X}.{change.segment_id:02X}.') + elif last_segment != change.segment_id: + last_segment = change.segment_id + outfile.write(f'\n {change.segment_id:02X}.') + else: + outfile.write(f'\n ') + tracks = 8 + for track_id in change.tracks.keys(): + outfile.write(f'{track_id:01X}') + tracks -= 1 + if tracks > 0: + outfile.write(' ' * tracks) + outfile.write(f' = {rom.read_byte(snes_to_pc(next(iter(change.tracks.values()))[0])):02X}') + + +def prep_instruments(instruments, types): + for instrument in instruments.values(): + for ins, lst in types.items(): + if instrument.type & ins > 0: + lst.append(instrument) + if hasattr(instruments[0], 'primary_type'): + for instrument in instruments.values(): + for ins, lst in types.items(): + if instrument.primary_type & ins > 0: + instrument.replacements += [i for i in lst if i not in instrument.replacements] + +def shuffle_instruments(instrument_changes, instruments, types, match_patterns=[0xFF]): + # randomize each instrument change + for change in instrument_changes: + if type(change.type) is list: + candidates = [instruments[i] for i in change.type] + elif change.type != 0: + candidates = [] + for n, match in enumerate(match_patterns): + cand = [] + for ins, lst in types.items(): + if match & ins > 0 and (change.type & ins > 0 or (n == 0 and change.type & match == 0)): + cand += [i for i in lst if i not in cand] + if n == 0: + if not len(cand): + for ins, lst in types.items(): + if match & ins > 0: + cand += [i for i in lst if i not in cand] + candidates = cand.copy() + elif len(cand): + candidates = [i for i in cand if i in candidates] + else: + candidates = instruments[change.orig_instrument].replacements + candidates += [instruments[i] for i in change.include_list if instruments[i] not in candidates] + candidates = [i for i in candidates if i.id not in change.banned_list] + change.target_instrument = random.choice(candidates).id + +def randomize_sfxinstruments(rom): + # categorize instruments in pools + ambients, melodies, beats, shorts, longs, softs, hards = [], [], [], [], [], [], [] + inst_lists = { + SFXType.Ambient: ambients, + SFXType.Melody: melodies, + SFXType.Beat: beats, + SFXType.Short: shorts, + SFXType.Long: longs, + SFXType.Soft: softs, + SFXType.Hard: hards, + } + match_patterns = [SFXType.Ambient | SFXType.Melody | SFXType.Beat, + SFXType.Short | SFXType.Long, + SFXType.Soft | SFXType.Hard] + prep_instruments(sfx_instruments, inst_lists) + shuffle_instruments(sfx_instrument_changes, sfx_instruments, inst_lists, match_patterns) + + for change in sfx_instrument_changes: + for addr in change.addresses: + rom.write_byte(snes_to_pc(addr), change.target_instrument) + +def randomize_songinstruments(rom): + # categorize instruments in pools + ambients, melodies, rhythms, beats = [], [], [], [] + inst_lists = { + SPCMusicType.Ambient: ambients, + SPCMusicType.Melody: melodies, + SPCMusicType.Rhythm: rhythms, + SPCMusicType.Beat: beats + } + prep_instruments(spc_instruments, inst_lists) + shuffle_instruments(spc_instrument_changes, spc_instruments, inst_lists) + + for change in spc_instrument_changes: + for track_addresses in change.tracks.values(): + for addr in track_addresses: + rom.write_byte(snes_to_pc(addr), change.target_instrument) + + +sfx_instruments = { # table @ $1A9C04 + 0x00: SFXInstrument("Fwoosh", 0x00, 0x00, [0xF6, 0x6A], 0xB8, 0x03).amb().long().soft(), + 0x01: SFXInstrument("Swish", 0x01, 0x01, [0x8E, 0xE0], 0xB8, 0x02).amb().long().soft(), + 0x02: SFXInstrument("Bomp", 0x02, 0x14, [0xFE, 0x6A], 0xB8, 0x02).beat().short().hard(), + 0x03: SFXInstrument("Ting", 0x03, 0x03, [0xFE, 0xF8], 0xB8, 0x0D).beat().short().hard(), + 0x04: SFXInstrument("Rrrrr", 0x04, 0x04, [0xFE, 0x6A], 0x7F, 0x03).mel().long().hard(), + 0x05: SFXInstrument("Clunk", 0x05, 0x02, [0xFE, 0x6A], 0x7F, 0x03).beat().short().hard(), + 0x06: SFXInstrument("Ching", 0x06, 0x05, [0xFE, 0x6A], 0x70, 0x03).amb().long().hard(), + 0x07: SFXInstrument("Fwomp", 0x07, 0x06, [0xFE, 0x6A], 0x70, 0x03).mel().long().hard(), + 0x08: SFXInstrument("Squee", 0x08, 0x08, [0xFA, 0x6A], 0x70, 0x03).mel().short().soft(), + 0x09: SFXInstrument("Unused", 0x09, 0x06, [0xFE, 0x6A], 0x70, 0x01).mel().long().hard(), + 0x0A: SFXInstrument("Bzzzrt", 0x0A, 0x07, [0xFE, 0x6A], 0x70, 0x05).mel().long().hard(), + 0x0B: SFXInstrument("Brrfft", 0x0B, 0x0B, [0xFE, 0x6A], 0xB8, 0x03).mel().long().hard(), + 0x0C: SFXInstrument("Brrwwww", 0x0C, 0x0C, [0xFE, 0xE0], 0xB8, 0x02).beat().short().hard(), + 0x0D: SFXInstrument("Twee", 0x0D, 0x0D, [0xF9, 0x6E], 0xB8, 0x03).mel().long().hard(), + 0x0E: SFXInstrument("Pwing", 0x0E, 0x0E, [0xFE, 0xF5], 0xB8, 0x07).mel().short().hard(),#?on melody since flute song has a bad note + 0x0F: SFXInstrument("Pling", 0x0F, 0x0F, [0xFE, 0xF5], 0xB8, 0x06).mel().short().hard(), + 0x10: SFXInstrument("Chshtsh", 0x10, 0x01, [0xFE, 0xFC], 0xB8, 0x03).beat().short().soft(), + 0x11: SFXInstrument("Splssh", 0x11, 0x10, [0x8E, 0xE0], 0xB8, 0x03).amb().short().soft(), + 0x12: SFXInstrument("Weewoo", 0x12, 0x08, [0x8E, 0xE0], 0xB8, 0x02).mel().short().soft(), + 0x13: SFXInstrument("Brbrbrb", 0x13, 0x14, [0x8E, 0xE0], 0xB8, 0x02).amb().beat().short().hard(), + 0x14: SFXInstrument("Bwow", 0x14, 0x0A, [0x88, 0xE0], 0xB8, 0x02).mel().long().hard(), + 0x15: SFXInstrument("Uughf", 0x15, 0x17, [0x8E, 0xE0], 0xB8, 0x02).mel().short().hard(), + 0x16: SFXInstrument("Aaaaaa", 0x16, 0x15, [0xFF, 0xE0], 0xB8, 0x04).mel().long().hard(), + 0x17: SFXInstrument("Twing", 0x17, 0x03, [0xDF, 0x11], 0xB8, 0x0F).beat().short().hard(), + 0x18: SFXInstrument("Whooo", 0x18, 0x01, [0x88, 0xE0], 0xB8, 0x01).amb().long().soft() +} + +spc_instruments = { # table @ $19FB1C + 0x00: SPCInstrument("Noise", 0x00, [0xFF, 0xE0], 0xB8, 0x0470), + 0x01: SPCInstrument("Rain", 0x01, [0xFF, 0xE0], 0xB8, 0x0790).amb(), + 0x02: SPCInstrument("Timpani", 0x02, [0xFF, 0xE0], 0xB8, 0x09C0).beat(), + 0x03: SPCInstrument("Square Wave", 0x03, [0xFF, 0xE0], 0xB8, 0x0400).bass().mel(), + 0x04: SPCInstrument("Saw Wave", 0x04, [0xFF, 0xE0], 0xB8, 0x0400).bass(), + 0x05: SPCInstrument("Clink", 0x05, [0xFF, 0xE0], 0xB8, 0x0470), + 0x06: SPCInstrument("Wobbly Lead", 0x06, [0xFF, 0xE0], 0xB8, 0x0470).amb(), + 0x07: SPCInstrument("Compound Saw", 0x07, [0xFF, 0xE0], 0xB8, 0x0470), + 0x08: SPCInstrument("Tweet", 0x08, [0xFF, 0xE0], 0xB8, 0x07A0).amb().beat(), + 0x09: SPCInstrument("Strings A", 0x09, [0x8F, 0xE9], 0xB8, 0x01E0).mel().bass(True), + 0x0A: SPCInstrument("Strings B", 0x0A, [0x8A, 0xE9], 0xB8, 0x01E0).mel().bass(True), + 0x0B: SPCInstrument("Trombone", 0x0B, [0xFF, 0xE0], 0xB8, 0x0300).mel().bass(True).beat(), + 0x0C: SPCInstrument("Cymbal", 0x0C, [0xFF, 0xE0], 0xB8, 0x03A0).beat(), + 0x0D: SPCInstrument("Ocarina", 0x0D, [0xFF, 0xE0], 0xB8, 0x0100).bass(), + 0x0E: SPCInstrument("Chimes", 0x0E, [0xFF, 0xEF], 0xB8, 0x0EA0).amb(), + 0x0F: SPCInstrument("Harp", 0x0F, [0xFF, 0xEF], 0xB8, 0x0600).mel().bass(True).beat(), + 0x10: SPCInstrument("Splash", 0x10, [0xFF, 0xE0], 0xB8, 0x03D0).amb().beat(), + 0x11: SPCInstrument("Trumpet", 0x11, [0x8F, 0xE0], 0xB8, 0x0300).mel().bass(True), + 0x12: SPCInstrument("Horn", 0x12, [0x8F, 0xE0], 0xB8, 0x06F0).mel().bass(True), + 0x13: SPCInstrument("Snare A", 0x13, [0xFD, 0xE0], 0xB8, 0x07A0).beat(), + 0x14: SPCInstrument("Snare B", 0x14, [0xFF, 0xE0], 0xB8, 0x07A0).beat(), + 0x15: SPCInstrument("Choir", 0x15, [0xFF, 0xE0], 0xB8, 0x03D0).mel().bass(True), + 0x16: SPCInstrument("Flute", 0x16, [0x8F, 0xE0], 0xB8, 0x0300).mel().bass(True), + 0x17: SPCInstrument("Oof", 0x17, [0xFF, 0xE0], 0xB8, 0x02C0).amb().beat(True), + 0x18: SPCInstrument("Piano", 0x18, [0xFE, 0x8F], 0xB8, 0x06F0).mel().bass(True) +} + +Am = SFXType.Ambient +Me = SFXType.Melody +Be = SFXType.Beat +Sf = SFXType.Soft +Hd = SFXType.Hard +Sh = SFXType.Short +Lg = SFXType.Long + +sfx_instrument_changes = [ + SFXInstrumentChange(0x01, 0x01, 0x01, [0x1A9A03, 0x1A9A13], type=Am, ban=[0x00, 0x06, 0x11]), # accompanied $01+02 + SFXInstrumentChange(0x01, 0x03, 0x01, [0x1A9A28, 0x1A9A38], type=Am, ban=[0x00, 0x06, 0x11]), # accompanied $03+04 + SFXInstrumentChange(0x01, 0x07, 0x13, [0x1A9AEA], type=Am|Be|Hd, inc=[0x08, 0x12]), + SFXInstrumentChange(0x01, 0x09, 0x18, [0x1A903F, 0x1A906D], type=Am, ban=[0x00, 0x06, 0x11]), # accompanied $09+0A + SFXInstrumentChange(0x01, 0x0B, 0x0D, [0x1A8F54, 0x1A8F13], type=Me, ban=[0x0F]), # accompanied $0B+0C + SFXInstrumentChange(0x01, 0x0D, 0x0E, [0x1A8EBF, 0x1A8ECE], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), # accompanied $0D+0E + SFXInstrumentChange(0x01, 0x0F, 0x0E, [0x1A8EDD, 0x1A8EEF], ban=[0x06, 0x0C]), # accompanied $0F+10 + SFXInstrumentChange(0x01, 0x11, 0x16, [0x1A925D, 0x1A9279], type=Me|Be, ban=[0x02, 0x09, 0x0C, 0x13, 0x15]), # accompanied $11+12 + SFXInstrumentChange(0x01, 0x13, 0x00, [0x1A8E83, 0x1A8E92], ban=[0x10]), # accompanied $13+14 + SFXInstrumentChange(0x01, 0x15, 0x0A, [0x1A8EA1, 0x1A8EB0], ban=[0x09, 0x10, 0x15]), # accompanied $15+16 + SFXInstrumentChange(0x01, 0x17, 0x0D, [0x1A8FD5, 0x1A8F94], type=Me, ban=[0x0F]), # accompanied $17+18 + + SFXInstrumentChange(0x02, 0x01, 0x01, [0x1A99C5], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x02, 0x01, [0x1A99D6], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x03, 0x02, [0x1A99E5], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x04, 0x02, [0x1A99F4], type=Am, ban=[0x06], inc=[0x02]), + SFXInstrumentChange(0x02, 0x05, 0x06, [0x1A998E], ban=[0x01, 0x03, 0x16, 0x17]), + SFXInstrumentChange(0x02, 0x06, 0x03, [0x1A9986], type=Hd, ban=[0x06, 0x09], inc=[0x12]), + SFXInstrumentChange(0x02, 0x08, 0x02, [0x1A9994], ban=[0x10, 0x18]), + SFXInstrumentChange(0x02, 0x08, 0x02, [0x1A999D], type=Me|Lg, inc=[0x02, 0x0C, 0x13]), + SFXInstrumentChange(0x02, 0x09, 0x00, [0x1A995E], ban=[0x08, 0x13]), + SFXInstrumentChange(0x02, 0x0A, 0x06, [0x1A9978], ban=[0x00, 0x03, 0x0C, 0x0E, 0x13, 0x14, 0x17, 0x18]), + SFXInstrumentChange(0x02, 0x0B, 0x02, [0x1A9829], type=Hd, ban=[0x09], inc=[0x12]), + SFXInstrumentChange(0x02, 0x0C, 0x13, [0x1A9A4D], type=Sh, ban=[0x10]), + SFXInstrumentChange(0x02, 0x0D, 0x00, [0x1A97C5], type=Am, ban=[0x13], inc=[0x10]), # accompanied $0D+3F + SFXInstrumentChange(0x02, 0x0E, 0x00, [0x1A97B5], type=Lg, ban=[0x09, 0x18], inc=[0x13]), + SFXInstrumentChange(0x02, 0x0F, 0x06, [0x1A9874], type=Lg, ban=[0x18]), + SFXInstrumentChange(0x02, 0x10, 0x05, [0x1A97AB], ban=[0x00, 0x09, 0x14, 0x18]), + SFXInstrumentChange(0x02, 0x11, 0x05, [0x1A97A1], ban=[0x00, 0x08, 0x10, 0x18]), + SFXInstrumentChange(0x02, 0x12, 0x02, [0x1A977E], ban=[0x00, 0x10]), + SFXInstrumentChange(0x02, 0x12, 0x01, [0x1A9787], ban=[0x10]), + SFXInstrumentChange(0x02, 0x13, 0x0D, [0x1A9751, 0x1A9766], type=Me, ban=[0x0F]), # accompanied $13+3E + SFXInstrumentChange(0x02, 0x14, 0x07, [0x1A9731], type=Hd, ban=[0x09]), + SFXInstrumentChange(0x02, 0x15, 0x07, [0x1A9741], type=Hd, ban=[0x09]), + SFXInstrumentChange(0x02, 0x16, 0x10, [0x1A96DD], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x17, 0x10, [0x1A96F5], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x18, 0x10, [0x1A9707], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x19, 0x10, [0x1A971F], ban=[0x00, 0x01, 0x06, 0x0C, 0x18]), + SFXInstrumentChange(0x02, 0x1A, 0x01, [0x1A96C7], type=Am, ban=[0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1B, 0x11, [0x1A96B8], type=Am, ban=[0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1C, 0x11, [0x1A96B2], type=Am, ban=[0x06, 0x13], inc=[0x08, 0x0F, 0x10, 0x12, 0x15, 0x17]), + SFXInstrumentChange(0x02, 0x1D, 0x16, [0x1A966C], ban=[0x09, 0x10, 0x11, 0x13, 0x15, 0x18]), + SFXInstrumentChange(0x02, 0x1E, 0x01, [0x1A9928], type=Lg, ban=[0x06]), + SFXInstrumentChange(0x02, 0x1F, 0x02, [0x1A969A], type=Me|Be|Hd, ban=[0x09]), + SFXInstrumentChange(0x02, 0x1F, 0x01, [0x1A96A0], ban=[0x00, 0x06, 0x18]), + SFXInstrumentChange(0x02, 0x20, 0x0D, [0x1A968B], type=Me|Lg, ban=[0x09]), + SFXInstrumentChange(0x02, 0x21, 0x02, [0x1A9680], type=Be|Hd), + SFXInstrumentChange(0x02, 0x21, 0x01, [0x1A9685], type=Sf, ban=[0x18]), + SFXInstrumentChange(0x02, 0x22, 0x05, [0x1A948E, 0x1A94B8], type=[0x01, 0x05, 0x0E, 0x0F, 0x10, 0x17]), # chained $3-18->22 + SFXInstrumentChange(0x02, 0x23, 0x01, [0x1A9662], ban=[0x06, 0x0C, 0x0E, 0x13]), + SFXInstrumentChange(0x02, 0x24, 0x11, [0x1A9656, 0x1A965C], type=Me|Lg, ban=[0x09, 0x0D, 0x16], inc=[0x11]), # accompanied $24+3D + SFXInstrumentChange(0x02, 0x25, 0x11, [0x1A9647], ban=[0x00, 0x01, 0x06, 0x0C, 0x10]), + SFXInstrumentChange(0x02, 0x26, 0x15, [0x1A9BF5], type=Me|Hd, inc=[0x11]), + SFXInstrumentChange(0x02, 0x27, 0x0B, [0x1A9603], type=Me|Lg, inc=[0x0E, 0x17]), + SFXInstrumentChange(0x02, 0x27, 0x13, [0x1A9631], type=Hd, inc=[0x08, 0x11, 0x12]), + SFXInstrumentChange(0x02, 0x28, 0x11, [0x1A9638], type=[0x10, 0x11, 0x12]), + SFXInstrumentChange(0x02, 0x29, 0x01, [0x1A97F0], type=Sf), # accompanied $29+3B + SFXInstrumentChange(0x02, 0x2A, 0x0C, [0x1A93E4], type=Be, inc=[0x08, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x16]), + SFXInstrumentChange(0x02, 0x2B, 0x0E, [0x1A93A3], type=Me, ban=[0x08, 0x09, 0x0D], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x02, 0x2C, 0x00, [0x1A938A, 0x1A9398], type=Sh, ban=[0x13]), # chained $2C->3A + SFXInstrumentChange(0x02, 0x2C, 0x17, [0x1A9393, 0x1A939D], type=Me|Sh, ban=[0x0E], inc=[0x03, 0x11, 0x17]), # chained $2C->3A + SFXInstrumentChange(0x02, 0x2D, 0x0F, [0x1A9457], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x02, 0x2E, 0x11, [0x1A937B], type=Sh, ban=[0x03, 0x10, 0x15, 0x17]), # accompanied $2E+39 + SFXInstrumentChange(0x02, 0x2F, 0x01, [0x1A92F8], type=Lg|Sf, ban=[0x00]), # accompanied $2F+38 + SFXInstrumentChange(0x02, 0x30, 0x15, [0x1A92A2], type=Me, inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x02, 0x31, 0x0D, [0x1A947F], type=Me, ban=[0x0E], inc=[0x17]), + SFXInstrumentChange(0x02, 0x32, 0x01, [0x1A90F8], type=Lg, ban=[0x00, 0x06]), + SFXInstrumentChange(0x02, 0x33, 0x07, [0x1A908D], type=Me|Lg), + SFXInstrumentChange(0x02, 0x35, 0x13, [0x1A9018], type=Am|Be, ban=[0x00, 0x06, 0x18], inc=[0x08, 0x12]), + SFXInstrumentChange(0x02, 0x37, 0x0E, [0x1A8DF4], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x02, 0x38, 0x11, [0x1A9320], type=Me|Am, ban=[0x00, 0x09], inc=[0x17]), # accompanied $2F+38 + SFXInstrumentChange(0x02, 0x39, 0x01, [0x1A934D], type=Am|Lg, ban=[0x00]), # accompanied $2E+39 + SFXInstrumentChange(0x02, 0x3B, 0x06, [0x1A9813], type=Hd, ban=[0x0C, 0x13, 0x14]), # accompanied $29+3B + SFXInstrumentChange(0x02, 0x3C, 0x04, [0x1A8DE8], type=Me, ban=[0x09], inc=[0x17]), + SFXInstrumentChange(0x02, 0x3F, 0x0D, [0x1A97E6], type=Me|Be|Hd, inc=[0x12]), # accompanied $0D+3F + + SFXInstrumentChange(0x03, 0x01, 0x07, [0x1A8DC9], type=Me, ban=[0x0D, 0x0E, 0x14, 0x16], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x02, 0x01, [0x1A98FF], ban=[0x03, 0x06, 0x09, 0x17]), + SFXInstrumentChange(0x03, 0x03, 0x13, [0x1A95FB], type=Me|Be, ban=[0x03, 0x09, 0x10, 0x14, 0x16, 0x17]), + SFXInstrumentChange(0x03, 0x04, 0x12, [0x1A95BF], type=Me, ban=[0x09]), + SFXInstrumentChange(0x03, 0x04, 0x06, [0x1A95C7], ban=[0x00, 0x18]), + SFXInstrumentChange(0x03, 0x05, 0x00, [0x1A9968], ban=[0x09, 0x10, 0x18]), + SFXInstrumentChange(0x03, 0x06, 0x02, [0x1A95A6], type=Sh, ban=[0x0C, 0x0E, 0x10, 0x11]), + SFXInstrumentChange(0x03, 0x07, 0x0C, [0x1A95EE], type=Me|Be, ban=[0x02, 0x09, 0x10, 0x13]), + SFXInstrumentChange(0x03, 0x08, 0x0C, [0x1A9597], type=Me|Be, ban=[0x02, 0x09, 0x10, 0x13]), + SFXInstrumentChange(0x03, 0x09, 0x0C, [0x1A9572], type=Me|Be, ban=[0x09, 0x10]), + SFXInstrumentChange(0x03, 0x0A, 0x03, [0x1A955A], type=Me|Be, ban=[0x10, 0x17], inc=[0x11, 0x13]), + SFXInstrumentChange(0x03, 0x0B, 0x0B, [0x1A9549], type=Me|Be, ban=[0x02, 0x13], inc=[0x11]), + SFXInstrumentChange(0x03, 0x0C, 0x0D, [0x1A953F], type=Me, ban=[0x09, 0x0A, 0x0B, 0x14, 0x15], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x03, 0x0D, 0x0E, [0x1A9566], type=Me, ban=[0x14], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x03, 0x0E, 0x02, [0x1A9533], ban=[0x00, 0x08, 0x0C, 0x0D, 0x0E, 0x13, 0x14, 0x18]), + SFXInstrumentChange(0x03, 0x0F, 0x0B, [0x1A986A, 0x1A983B, 0x1A9845, 0x1A9831], type=[0x03, 0x04, 0x08, 0x0B, 0x0D, 0x0F, 0x12, 0x17]), # accompanied $0F+3C+3D+3E+3F + SFXInstrumentChange(0x03, 0x10, 0x0E, [0x1A951E, 0x1A9527], type=Sh|Hd, ban=[0x03]), # accompanied $10+3B + SFXInstrumentChange(0x03, 0x11, 0x07, [0x1A9500], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x12, 0x07, [0x1A950F], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x13, 0x0D, [0x1A94EC], type=Me, ban=[0x09, 0x0B], inc=[0x03, 0x11, 0x17]), + SFXInstrumentChange(0x03, 0x14, 0x06, [0x1A981D], type=Hd, ban=[0x03, 0x09, 0x14], inc=[0x12]), + SFXInstrumentChange(0x03, 0x15, 0x13, [0x1A94E0], type=Hd, ban=[0x09, 0x14], inc=[0x12]), + SFXInstrumentChange(0x03, 0x16, 0x13, [0x1A94D4], type=Be, ban=[0x0C, 0x17]), + SFXInstrumentChange(0x03, 0x17, 0x08, [0x1A9957], type=Me, ban=[0x09, 0x0A, 0x0E]), + SFXInstrumentChange(0x03, 0x19, 0x07, [0x1A98BB], type=Me, ban=[0x08, 0x09], inc=[0x03, 0x05, 0x17]), + SFXInstrumentChange(0x03, 0x1A, 0x0F, [0x1A923B], type=Me|Lg, inc=[0x0F]), # accompanied $1A+38, but OWR + SFXInstrumentChange(0x03, 0x1B, 0x0E, [0x1A9467, 0x1A9471], type=Me, ban=[0x08]), # accompanied $1B+3A + SFXInstrumentChange(0x03, 0x1C, 0x0A, [0x1A8E13], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x1E, 0x01, [0x1A9442], ban=[0x03, 0x06, 0x0C, 0x0E]), + SFXInstrumentChange(0x03, 0x1F, 0x16, [0x1A93FC], type=Me|Lg, ban=[0x09]), + SFXInstrumentChange(0x03, 0x20, 0x0F, [0x1A9B1D], type=Me|Be|Hd), + SFXInstrumentChange(0x03, 0x21, 0x14, [0x1A9B93], type=Me, ban=[0x09, 0x0B, 0x15], inc=[0x17]), + SFXInstrumentChange(0x03, 0x22, 0x14, [0x1A9A80], type=Me|Lg, ban=[0x09, 0x0A]), + SFXInstrumentChange(0x03, 0x23, 0x01, [0x1A93B2], type=Am|Me|Lg, ban=[0x00], inc=[0x13]), # accompanied $23+39 + SFXInstrumentChange(0x03, 0x24, 0x0D, [0x1A93F4], type=Me, ban=[0x09], inc=[0x03, 0x17]), + SFXInstrumentChange(0x03, 0x25, 0x13, [0x1A924E], type=Am|Be, ban=[0x00, 0x06, 0x18], inc=[0x09]), + SFXInstrumentChange(0x03, 0x25, 0x06, [0x1A9254], type=Hd, ban=[0x02, 0x05, 0x09, 0x0C, 0x13, 0x14, 0x15]), + SFXInstrumentChange(0x03, 0x26, 0x00, [0x1A922C], type=Lg, ban=[0x06]), + SFXInstrumentChange(0x03, 0x27, 0x07, [0x1A91F1], type=Me|Hd), + SFXInstrumentChange(0x03, 0x28, 0x07, [0x1A9AA8], type=Me|Lg|Hd), + SFXInstrumentChange(0x03, 0x29, 0x07, [0x1A91D2], type=Me|Hd), + SFXInstrumentChange(0x03, 0x2A, 0x0A, [0x1A91C3], type=Me|Lg, inc=[0x03, 0x0E, 0x0F, 0x12, 0x17]), + SFXInstrumentChange(0x03, 0x2B, 0x0A, [0x1A91A4], ban=[0x06, 0x09, 0x18]), + SFXInstrumentChange(0x03, 0x2C, 0x0A, [0x1A9171], type=Me, ban=[0x09], inc=[0x17]), + SFXInstrumentChange(0x03, 0x2D, 0x0F, [0x1A915A, 0x1A9165], type=Me, ban=[0x08, 0x0E, 0x12]), # accompanied $2D+37 + SFXInstrumentChange(0x03, 0x2E, 0x0B, [0x1A910E, 0x1A9124, 0x1A9117], type=Me|Lg, ban=[0x14, 0x16]), # accompanied $2E+35+34 + SFXInstrumentChange(0x03, 0x2F, 0x0E, [0x1A9131, 0x1A9144], type=Me|Be|Hd, inc=[0x13]), # accompanied $2F+33 + SFXInstrumentChange(0x03, 0x30, 0x03, [0x1A8F04], type=Me|Be, ban=[0x0C, 0x13], inc=[0x11]), + SFXInstrumentChange(0x03, 0x31, 0x01, [0x1A8E7B], ban=[0x03, 0x06, 0x09, 0x0E, 0x17, 0x18]), + SFXInstrumentChange(0x03, 0x32, 0x04, [0x1A8E29], type=Me, ban=[0x08], inc=[0x03, 0x05, 0x17]), + SFXInstrumentChange(0x03, 0x36, 0x07, [0x1A8E58], type=Me|Lg, inc=[0x03, 0x0F, 0x17]), + # SFXInstrumentChange(0x03, 0x38, 0x0F, [0x1A9244]), # sound used for OWR function + SFXInstrumentChange(0x03, 0x39, 0x07, [0x1A93C8], type=Me|Be), # accompanied $23+39 + SFXInstrumentChange(0x03, 0x3E, 0x05, [0x1A984F], type=Be, ban=[0x02, 0x0C], inc=[0x0D, 0x0E, 0x0F]) +] + +Me = SPCMusicType.Melody +Rh = SPCMusicType.Rhythm +Be = SPCMusicType.Beat +Am = SPCMusicType.Ambient + +spc_instrument_changes = [ + SPCInstrumentChange(0x01, 0x00, {0x00: [0x1A9F5B], + 0x01: [0x1A9F9D], + 0x02: [0x1A9FBB], + 0x03: [0x1A9FDA], + 0x04: [0x1A9FE8]}, 0x0F), + SPCInstrumentChange(0x01, 0x01, {0x00: [0x1ACA1A], + 0x01: [0x1ACA39], + 0x02: [0x1ACA5E], + 0x07: [0x1ACC01]}, 0x0B), + SPCInstrumentChange(0x01, 0x01, {0x03: [0x1ACAA3], + 0x04: [0x1ACAE2]}, 0x11), + SPCInstrumentChange(0x01, 0x01, {0x05: [0x1ACB25, 0x1ACC78]}, 0x02), + SPCInstrumentChange(0x01, 0x01, {0x05: [0x1ACB3A, 0x1ACC51], + 0x06: [0x1ACBA9, 0x1ACC7D]}, 0x13), + SPCInstrumentChange(0x01, 0x01, {0x06: [0x1ACB94, 0x1ACCA3]}, 0x0C), + + SPCInstrumentChange(0x02, 0x00, {0x00: [0x1AA04B], + 0x03: [0x1AA10E], + 0x04: [0x1AA143], + 0x07: [0x1AA1D1]}, 0x0B), + SPCInstrumentChange(0x02, 0x00, {0x01: [0x1AA087], + 0x05: [0x1AA176]}, 0x11), + SPCInstrumentChange(0x02, 0x00, {0x02: [0x1AA0CC], + 0x06: [0x1AA1BF]}, 0x13), + SPCInstrumentChange(0x02, 0x00, {0x06: [0x1AA1C7]}, 0x0C), + SPCInstrumentChange(0x02, 0x01, {0x02: [0x1AA27B]}, 0x13), + SPCInstrumentChange(0x02, 0x01, {0x03: [0x1AA2A2]}, 0x0A), + SPCInstrumentChange(0x02, 0x01, {0x04: [0x1AA2CD]}, 0x02), + SPCInstrumentChange(0x02, 0x01, {0x05: [0x1AA2E0], + 0x07: [0x1AA34D]}, 0x0B), + SPCInstrumentChange(0x02, 0x02, {0x00: [0x1AA5A8], + 0x05: [0x1AA449]}, 0x0B), + SPCInstrumentChange(0x02, 0x02, {0x03: [0x1AA3FF], + 0x04: [0x1AA42A]}, 0x0A), + SPCInstrumentChange(0x02, 0x02, {0x06: [0x1AA49E, 0x1AA4CB]}, 0x13), + SPCInstrumentChange(0x02, 0x02, {0x06: [0x1AA4C0, 0x1AA4EA]}, 0x0C), + SPCInstrumentChange(0x02, 0x02, {0x06: [0x1AA752]}, 0x02), + + SPCInstrumentChange(0x03, 0x00, {0x00: [0x1AA84A], + 0x01: [0x1AA864], + 0x03: [0x1AA885]}, 0x0A), + SPCInstrumentChange(0x03, 0x01, {0x00: [0x1AA89E], + 0x01: [0x1AA8B8], + 0x03: [0x1AA8D9]}, 0x12), + SPCInstrumentChange(0x03, 0x01, {0x02: [0x1AAB86], + 0x04: [0x1AA8F2], + 0x05: [0x1AA93C]}, 0x0A), + + SPCInstrumentChange(0x04, 0x00, {0x00: [0x1AACA3], + 0x01: [0x1AACB1], + 0x02: [0x1AACC7]}, 0x12), + SPCInstrumentChange(0x04, 0x01, {0x00: [0x1AABF5], + 0x01: [0x1AAC0B]}, 0x12), + SPCInstrumentChange(0x04, 0x01, {0x02: [0x1AAC21]}, 0x12), + SPCInstrumentChange(0x04, 0x02, {0x01: [0x1AAC55], + 0x02: [0x1AAC6B]}, 0x12), + SPCInstrumentChange(0x04, 0x03, {0x00: [0x1AAD93], + 0x01: [0x1AACED], + 0x02: [0x1AAD07], + 0x03: [0x1AAD75], + 0x04: [0x1AADB1]}, 0x12), + + SPCInstrumentChange(0x05, 0x00, {0x00: [0x1AAE3F], + 0x02: [0x1AAE72], + 0x03: [0x1AAEAD], + 0x07: [0x1AAF02]}, 0x0A), + SPCInstrumentChange(0x05, 0x00, {0x01: [0x1AAE64]}, 0x09), + SPCInstrumentChange(0x05, 0x00, {0x04: [0x1AAEE8]}, 0x16), + SPCInstrumentChange(0x05, 0x01, {0x00: [0x1AB156], + 0x03: [0x1AAF48], + 0x04: [0x1AAF71], + 0x07: [0x1AB1D3]}, 0x0A), + SPCInstrumentChange(0x05, 0x01, {0x02: [0x1AB186]}, 0x16), + SPCInstrumentChange(0x05, 0x02, {0x00: [0x1AB088], + 0x03: [0x1AB0CF], + 0x04: [0x1AB0F8], + 0x07: [0x1AB11F]}, 0x0A), + SPCInstrumentChange(0x05, 0x02, {0x02: [0x1AB0A7]}, 0x16), + + SPCInstrumentChange(0x06, 0x00, {0x00: [0x1AB338]}, 0x0A), + SPCInstrumentChange(0x06, 0x01, {0x01: [0x1AB68F], + 0x02: [0x1AB69D], + 0x03: [0x1AB6B2], + 0x05: [0x1AB3D7]}, 0x0A), + SPCInstrumentChange(0x06, 0x02, {0x00: [0x1AB622], + 0x01: [0x1AB63A], + 0x02: [0x1AB648], + 0x03: [0x1AB670], + 0x04: [0x1AB6C4], + 0x05: [0x1AB49D]}, 0x0A), + SPCInstrumentChange(0x06, 0x03, {0x05: [0x1AB548]}, 0x0A), + SPCInstrumentChange(0x06, 0x04, {0x00: [0x1AB722], + 0x01: [0x1AB739], + 0x02: [0x1AB745], + 0x03: [0x1AB759], + 0x04: [0x1AB765], + 0x05: [0x1AB5E3]}, 0x0A), + + SPCInstrumentChange(0x07, 0x00, {0x00: [0x1ABB1F]}, 0x0A), + SPCInstrumentChange(0x07, 0x00, {0x01: [0x1ABB31], + 0x02: [0x1ABBE6], + 0x03: [0x1ABC0B]}, 0x09), + SPCInstrumentChange(0x07, 0x00, {0x04: [0x1ABB53, 0x1AB8C9]}, 0x16), + SPCInstrumentChange(0x07, 0x01, {0x04: [0x1AB8E6]}, 0x0E, type=Am|Me|Rh, ban=[0x01, 0x05, 0x06, 0x17]), + SPCInstrumentChange(0x07, 0x01, {0x05: [0x1AB8EB]}, 0x0A), + SPCInstrumentChange(0x07, 0x02, {0x04: [0x1AB981]}, 0x16), + SPCInstrumentChange(0x07, 0x03, {0x02: [0x1ABC37], + 0x03: [0x1ABC66]}, 0x09), + SPCInstrumentChange(0x07, 0x03, {0x04: [0x1ABA09]}, 0x16), + SPCInstrumentChange(0x07, 0x03, {0x05: [0x1ABC8F]}, 0x0A), + SPCInstrumentChange(0x07, 0x05, {0x02: [0x1ABC9D], + 0x03: [0x1ABCBA]}, 0x09), + SPCInstrumentChange(0x07, 0x05, {0x05: [0x1ABCD1]}, 0x0A), + SPCInstrumentChange(0x07, 0x06, {0x00: [0x1ABB72]}, 0x0A), + SPCInstrumentChange(0x07, 0x06, {0x01: [0x1ABB83], + 0x02: [0x1ABB95], + 0x03: [0x1ABBAA]}, 0x09), + SPCInstrumentChange(0x07, 0x06, {0x04: [0x1ABBBD]}, 0x16), + SPCInstrumentChange(0x07, 0x06, {0x05: [0x1ABCE6]}, 0x0A), + + SPCInstrumentChange(0x08, 0x00, {0x00: [0x1ABD3A], + 0x01: [0x1ABD5B]}, 0x06, type=Me|Rh|Am, ban=[0x05]), + SPCInstrumentChange(0x08, 0x00, {0x02: [0x1ABD70], + 0x05: [0x1ABE06]}, 0x0F), + SPCInstrumentChange(0x08, 0x00, {0x03: [0x1ABDAC]}, 0x0A), + SPCInstrumentChange(0x08, 0x00, {0x04: [0x1ABDC8]}, 0x01), + SPCInstrumentChange(0x08, 0x00, {0x06: [0x1ABE3A]}, 0x09), + + SPCInstrumentChange(0x09, 0x00, {0x00: [0x1AC25A], + 0x05: [0x1AC28E]}, 0x0A), + SPCInstrumentChange(0x09, 0x00, {0x01: [0x1AC26E]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x01, {0x01: [0x1ABF0A]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x01, {0x06: [0x1ABF43]}, 0x09), + SPCInstrumentChange(0x09, 0x02, {0x00: [0x1AC450], + 0x05: [0x1AC56D], + 0x07: [0x1AC595]}, 0x0A), + SPCInstrumentChange(0x09, 0x02, {0x01: [0x1AC2AF]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x02, {0x03: [0x1AC4B3], + 0x04: [0x1AC510]}, 0x11), + SPCInstrumentChange(0x09, 0x02, {0x06: [0x1AC2E9]}, 0x09), + SPCInstrumentChange(0x09, 0x03, {0x00: [0x1ABF63]}, 0x0A), + SPCInstrumentChange(0x09, 0x03, {0x01: [0x1ABF80]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x03, {0x03: [0x1ABFA4]}, 0x11), + SPCInstrumentChange(0x09, 0x03, {0x05: [0x1AC01C]}, 0x16), + SPCInstrumentChange(0x09, 0x04, {0x00: [0x1AC04D]}, 0x0A), + SPCInstrumentChange(0x09, 0x04, {0x01: [0x1AC05D]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x04, {0x02: [0x1AC5CF]}, 0x18), + SPCInstrumentChange(0x09, 0x04, {0x03: [0x1AC085], + 0x04: [0x1AC5ED]}, 0x11), + SPCInstrumentChange(0x09, 0x04, {0x05: [0x1AC137]}, 0x16), + SPCInstrumentChange(0x09, 0x04, {0x06: [0x1AC146]}, 0x12), + SPCInstrumentChange(0x09, 0x05, {0x00: [0x1AC178], + 0x07: [0x1AC229]}, 0x0A), + SPCInstrumentChange(0x09, 0x05, {0x01: [0x1AC196]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x05, {0x02: [0x1AC19E]}, 0x18), + SPCInstrumentChange(0x09, 0x05, {0x03: [0x1AC1D3], + 0x04: [0x1AC1F4]}, 0x12), + SPCInstrumentChange(0x09, 0x06, {0x00: [0x1AC317], + 0x07: [0x1AC3ED]}, 0x0A), + SPCInstrumentChange(0x09, 0x06, {0x01: [0x1AC332]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x06, {0x02: [0x1AC33A]}, 0x18), + SPCInstrumentChange(0x09, 0x06, {0x03: [0x1AC36F], + 0x04: [0x1AC3A4], + 0x05: [0x1AC3D9]}, 0x12), + SPCInstrumentChange(0x09, 0x07, {0x00: [0x1AC40A], + 0x05: [0x1AC43C]}, 0x0A), + SPCInstrumentChange(0x09, 0x07, {0x01: [0x1AC41C]}, 0x14, type=Am|Be, ban=[0x05, 0x06]), + SPCInstrumentChange(0x09, 0x07, {0x02: [0x1AC492]}, 0x18), + SPCInstrumentChange(0x09, 0x07, {0x03: [0x1AC680], + 0x04: [0x1AC6C1]}, 0x11), + + SPCInstrumentChange(0x0A, 0x00, {0x00: [0x1AC72F], + 0x01: [0x1AC751], + 0x02: [0x1AC772], + 0x03: [0x1AC793]}, 0x0F), + SPCInstrumentChange(0x0A, 0x00, {0x00: [0x1AC740], + 0x01: [0x1AC765], + 0x02: [0x1AC786], + 0x03: [0x1AC7A7], + 0x04: [0x1AC7B4, 0x1AC7C9], + 0x05: [0x1AC7D1]}, 0x09), + + SPCInstrumentChange(0x0B, 0x00, {0x00: [0x1A9EEC, 0x1A9D26]}, 0x0F, ban=[0x0B]), + SPCInstrumentChange(0x0B, 0x01, {0x01: [0x1A9D3F], + 0x02: [0x1A9D5A], + 0x03: [0x1A9D75], + 0x04: [0x1A9D90], + 0x05: [0x1A9DBB], + 0x06: [0x1A9DE9]}, 0x0F), + + SPCInstrumentChange(0x0C, 0x00, {0x00: [0x1AC83A], + 0x01: [0x1AC84A], + 0x02: [0x1AC857], + 0x03: [0x1AC864], + 0x04: [0x1AC871], + 0x05: [0x1AC87E]}, 0x0B), + SPCInstrumentChange(0x0C, 0x01, {0x02: [0x1AC89A], + 0x03: [0x1AC8AD]}, 0x11), + SPCInstrumentChange(0x0C, 0x01, {0x04: [0x1AC8B7]}, 0x0E, type=Rh|Am, ban=[0x05]), + SPCInstrumentChange(0x0C, 0x01, {0x05: [0x1AC8C3]}, 0x02), + SPCInstrumentChange(0x0C, 0x02, {0x02: [0x1AC8E0], + 0x03: [0x1AC8F3]}, 0x11), + SPCInstrumentChange(0x0C, 0x02, {0x04: [0x1AC8FD]}, 0x0E, type=Rh|Am, ban=[0x05]), + SPCInstrumentChange(0x0C, 0x02, {0x05: [0x1AC909]}, 0x02), + + SPCInstrumentChange(0x0D, 0x00, {0x00: [0x1AD003], + 0x03: [0x1AD02C], + 0x04: [0x1AD03A]}, 0x11), + SPCInstrumentChange(0x0D, 0x00, {0x01: [0x1AD010]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + SPCInstrumentChange(0x0D, 0x00, {0x02: [0x1AD07F]}, 0x14), + SPCInstrumentChange(0x0D, 0x01, {0x00: [0x1ACD10], + 0x04: [0x1ACD9A]}, 0x0B), + SPCInstrumentChange(0x0D, 0x01, {0x01: [0x1ACD41]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + SPCInstrumentChange(0x0D, 0x01, {0x03: [0x1ACD7F], + 0x05: [0x1ACDCA]}, 0x11), + SPCInstrumentChange(0x0D, 0x03, {0x00: [0x1ACE8E], + 0x01: [0x1ACEB8]}, 0x0A), + SPCInstrumentChange(0x0D, 0x03, {0x02: [0x1ACED4]}, 0x14), + SPCInstrumentChange(0x0D, 0x03, {0x03: [0x1ACEE0], + 0x04: [0x1ACF07]}, 0x11), + SPCInstrumentChange(0x0D, 0x04, {0x05: [0x1ACFE3]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x06, 0x10, 0x17]), + + SPCInstrumentChange(0x0E, 0x00, {0x00: [0x1AD29C]}, 0x16), + SPCInstrumentChange(0x0E, 0x00, {0x01: [0x1AD2AD], + 0x03: [0x1AD2CB], + 0x04: [0x1AD2D9]}, 0x18), + SPCInstrumentChange(0x0E, 0x00, {0x02: [0x1AD2BB]}, 0x12), + SPCInstrumentChange(0x0E, 0x01, {0x00: [0x1AD1A6]}, 0x16), + SPCInstrumentChange(0x0E, 0x01, {0x01: [0x1AD1AF], + 0x03: [0x1AD1CD]}, 0x18), + SPCInstrumentChange(0x0E, 0x01, {0x02: [0x1AD1C5]}, 0x0A), + SPCInstrumentChange(0x0E, 0x02, {0x00: [0x1AD24C]}, 0x16), + SPCInstrumentChange(0x0E, 0x02, {0x01: [0x1AD255], + 0x03: [0x1AD273]}, 0x18), + SPCInstrumentChange(0x0E, 0x02, {0x02: [0x1AD26B]}, 0x0A), + SPCInstrumentChange(0x0E, 0x03, {0x00: [0x1AD1E4], + 0x01: [0x1AD1ED], + 0x03: [0x1AD212], + 0x04: [0x1AD22F]}, 0x18), + SPCInstrumentChange(0x0E, 0x03, {0x02: [0x1AD20A]}, 0x12), + + SPCInstrumentChange(0x10, 0x00, {0x00: [0x1B816D], + 0x02: [0x1B81A0], + 0x03: [0x1B81C0], + 0x07: [0x1B827F]}, 0x0B), + SPCInstrumentChange(0x10, 0x00, {0x01: [0x1B818C]}, 0x11), + SPCInstrumentChange(0x10, 0x00, {0x04: [0x1B81E0], + 0x05: [0x1B8220, 0x1B8229]}, 0x02), + SPCInstrumentChange(0x10, 0x00, {0x05: [0x1B8224]}, 0x0C), + SPCInstrumentChange(0x10, 0x00, {0x06: [0x1B825F]}, 0x16), + SPCInstrumentChange(0x10, 0x01, {0x00: [0x1B811F]}, 0x0B), + SPCInstrumentChange(0x10, 0x01, {0x03: [0x1B813B], + 0x05: [0x1B814E]}, 0x0A), + SPCInstrumentChange(0x10, 0x02, {0x00: [0x1B829E], + 0x05: [0x1B8310]}, 0x0B), + SPCInstrumentChange(0x10, 0x02, {0x01: [0x1B82C0], + 0x03: [0x1B82E3], + 0x06: [0x1B8342]}, 0x0A), + SPCInstrumentChange(0x10, 0x02, {0x04: [0x1B8308], + 0x04: [0x1B83F2]}, 0x02), + SPCInstrumentChange(0x10, 0x03, {0x00: [0x1B8360], + 0x02: [0x1B83AD], + 0x05: [0x1B83FA]}, 0x0B), + SPCInstrumentChange(0x10, 0x03, {0x01: [0x1B8396], + 0x03: [0x1B83CC], + 0x06: [0x1B842E]}, 0x0A), + SPCInstrumentChange(0x10, 0x04, {0x00: [0x1B844D], + 0x02: [0x1B84A0]}, 0x0B), + SPCInstrumentChange(0x10, 0x04, {0x01: [0x1B847B], + 0x03: [0x1B84CD]}, 0x0A), + SPCInstrumentChange(0x10, 0x04, {0x04: [0x1B84ED], + 0x05: [0x1B84F5]}, 0x02), + SPCInstrumentChange(0x10, 0x05, {0x00: [0x1B8548], + 0x01: [0x1B8575], + 0x03: [0x1B85B6]}, 0x0A), + SPCInstrumentChange(0x10, 0x05, {0x02: [0x1B859B], + 0x07: [0x1B867F]}, 0x0B), + SPCInstrumentChange(0x10, 0x05, {0x04: [0x1B85D0], + 0x05: [0x1B862C]}, 0x02), + SPCInstrumentChange(0x10, 0x06, {0x02: [0x1B8726], + 0x06: [0x1B8745]}, 0x0A), + SPCInstrumentChange(0x10, 0x07, {0x00: [0x1B8768]}, 0x0B), + SPCInstrumentChange(0x10, 0x07, {0x00: [0x1B8B04], + 0x01: [0x1B8B10], + 0x02: [0x1B8775], + 0x06: [0x1B87AD]}, 0x0A), + SPCInstrumentChange(0x10, 0x07, {0x05: [0x1B8792]}, 0x16), + SPCInstrumentChange(0x10, 0x08, {0x02: [0x1B86B3]}, 0x0A), + SPCInstrumentChange(0x10, 0x09, {0x00: [0x1B8A63], + 0x05: [0x1B8879], + 0x06: [0x1B8AED]}, 0x0B), + SPCInstrumentChange(0x10, 0x09, {0x01: [0x1B8A87], + 0x02: [0x1B87E5]}, 0x0A), + SPCInstrumentChange(0x10, 0x0A, {0x00: [0x1B88C8], + 0x02: [0x1B8946], + 0x03: [0x1B88E5]}, 0x0B), + SPCInstrumentChange(0x10, 0x0A, {0x01: [0x1B892C], + 0x07: [0x1B8905]}, 0x0A), + SPCInstrumentChange(0x10, 0x0A, {0x04: [0x1B897D]}, 0x02), + + SPCInstrumentChange(0x11, 0x00, {0x00: [0x1B8C95], + 0x01: [0x1B8CA2], + 0x02: [0x1B8CB1], + 0x03: [0x1B8CC0], + 0x04: [0x1B8CCF]}, 0x0A, type=Me, ban=[0x0D]), + SPCInstrumentChange(0x11, 0x01, {0x05: [0x1B8CFF]}, 0x0A, ban=[0x04]), + SPCInstrumentChange(0x11, 0x02, {0x03: [0x1B8D6B]}, 0x09), + SPCInstrumentChange(0x11, 0x04, {0x04: [0x1B8E2B]}, 0x11), + SPCInstrumentChange(0x11, 0x05, {0x05: [0x1B90F6]}, 0x11), + + SPCInstrumentChange(0x12, 0x00, {0x00: [0x1B9275], + 0x01: [0x1B9282], + 0x05: [0x1B92DE]}, 0x0A), + SPCInstrumentChange(0x12, 0x00, {0x02: [0x1B9290], + 0x03: [0x1B92AB]}, 0x11), + SPCInstrumentChange(0x12, 0x00, {0x04: [0x1B92C6]}, 0x02), + SPCInstrumentChange(0x12, 0x00, {0x05: [0x1B92D3]}, 0x10, type=[0x08, 0x10, 0x17]), + SPCInstrumentChange(0x12, 0x01, {0x00: [0x1B917D], + 0x01: [0x1B918A], + 0x05: [0x1B91E6], + 0x06: [0x1B9229]}, 0x0A), + SPCInstrumentChange(0x12, 0x01, {0x02: [0x1B9198], + 0x03: [0x1B91B3]}, 0x11), + SPCInstrumentChange(0x12, 0x01, {0x04: [0x1B91CE]}, 0x02), + SPCInstrumentChange(0x12, 0x01, {0x05: [0x1B91DB], + 0x06: [0x1B921E]}, 0x10, type=[0x08, 0x10, 0x17]), + SPCInstrumentChange(0x12, 0x02, {0x00: [0x1B9313], + 0x01: [0x1B9320], + 0x05: [0x1B937C], + 0x06: [0x1B93BF]}, 0x0A), + SPCInstrumentChange(0x12, 0x02, {0x02: [0x1B932E], + 0x03: [0x1B9349]}, 0x11), + SPCInstrumentChange(0x12, 0x02, {0x04: [0x1B9364]}, 0x02), + SPCInstrumentChange(0x12, 0x02, {0x05: [0x1B9371], + 0x06: [0x1B93B4]}, 0x10, type=[0x08, 0x10, 0x17]), + + SPCInstrumentChange(0x13, 0x00, {0x00: [0x1B9458], + 0x02: [0x1B94DA], + 0x03: [0x1B953E], + 0x04: [0x1B95A2], + 0x05: [0x1B9606]}, 0x0B), + SPCInstrumentChange(0x13, 0x00, {0x01: [0x1B94A4]}, 0x11), + SPCInstrumentChange(0x13, 0x00, {0x06: [0x1B9650], + 0x07: [0x1B9696]}, 0x0F), + SPCInstrumentChange(0x13, 0x00, {0x06: [0x1B967B], + 0x07: [0x1B96C0]}, 0x02, ban=[0x0B]), + + SPCInstrumentChange(0x14, 0x00, {0x00: [0x1B9901, 0x1B97A8]}, 0x15), + SPCInstrumentChange(0x14, 0x01, {0x01: [0x1B97C4], + 0x02: [0x1B97DE], + 0x03: [0x1B97FD], + 0x04: [0x1B9813], + 0x05: [0x1B982A]}, 0x15), + + SPCInstrumentChange(0x15, 0x00, {0x00: [0x1B9A32], + 0x01: [0x1B9A50], + 0x02: [0x1B9A6D], + 0x03: [0x1B9A8A]}, 0x0B), + SPCInstrumentChange(0x15, 0x00, {0x04: [0x1B9AA0]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + SPCInstrumentChange(0x15, 0x01, {0x00: [0x1B9971]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + SPCInstrumentChange(0x15, 0x01, {0x01: [0x1B9984], + 0x02: [0x1B99AA], + 0x03: [0x1B99D7]}, 0x0B), + SPCInstrumentChange(0x15, 0x01, {0x04: [0x1B9A04]}, 0x14), + SPCInstrumentChange(0x15, 0x02, {0x00: [0x1B9B45]}, 0x02, type=Am|Be, ban=[0x01, 0x05, 0x10]), + SPCInstrumentChange(0x15, 0x02, {0x01: [0x1B9B58], + 0x02: [0x1B9B7E], + 0x03: [0x1B9BAB]}, 0x0B), + SPCInstrumentChange(0x15, 0x02, {0x04: [0x1B9BD8]}, 0x14), + + SPCInstrumentChange(0x16, 0x00, {0x00: [0x1B9CE6], + 0x01: [0x1B9CF1]}, 0x09), + SPCInstrumentChange(0x16, 0x01, {0x00: [0x1B9C82], + 0x01: [0x1B9C8B], + 0x02: [0x1B9C93], + 0x03: [0x1B9CAA]}, 0x09), + SPCInstrumentChange(0x16, 0x01, {0x04: [0x1B9CBA]}, 0x09), + SPCInstrumentChange(0x16, 0x02, {0x00: [0x1B9CFB], + 0x01: [0x1B9D44]}, 0x09), + SPCInstrumentChange(0x16, 0x03, {0x00: [0x1B9DBE], + 0x01: [0x1B9E47], + 0x05: [0x1B9EE4]}, 0x09), + + SPCInstrumentChange(0x17, 0x00, {0x00: [0x1BA287], + 0x03: [0x1BA26A]}, 0x0E, type=[0x0E, 0x0F, 0x18]), + SPCInstrumentChange(0x17, 0x01, {0x01: [0x1BA20F], + 0x03: [0x1BA24F]}, 0x0E, type=Rh, ban=[0x0B, 0x0D, 0x16]), + SPCInstrumentChange(0x17, 0x01, {0x02: [0x1BA231]}, 0x0F), + + SPCInstrumentChange(0x19, 0x00, {0x00: [0x1BA476], + 0x01: [0x1BA49C], + 0x02: [0x1BA4C1], + 0x03: [0x1BA4D5], + 0x04: [0x1BA4F4], + 0x05: [0x1BA513]}, 0x0A), + SPCInstrumentChange(0x19, 0x01, {0x00: [0x1BA357], + 0x01: [0x1BA379], + 0x03: [0x1BA38C], + 0x04: [0x1BA39D]}, 0x0A), + SPCInstrumentChange(0x19, 0x02, {0x02: [0x1BA3D3]}, 0x15), + SPCInstrumentChange(0x19, 0x03, {0x00: [0x1BA3F8]}, 0x16), + SPCInstrumentChange(0x19, 0x03, {0x02: [0x1BA40D]}, 0x0A), + + SPCInstrumentChange(0x1A, 0x00, {0x00: [0x1BA70E, 0x1BA71A], + 0x01: [0x1BA729], + 0x02: [0x1BA743], + 0x03: [0x1BA763], + 0x04: [0x1BA783], + 0x05: [0x1BA7A2], + 0x06: [0x1BA7B9], + 0x07: [0x1BA7D0]}, 0x0E, type=Rh|Am, ban=[0x01, 0x05, 0x06, 0x10]), + SPCInstrumentChange(0x1A, 0x01, {0x00: [0x1BA5C1], + 0x05: [0x1BA68F], + 0x06: [0x1BA6A6], + 0x07: [0x1BA6DD]}, 0x0A), + + SPCInstrumentChange(0x1B, 0x00, {0x00: [0x1BAA55], + 0x01: [0x1BAA66], + 0x02: [0x1BAA75], + 0x03: [0x1BAA86], + 0x04: [0x1BAA97]}, 0x0F), + SPCInstrumentChange(0x1B, 0x01, {0x00: [0x1BA956], + 0x01: [0x1BA96F], + 0x02: [0x1BA98A], + 0x03: [0x1BA9A5], + 0x04: [0x1BA9C0], + 0x05: [0x1BA9EB], + 0x06: [0x1BAA19]}, 0x0F), + + SPCInstrumentChange(0x1C, 0x00, {0x00: [0x1BACB6, 0x1BABAD]}, 0x09), + SPCInstrumentChange(0x1C, 0x01, {0x01: [0x1BABC7], + 0x02: [0x1BABE0], + 0x03: [0x1BABF4], + 0x04: [0x1BAC0B]}, 0x09), + SPCInstrumentChange(0x1C, 0x02, {0x00: [0x1BAC26], + 0x01: [0x1BAC44], + 0x02: [0x1BAC61]}, 0x09), + + SPCInstrumentChange(0x1D, 0x00, {0x00: [0x1BACEA], + 0x01: [0x1BAD06], + 0x02: [0x1BAD23], + 0x03: [0x1BAD40], + 0x04: [0x1BAD5D]}, 0x0B), + + SPCInstrumentChange(0x1E, 0x00, {0x00: [0x1BB14D], + 0x01: [0x1BB169], + 0x02: [0x1BB17E], + 0x03: [0x1BB193], + 0x04: [0x1BB1A8]}, 0x09), + SPCInstrumentChange(0x1E, 0x00, {0x05: [0x1BB1BD]}, 0x02), + + SPCInstrumentChange(0x1F, 0x00, {0x00: [0x1BAE86], + 0x03: [0x1BAEC6], + 0x04: [0x1BAEE4]}, 0x0B), + SPCInstrumentChange(0x1F, 0x00, {0x00: [0x1BAE9C]}, 0x18), + SPCInstrumentChange(0x1F, 0x00, {0x02: [0x1BAEBC]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), + SPCInstrumentChange(0x1F, 0x00, {0x06: [0x1BAF02]}, 0x02, ban=[0x10]), + SPCInstrumentChange(0x1F, 0x01, {0x03: [0x1BAE15], + 0x04: [0x1BAE32], + 0x05: [0x1BAE4F]}, 0x11), + SPCInstrumentChange(0x1F, 0x02, {0x01: [0x1BAF2C]}, 0x0B), + SPCInstrumentChange(0x1F, 0x02, {0x03: [0x1BAF53], + 0x04: [0x1BAF69], + 0x05: [0x1BAF7F]}, 0x11), + SPCInstrumentChange(0x1F, 0x04, {0x02: [0x1BAFAA]}, 0x13, type=Am|Be, ban=[0x01, 0x05, 0x0F]), + + SPCInstrumentChange(0x20, 0x00, {0x00: [0x1AD49A], + 0x01: [0x1AD4BA], + 0x02: [0x1AD4D3], + 0x03: [0x1AD4EE], + 0x04: [0x1AD507]}, 0x0A), + SPCInstrumentChange(0x20, 0x01, {0x05: [0x1AD475]}, 0x18), + + SPCInstrumentChange(0x21, 0x00, {0x00: [0x1AE8E3], + 0x01: [0x1AF18E], + 0x02: [0x1AF1A9], + 0x03: [0x1AF1B7], + 0x04: [0x1AF1D2], + 0x05: [0x1AF1E4], + 0x06: [0x1AF201], + 0x07: [0x1AF21C]}, 0x0F), + SPCInstrumentChange(0x21, 0x01, {0x00: [0x1AE518]}, 0x12), + SPCInstrumentChange(0x21, 0x01, {0x01: [0x1AE540], + 0x03: [0x1AE58D]}, 0x0B), + SPCInstrumentChange(0x21, 0x01, {0x02: [0x1AE564]}, 0x09), + SPCInstrumentChange(0x21, 0x01, {0x04: [0x1AE5B1], + 0x05: [0x1AE5D4]}, 0x0A), + SPCInstrumentChange(0x21, 0x01, {0x06: [0x1AE5EB, 0x1AE60F]}, 0x02, ban=[0x04]), + SPCInstrumentChange(0x21, 0x01, {0x06: [0x1AE5F9], + 0x07: [0x1AE61B]}, 0x0C), + SPCInstrumentChange(0x21, 0x02, {0x03: [0x1AE651], + 0x05: [0x1AE69A]}, 0x0A), + SPCInstrumentChange(0x21, 0x03, {0x00: [0x1AEC5E], + 0x01: [0x1AEC71], + 0x02: [0x1AEC90]}, 0x0A), + SPCInstrumentChange(0x21, 0x04, {0x01: [0x1AECB6]}, 0x0A), + SPCInstrumentChange(0x21, 0x04, {0x07: [0x1AF3A3]}, 0x0F), + SPCInstrumentChange(0x21, 0x05, {0x00: [0x1AE6D5], + 0x01: [0x1AE6FD], + 0x03: [0x1AE748], + 0x04: [0x1AE75E]}, 0x0A), + SPCInstrumentChange(0x21, 0x05, {0x02: [0x1AE72A]}, 0x09), + SPCInstrumentChange(0x21, 0x06, {0x00: [0x1AE774]}, 0x0A), + SPCInstrumentChange(0x21, 0x06, {0x02: [0x1AE7BE], + 0x04: [0x1AE825]}, 0x09), + SPCInstrumentChange(0x21, 0x07, {0x00: [0x1AED48], + 0x01: [0x1AED7B], + 0x03: [0x1AEDCD], + 0x04: [0x1AEDF4]}, 0x0A), + SPCInstrumentChange(0x21, 0x07, {0x02: [0x1AEDB1]}, 0x09), + SPCInstrumentChange(0x21, 0x08, {0x00: [0x1AE876], + 0x01: [0x1AE881]}, 0x0A), + SPCInstrumentChange(0x21, 0x0A, {0x02: [0x1AF17C], + 0x03: [0x1AE8BB]}, 0x09), + SPCInstrumentChange(0x21, 0x0B, {0x00: [0x1AEE0F]}, 0x11), + SPCInstrumentChange(0x21, 0x0B, {0x01: [0x1AEE22], + 0x03: [0x1AEE46], + 0x04: [0x1AEE74]}, 0x0A), + SPCInstrumentChange(0x21, 0x0C, {0x01: [0x1AEECD], + 0x03: [0x1AEEF9], + 0x04: [0x1AEF2B]}, 0x0A), + SPCInstrumentChange(0x21, 0x0D, {0x00: [0x1AE954], + 0x03: [0x1AE9D4], + 0x04: [0x1AEA03]}, 0x18), + SPCInstrumentChange(0x21, 0x0D, {0x00: [0x1AE971]}, 0x12), + SPCInstrumentChange(0x21, 0x0D, {0x01: [0x1AE983]}, 0x09), + SPCInstrumentChange(0x21, 0x0D, {0x01: [0x1AE9A6], + 0x03: [0x1AE9F3]}, 0x0B), + SPCInstrumentChange(0x21, 0x0D, {0x02: [0x1AE9B6]}, 0x0A), + SPCInstrumentChange(0x21, 0x0E, {0x00: [0x1AEA4F]}, 0x12), + SPCInstrumentChange(0x21, 0x0E, {0x01: [0x1AEA84], + 0x03: [0x1AEAE7], + 0x05: [0x1AEB94]}, 0x0B), + SPCInstrumentChange(0x21, 0x0E, {0x02: [0x1AEAAF]}, 0x09), + SPCInstrumentChange(0x21, 0x0E, {0x04: [0x1AEB69]}, 0x0A), + SPCInstrumentChange(0x21, 0x0E, {0x06: [0x1AEC03]}, 0x02), + SPCInstrumentChange(0x21, 0x0E, {0x07: [0x1AEC3C]}, 0x0C), + + SPCInstrumentChange(0x22, 0x00, {0x00: [0x1ADA2D], + 0x01: [0x1ADA41], + 0x02: [0x1ADA51], + 0x03: [0x1ADA6C], + 0x04: [0x1ADA83]}, 0x0A), + SPCInstrumentChange(0x22, 0x01, {0x05: [0x1ADAC9]}, 0x0A), + SPCInstrumentChange(0x22, 0x02, {0x07: [0x1ADB60]}, 0x0A), + SPCInstrumentChange(0x22, 0x03, {0x06: [0x1ADC10]}, 0x16), + SPCInstrumentChange(0x22, 0x05, {0x05: [0x1AD7CC]}, 0x09), + SPCInstrumentChange(0x22, 0x05, {0x06: [0x1AD803]}, 0x11), + SPCInstrumentChange(0x22, 0x05, {0x07: [0x1AD81B]}, 0x0A), + SPCInstrumentChange(0x22, 0x06, {0x02: [0x1AD8A6]}, 0x11), + SPCInstrumentChange(0x22, 0x06, {0x03: [0x1AD959]}, 0x13), + SPCInstrumentChange(0x22, 0x06, {0x06: [0x1AD9B7]}, 0x16), + SPCInstrumentChange(0x22, 0x07, {0x00: [0x1ADCCD]}, 0x0B), + SPCInstrumentChange(0x22, 0x07, {0x02: [0x1ADD73]}, 0x11), + SPCInstrumentChange(0x22, 0x07, {0x04: [0x1ADDE0], + 0x07: [0x1ADE9E]}, 0x0A), + SPCInstrumentChange(0x22, 0x07, {0x05: [0x1ADE14]}, 0x12), + SPCInstrumentChange(0x22, 0x07, {0x06: [0x1ADE62]}, 0x16), + SPCInstrumentChange(0x22, 0x08, {0x00: [0x1ADF02], + 0x04: [0x1ADFA8]}, 0x0B), + SPCInstrumentChange(0x22, 0x08, {0x01: [0x1ADF2A]}, 0x11), + SPCInstrumentChange(0x22, 0x08, {0x02: [0x1ADF83], + 0x05: [0x1ADFED]}, 0x09), + SPCInstrumentChange(0x22, 0x08, {0x06: [0x1AE02D], + 0x07: [0x1AE047]}, 0x0A) +] \ No newline at end of file diff --git a/source/classes/constants.py b/source/classes/constants.py index 45ec8987..36416fc5 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -57,6 +57,7 @@ SETTINGSTOPROCESS = { "item": { "hints": "hints", "pseudoboots": "pseudoboots", + 'collection_rate': 'collection_rate', "race": "race", "worldstate": "mode", @@ -139,8 +140,9 @@ SETTINGSTOPROCESS = { "uwpalettes": "uw_palettes", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", + "shuffle_songinstruments": "shuffle_songinstruments", 'msu_resume': 'msu_resume', - 'collection_rate': 'collection_rate', }, "generation": { "bps": "bps", @@ -148,7 +150,7 @@ SETTINGSTOPROCESS = { "createrom": "create_rom", "calcplaythrough": "calc_playthrough", "print_custom_yaml": "print_custom_yaml", - "saveonexit": "saveonexit" + "settingsonload": "settingsonload" } }, "startinventory": { diff --git a/source/dungeon/DungeonStitcher.py b/source/dungeon/DungeonStitcher.py index 504fc03b..84139157 100644 --- a/source/dungeon/DungeonStitcher.py +++ b/source/dungeon/DungeonStitcher.py @@ -72,11 +72,13 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] doors_to_connect, idx = {}, 0 all_regions = set() + bk_special = False for sector in builder.sectors: for door in sector.outstanding_doors: doors_to_connect[door.name] = door, idx idx += 1 all_regions.update(sector.regions) + bk_special |= check_for_special(sector.regions) finished = False # flag if standard and this is hyrule castle paths = determine_paths_for_dungeon(world, player, all_regions, name) @@ -95,9 +97,9 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon if hash_code not in hash_code_set: hash_code_set.add(hash_code) explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect, - world, player) + bk_special, world, player) if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions, - paths, entrance_regions, world, player): + paths, entrance_regions, bk_special, world, player): finished = True else: proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect, @@ -229,21 +231,24 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se return proposed_map, hash_code -def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player): +def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, bk_special, world, player): start = ExplorationState(dungeon=name) + bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special + start.big_key_special = bk_special original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map, - all_regions, valid_doors, world, player) + all_regions, valid_doors, bk_relevant, world, player) return original_state def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions, - paths, entrance_regions, world, player): + paths, entrance_regions, bk_special, world, player): all_visited = set() all_visited.update(exploration_state.visited_blue) all_visited.update(exploration_state.visited_orange) if len(all_regions.difference(all_visited)) > 0: return False - if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player): + if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, + bk_special, world, player): return False return True @@ -266,7 +271,7 @@ def check_for_special(regions): return False -def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player): +def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, bk_special, world, player): for path in paths: if type(path) is tuple: target = path[1] @@ -278,12 +283,13 @@ def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, propose else: target = path start_regions = entrance_regions - if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player): + if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, + bk_special, world, player): return False return True -def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player): +def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, bk_special, world, player): target_regions = set() if type(target) is not list: for region in all_regions: @@ -296,8 +302,10 @@ def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_re target_regions.add(region) start = ExplorationState(dungeon=name) + bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special + start.big_key_special = bk_special original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions, - valid_doors, world, player) + valid_doors, bk_relevant, world, player) for exp_door in original_state.unattached_doors: if not exp_door.door.blocked or exp_door.door.trapFlag != 0: @@ -531,7 +539,7 @@ class ExplorationState(object): self.crystal = exp_door.crystal return exp_door - def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False): + def visit_region(self, region, key_region=None, key_checks=False, bk_relevant=False): if region.type != RegionType.Dungeon: self.crystal = CrystalBarrier.Orange if self.crystal == CrystalBarrier.Either: @@ -552,8 +560,14 @@ class ExplorationState(object): self.ttl_locations += 1 if location not in self.found_locations: self.found_locations.append(location) - if not bk_flag: - self.bk_found.add(location) + if bk_relevant: + if self.big_key_special: + if special_big_key_found(self): + self.bk_found.add(location) + self.re_add_big_key_doors() + else: + self.bk_found.add(location) + self.re_add_big_key_doors() if location.name in dungeon_events and location.name not in self.events: if self.flooded_key_check(location): self.perform_event(location.name, key_region) @@ -574,6 +588,14 @@ class ExplorationState(object): return True return False + def re_add_big_key_doors(self): + self.big_key_opened = True + queue = collections.deque(self.big_doors) + while len(queue) > 0: + exp_door = queue.popleft() + self.avail_doors.append(exp_door) + self.big_doors.remove(exp_door) + def perform_event(self, location_name, key_region): self.events.add(location_name) queue = collections.deque(self.event_doors) @@ -640,7 +662,7 @@ class ExplorationState(object): self.append_door_to_list(door, self.avail_doors, flag) # same as above but traps are ignored, and flag is not used - def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player): + def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, bk_relevant, world, player): for door in get_doors(world, region, player): if door in proposed_map and door.name in valid_doors: self.visited_doors.add(door) @@ -654,14 +676,18 @@ class ExplorationState(object): other = self.find_door_in_list(door, self.unattached_doors) if self.crystal != other.crystal: other.crystal = CrystalBarrier.Either - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, - self.event_doors): + elif (door.req_event is not None and door.req_event not in self.events + and not self.in_door_list(door, self.event_doors)): self.append_door_to_list(door, self.event_doors) + elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player)) + and not self.big_key_opened): + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) # same as above but traps are checked for - def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player): + def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, bk_relevant, world, player): for door in get_doors(world, region, player): if door in proposed_map and door.name in valid_doors: self.visited_doors.add(door) @@ -675,9 +701,13 @@ class ExplorationState(object): other = self.find_door_in_list(door, self.unattached_doors) if self.crystal != other.crystal: other.crystal = CrystalBarrier.Either - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, - self.event_doors): + elif (door.req_event is not None and door.req_event not in self.events + and not self.in_door_list(door, self.event_doors)): self.append_door_to_list(door, self.event_doors) + elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player)) + and not self.big_key_opened): + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) @@ -863,16 +893,22 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg return local_state -def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player): +# bk_relevant means the big key doors need to be checks +def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, bk_relevant, + world, player): local_state = state.copy() for region in search_regions: - local_state.visit_region(region) + local_state.visit_region(region, bk_relevant=bk_relevant) if world.trap_door_mode[player] == 'vanilla': - local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, bk_relevant, world, player) else: - local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, bk_relevant, world, player) while len(local_state.avail_doors) > 0: explorable_door = local_state.next_avail_door() + if explorable_door.door.bigKey: + if bk_relevant and (not special_big_key_found(local_state) if local_state.big_key_special + else local_state.count_locations_exclude_specials(world, player) == 0): + continue if explorable_door.door in proposed_map: connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region else: @@ -880,11 +916,13 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi if connect_region is not None: if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player) and not local_state.visited(connect_region)): - local_state.visit_region(connect_region) + local_state.visit_region(connect_region, bk_relevant=bk_relevant) if world.trap_door_mode[player] == 'vanilla': - local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, + bk_relevant, world, player) else: - local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player) + local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, + bk_relevant, world, player) return local_state diff --git a/source/enemizer/enemy_deny.yaml b/source/enemizer/enemy_deny.yaml new file mode 100644 index 00000000..46842052 --- /dev/null +++ b/source/enemizer/enemy_deny.yaml @@ -0,0 +1,710 @@ +UwGeneralDeny: + - [ 0x0002, 0, [ "RollerVerticalDown", "Statue" ] ] #"Sewers - Rat Pots - Rat 1" + - [ 0x0002, 1, [ "RollerVerticalDown", "Statue" ] ] #"Sewers - Rat Pots - Rat 2" + - [ 0x0002, 2, [ "RollerVerticalUp", "Statue" ] ] #"Sewers - Rat Pots - Rat 3" + - [ 0x0002, 3, [ "RollerVerticalUp", "Statue" ] ] #"Sewers - Rat Pots - Rat 4" + - [ 0x0002, 4, [ "Statue" ] ] #"Sewers - Rat Pots - Rat 5" + - [ 0x0002, 15, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Sewers - Rat Pots - Rat 6" + - [ 0x0002, 16, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Sewers - Rat Pots - Rat 7" + - [ 0x0004, 1, ["Statue"]] + - [ 0x0004, 2, ["Statue"]] + - [ 0x0004, 3, ["Statue"]] + - [ 0x0004, 4, ["Statue"]] + - [ 0x0004, 15, ["Statue"]] + - [ 0x000a, 0, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Basement Ledge - Terrorpin 1" + - [ 0x000a, 1, [ "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Palace of Darkness - Basement Ledge - Terrorpin 2" + - [ 0x000b, 1, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Callback - Terrorpin 1" + - [ 0x000e, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Entrance - Freezor" + - [ 0x000e, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari Key - Top Bari" + - [ 0x000e, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari Key - Middle Bari" + - [ 0x0016, 0, [ "RollerVerticalDown", "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 1" + - [ 0x0016, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 2" + - [ 0x0016, 2, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Blue Bari" + - [ 0x0016, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Pool - Zol 3" + - [ 0x0017, 5, [ "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Bumper Room - Fire Bar (Clockwise)" + - [ 0x0019, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Dark Maze - Kodongo 1" + - [ 0x0019, 1, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 2" + - [ 0x0019, 2, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Palace of Darkness - Dark Maze - Kodongo 3" + - [ 0x0019, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft" ] ] #"Palace of Darkness - Dark Maze - Kodongo 4" + - [ 0x001a, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Compass Room - Mini Helmasaur 1" + - [ 0x001a, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Compass Room - Mini Helmasaur 2" + - [ 0x001b, 3, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 2 - Red Eyegore" + - [ 0x001b, 4, [ "RollerVerticalUp" ] ] #"Palace of Darkness - Mimics 2 - Green Eyegore L" + - [ 0x001e, 3, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 3" + - [ 0x001e, 4, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Red Bari 4" + - [ 0x001e, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 1" + - [ 0x001e, 6, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Blob Ambush - Zol 2" + - [ 0x001f, 0, [ "RollerHorizontalRight" ] ] #"Ice Palace - Big Key View - Pengator 1" + - [ 0x001f, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0021, 3, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 2" + - [ 0x0021, 4, [ "RollerVerticalDown", "RollerVerticalUp" ] ] #"Sewers - Dark U - Rat 3" + - [ 0x0024, 6, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0026, 1, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Statue" ] ] #"Swamp Palace - Big Spoon - Red Bari 1" + - [ 0x0026, 8, [ "AntiFairyCircle", "Bumper", "Statue" ] ] #"Swamp Palace - Big Spoon - Red Bari 3" + - [ 0x0026, 9, [ "RollerHorizontalRight", "Statue" ] ] #"Swamp Palace - Big Spoon - Kyameron" + - [ 0x0026, 10, [ "Statue" ] ] # multiple push statues in this room can cause issues + - [ 0x0026, 11, [ "Statue" ] ] # multiple push statues in this room can cause issues + - [ 0x0027, 0, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "FirebarCW" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 1" + - [ 0x0027, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 2" + - [ 0x0027, 2, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Mini Moldorm 3" + - [ 0x0027, 4, ["Bumper", "BigSpike", "AntiFairyCircle", "RollerVerticalDown", "RollerVerticalUp"]] + - [ 0x0027, 5, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Tower Of Hera - Petting Zoo - Kodongo 1" + - [ 0x0028, 4, [ "RollerVerticalUp" ] ] #"Swamp Palace - Entrance Ledge - Spike Trap" + - [ 0x002a, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] #"Palace of Darkness - Arena Main - Hardhat Beetle 1" + - [ 0x002a, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 2" + - [ 0x002a, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ]] + - [ 0x002a, 6, [ "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Arena Main - Hardhat Beetle 5" + - [ 0x002b, 5, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Fairies - Red Bari 2" + - [ 0x002e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 1" + - [ 0x002e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 2" + - [ 0x002e, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 3" + - [ 0x002e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 4" + - [ 0x002e, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 5" + - [ 0x002e, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "BigSpike", "FirebarCW", "FirebarCCW" ] ] #"Ice Palace - Penguin Chest - Pengator 6" + - [ 0x0034, 0, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Hover 1" + - [ 0x0034, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Hover 2" + - [ 0x0034, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Kyameron" + - [ 0x0034, 4, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - West Wing - Zol" + - [ 0x0035, 6, [ "RollerHorizontalRight" ] ] #"Swamp Palace - West Lever - Stalfos 2" + - [ 0x0035, 9, [ "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Swamp Palace - West Lever - Blue Bari" + - [ 0x0036, 7, [ "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Lobby - Hover 3" + - [ 0x0037, 7, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Water 1 - Blue Bari" + - [ 0x0038, 4, [ "RollerHorizontalRight" ] ] #"Swamp Palace - Long Hall - Kyameron 2" + - [ 0x0039, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Mini Helmasaur" + - [ 0x0039, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 1" + - [ 0x0039, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Skull Woods - Play Pen - Hardhat Beetle" + - [ 0x0039, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 2" + - [ 0x003b, 1, [ "Bumper" ]] + - [ 0x003c, 0, ["BigSpike"]] + - [ 0x003c, 1, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hookshot Cave - Blue Bari 1" + - [ 0x003c, 2, [ "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hookshot Cave - Blue Bari 2" + - [ 0x003d, 9, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Spark (Counterclockwise)" + - [ 0x003d, 10, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Spark (Clockwise) 1" + - [ 0x003d, 12, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Bunny Beam" + - [ 0x003d, 13, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Torches 2 - Antifairy" + - [ 0x003f, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "Statue"] ] #"Ice Palace - P Room - Stalfos Knight 1" + - [ 0x003f, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper", "Statue"] ] #"Ice Palace - P Room - Stalfos Knight 2" + - [ 0x003f, 4, [ "Wizzrobe", "Statue", "Bumper", "BigSpike", "AntiFairyCircle"]] # Wizzrobes can't spawn on pots + - [ 0x0040, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Bridge - Blue Guard 1 + - [ 0x0040, 1, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Bridge - Blue Guard 2 + - [ 0x0041, 0, [ "RollerHorizontalLeft" ] ] #"Sewers - Dark Cactus - Rat 1" + - [ 0x0041, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Sewers - Dark Cactus - Rat 2" + - [ 0x0041, 2, [ "RollerVerticalUp" ] ] #"Sewers - Dark Cactus - Rat 3" + - [ 0x0042, 0, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 1" + - [ 0x0042, 1, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 2" + - [ 0x0042, 2, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 3" + - [ 0x0042, 3, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 4" + - [ 0x0042, 4, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 5" + - [ 0x0042, 5, [ "SparkCW", "SparkCCW", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sewers - Dark Rope Corridor - Rope 6" + - [ 0x0044, 4, [ "RollerVerticalUp", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Zol" + - [ 0x0044, 6, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "BigSpike" ] ] #"Thieves' Town - Joke Room - Red Bari" + - [ 0x0044, 8, [ "Statue", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Joke Room - Blue Bari 4" + - [ 0x0045, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Basement Block Totems - Red Zazak" + - [ 0x0045, 4, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0045, 7, [ "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Cells - Blue Zazak 4" + - [ 0x0045, 8, [ "RollerHorizontalRight" ] ] #"Thieves' Town - Cells - Zol" + - [ 0x0046, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 1" + - [ 0x0046, 2, [ "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 2" + - [ 0x0046, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper", "Statue" ] ] #"Swamp Palace - Big O Top - Hover 3" + - [ 0x0049, 5, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 2" + - [ 0x0049, 7, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Bari Pits - Gibdo 4" + - [ 0x0049, 8, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Skull Woods - Bari Pits - Gibdo 5" + - [ 0x004b, 0, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Palace of Darkness - Mimics 1 - Red Eyegore" + - [ 0x004b, 1, [ "RollerHorizontalRight" ] ] #"Palace of Darkness - Warp Hint - Antifairy 1" + - [ 0x004b, 5, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 1" + - [ 0x004b, 6, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 2" + - [ 0x004b, 7, [ "AntiFairyCircle", "BigSpike" ] ] #"Palace of Darkness - Jelly Hall - Blue Bari 3" + - [ 0x004e, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 1" + - [ 0x004e, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 2" + - [ 0x004e, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Blob Alley - Zol 3" + - [ 0x0050, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Guard" + - [ 0x0050, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 1" + - [ 0x0050, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Hyrule Castle - North West Passage - Green Knife Guard 2" + - [ 0x0052, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Guard" + - [ 0x0052, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 1" + - [ 0x0052, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "Bumper" ] ] #"Hyrule Castle - North East Passage - Green Knife Guard 2" + - [ 0x0053, 1, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - Bridge - Beamos 1" + - [ 0x0053, 5, [ "RollerVerticalDown" ] ] #"Desert Palace - Popo Genocide - Popo TL" + - [ 0x0053, 7, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - Bridge - Popo 5" + - [ 0x0055, 1, [ "RollerVerticalUp", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Secret Passage Exit - Green Knife Guard 1" + - [ 0x0057, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0057, 1, ["Statue"]] # Statue switch issues + - [ 0x0057, 2, [ "RollerVerticalUp", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Spike Trap" + - [ 0x0057, 3, ["Statue"]] # Statue switch issues + - [ 0x0057, 4, ["Statue"]] # Statue switch issues + - [ 0x0057, 5, ["Statue"]] # Statue switch issues + - [ 0x0057, 7, [ "RollerVerticalUp", "RollerVerticalDown", "Statue" ] ] #"Skull Woods - Big Key Room - Gibdo 2" + - [ 0x0057, 8, ["Statue"]] # Statue switch issues + - [ 0x0057, 9, ["Statue"]] # Statue switch issues + - [ 0x0057, 10, ["Statue"]] # Statue switch issues + - [ 0x0057, 11, ["Statue"]] # Statue switch issues + - [ 0x0057, 12, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "BigSpike", "SpikeBlock", "Statue"]] #"Skull Woods - Big Key Room - Gibdo 6" + - [ 0x0057, 13, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Blue Bari 1" + - [ 0x0057, 14, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "Statue" ] ] #"Skull Woods - Big Key Room - Blue Bari 2" + - [ 0x0058, 0, ["Statue"]] + - [ 0x0058, 1, ["Statue"]] + - [ 0x0058, 2, ["Statue"]] + - [ 0x0058, 3, ["Statue"]] + - [ 0x0058, 4, ["Statue"]] + - [ 0x0058, 6, ["Statue"]] + - [ 0x0058, 7, [ "RollerHorizontalLeft", "Statue" ] ] #"Skull Woods - Lever Room - Hardhat Beetle 2" + - [ 0x0058, 8, ["Statue"]] + - [ 0x0059, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Mini Moldorm 1" + - [ 0x0059, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Mini Moldorm 2" + - [ 0x0059, 9, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Skull Woods - Bridge Room - Gibdo 1" + - [ 0x005e, 3, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Pit Trap - Big Spike Trap" + - [ 0x005e, 4, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ice Palace - Pit Trap - Fire Bar (Clockwise)" + - [ 0x005f, 0, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Ice Palace - Bari University - Blue Bari 1" + - [ 0x005f, 1, [ "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Ice Palace - Bari University - Blue Bari 2" + - [ 0x0060, 0, [ "RollerVerticalUp", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Hyrule Castle - West - Blue Guard" + - [ 0x0062, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Hyrule Castle - East - Blue Guard" + - [ 0x0064, 2, [ "Bumper" , "Beamos" ] ] #"Thieves' Town - Attic Hall Left - Keese 2" + - [ 0x0064, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x0064, 4, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Hall Left - Rat 1" + - [ 0x0065, 0, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 1" + - [ 0x0065, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Attic Window - Rat 2" + - [ 0x0065, 2, [ "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Attic Window - Rat 3" + - [ 0x0066, 0, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Swamp Palace - Waterfall Room - Hover 1" + - [ 0x0066, 2, [ "AntiFairyCircle", "Bumper"]] + - [ 0x0067, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Blue Bari 1" + - [ 0x0067, 2, ["Bumper"]] #"Skull Woods - Firebar Pits - Blue Bari 2" + - [ 0x0067, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 1" + - [ 0x0067, 4, [ "AntiFairyCircle", "Bumper" ]] + - [ 0x0067, 5, [ "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 3" + - [ 0x0067, 6, [ "RollerVerticalDown" ] ] #"Skull Woods - Firebar Pits - Hardhat Beetle 4" + - [ 0x0067, 7, [ "Beamos", "AntiFairyCircle", "Bumper", "BunnyBeam" ] ] #"Skull Woods - Firebar Pits - Fire Bar (Clockwise)" + - [ 0x006a, 0, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 1" + - [ 0x006a, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 2" + - [ 0x006a, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Palace of Darkness - Dark Alley - Antifairy 1" + - [ 0x006a, 4, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 3" + - [ 0x006a, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Palace of Darkness - Dark Alley - Terrorpin 4" + - [ 0x006b, 7, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Mimics 1 - Spike Trap 1" + - [ 0x0071, 0, [ "RollerHorizontalLeft" ] ] #"Hyrule Castle - Basement Trap - Green Guard" + - [ 0x0074, 0, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - North Hallway - Red Devalant 1" + - [ 0x0074, 1, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - North Hallway - Red Devalant 2" + - [ 0x0074, 4, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - North Hallway - Leever 1" + - [ 0x0074, 5, [ "AntiFairyCircle", "Bumper" ] ] #"Desert Palace - North Hallway - Leever 2" + - [ 0x0076, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Hover 1" + - [ 0x0076, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Kyameron" + - [ 0x0076, 3, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Hover 2" + - [ 0x0076, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Swamp Palace - Toilet Left - Zol" + - [ 0x0076, 6, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Swamp Palace - Toilet Left - Blue Bari" + - [ 0x007b, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - DMs Room - Blue Bari 1" + - [ 0x007b, 1, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - DMs Room - Blue Bari 2" + - [ 0x007b, 6, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Statue" + - [ 0x007b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - DMs Room - Hardhat Beetle" + - [ 0x007c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Counterclockwise)" + - [ 0x007c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Spike Trap" + - [ 0x007c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Fire Bar (Clockwise)" + - [ 0x007c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Randomizer Room - Hardhat Beetle" + - [ 0x007d, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 1" + - [ 0x007d, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 2" + - [ 0x007d, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 3" + - [ 0x007d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - The Zoo - Fire Snake 4" + - [ 0x007d, 4, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [ 0x007d, 7, ["RollerVerticalUp", "RollerHorizontalLeft", "StalfosKnight", "Geldman", "Blob", "Stal"]] #"Ganon's Tower - The Zoo - Mini Helmasaur" + - [ 0x007d, 8, ["RollerVerticalUp", "RollerHorizontalLeft", "RollerHorizontalRight", "StalfosKnight", "Geldman", "Blob", "Stal"]] #"Ganon's Tower - The Zoo - Red Bari" + - [ 0x007d, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] +# todo - consider adding firesnake to 0-3: has a hard time moving, could block hookshots for quite a while + - [ 0x007f, 0, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 1" + - [ 0x007f, 1, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper", "ArmosStatue" ] ] #"Ice Palace - Big Spikes - Red Bari 2" + - [ 0x007f, 2, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 3" + - [ 0x007f, 3, [ "Statue", "SparkCW", "SparkCCW", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Big Spikes - Red Bari 4" + - [ 0x007f, 4, [ "RollerVerticalDown" ] ] #"Ice Palace - Big Spikes - Big Spike Trap 1" + - [ 0x007f, 5, [ "RollerVerticalDown" ] ] #"Ice Palace - Big Spikes - Big Spike Trap 2" + - [ 0x0082, 0, [ "RollerVerticalDown" ] ] #"Hyrule Castle - Basement Playpit - Blue Guard 1" + - [ 0x0082, 2, [ "RollerVerticalUp" ] ] #"Hyrule Castle - Basement Playpit - Blue Guard 3" + - [ 0x0083, 0, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Desert Palace - Left Hallway - Blue Devalant 1" + - [ 0x0084, 0, [ "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Main Room - Left - Leever 1" + - [ 0x0084, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Main Room - Left - Leever 2" + - [ 0x0085, 2, [ "RollerHorizontalRight" ] ] #"Desert Palace - Compass Room - Popo TL" + - [ 0x0085, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Desert Palace - Right Hallway - Leever 2" + - [ 0x008b, 3, ["RollerHorizontalRight"]] + - [ 0x008b, 4, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper", "BigSpike"]] #"Ganon's Tower - Map Room - Spike Trap" + - [ 0x008b, 6, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Map Room - Fire Bar (Clockwise)" + - [ 0x008b, 7, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Map Room - Fire Bar (Counterclockwise)" + - [ 0x008c, 14, ["AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x008d, 1, [ "AntiFairyCircle", "Bumper" ] ] #"Ganon's Tower - Tile Room - Yomo Medusa T" + - [ 0x008d, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Tile Room - Spike Trap" + - [ 0x008d, 8, [ "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Tile Room - Stalfos" + - [ 0x008d, 9, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Tile Room - Fire Bar (Clockwise)" + - [ 0x008d, 10, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ganon's Tower - Tile Room - Blue Bari 1" + - [ 0x008d, 12, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Ganon's Tower - Tile Room - Blue Bari 2" + - [ 0x008e, 2, [ "Wizzrobe", "Statue"] ] # Wizzrobes can't spawn on pots + - [ 0x0092, 8, [ "RollerVerticalUp", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Misery Mire - Dark Weave - Spike Trap" + - [ 0x0092, 9, [ "RollerHorizontalRight" ] ] #"Misery Mire - Dark Weave - Antifairy 3" + - [ 0x0092, 10, [ "RollerHorizontalLeft" ] ] #"Misery Mire - Dark Weave - Stalfos" + - [ 0x0095, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 1" + - [ 0x0095, 1, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 2" + - [ 0x0095, 2, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 3" + - [ 0x0095, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock" ] ] #"Ganon's Tower - Conveyer Falling Bridge - Red Spear Guard 4" + - [ 0x0096, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Torches 1 - Fire Bar (Clockwise)" + - [ 0x0098, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 1" + - [ 0x0098, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 2" + - [ 0x0098, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 3" + - [ 0x0098, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 4" + - [ 0x0098, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Entrance - Zol 5" + - [ 0x009b, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 1" + - [ 0x009b, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 2" + - [ 0x009b, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] + - [ 0x009b, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 4" + - [ 0x009b, 8, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 5" + - [ 0x009b, 9, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 6" + - [ 0x009b, 10, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Spike Switch - Spike Trap 7" + - [ 0x009c, 1, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Mini Helmasaur" + - [ 0x009c, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 2" + - [ 0x009c, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 3" + - [ 0x009c, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 4" + - [ 0x009c, 5, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Invisible Floor Maze - Hardhat Beetle 5" + - [ 0x009d, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ganon's Tower - Compass Room - Gibdo 2" + - [ 0x009d, 6, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 1" + - [ 0x009d, 7, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Compass Room - Blue Bari 2" + - [ 0x009d, 8, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Ganon's Tower - Compass Room - Blue Bari 3" + - [ 0x009e, 0, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 1" + - [ 0x009e, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 2" + - [ 0x009e, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Stalfos Knight" + - [ 0x009e, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Fairy Drop - blue - Red Bari 3" + - [ 0x00a0, 1, [ "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Boss Antechamber - Antifairy" + - [ 0x00a1, 2, [ "Statue", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Fish Room - Spark (Clockwise) 2" + - [ 0x00a1, 7, [ "Wizzrobe", "Statue"] ] # Wizzrobes can't spawn on pots + - [ 0x00a5, 2, [ "BigSpike" ] ] #"GT Wizzrobes 1 - Wizzrobe 3" + - [ 0x00a5, 10, [ "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ganon's Tower - Laser Bridge - Red Spear Guard" + - [ 0x00a8, 1, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 2" + - [ 0x00a8, 3, [ "RollerVerticalDown", "RollerHorizontalLeft" ] ] #"Eastern Palace - West Wing - Top - Stalfos 4" + - [ 0x00a9, 1, [ "RollerHorizontalRight", "RollerHorizontalLeft" ] ] + - [ 0x00aa, 4, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - East Wing - Top - Stalfos 3" + - [ 0x00aa, 5, [ "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - East Wing - Top - Popo B 2" + - [ 0x00ab, 7, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Thieves' Town - Spike Dodge - Spike Trap 6" + - [ 0x00ae, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 1" + - [ 0x00ae, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Ice Palace - Ice T - Blue Bari 2" + - [ 0x00af, 0, [ "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Ice Clock - Fire Bar (Clockwise)" + - [ 0x00b1, 2, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 1" + - [ 0x00b1, 3, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Misery Mire - Hourglass - Spike Trap 2" + - [ 0x00b1, 4, ["Bumper", "BigSpike", "AntiFairyCircle" ]] + - [ 0x00b2, 1, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00b2, 3, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00b2, 6, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Misery Mire - Sluggula Cross - Sluggula TR" + - [ 0x00b2, 8, [ "RollerVerticalDown" ] ] #"Misery Mire - Popo Push - Medusa 1" + - [ 0x00b2, 9, [ "RollerVerticalUp" ] ] #"Misery Mire - Sluggula Cross - Sluggula BL" + - [ 0x00b3, 0, [ "RollerVerticalUp", "RollerHorizontalRight", "BigSpike", "SpikeBlock" ] ] #"Misery Mire - Spike Room - Stalfos 1" + - [ 0x00b3, 2, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "Bumper" ] ] #"Misery Mire - Spike Room - Beamos" + - [ 0x00b3, 3, [ "AntiFairyCircle", "Bumper" ] ] #"Misery Mire - Spike Room - Yomo Medusa" + - [ 0x00b6, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Tile Room - Zol 1" + - [ 0x00b6, 8, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Tile Room - Zol 2" + - [ 0x00ba, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Antifairy 1" + - [ 0x00ba, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Antifairy 2" + - [ 0x00ba, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Popo B 1" + - [ 0x00ba, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Dark Stalfos - Popo B 2" + - [ 0x00bb, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Gibo 1" + - [ 0x00bb, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Antifairy 1" + - [ 0x00bb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Gibo 3" + - [ 0x00bb, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Fire Snake" + - [ 0x00bb, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Spikeveyer - Gibo 4" + - [ 0x00bb, 8, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Gibo 5" + - [ 0x00bb, 9, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Thieves' Town - Spikeveyer - Antifairy 2" + - [ 0x00bc, 6, [ "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Toilet - Stalfos 2" + - [ 0x00bc, 7, [ "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Toilet - Stalfos 3" + - [ 0x00bc, 8, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Thieves' Town - Toilet - Stalfos 4" + - [ 0x00bf, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on collision + - [ 0x00c1, 3, [ "RollerVerticalUp", "RollerHorizontalLeft" ] ] #"Misery Mire - 4 Rails - Stalfos 1" + - [ 0x00c2, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Misery Mire - Main Lobby - blue - Fire Snake 1" + - [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur" + - [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4" + - [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1" + - [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2" + - [ 0x00cb, 9, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] + - [ 0x00cb, 10, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] + - [ 0x00cb, 11, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00cc, 8, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) + - [ 0x00cc, 12, [ "RollerVerticalUp", "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #Prevents Pot access (Beamos okay?) + - [ 0x00ce, 0, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCCW", "Bumper" ] ] #"Ice Palace - Over Boss - top - Red Bari 1" + - [ 0x00ce, 1, [ "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCW", "Bumper" ] ] #"Ice Palace - Over Boss - top - Red Bari 2" + - [ 0x00ce, 3, [ "SparkCW", "SparkCCW", "RollerVerticalDown", "RollerVerticalUp", "RollerHorizontalRight", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "Antifairy", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Ice Palace - Over Boss - top - Statue" + - [ 0x00ce, 4, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 5, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 6, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00ce, 7, [ "RollerVerticalDown", "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Beamos", "Bumper", "FirebarCW", "FirebarCCW"]] + - [ 0x00d0, 0, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 1, [ "AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x00d0, 4, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 5, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 6, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 7, [ "Statue", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper"]] + - [ 0x00d0, 9, [ "AntiFairyCircle", "BigSpike", "Bumper"]] + - [ 0x00d0, 6, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] # Agahnims Tower - Dark Maze - Blue Guard 2 + - [ 0x00d2, 8, [ "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Misery Mire - Mire 2 - Popo BL" + - [ 0x00d5, 4, [ "Statue", "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "FirebarCW", "FirebarCCW", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Eye Bridge - Hardhat Beetle" + - [ 0x00d8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Red Eyegore L" + - [ 0x00d8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Red Eyegore R" + - [ 0x00d8, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B TL" + - [ 0x00d8, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B TR" + - [ 0x00d8, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B LT" + - [ 0x00d8, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo B RT" + - [ 0x00d8, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo LB" + - [ 0x00d8, 7, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Eastern Palace - Kill Room 2 - Popo RB" + - [ 0x00d8, 8, [ "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Eastern Palace - Kill Room 1 - Red Eyegore" + - [ 0x00d9, 1, [ "RollerHorizontalRight" ] ] #"Eastern Palace - Dodgeball - Green Eyegore 1" + - [ 0x00db, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots + - [ 0x00dc, 2, [ "AntiFairyCircle", "BigSpike", "Bumper" ] ] + - [ 0x00dc, 9, [ "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Thieves' Town - Grand Room SE - Fire Snake 2" + - [ 0x00df, 0, [ "RollerVerticalDown", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Paradox Cave - Top - Mini Moldorm 1" + - [ 0x00df, 1, [ "RollerVerticalDown", "RollerHorizontalRight", "AntiFairyCircle" ] ] #"Paradox Cave - Top - Mini Moldorm 2" + - [ 0x00e4, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 1" + - [ 0x00e4, 1, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 2" + - [ 0x00e4, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 3" + - [ 0x00e5, 4, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 5" + - [ 0x00e5, 5, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 6" + - [ 0x00e7, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 1" + - [ 0x00e7, 1, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 2" + - [ 0x00e7, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 3" + - [ 0x00e7, 3, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 4" + - [ 0x00e7, 4, [ "RollerVerticalDown", "Beamos", "AntiFairyCircle", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 5" + - [ 0x00e7, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 6" + - [ 0x00e7, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 7" + - [ 0x00e8, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "AntiFairyCircle", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 1" + - [ 0x00e8, 1, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Super Bunny Exit - Hardhat Beetle 2" + - [ 0x00ee, 0, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 1" + - [ 0x00ee, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 2" + - [ 0x00ee, 2, [ "RollerHorizontalLeft", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Mini Moldorm 3" + - [ 0x00ee, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Blue Bari 1" + - [ 0x00ee, 4, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Sprial Cave Top - Blue Bari 2" + - [ 0x00ef, 1, [ "RollerVerticalUp", "RollerVerticalDown" ] ] #"Paradox Cave - Middle - Mini Moldorm 2" + - [ 0x00f1, 0, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 1" + - [ 0x00f1, 1, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 2" + - [ 0x00f1, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 3" + - [ 0x00f1, 3, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 4" + - [ 0x00f1, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 5" + - [ 0x00f1, 5, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight" ] ] #"Old Man Maze - Keese 6" + - [ 0x00fd, 0, [ "Bumper" ] ] + - [ 0x0107, 1, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] + - [ 0x0107, 2, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] + - [0x010b, 6, ["RollerHorizontalRight"]] + - [0x010c, 6, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] + - [0x010c, 7, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] +OwGeneralDeny: + - [0x03, 2, ["Gibo"]] # OldMan eating Gibo + - [0x03, 4, ["Gibo"]] # OldMan eating Gibo + - [0x03, 5, ["Gibo"]] # OldMan eating Gibo + - [0x03, 6, ["Gibo"]] # OldMan eating Gibo + - [0x03, 8, ["Gibo"]] # OldMan eating Gibo + - [0x03, 9, ["Gibo"]] # OldMan eating Gibo + - [0x03, 10, ["Gibo"]] # OldMan eating Gibo + - [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal + - [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here + - [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]] + - [0x5e, 0, ["Gibo"]] # kiki eating Gibo + - [0x5e, 1, ["Gibo"]] # kiki eating Gibo + - [0x5e, 2, ["Gibo"]] # kiki eating Gibo + - [0x5e, 3, ["Gibo"]] # kiki eating Gibo + - [0x5e, 4, ["RollerVerticalUp", "Gibo"]] # forbid that one roller for kiki pod, and the kiki eating Gibo + - [0x5e, 5, ["Gibo"]] # kiki eating Gibo + - [0x5e, 6, ["Gibo"]] # kiki eating Gibo + - [0x5e, 7, ["Gibo"]] # kiki eating Gibo + - [0x5e, 8, ["Gibo"]] # kiki eating Gibo + - [0x5e, 9, ["Gibo"]] # kiki eating Gibo + - [0x5e, 10, ["Gibo"]] # kiki eating Gibo + - [0x5e, 11, ["Gibo"]] # kiki eating Gibo + - [0x5e, 12, ["Gibo"]] # kiki eating Gibo + - [0x5e, 13, ["Gibo"]] # kiki eating Gibo + - [0x5e, 14, ["Gibo"]] # kiki eating Gibo + - [0x5e, 15, ["Gibo"]] # kiki eating Gibo + - [0x5e, 16, ["Gibo"]] # kiki eating Gibo + - [0x5e, 17, ["Gibo"]] # kiki eating Gibo + - [0x5e, 18, ["Gibo"]] # kiki eating Gibo + - [0x5e, 19, ["Gibo"]] # kiki eating Gibo + - [0x5e, 20, ["Gibo"]] # kiki eating Gibo + - [0x77, 1, ["Bumper"]] # soft-lock potential near ladder +UwEnemyDrop: + - [0x0085, 9, ["Babasu"]] # ran off the edge and didn't return + - [0x00cb, 3, ["Zoro"]] # layer issues + - [0x00cb, 5, ["Zoro"]] # layer issues + - [0x00cb, 9, ["Zoro"]] # layer issues + - [0x00cb, 10, ["Zoro"]] # layer issues + - [0x00cc, 5, ["Babasu"]] # little hard to see and kill appropriately +# the following are behind rails or otherwise unactivate-able + - [0x0077, 4, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] # can't activate here + - [0x0077, 5, ["StalfosKnight", "Geldman", "Blob", "Stal", "Wizzrobe"]] + - [0x008D, 10, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [0x008D, 12, ["StalfosKnight", "Geldman", "Blob", "Stal"]] + - [0x00b0, 7, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay + - [0x00b0, 8, ["StalfosKnight", "Blob", "Stal", "Wizzrobe"]] # blocked, but Geldmen are probably okay +# the following are not allowed at certain pits (or on conveyors near pits) +# because they despawned or clipped away or immediately fell, etc + - [0x003d, 9, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x003d, 10, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0044, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 4, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0044, 8, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0049, 10, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007b, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007b, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x007f, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x007f, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x0095, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x0095, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic"]] + - [0x00b5, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00b5, 1, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00b5, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 2, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 3, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 4, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 5, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Bumper", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00c6, 6, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + - [0x00e6, 0, ["HardhatBeetle", "Wizzrobe", "MiniHelmasaur", "BlueGuard", "GreenGuard", "RedSpearGuard", + "BluesainBolt", "UsainBolt", "BlueArcher", "GreenBushGuard", "RedJavelinGuard", "RedBushGuard", + "BombGuard", "GreenKnifeGuard", "Stal", "GreenMimic", "RedMimic", "StalfosKnight", "Geldman", "Blob"]] + # wizzrobe despawn issues - on pots/blocks - too close to some object + - [0x0013, 3, ["Wizzrobe"]] + - [0x0016, 0, ["Wizzrobe"]] + - [0x0016, 1, ["Wizzrobe"]] + - [0x0016, 2, ["Wizzrobe"]] + - [0x0016, 3, ["Wizzrobe"]] + - [0x0017, 5, ["Wizzrobe", "Stal"]] + - [0x0019, 2, ["Wizzrobe"]] + - [0x0019, 3, ["Wizzrobe"]] + - [0x001e, 1, ["Wizzrobe"]] + - [0x001e, 2, ["Wizzrobe"]] + - [0x0027, 5, ["Wizzrobe"]] + - [0x0027, 6, ["Wizzrobe"]] + - [0x002a, 3, ["Wizzrobe"]] + - [0x002a, 7, ["Wizzrobe"]] + - [0x002e, 4, ["Wizzrobe"]] + - [0x0035, 5, ["Wizzrobe"]] + - [0x0036, 8, ["Wizzrobe"]] + - [0x003b, 0, ["Wizzrobe"]] + - [0x003b, 2, ["Wizzrobe"]] + - [0x003b, 4, ["Wizzrobe"]] + - [0x003b, 6, ["Wizzrobe"]] + - [0x003c, 1, ["Wizzrobe"]] + - [0x003d, 11, ["Wizzrobe"]] + - [0x003d, 12, ["Wizzrobe"]] + - [0x003d, 13, ["Wizzrobe"]] + - [0x004b, 2, ["Wizzrobe"]] + - [0x004b, 6, ["Wizzrobe"]] + - [0x004b, 7, ["Wizzrobe"]] + - [0x004e, 3, ["Wizzrobe", "Stal"]] + - [0x0054, 3, ["Wizzrobe", "Stal"]] + - [0x0055, 2, ["Wizzrobe"]] # slightly on wall + - [0x005e, 4, ["Wizzrobe", "Stal"]] + - [0x0065, 3, ["Wizzrobe"]] + - [0x0067, 5, ["Wizzrobe"]] + - [0x0067, 6, ["Wizzrobe"]] + - [0x0067, 7, ["Wizzrobe", "Stal"]] + - [0x0067, 8, ["Wizzrobe", "Stal"]] + - [0x0074, 5, ["Wizzrobe"]] + - [0x007c, 1, ["Wizzrobe", "Stal"]] + - [0x007c, 3, ["Wizzrobe", "Stal"]] + - [0x007e, 1, ["Wizzrobe", "Stal"]] + - [0x007e, 6, ["Wizzrobe", "Stal"]] + - [0x0083, 9, ["Wizzrobe"]] + - [0x008b, 6, ["Wizzrobe", "Stal"]] + - [0x008b, 7, ["Wizzrobe", "Stal"]] + - [0x008d, 9, ["Wizzrobe", "Stal"]] + - [0x0096, 0, ["Wizzrobe", "Stal"]] + - [0x009b, 11, ["Wizzrobe"]] + - [0x009f, 5, ["Wizzrobe", "Stal"]] + - [0x00a1, 1, ["Wizzrobe"]] + - [0x00aa, 5, ["Wizzrobe"]] + - [0x00af, 0, ["Wizzrobe", "Stal"]] + - [0x00b0, 1, ["Wizzrobe"]] + - [0x00b0, 2, ["Wizzrobe"]] + - [0x00b2, 4, ["Wizzrobe"]] + - [0x00b8, 2, ["Wizzrobe"]] + - [0x00bf, 1, ["Wizzrobe"]] + - [0x00c1, 3, ["Wizzrobe", "Stal"]] + - [0x00c2, 0, ["Wizzrobe"]] + - [0x00c2, 3, ["Wizzrobe"]] + - [0x00c2, 7, ["Wizzrobe"]] + - [0x00ce, 4, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 5, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 6, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00ce, 7, ["Wizzrobe", "Leever", "BlueGuard", "GreenGuard", "RedSpearGuard", "BluesainBolt", "UsainBolt", + "BlueArcher", "RedJavelinGuard", "GreenKnifeGuard", "GreenMimic", "RedMimic"]] + - [0x00d0, 0, ["Wizzrobe"]] + - [0x00d0, 2, ["Wizzrobe"]] + - [0x00d0, 4, ["Wizzrobe"]] + - [0x00d0, 5, ["Wizzrobe"]] + - [0x00d0, 9, ["Wizzrobe"]] + - [0x00d5, 4, ["Wizzrobe"]] + - [0x00df, 1, ["Wizzrobe"]] # slightly on wall + - [0x00e7, 4, ["Wizzrobe"]] # slightly on wall + - [0x00fd, 1, ["Wizzrobe"]] # slightly on rock + - [0x010c, 4, ["Wizzrobe"]] + - [0x010c, 5, ["Wizzrobe"]] + # other mimic cave spots are in the rail section + + # enemies that have problems with conveyors + - [0x003b, 0, ["GreenMimic", "RedMimic"]] + - [0x003b, 1, ["GreenMimic", "RedMimic"]] + - [0x003b, 2, ["GreenMimic", "RedMimic"]] + - [0x003b, 3, ["GreenMimic", "RedMimic"]] + - [0x003b, 4, ["GreenMimic", "RedMimic"]] + - [0x003b, 5, ["GreenMimic", "RedMimic"]] + - [0x003b, 6, ["GreenMimic", "RedMimic"]] + - [0x003d, 6, ["GreenMimic", "RedMimic"]] + - [0x003d, 7, ["GreenMimic", "RedMimic"]] + - [0x003e, 8, ["GreenMimic", "RedMimic"]] + - [0x003e, 9, ["GreenMimic", "RedMimic"]] + - [0x003e, 10, ["GreenMimic", "RedMimic"]] + - [0x003e, 11, ["GreenMimic", "RedMimic"]] + - [0x0044, 0, ["GreenMimic", "RedMimic"]] + - [0x0044, 1, ["GreenMimic", "RedMimic"]] + - [0x0044, 2, ["GreenMimic", "RedMimic"]] + - [0x0044, 3, ["GreenMimic", "RedMimic"]] + - [0x0044, 5, ["GreenMimic", "RedMimic"]] + - [0x004c, 0, ["GreenMimic", "RedMimic"]] + - [0x004c, 1, ["GreenMimic", "RedMimic"]] + - [0x004c, 2, ["GreenMimic", "RedMimic"]] + - [0x004c, 3, ["GreenMimic", "RedMimic"]] + - [0x004c, 4, ["GreenMimic", "RedMimic"]] + - [0x004c, 5, ["GreenMimic", "RedMimic"]] + - [0x004c, 6, ["GreenMimic", "RedMimic"]] + - [0x004c, 7, ["GreenMimic", "RedMimic"]] + - [0x005d, 3, ["GreenMimic", "RedMimic"]] + - [0x005d, 4, ["GreenMimic", "RedMimic"]] + - [0x005d, 5, ["GreenMimic", "RedMimic"]] + - [0x005d, 6, ["GreenMimic", "RedMimic"]] + - [0x005d, 8, ["GreenMimic", "RedMimic"]] + - [0x005d, 9, ["GreenMimic", "RedMimic"]] + - [0x005d, 10, ["GreenMimic", "RedMimic"]] + - [0x005d, 11, ["GreenMimic", "RedMimic"]] + - [0x005d, 12, ["GreenMimic", "RedMimic"]] +# - [0x006d, ?, ["GreenMimic", "RedMimic"]] # conveyor doesn't hit edge +# - [0x008b, ?, ["GreenMimic", "RedMimic"]] # conveyor doesn't hit edge + - [0x0092, 2, ["GreenMimic", "RedMimic"]] + - [0x00a5, 0, ["GreenMimic", "RedMimic"]] + - [0x00a5, 1, ["GreenMimic", "RedMimic"]] + - [0x00a5, 4, ["GreenMimic", "RedMimic"]] + - [0x00a5, 5, ["GreenMimic", "RedMimic"]] + - [0x00a5, 6, ["GreenMimic", "RedMimic"]] + - [0x00bb, 1, ["GreenMimic", "RedMimic"]] + - [0x00bb, 4, ["GreenMimic", "RedMimic"]] + - [0x00bb, 5, ["GreenMimic", "RedMimic"]] + - [0x00bb, 6, ["GreenMimic", "RedMimic"]] + - [0x00bb, 7, ["GreenMimic", "RedMimic"]] + - [0x00bb, 8, ["GreenMimic", "RedMimic"]] + - [0x00bb, 9, ["GreenMimic", "RedMimic"]] + - [0x00bb, 10, ["GreenMimic", "RedMimic"]] + - [0x00bc, 0, ["GreenMimic", "RedMimic"]] + - [0x00bc, 1, ["GreenMimic", "RedMimic"]] + - [0x00bc, 2, ["GreenMimic", "RedMimic"]] + - [0x00bc, 3, ["GreenMimic", "RedMimic"]] + - [0x00bc, 4, ["GreenMimic", "RedMimic"]] + - [0x00bc, 5, ["GreenMimic", "RedMimic"]] + - [0x00c1, 5, ["GreenMimic", "RedMimic"]] + - [0x00c1, 8, ["GreenMimic", "RedMimic"]] + - [0x00c1, 9, ["GreenMimic", "RedMimic"]] + - [0x00c1, 10, ["GreenMimic", "RedMimic"]] + - [0x00c1, 11, ["GreenMimic", "RedMimic"]] + - [0x00d1, 5, ["GreenMimic", "RedMimic"]] + - [0x00d1, 6, ["GreenMimic", "RedMimic"]] + + # the following are all slightly in the wall on spawn - not too applicable right now, these don't drop anyway + - [0x0064, 0, ["Leever"]] + - [0x00e5, 3, ["Wizzrobe"]] + - [0x00e5, 4, ["Leever", "Wizzrobe"]] + - [0x00e5, 5, ["Leever", "Wizzrobe"]] + # the pit one in 0xe6 room is in the pit section + - [0x00e6, 1, ["Leever", "Wizzrobe"]] + - [0x00e6, 2, ["Leever", "Wizzrobe"]] + - [0x00e6, 3, ["Leever", "Wizzrobe"]] + - [0x00e6, 4, ["Leever", "Wizzrobe"]] + - [0x00e7, 0, ["Wizzrobe"]] + - [0x00e7, 1, ["Wizzrobe"]] + - [0x00e7, 2, ["Wizzrobe"]] + - [0x00e7, 3, ["Leever"]] + - [0x00e7, 4, ["Leever", "Wizzrobe"]] + - [0x00e7, 5, ["Leever", "Wizzrobe"]] + - [0x00e7, 6, ["Wizzrobe"]] + - [0x00f0, 2, ["Leever"]] # clipped away + - [0x00f0, 3, ["Leever"]] + - [0x00f0, 4, ["Leever"]] + - [0x00f0, 5, ["Leever"]] + - [0x00f0, 6, ["Leever"]] + - [0x00f0, 8, ["Leever"]] + - [0x00f1, 0, ["Leever", "Wizzrobe"]] + - [0x00f1, 1, ["Leever", "Wizzrobe"]] + - [0x00f1, 2, ["Wizzrobe"]] + - [0x00f1, 3, ["Wizzrobe"]] + - [0x00f1, 4, ["Wizzrobe"]] + - [0x00f1, 5, ["Wizzrobe"]] + - [0x00f1, 6, ["Leever", "Wizzrobe"]] + - [0x00f1, 7, ["Leever", "Wizzrobe"]] + - [0x00f1, 8, ["Leever", "Wizzrobe"]] + - [0x00f1, 9, ["Leever", "Wizzrobe"]] + diff --git a/source/gui/adjust/overview.py b/source/gui/adjust/overview.py index f7c6faf2..a26bd750 100644 --- a/source/gui/adjust/overview.py +++ b/source/gui/adjust/overview.py @@ -107,6 +107,8 @@ def adjust_page(top, parent, settings): "reduce_flashing": "reduce_flashing", 'msu_resume': 'msu_resume', "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", + "shuffle_songinstruments": "shuffle_songinstruments", } guiargs = Namespace() for option in options: @@ -158,6 +160,8 @@ def adjust_page(top, parent, settings): "nobgm": "disablemusic", "reduce_flashing": "reduce_flashing", "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", + "shuffle_songinstruments": "shuffle_songinstruments", "msu_resume": "msu_resume" } guiargs = Namespace() diff --git a/source/gui/bottom.py b/source/gui/bottom.py index beb77a1f..d50010f2 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -6,7 +6,7 @@ import random import re from CLI import parse_cli from Fill import FillError -from Main import main, EnemizerError +from Main import main, export_yaml, EnemizerError from Utils import local_path, output_path, open_file, update_deprecated_args import source.classes.constants as CONST from source.gui.randomize.multiworld import multiworld_page @@ -68,6 +68,15 @@ def bottom_frame(self, parent, args=None): self.widgets[key].pack(side=LEFT) def generateRom(): + guiargs = create_guiargs(parent) + argsDump = vars(guiargs) + from Gui import save_settings + if parent.randomSprite.get(): + argsDump['sprite'] = 'random' + elif argsDump['sprite']: + argsDump['sprite'] = argsDump['sprite'].name + save_settings(parent, argsDump, "last.json") + guiargs = create_guiargs(parent) # get default values for missing parameters for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): @@ -141,9 +150,45 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Generate Patched Rom', command=generateRom) + self.widgets[widget].pieces["button"] = Button(self, command=generateRom) # button: pack self.widgets[widget].pieces["button"].pack(side=LEFT) + self.widgets[widget].pieces["button"].configure(bg="#CCCCCC") + + def exportYaml(): + guiargs = create_guiargs(parent) + # get default values for missing parameters + for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items(): + if k not in vars(guiargs): + setattr(guiargs, k, v) + elif type(v) is dict: # use same settings for every player + setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) + + filename = None + try: + from tkinter import filedialog + filename = filedialog.asksaveasfilename(initialdir=guiargs.outputpath, title="Save file", filetypes=(("Yaml Files", (".yaml", ".yml")), ("All Files", "*"))) + if filename is not None and filename != '': + guiargs.outputpath = parent.settings["outputpath"] = os.path.dirname(filename) + guiargs.outputname = os.path.splitext(os.path.basename(filename))[0] + export_yaml(args=guiargs, fish=parent.fish) + except (FillError, EnemizerError, Exception, RuntimeError) as e: + logging.exception(e) + messagebox.showerror(title="Error while exporting yaml", message=str(e)) + else: + if filename is not None and filename != '': + successMsg = "File Exported" + # FIXME: English + messagebox.showinfo(title="Success", message=successMsg) + + ## Export Yaml Button + widget = "exportyaml" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self, command=exportYaml) + self.widgets[widget].pieces["button"].pack(side=LEFT) def open_output(): if output_path.cached_path is None: @@ -154,13 +199,15 @@ def bottom_frame(self, parent, args=None): open_file(output_path('.')) - ## Output Button - # widget ID - widget = "outputdir" + def select_output(): + from tkinter import filedialog + folder_selected = filedialog.askdirectory() + if folder_selected is not None and folder_selected != '': + args.outputpath = parent.settings["outputpath"] = folder_selected - # Empty object + ## Output Button + widget = "outputdir" self.widgets[widget] = Empty() - # pieces self.widgets[widget].pieces = {} # storagevar @@ -168,8 +215,16 @@ def bottom_frame(self, parent, args=None): # button self.widgets[widget].type = "button" - self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=open_output) - # button: pack + self.widgets[widget].pieces["button"] = Button(self, command=select_output) + self.widgets[widget].pieces["button"].pack(side=RIGHT) + + ## Save Settings Button + widget = "savesettings" + self.widgets[widget] = Empty() + self.widgets[widget].pieces = {} + # button + self.widgets[widget].type = "button" + self.widgets[widget].pieces["button"] = Button(self) self.widgets[widget].pieces["button"].pack(side=RIGHT) ## Documentation Button @@ -253,7 +308,11 @@ def create_guiargs(parent): "heartbeep": "heartbeep", "menuspeed": "fastmenu", "owpalettes": "ow_palettes", - "uwpalettes": "uw_palettes" + "uwpalettes": "uw_palettes", + "reduce_flashing": "reduce_flashing", + "shuffle_sfx": "shuffle_sfx", + "shuffle_sfxinstruments": "shuffle_sfxinstruments", + "shuffle_songinstruments": "shuffle_songinstruments" } for adjustarg in adjustargs: internal = adjustargs[adjustarg] diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index aec14a72..a1f07d7d 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -158,6 +158,22 @@ def loadcliargs(gui, args, settings=None): label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Export Yaml button + mainpage = "bottom" + subpage = "content" + widget = "exportyaml" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + + # Set Save Settings button + mainpage = "bottom" + subpage = "content" + widget = "savesettings" + # set textbox/frame label + label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget) + gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label) + # Set Output Directory button mainpage = "bottom" subpage = "content" @@ -206,7 +222,11 @@ def loadadjustargs(gui, settings): "heartbeep": "adjust.heartbeep", "menuspeed": "adjust.menuspeed", "owpalettes": "adjust.owpalettes", - "uwpalettes": "adjust.uwpalettes" + "uwpalettes": "adjust.uwpalettes", + "reduce_flashing": "adjust.reduce_flashing", + "shuffle_sfx": "adjust.shuffle_sfx", + "shuffle_sfxinstruments": "adjust.shuffle_sfxinstruments", + "shuffle_songinstruments": "adjust.shuffle_songinstruments" } } } diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 1bdd8950..e30e7141 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -129,11 +129,11 @@ def create_item_pool_config(world): groups = LocationGroup('Major').locs(init_set) if world.bigkeyshuffle[player]: groups.locations.extend(mode_grouping['Big Keys']) - if world.dropshuffle[player] != 'none': + if world.dropshuffle[player]: groups.locations.extend(mode_grouping['Big Key Drops']) if world.keyshuffle[player] != 'none': groups.locations.extend(mode_grouping['Small Keys']) - if world.dropshuffle[player] != 'none': + if world.dropshuffle[player]: groups.locations.extend(mode_grouping['Key Drops']) if world.pottery[player] not in ['none', 'cave']: groups.locations.extend(mode_grouping['Pot Keys']) diff --git a/source/overworld/EntranceShuffle2.py b/source/overworld/EntranceShuffle2.py index 87bcdf1d..76b459f8 100644 --- a/source/overworld/EntranceShuffle2.py +++ b/source/overworld/EntranceShuffle2.py @@ -13,8 +13,11 @@ class EntrancePool(object): self.inverted = False self.coupled = True self.swapped = False + self.assumed_loose_caves = False + self.keep_drops_together = True self.default_map = {} self.one_way_map = {} + self.combine_map = {} self.skull_handled = False self.links_on_mountain = False self.decoupled_entrances = [] @@ -54,6 +57,7 @@ def link_entrances_new(world, player): avail_pool.entrances = set(i_drop_map.keys()).union(i_entrance_map.keys()).union(i_single_ent_map.keys()) avail_pool.exits = set(i_entrance_map.values()).union(i_drop_map.values()).union(i_single_ent_map.values()) avail_pool.inverted = world.mode[player] == 'inverted' + avail_pool.assumed_loose_caves = world.shuffle[player] == 'district' inverted_substitution(avail_pool, avail_pool.entrances, True, True) inverted_substitution(avail_pool, avail_pool.exits, False, True) avail_pool.original_entrances.update(avail_pool.entrances) @@ -68,6 +72,7 @@ def link_entrances_new(world, player): default_map['Agahnims Tower'] = 'Ganons Tower Exit' avail_pool.default_map = default_map avail_pool.one_way_map = one_way_map + avail_pool.combine_map = {**default_map, **one_way_map} global LW_Entrances, DW_Entrances LW_Entrances = [] @@ -93,6 +98,8 @@ def link_entrances_new(world, player): raise RuntimeError(f'Shuffle mode {mode} is not yet supported') mode_cfg = copy.deepcopy(modes[mode]) avail_pool.swapped = mode_cfg['undefined'] == 'swap' + avail_pool.keep_drops_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True + avail_pool.coupled = mode_cfg['decoupled'] != 'on' if 'decoupled' in mode_cfg else True if avail_pool.is_standard(): do_standard_connections(avail_pool) pool_list = mode_cfg['pools'] if 'pools' in mode_cfg else {} @@ -106,8 +113,7 @@ def link_entrances_new(world, player): connect_random(holes, targets, avail_pool) elif special_shuffle == 'normal_drops': cross_world = mode_cfg['cross_world'] == 'on' if 'cross_world' in mode_cfg else False - keep_together = mode_cfg['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_cfg else True - do_holes_and_linked_drops(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, cross_world, keep_together) + do_holes_and_linked_drops(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, cross_world) elif special_shuffle == 'fixed_shuffle': do_fixed_shuffle(avail_pool, pool['entrances']) elif special_shuffle == 'same_world': @@ -126,10 +132,18 @@ def link_entrances_new(world, player): do_limited_shuffle_exclude_drops(pool, avail_pool, False) elif special_shuffle == 'vanilla': do_vanilla_connect(pool, avail_pool) + elif special_shuffle == 'district': + drops = [] + world_limiter = LW_Entrances if pool['condition'] == 'lightworld' else DW_Entrances + entrances = [e for e in pool['entrances'] if e in world_limiter] + if 'drops' in pool: + drops = [e for e in pool['drops'] if combine_linked_drop_map[e] in world_limiter] + entrances, exits = find_entrances_and_exits(avail_pool, entrances+drops) + do_main_shuffle(entrances, exits, avail_pool, mode_cfg) elif special_shuffle == 'skull': entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances']) rem_ent = None - if avail_pool.world.shuffle[avail_pool.player] in ['dungeons-simple', 'simple', 'restricted'] \ + if avail_pool.world.shuffle[avail_pool.player] in ['dungeonssimple', 'simple', 'restricted'] \ and not avail_pool.world.is_tile_swapped(0x00, avail_pool.player): rem_ent = random.choice(['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']) entrances.remove(rem_ent) @@ -149,6 +163,8 @@ def link_entrances_new(world, player): do_vanilla_connections(avail_pool) elif undefined_behavior in ['shuffle', 'swap']: do_main_shuffle(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, mode_cfg) + elif undefined_behavior == 'error': + assert len(avail_pool.entrances)+len(avail_pool.exits) == 0, 'Not all entrances were placed in their districts' # afterward @@ -191,10 +207,8 @@ def do_vanilla_connections(avail_pool): def do_main_shuffle(entrances, exits, avail, mode_def): cross_world = mode_def['cross_world'] == 'on' if 'cross_world' in mode_def else False - avail.coupled = mode_def['decoupled'] != 'on' if 'decoupled' in mode_def else True # drops and holes - keep_together = mode_def['keep_drops_together'] == 'on' if 'keep_drops_together' in mode_def else True - do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_together) + do_holes_and_linked_drops(entrances, exits, avail, cross_world) if not avail.coupled: avail.decoupled_entrances.extend(entrances) @@ -209,10 +223,6 @@ def do_main_shuffle(entrances, exits, avail, mode_def): if not avail.coupled: avail.decoupled_entrances.remove('Agahnims Tower') avail.decoupled_exits.remove('Ganons Tower Exit') - if avail.swapped: - connect_swap('Agahnims Tower', 'Ganons Tower Exit', avail) - entrances.remove('Ganons Tower') - exits.remove('Agahnims Tower Exit') elif 'Ganons Tower' in entrances: connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail) entrances.remove('Ganons Tower') @@ -310,7 +320,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def): return not avail.is_standard() or x != 'Bonk Fairy (Light)' # old man S&Q cave - if not cross_world: + if not cross_world and not avail.assumed_loose_caves: #TODO: Add Swapped ER support for this # OM Cave entrance in lw/dw if cross_world off if 'Old Man Cave Exit (West)' in rem_exits: @@ -382,7 +392,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def): def do_old_man_cave_exit(entrances, exits, avail, cross_world): if 'Old Man Cave Exit (East)' in exits: from EntranceShuffle import build_accessible_region_list - if not avail.world.is_tile_swapped(0x03, avail.player): + if not avail.world.is_tile_swapped(0x03, avail.player) or avail.world.shuffle[avail.player] == 'district': region_name = 'West Death Mountain (Top)' else: region_name = 'West Dark Death Mountain (Top)' @@ -420,11 +430,14 @@ def do_blacksmith(entrances, exits, avail): if avail.world.logic[avail.player] in ['noglitches', 'minorglitches'] and (avail.world.is_tile_swapped(0x29, avail.player) == avail.inverted): assumed_inventory.append('Titans Mitts') + blacksmith_options = list() if not avail.world.is_bombshop_start(avail.player): - links_region = avail.world.get_entrance('Links House Exit', avail.player).connected_region.name + links_region = avail.world.get_entrance('Links House Exit', avail.player).connected_region else: - links_region = avail.world.get_entrance('Big Bomb Shop Exit', avail.player).connected_region.name - blacksmith_options = list(get_accessible_entrances(links_region, avail, assumed_inventory, False, True, True)) + links_region = avail.world.get_entrance('Big Bomb Shop Exit', avail.player).connected_region + if links_region is not None: + links_region = links_region.name + blacksmith_options = list(get_accessible_entrances(links_region, avail, assumed_inventory, False, True, True)) if avail.inverted: dark_sanc = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name @@ -440,6 +453,9 @@ def do_blacksmith(entrances, exits, avail): blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances] blacksmith_options = [x for x in blacksmith_options if x in entrances] + if avail.world.shuffle[avail.player] == 'district' and not len(blacksmith_options): + blacksmith_options = [e for e in entrances if e not in Forbidden_Swap_Entrances or not avail.swapped] + assert len(blacksmith_options), 'No available entrances left to place Blacksmith' blacksmith_choice = random.choice(blacksmith_options) connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail) @@ -454,11 +470,21 @@ def do_blacksmith(entrances, exits, avail): def do_standard_connections(avail): - connect_two_way('Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', avail) - # cannot move uncle cave - connect_two_way('Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', avail) - connect_entrance('Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', avail) + std_exits = ['Hyrule Castle Exit (South)', 'Hyrule Castle Secret Entrance Exit'] + if not avail.keep_drops_together: + random.shuffle(std_exits) connect_two_way('Links House', 'Links House Exit', avail) + connect_entrance('Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', avail) + if avail.coupled: + connect_two_way('Hyrule Castle Entrance (South)', std_exits[0], avail) + # cannot move uncle cave + connect_two_way('Hyrule Castle Secret Entrance Stairs', std_exits[1], avail) + else: + connect_entrance('Hyrule Castle Entrance (South)', std_exits[0], avail) + connect_entrance('Hyrule Castle Secret Entrance Stairs', std_exits[1], avail) + random.shuffle(std_exits) + connect_exit(std_exits[0], 'Hyrule Castle Entrance (South)', avail) + connect_exit(std_exits[1], 'Hyrule Castle Secret Entrance Stairs', avail) def remove_from_list(t_list, removals): @@ -466,7 +492,7 @@ def remove_from_list(t_list, removals): t_list.remove(r) -def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_together): +def do_holes_and_linked_drops(entrances, exits, avail, cross_world): holes_to_shuffle = [x for x in entrances if x in drop_map] if not avail.world.shuffle_ganon: @@ -483,7 +509,7 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe remove_from_list(entrances, ['Pyramid Hole', 'Pyramid Entrance']) remove_from_list(exits, ['Pyramid', 'Pyramid Exit']) - if not keep_together: + if not avail.keep_drops_together: targets = [avail.one_way_map[x] for x in holes_to_shuffle] connect_random(holes_to_shuffle, targets, avail) remove_from_list(entrances, holes_to_shuffle) @@ -567,6 +593,8 @@ def do_dark_sanc(entrances, exits, avail): forbidden.extend(Forbidden_Swap_Entrances) if not avail.world.is_bombshop_start(avail.player): forbidden.append('Links House') + else: + forbidden.append('Big Bomb Shop') if avail.world.owShuffle[avail.player] == 'vanilla': choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances] else: @@ -602,7 +630,7 @@ def do_links_house(entrances, exits, avail, cross_world): forbidden.append('Mimic Cave') if avail.world.is_bombshop_start(avail.player) and (avail.inverted == avail.world.is_tile_swapped(0x03, avail.player)): forbidden.extend(['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)']) - if avail.inverted: + if avail.inverted and avail.world.shuffle[avail.player] != 'district': dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name forbidden.extend(get_nearby_entrances(avail, dark_sanc_region)) else: @@ -644,7 +672,7 @@ def do_links_house(entrances, exits, avail, cross_world): sanc_spawn_can_be_dark = (not avail.inverted and avail.world.doorShuffle[avail.player] in ['partitioned', 'crossed'] and avail.world.intensity[avail.player] >= 3) - if cross_world and not sanc_spawn_can_be_dark: + if (cross_world and not sanc_spawn_can_be_dark) or avail.world.shuffle[avail.player] == 'district': possible = [e for e in entrance_pool if e not in forbidden] else: world_list = LW_Entrances if not avail.inverted else DW_Entrances @@ -676,7 +704,7 @@ def do_links_house(entrances, exits, avail, cross_world): return if avail.world.shuffle[avail.player] in ['lite', 'lean']: rem_exits = [e for e in avail.exits if e in Connector_Exit_Set and e not in Dungeon_Exit_Set] - multi_exit_caves = figure_out_connectors(rem_exits) + multi_exit_caves = figure_out_connectors(rem_exits, avail) if cross_world: possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List] possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots] @@ -685,7 +713,7 @@ def do_links_house(entrances, exits, avail, cross_world): possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List and e in world_list] possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots and e in world_list] else: - multi_exit_caves = figure_out_connectors(exits) + multi_exit_caves = figure_out_connectors(exits, avail) entrance_pool = entrances if avail.coupled else avail.decoupled_entrances if cross_world: possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List] @@ -711,7 +739,10 @@ def do_links_house(entrances, exits, avail, cross_world): connect_entrance(chosen_dm_escape, chosen_exit_start, avail) connect_exit(chosen_exit_end, chosen_landing, avail) entrances.remove(chosen_dm_escape) + avail.decoupled_exits.remove(chosen_exit_start) avail.decoupled_entrances.remove(chosen_landing) + # chosen cave has already been removed from exits + exits.add(chosen_exit_start) # this needs to be added back in if len(chosen_cave): exits.update([x for x in chosen_cave]) exits.update([x for item in multi_exit_caves for x in item]) @@ -829,12 +860,22 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo return found_entrances -def figure_out_connectors(exits): +def figure_out_connectors(exits, avail): multi_exit_caves = [] - for item in Connector_List: + cave_list = list(Connector_List) + if avail.assumed_loose_caves: + sw_list = ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'] + random.shuffle(sw_list) + cave_list.extend([sw_list]) + cave_list.extend([[entrance_map[e]] for e in linked_drop_map.values() if 'Inverted ' not in e]) + for item in cave_list: if all(x in exits for x in item): remove_from_list(exits, item) multi_exit_caves.append(list(item)) + elif avail.assumed_loose_caves and any(x in exits for x in item): + remaining = [i for i in item if i in exits] + remove_from_list(exits, remaining) + multi_exit_caves.append(list(remaining)) return multi_exit_caves @@ -1012,7 +1053,7 @@ def figure_out_must_exits_same_world(entrances, exits, avail): for x in entrances: lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x) - multi_exit_caves = figure_out_connectors(exits) + multi_exit_caves = figure_out_connectors(exits, avail) must_exit_lw, must_exit_dw = must_exits_helper(avail) must_exit_lw = must_exit_filter(avail, must_exit_lw, lw_entrances) @@ -1022,7 +1063,7 @@ def figure_out_must_exits_same_world(entrances, exits, avail): def figure_out_must_exits_cross_world(entrances, exits, avail): - multi_exit_caves = figure_out_connectors(exits) + multi_exit_caves = figure_out_connectors(exits, avail) must_exit_lw, must_exit_dw = must_exits_helper(avail) must_exit = must_exit_filter(avail, must_exit_lw + must_exit_dw, entrances) @@ -1086,7 +1127,7 @@ def do_cross_world_connectors(entrances, caves, avail): avail.decoupled_entrances.remove(choice) else: if avail.swapped and len(entrances) > 1: - chosen_entrance = next(e for e in entrances if combine_map[e] != ext) + chosen_entrance = next(e for e in entrances if avail.combine_map[e] != ext) entrances.remove(chosen_entrance) else: chosen_entrance = entrances.pop() @@ -1135,7 +1176,7 @@ def do_fixed_shuffle(avail, entrance_list): choice = choices[i] elif rules.must_exit_to_lw: lw_exits = set() - for e, x in combine_map.items(): + for e, x in avail.combine_map.items(): if x in avail.exits: region = avail.world.get_entrance(e, avail.player).parent_region if region.type == RegionType.LightWorld: @@ -1145,9 +1186,12 @@ def do_fixed_shuffle(avail, entrance_list): new_x = 'Agahnims Tower Exit' elif x == 'Agahnims Tower Exit': new_x = 'Ganons Tower Exit' + if avail.world.is_bombshop_start(avail.player): + if x == 'Links House Exit': + new_x = 'Big Bomb Shop' + elif x == 'Big Bomb Shop': + new_x = 'Links House Exit' lw_exits.add(new_x) - if avail.world.shufflelinks[avail.player] or avail.world.shuffle[avail.player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']: - lw_exits.update({'Big Bomb Shop'} if avail.world.is_bombshop_start(avail.player) else {'Links House Exit'}) filtered_choices = {i: opt for i, opt in choices.items() if all(t in lw_exits for t in opt[2])} _, choice = random.choice(list(filtered_choices.items())) else: @@ -1268,6 +1312,22 @@ def do_limited_shuffle_exclude_drops(pool_def, avail, lw=True): must_exit = set(must_exit_lw if lw else must_exit_dw) base_set = LW_Entrances if lw else DW_Entrances entrance_pool = [x for x in base_set if x in avail.entrances and x not in reserved_drops] + if not avail.world.shuffle_ganon[avail.player]: + if avail.world.is_atgt_swapped(avail.player): + if 'Agahnims Tower' in entrance_pool: + connect_two_way('Agahnims Tower', 'Ganons Tower Exit', avail) + entrance_pool.remove('Agahnims Tower') + exits.remove('Ganons Tower Exit') + if not avail.coupled: + avail.decoupled_entrances.remove('Agahnims Tower') + avail.decoupled_exits.remove('Ganons Tower Exit') + elif 'Ganons Tower' in entrance_pool: + connect_two_way('Ganons Tower', 'Ganons Tower Exit', avail) + entrance_pool.remove('Ganons Tower') + exits.remove('Ganons Tower Exit') + if not avail.coupled: + avail.decoupled_entrances.remove('Ganons Tower') + avail.decoupled_exits.remove('Ganons Tower Exit') random.shuffle(entrance_pool) for next_exit in exits: if next_exit not in Connector_Exit_Set: @@ -1314,7 +1374,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if avail.world.logic[avail.player] in ['owglitches', 'nologic']: + if avail.world.logic[avail.player] in ['owglitches', 'hybridglitches', 'nologic']: import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(avail.world, avail.player): invalid_connections[entrance] = set() @@ -1323,7 +1383,7 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if entrance not in entrances: entrances.append(entrance) if avail.swapped: - swap_forbidden = [e for e in entrances if combine_map[e] in must_exit] + swap_forbidden = [e for e in entrances if avail.combine_map[e] in must_exit] for e in swap_forbidden: entrances.remove(e) entrances.sort() # sort these for consistency @@ -1358,11 +1418,21 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): # find multi exit cave candidates = [] for candidate in cave_options: - if not isinstance(candidate, str) and len(candidate) > 1 and (candidate in used_caves + allow_single = avail.assumed_loose_caves or len(candidate) > 1 + if not isinstance(candidate, str) and allow_single and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances): - if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped + if not avail.swapped or (avail.combine_map[exit] not in candidate and not any(e for e in must_exit if avail.combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped candidates.append(candidate) cave = random.choice(candidates) + + if avail.swapped and len(candidates) > 1 and not avail.world.is_tile_swapped(0x03, avail.player): + DM_Connector_Prefixes = ['Spectacle Rock Cave', 'Old Man House', 'Death Mountain Return'] + if any(p for p in DM_Connector_Prefixes if p in cave[0]): # if chosen cave is a DM connector + remain = [p for p in DM_Connector_Prefixes if len([e for e in entrances if p in e]) > 0] # gets remaining DM caves left in pool + if len(remain) == 1: # guarantee that old man rescue cave can still be placed + candidates.remove(cave) + cave = random.choice(candidates) + if cave is None: raise RuntimeError('No more caves left. Should not happen!') @@ -1380,10 +1450,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if len(cave) == 2: entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit - and (not avail.swapped or rnd_cave[0] != combine_map[e])) + and (not avail.swapped or rnd_cave[0] != avail.combine_map[e])) entrances.remove(entrance) connect_two_way(entrance, rnd_cave[0], avail) - if avail.swapped and combine_map[entrance] != rnd_cave[0]: + if avail.swapped and avail.combine_map[entrance] != rnd_cave[0]: swap_ent, _ = connect_cave_swap(entrance, rnd_cave[0], cave) entrances.remove(swap_ent) if cave in used_caves: @@ -1392,6 +1462,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): if entrance in invalid_connections: for exit2 in invalid_connections[entrance]: invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]).union(invalid_cave_connections[tuple(cave)]) + elif len(cave) == 1 and avail.assumed_loose_caves: + #TODO: keep track of caves we use for must exits that are unaccounted here + # the other exits of the cave should NOT be used to satisfy must-exit later + pass elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit cave_entrances = [] for cave_exit in rnd_cave[:-1]: @@ -1400,11 +1474,11 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): cave_entrances.append(entrance) else: entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit - and (not avail.swapped or cave_exit != combine_map[e])) + and (not avail.swapped or cave_exit != avail.combine_map[e])) cave_entrances.append(entrance) entrances.remove(entrance) connect_two_way(entrance, cave_exit, avail) - if avail.swapped and combine_map[entrance] != cave_exit: + if avail.swapped and avail.combine_map[entrance] != cave_exit: swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) entrances.remove(swap_ent) if entrance not in invalid_connections: @@ -1431,11 +1505,11 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit): continue else: entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)] - and (not avail.swapped or cave_exit != combine_map[e])) + and (not avail.swapped or cave_exit != avail.combine_map[e])) invalid_cave_connections[tuple(cave)] = set() entrances.remove(entrance) connect_two_way(entrance, cave_exit, avail) - if avail.swapped and combine_map[entrance] != cave_exit: + if avail.swapped and avail.combine_map[entrance] != cave_exit: swap_ent, _ = connect_cave_swap(entrance, cave_exit, cave) entrances.remove(swap_ent) cave_options.remove(cave) @@ -1516,14 +1590,12 @@ def find_entrances_and_exits(avail_pool, entrance_pool): entrances, targets = [], [] inverted_substitution(avail_pool, entrance_pool, True) for item in entrance_pool: - if item == 'Ganons Tower' and not avail_pool.world.shuffle_ganon[avail_pool.player]: - continue if item in avail_pool.entrances: entrances.append(item) - if item in entrance_map and entrance_map[item] in avail_pool.exits: - targets.append(entrance_map[item]) - elif item in single_entrance_map and single_entrance_map[item] in avail_pool.exits: - targets.append(single_entrance_map[item]) + if item in avail_pool.default_map and avail_pool.default_map[item] in avail_pool.exits: + targets.append(avail_pool.default_map[item]) + elif item in avail_pool.one_way_map and avail_pool.one_way_map[item] in avail_pool.exits: + targets.append(avail_pool.one_way_map[item]) return entrances, targets @@ -1555,11 +1627,11 @@ def connect_swapped(entrancelist, targetlist, avail, two_way=False): random.shuffle(entrancelist) sorted_targets = list() for ent in entrancelist: - if ent in combine_map: - if combine_map[ent] not in targetlist: - logging.getLogger('').error(f'{combine_map[ent]} not in target list, cannot swap entrance') - raise Exception(f'{combine_map[ent]} not in target list, cannot swap entrance') - sorted_targets.append(combine_map[ent]) + if ent in avail.combine_map: + if avail.combine_map[ent] not in targetlist: + logging.getLogger('').error(f'{avail.combine_map[ent]} not in target list, cannot swap entrance') + raise Exception(f'{avail.combine_map[ent]} not in target list, cannot swap entrance') + sorted_targets.append(avail.combine_map[ent]) if len(sorted_targets): targetlist = list(sorted_targets) else: @@ -1580,9 +1652,9 @@ def connect_swapped(entrancelist, targetlist, avail, two_way=False): def connect_swap(entrance, exit, avail): - swap_exit = combine_map[entrance] + swap_exit = avail.combine_map[entrance] if swap_exit != exit: - swap_entrance = next(e for e, x in combine_map.items() if x == exit) + swap_entrance = next(e for e, x in avail.combine_map.items() if x == exit) if swap_entrance in ['Pyramid Entrance', 'Pyramid Hole'] and avail.world.is_tile_swapped(0x1b, avail.player): swap_entrance = 'Inverted ' + swap_entrance if entrance in entrance_map: @@ -1675,7 +1747,7 @@ def connect_entrance(entrancename, exit_name, avail): def connect_exit(exit_name, entrancename, avail): - world, player = avail.world, avail. player + world, player = avail.world, avail.player entrance = world.get_entrance(entrancename, player) exit = world.get_entrance(exit_name, player) @@ -1683,13 +1755,18 @@ def connect_exit(exit_name, entrancename, avail): if exit.connected_region is not None: exit.connected_region.entrances.remove(exit) - exit.connect(entrance.parent_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) + dest_region = entrance.parent_region + if dest_region.name == 'Pyramid Crack': + # Needs to logically exit into greater Pyramid Area + dest_region = entrance.parent_region.entrances[0].parent_region + + exit.connect(dest_region, door_addresses[entrance.name][1], exit_ids[exit.name][1]) if exit_name != 'Chris Houlihan Room Exit': if avail.coupled: avail.entrances.remove(entrancename) avail.exits.remove(exit_name) world.spoiler.set_entrance(entrance.name, exit.name, 'exit', player) - logging.getLogger('').debug(f'Connected (exit) {entrance.name} to {exit.name}') + logging.getLogger('').debug(f'Connected (exit) {exit.name} to {entrance.name}') def connect_two_way(entrancename, exit_name, avail): @@ -2056,6 +2133,157 @@ modes = { }, } }, + 'district': { + 'undefined': 'error', + 'keep_drops_together': 'off', + 'cross_world': 'off', + 'pools': { + 'northwest_hyrule': { + 'special': 'district', + 'condition': 'lightworld', + 'drops': ['Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'North Fairy Cave Drop', + + 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'], + 'entrances': ['Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', 'North Fairy Cave', + 'Lost Woods Gamble', 'Lumberjack House', 'Old Man Cave (West)', 'Death Mountain Return Cave (West)', + 'Fortune Teller (Light)', 'Bonk Rock Cave', 'Graveyard Cave', 'Kings Grave', + + 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Dark Lumberjack Shop', + 'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', + 'Red Shield Shop'] + }, + 'northwest_dark_world': { + 'special': 'district', + 'condition': 'darkworld', + 'drops': ['Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', + 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole', + + 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'North Fairy Cave Drop', + 'Kakariko Well Drop', 'Bat Cave Drop'], + 'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', + 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Dark Lumberjack Shop', + 'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', + 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Dark World Shop', 'Brewery', + 'Red Shield Shop', 'Hammer Peg Cave', + + 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', 'North Fairy Cave', + 'Kakariko Well Cave', 'Bat Cave Cave', 'Lost Woods Gamble', 'Lumberjack House', 'Fortune Teller (Light)', + 'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Bonk Rock Cave', 'Graveyard Cave', + 'Kings Grave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)', 'Snitch Lady (West)', + 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Bush Covered House', 'Light World Bomb Hut', + 'Kakariko Shop', 'Tavern North', 'Tavern (Front)', 'Blacksmiths Hut'] + }, + 'central_hyrule': { + 'special': 'district', + 'condition': 'lightworld', + 'drops': ['Hyrule Castle Secret Entrance Drop', 'Inverted Pyramid Hole', + + 'Pyramid Hole'], + 'entrances': ['Hyrule Castle Secret Entrance Stairs', 'Inverted Pyramid Entrance', 'Agahnims Tower', + 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (South)', + 'Bonk Fairy (Light)', 'Links House', 'Cave 45', 'Light Hype Fairy', 'Dam', + + 'Pyramid Entrance', 'Pyramid Fairy', 'Bonk Fairy (Dark)', 'Big Bomb Shop', 'Hype Cave', 'Swamp Palace'] + }, + 'kakariko': { + 'special': 'district', + 'condition': 'lightworld', + 'drops': ['Kakariko Well Drop', 'Bat Cave Drop'], + 'entrances': ['Kakariko Well Cave', 'Bat Cave Cave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)', + 'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Bush Covered House', + 'Light World Bomb Hut', 'Kakariko Shop', 'Tavern North', 'Tavern (Front)', 'Blacksmiths Hut', + 'Two Brothers House (West)', 'Two Brothers House (East)', 'Library', 'Kakariko Gamble Game', + + 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Dark World Shop', 'Brewery', + 'Hammer Peg Cave', 'Archery Game'] + }, + 'eastern_hyrule': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Waterfall of Wishing', 'Potion Shop', 'Sahasrahlas Hut', 'Eastern Palace', 'Lake Hylia Fairy', + 'Long Fairy Cave', + + 'Dark Potion Shop', 'Palace of Darkness Hint', 'Palace of Darkness', 'Dark Lake Hylia Fairy', + 'East Dark World Hint'] + }, + 'lake_hylia': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Capacity Upgrade', 'Mini Moldorm Cave', + 'Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave', + + 'Dark Lake Hylia Shop', 'Ice Palace', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', + 'Dark Lake Hylia Ledge Spike Cave'] + }, + 'desert': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Desert Palace Entrance (North)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (South)', + 'Desert Palace Entrance (East)', 'Checkerboard Cave', 'Aginahs Cave', 'Desert Fairy', '50 Rupee Cave', + + 'Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint'] + }, + 'death_mountain': { + 'special': 'district', + 'condition': 'lightworld', + 'entrances': ['Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave', + 'Death Mountain Return Cave (East)', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', + 'Spiral Cave', 'Spiral Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Bottom)', + 'Mimic Cave', 'Hookshot Fairy', 'Paradox Cave (Top)', 'Paradox Cave (Middle)', 'Paradox Cave (Bottom)', + + 'Ganons Tower', 'Dark Death Mountain Fairy', 'Spike Cave', 'Superbunny Cave (Bottom)', 'Superbunny Cave (Top)', + 'Dark Death Mountain Shop', 'Hookshot Cave', 'Hookshot Cave Back Entrance', + 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock'] + }, + 'dark_death_mountain': { + 'special': 'district', + 'condition': 'darkworld', + 'entrances': ['Ganons Tower', 'Dark Death Mountain Fairy', 'Spike Cave', 'Superbunny Cave (Bottom)', 'Superbunny Cave (Top)', + 'Dark Death Mountain Shop', 'Hookshot Cave', 'Hookshot Cave Back Entrance', + 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock', + + 'Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave', + 'Death Mountain Return Cave (East)', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', + 'Spiral Cave', 'Spiral Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Bottom)', + 'Mimic Cave', 'Hookshot Fairy', 'Paradox Cave (Top)', 'Paradox Cave (Middle)', 'Paradox Cave (Bottom)'] + }, + 'south_dark_world': { + 'special': 'district', + 'condition': 'darkworld', + 'entrances': ['Archery Game', 'Bonk Fairy (Dark)', 'Big Bomb Shop', 'Hype Cave', 'Dark Lake Hylia Shop', 'Ice Palace', + 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', + 'Swamp Palace', + + 'Two Brothers House (West)', 'Two Brothers House (East)', 'Library', 'Kakariko Gamble Game', + 'Bonk Fairy (Light)', 'Links House', 'Cave 45', 'Desert Fairy', '50 Rupee Cave', 'Dam', + 'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Capacity Upgrade', + 'Mini Moldorm Cave', 'Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave'] + }, + 'east_dark_world': { + 'special': 'district', + 'condition': 'darkworld', + 'drops': ['Pyramid Hole', + + 'Hyrule Castle Secret Entrance Drop', 'Inverted Pyramid Hole'], + 'entrances': ['Pyramid Entrance', 'Pyramid Fairy', 'Dark Potion Shop', 'Palace of Darkness Hint', 'Palace of Darkness', + 'Dark Lake Hylia Fairy', 'East Dark World Hint', + + 'Hyrule Castle Secret Entrance Stairs', 'Inverted Pyramid Entrance', 'Waterfall of Wishing', 'Potion Shop', + 'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', + 'Hyrule Castle Entrance (South)', 'Sahasrahlas Hut', 'Eastern Palace', 'Lake Hylia Fairy', 'Long Fairy Cave'] + }, + 'mire': { + 'special': 'district', + 'condition': 'darkworld', + 'entrances': ['Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint', + + 'Desert Palace Entrance (North)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (South)', + 'Desert Palace Entrance (East)', 'Checkerboard Cave', 'Aginahs Cave'] + } + } + }, 'swapped': { 'undefined': 'swap', 'keep_drops_together': 'on', @@ -2117,7 +2345,7 @@ drop_map = { } linked_drop_map = { - 'Hyrule Castle Secret Entrance Drop': 'Hyrule Castle Secret Entrance Stairs', + 'Hyrule Castle Secret Entrance Drop': 'Hyrule Castle Secret Entrance Stairs', 'Kakariko Well Drop': 'Kakariko Well Cave', 'Bat Cave Drop': 'Bat Cave Cave', 'North Fairy Cave Drop': 'North Fairy Cave', @@ -2128,6 +2356,13 @@ linked_drop_map = { 'Inverted Pyramid Hole': 'Inverted Pyramid Entrance' } +sw_linked_drop_map = { + 'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (West)', + 'Skull Woods First Section Hole (North)': 'Skull Woods First Section Door', + 'Skull Woods First Section Hole (West)': 'Skull Woods First Section Door', + 'Skull Woods First Section Hole (East)': 'Skull Woods First Section Door' +} + entrance_map = { 'Desert Palace Entrance (South)': 'Desert Palace Exit (South)', 'Desert Palace Entrance (West)': 'Desert Palace Exit (West)', @@ -2161,7 +2396,7 @@ entrance_map = { 'Links House': 'Links House Exit', - 'Hyrule Castle Secret Entrance Stairs': 'Hyrule Castle Secret Entrance Exit', + 'Hyrule Castle Secret Entrance Stairs': 'Hyrule Castle Secret Entrance Exit', 'Kakariko Well Cave': 'Kakariko Well Exit', 'Bat Cave Cave': 'Bat Cave Exit', 'North Fairy Cave': 'North Fairy Cave Exit', @@ -2235,7 +2470,7 @@ single_entrance_map = { 'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing' } -combine_map = {**entrance_map, **single_entrance_map, **drop_map} +combine_linked_drop_map = {**linked_drop_map, **sw_linked_drop_map} LW_Entrances = [] DW_Entrances = [] @@ -2656,7 +2891,7 @@ door_addresses = {'Links House': (0x00, (0x0104, 0x2c 'Mire Hint': (0x61, (0x0114, 0x70, 0x0654, 0x0cc5, 0x02aa, 0x0d16, 0x0328, 0x0d32, 0x032f, 0x09, 0xf7, 0x0000, 0x0000), 0x00), 'Mire Fairy': (0x55, (0x0115, 0x70, 0x03a8, 0x0c6a, 0x013a, 0x0cb7, 0x01b8, 0x0cd7, 0x01bf, 0x06, 0xfa, 0x0000, 0x0000), 0x00), 'Spike Cave': (0x40, (0x0117, 0x43, 0x0ed4, 0x01e4, 0x08aa, 0x0236, 0x0928, 0x0253, 0x092f, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), - 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0daa, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), + 'Dark Death Mountain Shop': (0x6D, (0x0112, 0x45, 0x0ee0, 0x01e3, 0x0d00, 0x0236, 0x0da8, 0x0252, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), 'Dark Death Mountain Fairy': (0x6F, (0x0115, 0x43, 0x1400, 0x0294, 0x0600, 0x02e8, 0x0678, 0x0303, 0x0685, 0x0a, 0xf6, 0x0000, 0x0000), 0x00), 'Mimic Cave': (0x4E, (0x010c, 0x05, 0x07e0, 0x0103, 0x0d00, 0x0156, 0x0d78, 0x0172, 0x0d7d, 0x0b, 0xf5, 0x0000, 0x0000), 0x00), 'Big Bomb Shop': (0x52, (0x011c, 0x6c, 0x0506, 0x0a9a, 0x0832, 0x0ae7, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfa, 0x0816, 0x0000), 0x00), diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index ce8831a7..04285309 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -46,8 +46,9 @@ def roll_settings(weights): ret.algorithm = get_choice('algorithm') - glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches', - 'owg': 'owglitches', 'minorglitches': 'minorglitches'} + glitch_map = {'none': 'noglitches', 'minorglitches': 'minorglitches', 'no_logic': 'nologic', + 'hmg': 'hybridglitches', 'hybridglitches': 'hybridglitches', + 'owg': 'owglitches', 'owglitches': 'owglitches'} glitches_required = get_choice('glitches_required') if glitches_required is not None: if glitches_required not in glitch_map.keys(): @@ -78,9 +79,9 @@ def roll_settings(weights): overworld_shuffle = get_choice('overworld_shuffle') ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' ret.ow_terrain = get_choice('overworld_terrain') == 'on' - valid_options = {'none', 'polar', 'grouped', 'limited', 'chaos'} + valid_options = {'none': 'none', 'polar': 'polar', 'grouped': 'polar', 'chaos': 'unrestricted', 'unrestricted': 'unrestricted'} ret.ow_crossed = get_choice('overworld_crossed') - ret.ow_crossed = ret.ow_crossed if ret.ow_crossed in valid_options else 'none' + ret.ow_crossed = valid_options[ret.ow_crossed] if ret.ow_crossed in valid_options else 'none' ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on' ret.ow_mixed = get_choice('overworld_swap') == 'on' ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on' @@ -114,6 +115,7 @@ def roll_settings(weights): ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on' ret.shufflepots = get_choice('pot_shuffle') == 'on' + ret.aga_randomness = get_choice('aga_randomness') == 'on' ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent' ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize') @@ -231,6 +233,8 @@ def roll_settings(weights): ret.ow_palettes = get_choice('ow_palettes', romweights) ret.uw_palettes = get_choice('uw_palettes', romweights) ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on' + ret.shuffle_sfxinstruments = get_choice('shuffle_sfxinstruments', romweights) == 'on' + ret.shuffle_songinstruments = get_choice('shuffle_songinstruments', romweights) == 'on' ret.msu_resume = get_choice('msu_resume', romweights) == 'on' return ret diff --git a/test/customizer/hmg/fireless_ice.yaml b/test/customizer/hmg/fireless_ice.yaml new file mode 100644 index 00000000..86affd94 --- /dev/null +++ b/test/customizer/hmg/fireless_ice.yaml @@ -0,0 +1,35 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Ice Palace - Compass Chest: Fire Rod + Ice Palace - Freezor Chest: Bombos diff --git a/test/customizer/hmg/hammer_in_swamp.yaml b/test/customizer/hmg/hammer_in_swamp.yaml new file mode 100644 index 00000000..f2e3aae7 --- /dev/null +++ b/test/customizer/hmg/hammer_in_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Hammer diff --git a/test/customizer/hmg/mearl_in_pod.yaml b/test/customizer/hmg/mearl_in_pod.yaml new file mode 100644 index 00000000..ec2d5b9a --- /dev/null +++ b/test/customizer/hmg/mearl_in_pod.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Palace of Darkness - Shooter Room: Moon Pearl diff --git a/test/customizer/hmg/mirrorless_swamp.yaml b/test/customizer/hmg/mirrorless_swamp.yaml new file mode 100644 index 00000000..c8a109c3 --- /dev/null +++ b/test/customizer/hmg/mirrorless_swamp.yaml @@ -0,0 +1,42 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Flippers + - Lamp + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +placements: + 1: + Swamp Palace - Big Chest: Magic Mirror diff --git a/test/customizer/hmg/pod_as_connector.yaml b/test/customizer/hmg/pod_as_connector.yaml new file mode 100644 index 00000000..48f72d6e --- /dev/null +++ b/test/customizer/hmg/pod_as_connector.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Palace of Darkness Exit + Lake Hylia Fortune Teller: Spectacle Rock Cave Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl diff --git a/test/customizer/hmg/swamp_as_connector.yaml b/test/customizer/hmg/swamp_as_connector.yaml new file mode 100644 index 00000000..ae343937 --- /dev/null +++ b/test/customizer/hmg/swamp_as_connector.yaml @@ -0,0 +1,55 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: crossed +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +entrances: + 1: + entrances: + Dark Lake Hylia Ledge Hint: Dark World Hammer Peg Cave + exits: + Links House: Chris Houlihan Room Exit + two-way: + Dark Lake Hylia Ledge Fairy: Swamp Palace Exit + Lake Hylia Fortune Teller: Misery Mire Exit + Links House: Links House Exit +placements: + 1: + Peg Cave: Moon Pearl + diff --git a/test/customizer/hmg/swamp_small_in_swamp_back.yaml b/test/customizer/hmg/swamp_small_in_swamp_back.yaml new file mode 100644 index 00000000..b57cc9b6 --- /dev/null +++ b/test/customizer/hmg/swamp_small_in_swamp_back.yaml @@ -0,0 +1,44 @@ +meta: + players: 1 +settings: + 1: + accessibility: items + door_shuffle: vanilla + logic: hybridglitches + mode: open + shuffle: vanilla +start_inventory: + 1: + - Hookshot + - Lamp + - Hammer + - Magic Mirror + - Tempered Sword + - Boss Heart Container + - Boss Heart Container + - Boss Heart Container +doors: + 1: + doors: + Hera Beetles WS: Hera Startile Corner ES + Hera Startile Corner ES: Hera Beetles WS + Hera Startile Corner NW: Hera Startile Wide SW + Hera Startile Wide SW: Hera Startile Corner NW + Skull 1 Lobby ES: Skull Map Room WS + Skull 2 West Lobby ES: Skull Small Hall WS + Skull 2 West Lobby NW: Skull X Room SW + Skull Big Chest N: Skull Pull Switch S + Skull Compass Room WS: Skull Left Drop ES + Skull Final Drop WS: Skull Spike Corner ES + Skull Left Drop ES: Skull Compass Room WS + Skull Map Room WS: Skull 1 Lobby ES + Skull Pot Circle WN: Skull Pull Switch EN + Skull Pull Switch EN: Skull Pot Circle WN + Skull Pull Switch S: Skull Big Chest N + Skull Small Hall WS: Skull 2 West Lobby ES + Skull Spike Corner ES: Skull Final Drop WS + Skull X Room SW: Skull 2 West Lobby NW + lobbies: {} +# placements: +# 1: +# Swamp Palace - Entrance: Boss Heart Container diff --git a/test/customizer/zelda_escape.yaml b/test/customizer/zelda_escape.yaml new file mode 100644 index 00000000..e779bbac --- /dev/null +++ b/test/customizer/zelda_escape.yaml @@ -0,0 +1,14 @@ +meta: + players: 1 +settings: + 1: + door_shuffle: crossed + intensity: 3 + mode: standard + pottery: keys + dropshuffle: 'on' +doors: + 1: + doors: + Hyrule Dungeon Cellblock Up Stairs: + dest: Ice Hammer Block Down Stairs diff --git a/test/suite/hmg/entrance_bunny_pocket_sw.yaml b/test/suite/hmg/entrance_bunny_pocket_sw.yaml new file mode 100644 index 00000000..eba7743d --- /dev/null +++ b/test/suite/hmg/entrance_bunny_pocket_sw.yaml @@ -0,0 +1,34 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches + shuffle: crossed +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Pyramid Fairy - Left: True +entrances: + 1: + entrances: + Skull Woods Final Section: Pyramid Fairy + two-way: + Chicken House: Two Brothers House Exit (West) + Skull Woods Second Section Door (West): Two Brothers House Exit (East) + diff --git a/test/suite/hmg/fireless_ice.yaml b/test/suite/hmg/fireless_ice.yaml new file mode 100644 index 00000000..79b31f59 --- /dev/null +++ b/test/suite/hmg/fireless_ice.yaml @@ -0,0 +1,17 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove +placements: + 1: + Ice Palace - Map Chest: Bombos + Ice Palace - Iced T Room: Fire Rod diff --git a/test/suite/hmg/flippers_locked_flippers.yaml b/test/suite/hmg/flippers_locked_flippers.yaml new file mode 100644 index 00000000..be71e07b --- /dev/null +++ b/test/suite/hmg/flippers_locked_flippers.yaml @@ -0,0 +1,20 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword +advanced_placements: + 1: + - type: Verification + item: Flippers + locations: + Zora's Ledge: True + Hobo: True + Ice Palace - Boss: True + Swamp Palace - Entrance: False + diff --git a/test/suite/hmg/flippers_wraps.yaml b/test/suite/hmg/flippers_wraps.yaml new file mode 100644 index 00000000..3418b4ec --- /dev/null +++ b/test/suite/hmg/flippers_wraps.yaml @@ -0,0 +1,29 @@ +meta: + players: 1 +settings: + 1: + logic: owglitches +start_inventory: + 1: + - Pegasus Boots + - Moon Pearl + - Progressive Sword + - Flippers +placements: + 1: + Peg Cave: Magic Mirror +advanced_placements: + 1: + - type: Verification + item: Hammer + locations: + Link's House: True + Magic Bat: False +advanced_placements: + 1: + - type: Verification + item: Progressive Glove + locations: + Link's House: True + Ice Palace - Freezor Chest: False + diff --git a/test/suite/hmg/hera_from_mire.yaml b/test/suite/hmg/hera_from_mire.yaml new file mode 100644 index 00000000..3e7d0c49 --- /dev/null +++ b/test/suite/hmg/hera_from_mire.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Big Key (Tower of Hera) + locations: + Tower of Hera - Big Key Chest: True + Tower of Hera - Basement Cage: True + Tower of Hera - Map Chest: True + Tower of Hera - Compass Chest: True + Tower of Hera - Big Chest: True + Tower of Hera - Boss: True \ No newline at end of file diff --git a/test/suite/hmg/inverted_inaccessible_desert.yaml b/test/suite/hmg/inverted_inaccessible_desert.yaml new file mode 100644 index 00000000..bb8ad8aa --- /dev/null +++ b/test/suite/hmg/inverted_inaccessible_desert.yaml @@ -0,0 +1,26 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted + shuffle: crossed + +start_inventory: + 1: + - Pegasus Boots + - Progressive Sword + - Hammer + - Fire Rod +placements: + 1: + Desert Palace - Boss: Moon Pearl +entrances: + 1: + two-way: + Skull Woods Final Section: Desert Palace Exit (West) + Skull Woods Second Section Door (West): Desert Palace Exit (East) + Thieves Town: Thieves Town Exit + Hyrule Castle Entrance (East): Desert Palace Exit (South) + Hyrule Castle Entrance (West): Desert Palace Exit (North) + diff --git a/test/suite/hmg/inverted_moon_pearl_locs.yaml b/test/suite/hmg/inverted_moon_pearl_locs.yaml new file mode 100644 index 00000000..9ae1a9e1 --- /dev/null +++ b/test/suite/hmg/inverted_moon_pearl_locs.yaml @@ -0,0 +1,31 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches + mode: inverted +start_inventory: + 1: + - Flippers + - Progressive Sword + - Progressive Sword + - Book of Mudora + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Tower of Hera - Big Chest: True + Desert Palace - Big Chest: True + Eastern Palace - Big Chest: True + Bombos Tablet: True + Cave 45: True + + + + diff --git a/test/suite/hmg/moon_pearl_locs.yaml b/test/suite/hmg/moon_pearl_locs.yaml new file mode 100644 index 00000000..a18d53d1 --- /dev/null +++ b/test/suite/hmg/moon_pearl_locs.yaml @@ -0,0 +1,48 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Pegasus Boots + - Flippers + - Fire Rod + - Book of Mudora + - Progressive Sword + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Compass Chest: True + Skull Woods - Bridge Room: True + Palace of Darkness - Shooter Room: True + Palace of Darkness - The Arena - Bridge: True + Palace of Darkness - Stalfos Basement: True + Palace of Darkness - Big Key Chest: True + Palace of Darkness - The Arena - Ledge: True + Palace of Darkness - Map Chest: True + Palace of Darkness - Compass Chest: True + Palace of Darkness - Dark Basement - Left: True + Palace of Darkness - Dark Basement - Right: True + Palace of Darkness - Dark Maze - Top: True + Palace of Darkness - Dark Maze - Bottom: True + Palace of Darkness - Big Chest: True + Palace of Darkness - Harmless Hellway: True + Palace of Darkness - Boss: True + Bombos Tablet: True + C-Shaped House: True + Pyramid Fairy - Left: True + Swamp Palace - Entrance: False + Thieves' Town - Map Chest: False + + + + diff --git a/test/suite/hmg/pearlless_sw.yaml b/test/suite/hmg/pearlless_sw.yaml new file mode 100644 index 00000000..c26ce1d9 --- /dev/null +++ b/test/suite/hmg/pearlless_sw.yaml @@ -0,0 +1,25 @@ +meta: + players: 1 + +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Pegasus Boots + - Progressive Sword + - Hammer + - Progressive Glove + - Progressive Glove + - Fire Rod + - Book of Mudora + - Bottle + - Magic Mirror + - Lamp +advanced_placements: + 1: + - type: Verification + item: Moon Pearl + locations: + Skull Woods - Bridge Room: True diff --git a/test/suite/hmg/swamp_from_mire.yaml b/test/suite/hmg/swamp_from_mire.yaml new file mode 100644 index 00000000..f3873909 --- /dev/null +++ b/test/suite/hmg/swamp_from_mire.yaml @@ -0,0 +1,43 @@ +meta: + players: 1 +settings: + 1: + logic: hybridglitches +start_inventory: + 1: + - Flippers + - Moon Pearl + - Progressive Sword + - Lamp + - Magic Mirror + - Ether + - Quake + - Bombos +advanced_placements: + 1: + - type: Verification + item: Small Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True + - type: Verification + item: Big Key (Swamp Palace) + locations: + Swamp Palace - Entrance: True + Swamp Palace - Map Chest: True + Swamp Palace - Big Chest: True + Swamp Palace - Compass Chest: True + Swamp Palace - West Chest: True + Swamp Palace - Big Key Chest: True + Swamp Palace - Flooded Room - Left: True + Swamp Palace - Flooded Room - Right: True + Swamp Palace - Waterfall Room: True + Swamp Palace - Boss: True \ No newline at end of file