diff --git a/BaseClasses.py b/BaseClasses.py index 1ed73865..3b67ffcc 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -26,6 +26,7 @@ class World(object): self.owShuffle = owShuffle.copy() self.owSwap = owSwap.copy() self.owKeepSimilar = {} + self.owFluteShuffle = {} self.shuffle = shuffle.copy() self.doorShuffle = doorShuffle.copy() self.intensity = {} @@ -76,6 +77,7 @@ class World(object): self.owswaps = {} self.owedges = [] self._owedge_cache = {} + self.owflutespots = {} self.doors = [] self._door_cache = {} self.paired_doors = {} @@ -2157,6 +2159,7 @@ class Spoiler(object): 'ow_shuffle': self.world.owShuffle, 'ow_swap': self.world.owSwap, 'ow_keepsimilar': self.world.owKeepSimilar, + 'ow_fluteshuffle': self.world.owFluteShuffle, 'shuffle': self.world.shuffle, 'door_shuffle': self.world.doorShuffle, 'intensity': self.world.intensity, @@ -2239,6 +2242,7 @@ class Spoiler(object): outfile.write('Overworld Tile Swap:'.ljust(line_width) + '%s\n' % self.metadata['ow_swap'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla': outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No')) + outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_fluteshuffle'][player] != 'vanilla' else 'No')) outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player]) outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player]) outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player]) diff --git a/CLI.py b/CLI.py index e1c61fd1..3e13f790 100644 --- a/CLI.py +++ b/CLI.py @@ -93,7 +93,8 @@ def parse_cli(argv, no_defaults=False): for player in range(1, multiargs.multi + 1): playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', 'ow_swap', 'ow_keepsimilar', + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', + 'ow_shuffle', 'ow_swap', 'ow_keepsimilar', 'ow_fluteshuffle', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', @@ -144,6 +145,7 @@ def parse_settings(): "ow_shuffle": "vanilla", "ow_swap": "vanilla", "ow_keepsimilar": False, + "ow_fluteshuffle": "vanilla", "shuffle": "vanilla", "shufflelinks": False, diff --git a/Main.py b/Main.py index 96fbe6c2..f7dee19f 100644 --- a/Main.py +++ b/Main.py @@ -74,6 +74,7 @@ def main(args, seed=None, fish=None): world.crystals_ganon_orig = args.crystals_ganon.copy() world.crystals_gt_orig = args.crystals_gt.copy() world.owKeepSimilar = args.ow_keepsimilar.copy() + world.owFluteShuffle = args.ow_fluteshuffle.copy() world.open_pyramid = args.openpyramid.copy() world.boss_shuffle = args.shufflebosses.copy() world.enemy_shuffle = args.shuffleenemies.copy() @@ -383,6 +384,8 @@ def copy_world(world): ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy() + ret.owKeepSimilar = world.owKeepSimilar.copy() + ret.owFluteShuffle = world.owFluteShuffle.copy() ret.open_pyramid = world.open_pyramid.copy() ret.boss_shuffle = world.boss_shuffle.copy() ret.enemy_shuffle = world.enemy_shuffle.copy() diff --git a/Mystery.py b/Mystery.py index 64f2fa73..9fb6155a 100644 --- a/Mystery.py +++ b/Mystery.py @@ -133,9 +133,11 @@ def roll_settings(weights): overworld_shuffle = get_choice('overworld_shuffle') ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' - overworld_shuffle = get_choice('overworld_swap') + overworld_swap = get_choice('overworld_swap') ret.ow_swap = overworld_swap if overworld_swap != 'none' else 'vanilla' ret.ow_keepsimilar = get_choice('ow_keepsimilar') + overworld_flute = get_choice('overworld_flute') + ret.ow_swap = overworld_flute if overworld_flute != 'none' else 'vanilla' entrance_shuffle = get_choice('entrance_shuffle') ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' door_shuffle = get_choice('door_shuffle') diff --git a/OWEdges.py b/OWEdges.py index 671f3609..3eb17751 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -722,8 +722,8 @@ OWTileGroups = { ], [ 'East Dark Death Mountain (Top)', - 'East Dark Death Mountain (Bottom)', - 'East Dark Death Mountain (Bottom Left)' + 'East Dark Death Mountain (Bottom Left)', + 'East Dark Death Mountain (Bottom)' ] ), ("East Mountain", "Entrance"): ( diff --git a/OverworldShuffle.py b/OverworldShuffle.py index a46b4a71..8da0ac96 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1,4 +1,5 @@ import random, logging, copy +from sortedcontainers import SortedList from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot from OWEdges import OWTileGroups, OWEdgeGroups, OpenStd, parallel_links, IsParallel @@ -141,14 +142,39 @@ def link_overworld(world, player): region = world.get_region(name, player) region.type = RegionType.LightWorld - #make new connections - for owid in flute_connections.keys(): - (spot, dest) = flute_connections[owid] - if (world.mode[player] == 'inverted') == (owid in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - connect_simple(world, spot, dest[0], player) - else: - connect_simple(world, spot, dest[1], player) + # flute shuffle + def connect_flutes(flute_destinations): + for o in range(0, len(flute_destinations)): + owid = flute_destinations[o] + regions = flute_data[owid][0] + if (world.mode[player] == 'inverted') == (owid in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + 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': + connect_flutes(default_flute_connections) + else: + flute_pool = list(flute_data.keys()) + new_spots = SortedList() + + # guarantee desert/mire access + flute_pool.remove(0x30) + new_spots.add(0x30) + # guarantee mountain access + owid = random.randint(0, 2) * 2 + 3 + flute_pool.remove(owid) + new_spots.add(owid) + + random.shuffle(flute_pool) + f = 0 + while len(new_spots) < 8: + new_spots.add(flute_pool[f]) + f += 1 + world.owflutespots[player] = new_spots + connect_flutes(new_spots) + + # make new connections for owid in ow_connections.keys(): if (world.mode[player] == 'inverted') == (owid in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): for (exitname, regionname) in ow_connections[owid][0]: @@ -688,15 +714,9 @@ mandatory_connections = [('Flute Away', 'Flute Sky'), ('Dark Tree Line WC Cliff Water Drop', 'Dark Tree Line Water') #fake flipper ] -flute_connections = {0x03: ('Flute Spot 1', ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)']), - 0x16: ('Flute Spot 2', ['Potion Shop Area', 'Dark Witch Area']), - 0x18: ('Flute Spot 3', ['Kakariko Area', 'Village of Outcasts Area']), - 0x2c: ('Flute Spot 4', ['Links House Area', 'Big Bomb Shop Area']), - 0x2f: ('Flute Spot 5', ['Eastern Nook Area', 'Palace of Darkness Nook Area']), - 0x30: ('Flute Spot 6', ['Desert Palace Teleporter Ledge', 'Misery Mire Teleporter Ledge']), - 0x3b: ('Flute Spot 7', ['Dam Area', 'Swamp Area']), - 0x3f: ('Flute Spot 8', ['Octoballoon Area', 'Bomber Corner Area']) - } +default_flute_connections = [ + 0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f +] ow_connections = { 0x00: ([ @@ -1193,3 +1213,47 @@ default_connections = [('Lost Woods SW', 'Lost Woods Pass NW'), ('West Dark Death Mountain ES', 'East Dark Death Mountain WS'), ('East Dark Death Mountain EN', 'Turtle Rock WN') ] + +flute_data = { + #OWID LW Region DW Region VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX + 0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290), + 0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550), + 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680), + 0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8), + 0x07: (['Death Mountain TR Pegs', 'Turtle Rock Area'], 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20), + 0x0a: (['Mountain Entry Area', 'Bumper Cave Area'], 0x0180, 0x0220, 0x0206, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488), + 0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x02d0, 0x0f38), + 0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x0448, 0x0058), + 0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x0588, 0x0318), + 0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x0578, 0x04f8), + 0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x0508, 0x0758), + 0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x0580, 0x0928, 0x0580, 0x0948), + 0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x04f8, 0x0b60), + 0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x0598, 0x0ccf), + 0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x04e0, 0x0f68), + 0x18: (['Kakariko Area', 'Village of Outcasts Area'], 0x0b30, 0x0759, 0x017e, 0x07c8, 0x01f8, 0x07c6, 0x020b, 0x0007, 0x0002, 0x07c0, 0x0210, 0x01f8, 0x07c6), + 0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0548), + 0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07d8, 0x07f8), + 0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0720, 0x0a80), + 0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80), + 0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0908, 0x04d8), + 0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x08b8, 0x0b08), + 0x28: (['Maze Race Area', 'Dig Game Area'], 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0b88, 0x00b8), + 0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0ae0, 0x02c0), + 0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b10, 0x04e8), + 0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b30, 0x0770), + 0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0b20, 0x08b8), + 0x2d: (['Stone Bridge Area', 'Hammer Bridge South Area'], 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0), + 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58), + 0x2f: (['Eastern Nook Area', 'Palace of Darkness Nook Area'], 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30), + 0x30: (['Desert Palace Teleporter Ledge', 'Misery Mire Teleporter Ledge'], 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070), + 0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0cd6, 0x05a8), + 0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0c80, 0x0628), + 0x34: (['Statues Area', 'Hype Cave Area'], 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0d60, 0x08d8), + 0x35: (['Lake Hylia Area', 'Ice Lake Area'], 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88), + 0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d48, 0x0ed0), + 0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0e70, 0x0540), + 0x3b: (['Dam Area', 'Swamp Area'], 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0f30, 0x0770), + 0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0f38, 0x0898), + 0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0f80, 0x0ef0) +} diff --git a/Plando.py b/Plando.py index 5b66876e..254a92b6 100755 --- a/Plando.py +++ b/Plando.py @@ -23,7 +23,7 @@ def main(args): start_time = time.process_time() # initialize the world - world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False) + world = World(1, 'vanilla', 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False) world.player_names[1].append("Player 1") logger = logging.getLogger('') diff --git a/README.md b/README.md index 67d95cdd..53c6ce11 100644 --- a/README.md +++ b/README.md @@ -85,29 +85,45 @@ OW tiles remain in their original world, but transitions can now be travel cross This keeps similar edge transitions together. ie. The 2 west edges of Potion Shop will be paired to another set of two similar edges +## Flute Shuffle (--ow_fluteshuffle) + +### Vanilla + +Flute spots remain unchanged. + +### Random + +New flute spots are chosen at random. You can also cancel out of the flute menu by pressing X. + # Command Line Options ``` --h, --help +-h, --help ``` Show the help message and exit. ``` ---ow_shuffle +--ow_shuffle ``` For specifying the overworld layout shuffle you want as above. (default: vanilla) ``` ---ow_swap +--ow_swap ``` For specifying the overworld tile swap you want as above. (default: vanilla) ``` ---ow_keepsimilar +--ow_keepsimilar ``` This keeps similar edge transitions paired together with other pairs of transitions + +``` +--ow_fluteshuffle +``` + +For randomizing the flute spots around the overworld diff --git a/Rom.py b/Rom.py index 7b2a9074..adfb4f1b 100644 --- a/Rom.py +++ b/Rom.py @@ -24,10 +24,11 @@ from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc from Items import ItemFactory from EntranceShuffle import door_addresses, exit_ids +from OverworldShuffle import default_flute_connections, flute_data JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '5fecbc1016544cfdcc596abffaed6736' +RANDOMIZERBASEHASH = '6e44346357f8a9471a8499ab787635c1' class JsonRom(object): @@ -625,6 +626,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): owFlags = 0 if world.owKeepSimilar[player]: owFlags |= 0x1 + if world.owFluteShuffle[player] != 'vanilla': + owFlags |= 0x100 write_int16(rom, 0x150004, owFlags) @@ -645,6 +648,37 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc) write_int16(rom, edge.getAddress() + 0x0e, edge.getTarget()) + # patch flute spots + if world.owFluteShuffle[player] == 'vanilla': + flute_spots = default_flute_connections + else: + flute_spots = world.owflutespots[player] + + for o in range(0, len(flute_spots)): + owid = flute_spots[o] + offset = 0 + if (world.mode[player] == 'inverted') != (owid in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + offset = 0x40 + + data = flute_data[owid] + write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid + write_int16(rom, snes_to_pc(0x02E86B + (o * 2)), data[1]) #vram + write_int16(rom, snes_to_pc(0x02E88D + (o * 2)), data[2]) # BG scroll Y + write_int16(rom, snes_to_pc(0x02E8AF + (o * 2)), data[3]) # BG scroll X + write_int16(rom, snes_to_pc(0x02E8D1 + (o * 2)), data[12] if offset > 0 and len(data) > 12 else data[4]) # link Y + write_int16(rom, snes_to_pc(0x02E8F3 + (o * 2)), data[13] if offset > 0 and len(data) > 12 else data[5]) # link X + write_int16(rom, snes_to_pc(0x02E915 + (o * 2)), data[6]) # cam Y + write_int16(rom, snes_to_pc(0x02E937 + (o * 2)), data[7]) # cam X + write_int16(rom, snes_to_pc(0x02E959 + (o * 2)), data[8]) # unknown 1 + write_int16(rom, snes_to_pc(0x02E97B + (o * 2)), data[9]) # unknown 2 + + # flute menu blips + rom.buffer[snes_to_pc(0x0AB783 + o)] = data[11] & 0xff # X low byte + rom.buffer[snes_to_pc(0x0AB78B + o)] = data[11] // 0x100 # X high byte + rom.buffer[snes_to_pc(0x0AB793 + o)] = data[10] & 0xff # Y low byte + rom.buffer[snes_to_pc(0x0AB79B + o)] = data[10] // 0x100 # Y high byte + + # patch entrance/exits/holes for region in world.regions: for exit in region.exits: @@ -2285,7 +2319,7 @@ def set_inverted_mode(world, player, rom): write_int16(rom, 0x15AEE + 2*0x25, 0x000C) if (world.mode[player] == 'inverted') != (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E849), 0x0043) #flute spot + #write_int16(rom, snes_to_pc(0x02E849), 0x0043) #flute spot if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']: rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) # mountain cave starts on OW @@ -2352,12 +2386,12 @@ def set_inverted_mode(world, player, rom): write_int16(rom, 0x15AEE + 2*0x18, 0x00E6) # DMD west UW to bumper cave top entrance if (world.mode[player] == 'inverted') != (0x10 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): rom.write_bytes(snes_to_pc(0x1BC67A), [0x2E, 0x0B, 0x82]) # add warp under rock - if (world.mode[player] == 'inverted') != (0x16 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E84B), 0x0056) #flute spot - if (world.mode[player] == 'inverted') != (0x18 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E84D), 0x0058) #flute spot - write_int16(rom, snes_to_pc(0x02E8D5), 0x07C8) #flute spot - write_int16(rom, snes_to_pc(0x02E8F7), 0x01F8) #flute spot + #if (world.mode[player] == 'inverted') != (0x16 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + #write_int16(rom, snes_to_pc(0x02E84B), 0x0056) #flute spot + #if (world.mode[player] == 'inverted') != (0x18 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + #write_int16(rom, snes_to_pc(0x02E84D), 0x0058) #flute spot + #write_int16(rom, snes_to_pc(0x02E8D5), 0x07C8) #flute spot + #write_int16(rom, snes_to_pc(0x02E8F7), 0x01F8) #flute spot if (world.mode[player] == 'inverted') != (0x1B in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): write_int16(rom, 0x15AEE + 2 * 0x06, 0x0020) # post aga hyrule castle spawn rom.write_byte(0x15B8C + 0x06, 0x1B) @@ -2466,26 +2500,26 @@ def set_inverted_mode(world, player, rom): if (world.mode[player] == 'inverted') != (0x29 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05]) #frog pickup on contact if (world.mode[player] == 'inverted') != (0x2C in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E84F), 0x006C) #flute spot + #write_int16(rom, snes_to_pc(0x02E84F), 0x006C) #flute spot if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: rom.write_byte(0x15B8C, 0x6C) #exit links at bomb shop area rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house rom.write_byte(0xDBB73 + 0x52, 0x01) if (world.mode[player] == 'inverted') != (0x2F in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E851), 0x006F) #flute spot + #write_int16(rom, snes_to_pc(0x02E851), 0x006F) #flute spot rom.write_bytes(snes_to_pc(0x1BC80D), [0xB2, 0x0B, 0x82]) # add warp under rock if (world.mode[player] == 'inverted') != (0x30 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E853), 0x0070) #flute spot + #write_int16(rom, snes_to_pc(0x02E853), 0x0070) #flute spot rom.write_bytes(snes_to_pc(0x1BC81E), [0x94, 0x1D, 0x82]) # add warp under rock if (world.mode[player] == 'inverted') != (0x33 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): rom.write_bytes(snes_to_pc(0x1BC3DF), [0xD8, 0xD1]) # add warp under rock rom.write_bytes(snes_to_pc(0x1BD1D8), [0xA8, 0x02, 0x82, 0xFF, 0xFF]) # add warp under rock if (world.mode[player] == 'inverted') != (0x35 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) # add warp under rock - if (world.mode[player] == 'inverted') != (0x3B in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E855), 0x007B) #flute spot - if (world.mode[player] == 'inverted') != (0x3F in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - write_int16(rom, snes_to_pc(0x02E857), 0x007F) #flute spot + #if (world.mode[player] == 'inverted') != (0x3B in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + #write_int16(rom, snes_to_pc(0x02E855), 0x007B) #flute spot + #if (world.mode[player] == 'inverted') != (0x3F in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + #write_int16(rom, snes_to_pc(0x02E857), 0x007F) #flute spot if world.mode[player] == 'inverted': rom.write_byte(0x15B8C + 0x3D, rom.buffer[0x15B8C]) # houlihan exit diff --git a/asm/owrando.asm b/asm/owrando.asm index f0094b38..3bfb5b67 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -106,7 +106,7 @@ OWWorldCheck16: OWFluteCancel: { - lda.l OWFlags+1 : and #$10 : bne + + lda.l OWFlags+1 : and #$01 : bne + jsl $02e99d : rtl + lda $7f5006 : cmp #$01 : beq + jsl $02e99d @@ -117,7 +117,7 @@ OWFluteCancel2: lda $f2 : ora $f0 : and #$c0 : bne + jml $0ab7bd + inc $0200 - lda.l OWFlags+1 : and #$10 : beq + + lda.l OWFlags+1 : and #$01 : beq + lda $f2 : cmp #$40 : bne + lda #$01 : sta $7f5006 + rtl diff --git a/data/base2current.bps b/data/base2current.bps index 61633070..d788ef02 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 34066b63..ebb0a9ba 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -127,6 +127,12 @@ "action": "store_true", "type": "bool" }, + "ow_fluteshuffle": { + "choices": [ + "vanilla", + "random" + ] + }, "shuffle": { "choices": [ "vanilla", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 97ef3fd4..4fb6f25f 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -214,6 +214,11 @@ "ow_keepsimilar": [ "This keeps similar edge transitions together. ie. the two west edges on", "Potion Shop will be paired with another similar pair." ], + "ow_fluteshuffle": [ + "This randomizes the flute spot destinations.", + "Vanilla: All flute spots remain unchanged.", + "Random: New flute spots will be generated." + ], "door_shuffle": [ "Select Door Shuffling Algorithm. (default: %(default)s)", "Basic: Doors are mixed within a single dungeon.", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 007bb73d..f0cab9b1 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -120,6 +120,10 @@ "randomizer.overworld.overworldswap.crossed": "Crossed", "randomizer.overworld.keepsimilar": "Keep Similar Edges Together", + + "randomizer.overworld.overworldflute": "Flute Shuffle", + "randomizer.overworld.overworldflute.vanilla": "Vanilla", + "randomizer.overworld.overworldflute.random": "Random", "randomizer.entrance.openpyramid": "Pre-open Pyramid Hole", "randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index b4b63e1f..13483ffe 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -17,6 +17,14 @@ "mixed", "crossed" ] + }, + "overworldflute": { + "type": "selectbox", + "default": "vanilla", + "options": [ + "vanilla", + "random" + ] } }, "rightOverworldFrame": { diff --git a/resources/app/meta/manifests/pip_requirements.txt b/resources/app/meta/manifests/pip_requirements.txt index faa3c48f..7b7724d8 100644 --- a/resources/app/meta/manifests/pip_requirements.txt +++ b/resources/app/meta/manifests/pip_requirements.txt @@ -1,6 +1,7 @@ aenum fast-enum python-bps-continued +sortedcontainers colorama aioconsole websockets diff --git a/source/classes/constants.py b/source/classes/constants.py index ddab86c0..d345c637 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -75,7 +75,8 @@ SETTINGSTOPROCESS = { "overworld": { "overworldshuffle": "ow_shuffle", "overworldswap": "ow_swap", - "keepsimilar": "ow_keepsimilar" + "keepsimilar": "ow_keepsimilar", + "overworldflute": "ow_fluteshuffle" }, "entrance": { "openpyramid": "openpyramid",