diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bef8bcc..5fe8b5e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +# 0.7.0.2 +- Fixed money error for bombbag/take-anys +- Fixed palette issue for map check/flute menu +- Fixed issue with dungeon icons showing in opposite world in Inverted +- Fixed Links position in map checks when in Special OW areas +- Fixed buffered sword issue in OW Shuffle water transitions +- Added PlasmaKappa tribute TF room text + # 0.7.0.1 - Fixed buggy sprites in post-Aga Zora's Domain - Fixed L/R map switch when in special OW screens diff --git a/Fill.py b/Fill.py index 9db0eef0..87c256d5 100644 --- a/Fill.py +++ b/Fill.py @@ -1112,7 +1112,7 @@ def balance_money_progression(world): slot = shop_to_location_table[location.parent_region.name].index(location.name) shop = location.parent_region.shop shop_item = shop.inventory[slot] - if location.item and interesting_item(location, location.item, world, location.item.player): + if shop_item and location.item and interesting_item(location, location.item, world, location.item.player): if location.item.name.startswith('Rupee') and loc_player == location.item.player: if shop_item['price'] < rupee_chart[location.item.name]: wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block diff --git a/ItemList.py b/ItemList.py index 273a370d..662b1b61 100644 --- a/ItemList.py +++ b/ItemList.py @@ -364,9 +364,10 @@ def generate_itempool(world, player): world.escape_assist[player].append('bombs') for (location, item) in placed_items.items(): - world.push_item(world.get_location(location, player), ItemFactory(item, player), False) - world.get_location(location, player).event = True - world.get_location(location, player).locked = True + loc = world.get_location(location, player) + world.push_item(loc, ItemFactory(item, player), False) + loc.event = True + loc.locked = True if world.shopsanity[player] and not skip_pool_adjustments: for shop in world.shops[player]: diff --git a/Main.py b/Main.py index 7a4ae50b..ba3f7016 100644 --- a/Main.py +++ b/Main.py @@ -50,7 +50,6 @@ from Items import ItemFactory from KeyDoorShuffle import validate_key_placement from OverworldGlitchRules import create_owg_connections from OverworldShuffle import ( - create_dynamic_flute_exits, create_dynamic_mirror_exits, link_overworld, update_world_regions, @@ -86,6 +85,7 @@ from source.item.FillUtil import ( verify_item_pool_config, ) from source.overworld.EntranceShuffle2 import link_entrances_new +from source.overworld.FluteShuffle import create_dynamic_flute_exits from source.rom.DataTables import init_data_tables from source.tools.BPS import create_bps_from_data from UnderworldGlitchRules import ( diff --git a/OverworldShuffle.py b/OverworldShuffle.py index e144faee..635f7741 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -25,6 +25,11 @@ from OWEdges import ( ) from Regions import mark_light_dark_world_regions from source.overworld.EntranceShuffle2 import connect_simple +from source.overworld.FluteShuffle import ( + default_flute_connections, + flute_data, + shuffle_flute_spots, +) from Utils import bidict parallel_links_new = None # needs to be globally available, reset every new generation/player @@ -349,6 +354,7 @@ def link_overworld(world, player): world.owcrossededges[player].extend(edge_set) assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges" + # TODO: Someday don't force parallels also? Keeping for now to ensure full reachability for edge in copy.deepcopy(world.owcrossededges[player]): if edge in parallel_links_new: if parallel_links_new[edge] not in world.owcrossededges[player]: @@ -587,169 +593,8 @@ def link_overworld(world, player): # flute shuffle logging.getLogger('').debug('Shuffling flute spots') - def connect_flutes(flute_destinations): - for o in range(0, len(flute_destinations)): - owid = flute_destinations[o] - regions = flute_data[owid][0] - if not world.is_tile_swapped(owid, player): - connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player) - else: - connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player) - - if world.owFluteShuffle[player] == 'vanilla': - flute_spots = default_flute_connections.copy() - sort_flute_spots(world, player, flute_spots) - world.owflutespots[player] = flute_spots - connect_flutes(flute_spots) - else: - flute_spots = 8 - flute_pool = list(flute_data.keys()) - new_spots = list() - ignored_regions = set() - used_flute_regions = [] - forbidden_spots = [] - forbidden_regions = [] + shuffle_flute_spots(world, player) - def addSpot(owid, ignore_proximity, forced): - if world.owFluteShuffle[player] == 'balanced': - def getIgnored(regionname, base_owid, owid): - region = world.get_region(regionname, player) - for exit in region.exits: - if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored: - if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid): - new_ignored.add(exit.connected_region.name) - getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name]) - if regionname in one_way_ledges: - for ledge_region in one_way_ledges[regionname]: - if ledge_region not in new_ignored: - new_ignored.add(ledge_region) - getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region]) - - if not world.is_tile_swapped(owid, player): - new_region = flute_data[owid][0][0] - else: - new_region = flute_data[owid][0][1] - - if new_region in ignored_regions and not forced: - return False - - new_ignored = {new_region} - getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region]) - if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): - return False - ignored_regions.update(new_ignored) - if owid in flute_pool: - flute_pool.remove(owid) - if ignore_proximity and not forced: - logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') - logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') - new_spots.append(owid) - else: - # TODO: Inspect later, seems to happen only with 'random' flute shuffle - logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}') - return True - - if world.customizer: - custom_spots = world.customizer.get_owflutespots() - if custom_spots and player in custom_spots: - if 'force' in custom_spots[player]: - for id in custom_spots[player]['force']: - owid = id & 0xBF - addSpot(owid, True, True) - flute_spots -= 1 - if not world.is_tile_swapped(owid, player): - used_flute_regions.append(flute_data[owid][0][0]) - else: - used_flute_regions.append(flute_data[owid][0][1]) - if 'forbid' in custom_spots[player]: - for id in custom_spots[player]['forbid']: - owid = id & 0xBF - if owid not in new_spots: - forbidden_spots.append(owid) - if not world.is_tile_swapped(owid, player): - forbidden_regions.append(flute_data[owid][0][0]) - else: - forbidden_regions.append(flute_data[owid][0][1]) - - # determine sectors (isolated groups of regions) to place flute spots - flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots} - flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]] - flute_sectors = [s for s in flute_sectors if len(s[1]) > 0] - region_total = sum([c for c,_ in flute_sectors]) - sector_total = len(flute_sectors) - empty_sector_total = 0 - sector_has_spot = [] - - # determine which sectors still need a flute spot - for sector in flute_sectors: - already_has_spot = any(region in sector for region in used_flute_regions) - sector_has_spot.append(already_has_spot) - if not already_has_spot: - empty_sector_total += 1 - if flute_spots < empty_sector_total: - logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail') - # pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable - for i in range(len(flute_sectors)): - if not sector_has_spot[i]: - sector_has_spot[i] = True - empty_sector_total -= 1 - if flute_spots == empty_sector_total: - break - - # distribute flute spots for each sector - for i in range(len(flute_sectors)): - sector = flute_sectors[i] - sector_total -= 1 - if not sector_has_spot[i]: - empty_sector_total -= 1 - spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) - target_spots = len(new_spots) + spots_to_place - logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)') - - if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]): - addSpot(0x30, True, True) # guarantee desert/mire access - - random.shuffle(sector[1]) - f = 0 - t = 0 - while len(new_spots) < target_spots: - if f >= len(sector[1]): - f = 0 - t += 1 - if t > 5: - raise GenerationException('Infinite loop detected in flute shuffle') - owid = flute_regions[sector[1][f]] - if owid not in new_spots and owid not in forbidden_spots: - addSpot(owid, t > 0, False) - f += 1 - - region_total -= sector[0] - flute_spots -= spots_to_place - - # connect new flute spots - sort_flute_spots(world, player, new_spots) - world.owflutespots[player] = new_spots - connect_flutes(new_spots) - - # update spoiler - s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)])) - text_output = flute_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[0x32],s[0x33],s[0x34], s[0x37], - s[0x30], s[0x35], - s[0x3a],s[0x3b],s[0x3c], s[0x3f]) - world.spoiler.set_map('flute', text_output, new_spots, player) - - create_dynamic_flute_exits(world, player) def connect_custom(world, connected_edges, groups, forced, player): forced_crossed, forced_noncrossed = forced @@ -1325,28 +1170,6 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player): groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches) return groups -def sort_flute_spots(world, player, flute_spots): - if world.owLayout[player] != 'grid': - flute_spots.sort(key=lambda id: flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04) - else: - world_layout = world.owgrid[player][0] if world.mode[player] != 'inverted' else world.owgrid[player][1] - layout_list = sum(world_layout, []) - layout_map = {id & 0xBF: i for i, id in enumerate(layout_list)} - flute_spots.sort(key=lambda id: layout_map[flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04]) - -def create_dynamic_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 - exit = Entrance(region.player, exitname, region) - exit.spot_type = 'Flute' - exit.connect(world.get_region('Flute Sky', player)) - region.exits.append(exit) - world.initialize_regions() - def get_mirror_exit_name(from_region, to_region): if from_region in mirror_connections and to_region in mirror_connections[from_region]: if len(mirror_connections[from_region]) == 1: @@ -1918,10 +1741,6 @@ default_whirlpool_connections = [ ((0x35, 'Lake Hylia Whirlpool', 'Lake Hylia Water'), (0x0f, 'Zora Whirlpool', 'Zora Waterfall Water')), ((0x12, 'Kakariko Pond Whirlpool', 'Kakariko Pond Area'), (0x3f, 'Octoballoon Whirlpool', 'Octoballoon Water')), ((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water')) -] - -default_flute_connections = [ - 0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f ] ow_connections = { @@ -2589,51 +2408,6 @@ isolated_regions = [ 'Pyramid Water' ] -flute_data = { - #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, 0x01d8, 0x0518), - 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), - 0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98), - 0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0), - 0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8), - 0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58), - 0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088), - 0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318), - 0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8), - 0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738), - 0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948), - 0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78), - 0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f), - 0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48), - 0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8), - 0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518), - 0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8), - 0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70), - 0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0), - 0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8), - 0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18), - 0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8), - 0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0), - 0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8), - 0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760), - 0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8), - 0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8), - 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70), - 0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00), - 0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070), - 0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528), - 0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688), - 0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8), - #0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90), - 0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8), - 0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0), - 0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570), - 0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770), - 0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898), - 0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0) -} - ow_loc_prize_table = { 'Master Sword Pedestal': (0x06d, 0x070), 'Hobo': (0xb80, 0xb90), @@ -2714,23 +2488,3 @@ H(38)| sss s| +-+-+-+-+-+-+-+-+ +-+ | s +-+-+-+ s +-+ Zora: |s| H(38)| |s|s|s| |s| +-+ +---+-+-+-+---+-+""" - -flute_spoiler_table = \ -""" 0 1 2 3 4 5 6 7 - +---+-+---+---+-+ - 01234567 A(00)| |s| | |s| - +--------+ | s +-+ s | s +-+ -A(00)|s ss s s| B(08)| |s| | |s| -B(08)| s s| +-+-+-+-+-+-+-+-+ -C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s| -D(18)|s ss ss | +-+-+-+-+-+-+-+-+ -E(20)| s s | D(18)| |s| |s| | -F(28)|ssssssss| | s +-+ s +-+ s | -G(30)|s ssss s| E(20)| |s| |s| | -H(38)| sss s| +-+-+-+-+-+-+-+-+ - +--------+ F(28)|s|s|s|s|s|s|s|s| - +-+-+-+-+-+-+-+-+ - G(30)| |s|s|s| |s| - | s +-+-+-+ s +-+ - H(38)| |s|s|s| |s| - +---+-+-+-+---+-+""" diff --git a/Rom.py b/Rom.py index 3af11911..e0e844d4 100644 --- a/Rom.py +++ b/Rom.py @@ -47,6 +47,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings from source.item.FillUtil import valid_pot_items from source.overworld.EntranceData import door_addresses, ow_prize_table from source.overworld.EntranceShuffle2 import exit_ids +from source.overworld.FluteShuffle import default_flute_connections, flute_data from Text import ( Blacksmiths_texts, Blind_texts, @@ -84,7 +85,7 @@ from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc from Versions import DRVersion, GKVersion, ORVersion JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '00f12bfc7513694db032237fbc35b4f8' +RANDOMIZERBASEHASH = '2b779fef9f1a744288cbbfdf1c55d76e' class JsonRom(object): @@ -536,7 +537,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): else: flute_spots = world.owflutespots[player] owFlags |= 0x0100 - write_int16(rom, snes_to_pc(0x0AB7F7), 0xEAEA) + if world.owFluteShuffle[player] != 'vanilla': + write_int16(rom, snes_to_pc(0x0AB80B), 0xEAEA) flute_writes = [(f, flute_data[f][1]) for f in flute_spots] for o in range(0, len(flute_writes)): @@ -1580,7 +1582,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None): map_x = x_map_position[idx] map_y = y_map_position[idx] if owid != 0xFF: - if (owid < 0x40) == (world.is_tile_swapped(owid, player)): + if (owid < 0x40) == (owid in world.owswaps[player][0]): coord_flags |= 0x8000 # world indicator flag if coord_flags & 0x4000 == 0: map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0) diff --git a/Text.py b/Text.py index ea46a307..c8cccada 100644 --- a/Text.py +++ b/Text.py @@ -135,6 +135,7 @@ Triforce_texts = [ " I promise the\n next seed will\n be better.", "\n Honk.", " Breakfast\n is served!", + "\n send help", ] BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!'] Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?'] diff --git a/Versions.py b/Versions.py index 7dca9f70..7ca3dd9c 100644 --- a/Versions.py +++ b/Versions.py @@ -1,3 +1,3 @@ GKVersion = '1.0.0' -ORVersion = '0.7.0.1' +ORVersion = '0.7.0.2' DRVersion = '1.5.2-u' diff --git a/data/base2current.bps b/data/base2current.bps index f5a975c6..f6038956 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index c5695cfe..a3703ddc 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -11,6 +11,7 @@ from yaml.representer import Representer import RaceRandom as random from BaseClasses import DoorType, LocationType from OverworldShuffle import default_flute_connections, flute_data +from source.overworld.FluteShuffle import default_flute_connections, flute_data from source.tools.MysteryUtils import get_weights, roll_settings from Utils import HexInt, hex_representer diff --git a/source/overworld/FluteShuffle.py b/source/overworld/FluteShuffle.py new file mode 100644 index 00000000..4067a49a --- /dev/null +++ b/source/overworld/FluteShuffle.py @@ -0,0 +1,270 @@ +import copy +import logging + +import RaceRandom as random +from BaseClasses import Entrance, RegionType, Terrain +from DungeonGenerator import GenerationException +from OWEdges import OWTileRegions +from source.overworld.EntranceShuffle2 import connect_simple + + +def shuffle_flute_spots(world, player): + def connect_flutes(flute_destinations): + for o in range(0, len(flute_destinations)): + owid = flute_destinations[o] + regions = flute_data[owid][0] + if not world.is_tile_swapped(owid, player): + connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player) + else: + connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player) + + if world.owFluteShuffle[player] == 'vanilla': + flute_spots = default_flute_connections.copy() + sort_flute_spots(world, player, flute_spots) + world.owflutespots[player] = flute_spots + connect_flutes(flute_spots) + else: + from OverworldShuffle import one_way_ledges + + flute_spots = 8 + flute_pool = list(flute_data.keys()) + new_spots = list() + ignored_regions = set() + used_flute_regions = [] + forbidden_spots = [] + forbidden_regions = [] + + def addSpot(owid, ignore_proximity, forced): + if world.owFluteShuffle[player] == 'balanced': + def getIgnored(regionname, base_owid, owid): + region = world.get_region(regionname, player) + for exit in region.exits: + if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored: + if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid): + new_ignored.add(exit.connected_region.name) + getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name]) + if regionname in one_way_ledges: + for ledge_region in one_way_ledges[regionname]: + if ledge_region not in new_ignored: + new_ignored.add(ledge_region) + getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region]) + + if not world.is_tile_swapped(owid, player): + new_region = flute_data[owid][0][0] + else: + new_region = flute_data[owid][0][1] + + if new_region in ignored_regions and not forced: + return False + + new_ignored = {new_region} + getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region]) + if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): + return False + ignored_regions.update(new_ignored) + if owid in flute_pool: + flute_pool.remove(owid) + if ignore_proximity and not forced: + logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') + logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') + new_spots.append(owid) + else: + # TODO: Inspect later, seems to happen only with 'random' flute shuffle + logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}') + return True + + if world.customizer: + custom_spots = world.customizer.get_owflutespots() + if custom_spots and player in custom_spots: + if 'force' in custom_spots[player]: + for id in custom_spots[player]['force']: + owid = id & 0xBF + addSpot(owid, True, True) + flute_spots -= 1 + if not world.is_tile_swapped(owid, player): + used_flute_regions.append(flute_data[owid][0][0]) + else: + used_flute_regions.append(flute_data[owid][0][1]) + if 'forbid' in custom_spots[player]: + for id in custom_spots[player]['forbid']: + owid = id & 0xBF + if owid not in new_spots: + forbidden_spots.append(owid) + if not world.is_tile_swapped(owid, player): + forbidden_regions.append(flute_data[owid][0][0]) + else: + forbidden_regions.append(flute_data[owid][0][1]) + + # determine sectors (isolated groups of regions) to place flute spots + flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots} + flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]] + flute_sectors = [s for s in flute_sectors if len(s[1]) > 0] + region_total = sum([c for c,_ in flute_sectors]) + sector_total = len(flute_sectors) + empty_sector_total = 0 + sector_has_spot = [] + + # determine which sectors still need a flute spot + for sector in flute_sectors: + already_has_spot = any(region in sector for region in used_flute_regions) + sector_has_spot.append(already_has_spot) + if not already_has_spot: + empty_sector_total += 1 + if flute_spots < empty_sector_total: + logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail') + # pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable + for i in range(len(flute_sectors)): + if not sector_has_spot[i]: + sector_has_spot[i] = True + empty_sector_total -= 1 + if flute_spots == empty_sector_total: + break + + # distribute flute spots for each sector + for i in range(len(flute_sectors)): + sector = flute_sectors[i] + sector_total -= 1 + if not sector_has_spot[i]: + empty_sector_total -= 1 + spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) + target_spots = len(new_spots) + spots_to_place + logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)') + + if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]): + addSpot(0x30, True, True) # guarantee desert/mire access + + random.shuffle(sector[1]) + f = 0 + t = 0 + while len(new_spots) < target_spots: + if f >= len(sector[1]): + f = 0 + t += 1 + if t > 5: + raise GenerationException('Infinite loop detected in flute shuffle') + owid = flute_regions[sector[1][f]] + if owid not in new_spots and owid not in forbidden_spots: + addSpot(owid, t > 0, False) + f += 1 + + region_total -= sector[0] + flute_spots -= spots_to_place + + # connect new flute spots + sort_flute_spots(world, player, new_spots) + world.owflutespots[player] = new_spots + connect_flutes(new_spots) + + # update spoiler + #new_spots = list(map(lambda o: flute_data[o][1], new_spots)) + s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)])) + text_output = flute_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[0x32],s[0x33],s[0x34], s[0x37], + s[0x30], s[0x35], + s[0x3a],s[0x3b],s[0x3c], s[0x3f]) + world.spoiler.set_map('flute', text_output, new_spots, player) + create_dynamic_flute_exits(world, player) + + +def sort_flute_spots(world, player, flute_spots): + if world.owLayout[player] != 'grid': + flute_spots.sort(key=lambda id: flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04) + else: + world_layout = world.owgrid[player][0] if world.mode[player] != 'inverted' else world.owgrid[player][1] + layout_list = sum(world_layout, []) + layout_map = {id & 0xBF: i for i, id in enumerate(layout_list)} + flute_spots.sort(key=lambda id: layout_map[flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04]) + + +def create_dynamic_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 + exit = Entrance(region.player, exitname, region) + exit.spot_type = 'Flute' + exit.connect(world.get_region('Flute Sky', player)) + region.exits.append(exit) + world.initialize_regions() + + +default_flute_connections = [ + 0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f +] + +flute_data = { + #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, 0x01d8, 0x0518), + 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), + 0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98), + 0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0), + 0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8), + 0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58), + 0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088), + 0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318), + 0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8), + 0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738), + 0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948), + 0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78), + 0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f), + 0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48), + 0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8), + 0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518), + 0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8), + 0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70), + 0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0), + 0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8), + 0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18), + 0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8), + 0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0), + 0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8), + 0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760), + 0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8), + 0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8), + 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70), + 0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00), + 0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070), + 0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528), + 0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688), + 0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8), + #0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90), + 0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8), + 0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0), + 0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570), + 0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770), + 0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898), + 0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0) +} + +flute_spoiler_table = \ +""" 0 1 2 3 4 5 6 7 + +---+-+---+---+-+ + 01234567 A(00)| |s| | |s| + +--------+ | s +-+ s | s +-+ +A(00)|s ss s s| B(08)| |s| | |s| +B(08)| s s| +-+-+-+-+-+-+-+-+ +C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s| +D(18)|s ss ss | +-+-+-+-+-+-+-+-+ +E(20)| s s | D(18)| |s| |s| | +F(28)|ssssssss| | s +-+ s +-+ s | +G(30)|s ssss s| E(20)| |s| |s| | +H(38)| sss s| +-+-+-+-+-+-+-+-+ + +--------+ F(28)|s|s|s|s|s|s|s|s| + +-+-+-+-+-+-+-+-+ + G(30)| |s|s|s| |s| + | s +-+-+-+ s +-+ + H(38)| |s|s|s| |s| + +---+-+-+-+---+-+""" diff --git a/source/overworld/LayoutGenerator.py b/source/overworld/LayoutGenerator.py index 467941ab..15c77bc3 100644 --- a/source/overworld/LayoutGenerator.py +++ b/source/overworld/LayoutGenerator.py @@ -1,9 +1,10 @@ import copy import logging -import RaceRandom as random import random as _random -from typing import List, Dict, Optional, Set, Tuple -from BaseClasses import OWEdge, World, Direction, Terrain +from typing import Dict, List, Optional, Set, Tuple + +import RaceRandom as random +from BaseClasses import Direction, OWEdge, Terrain, World from OverworldShuffle import connect_two_way, validate_layout ENABLE_KEEP_SIMILAR_SPECIAL_HANDLING = False diff --git a/source/overworld/LayoutVisualizer.py b/source/overworld/LayoutVisualizer.py index 8dbdea34..3bc76b77 100644 --- a/source/overworld/LayoutVisualizer.py +++ b/source/overworld/LayoutVisualizer.py @@ -2,10 +2,13 @@ import logging import os from datetime import datetime from typing import Dict, List + from PIL import Image, ImageDraw + from BaseClasses import Direction, OWEdge from source.overworld.LayoutGenerator import Screen + def get_edge_lists(grid: List[List[List[int]]], overworld_screens: Dict[int, Screen], large_screen_quadrant_info: Dict[int, Dict]) -> Dict: