From 9153dbd38268cc62008f9e775312698fd7b4c766 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 12 Aug 2021 13:44:47 -0600 Subject: [PATCH 01/12] Fixed a problem that cropped up in retro+vanilla with big key placement --- Dungeons.py | 7 +++++++ KeyDoorShuffle.py | 7 ++++--- Main.py | 2 +- RELEASENOTES.md | 4 ++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Dungeons.py b/Dungeons.py index 596da920..81fc4941 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -375,6 +375,13 @@ flexible_starts = { 'Skull Woods': ['Skull Left Drop', 'Skull Pinball'] } +default_key_counts = { + 'Hyrule Castle': 1, 'Eastern Palace': 0, 'Desert Palace': 1, + 'Tower of Hera': 1, 'Agahnims Tower': 2, 'Palace of Darkness': 6, + 'Swamp Palace': 1, 'Skull Woods': 3, 'Thieves Town': 1, + 'Ice Palace': 2, 'Misery Mire': 3, 'Turtle Rock': 4, 'Ganons Tower': 4 +} + dungeon_keys = { 'Hyrule Castle': 'Small Key (Escape)', 'Eastern Palace': 'Small Key (Eastern Palace)', diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 549a739c..3ce9132f 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -4,7 +4,7 @@ from collections import defaultdict, deque from BaseClasses import DoorType from Regions import dungeon_events -from Dungeons import dungeon_keys, dungeon_bigs +from Dungeons import dungeon_keys, dungeon_bigs, default_key_counts from DungeonGenerator import ExplorationState, special_big_key_doors @@ -1384,9 +1384,10 @@ def create_key_counters(key_layout, world, player): flat_proposal = key_layout.flat_prop state = ExplorationState(dungeon=key_layout.sector.name) if world.doorShuffle[player] == 'vanilla': - state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys) + state.key_locations = default_key_counts[key_layout.sector.name] else: - state.key_locations = world.dungeon_layouts[player][key_layout.sector.name].key_doors_num + builder = world.dungeon_layouts[player][key_layout.sector.name] + state.key_locations = builder.key_doors_num - builder.key_drop_cnt state.big_key_special, special_region = False, None for region in key_layout.sector.regions: for location in region.locations: diff --git a/Main.py b/Main.py index df4426e8..eb36681f 100644 --- a/Main.py +++ b/Main.py @@ -28,7 +28,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.5.0.2-u' +__version__ = '0.5.0.3-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b6be375d..3b414321 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,10 @@ CLI: ```--bomblogic``` # Bug Fixes and Notes. +* 0.5.0.3 + * Fixed a bug in retro+vanilla and big key placement + * Fixed a problem with shops not registering in the Multiclient until you visit one + * Fixed a bug in the Mystery code with sfx * 0.5.0.2 * --shuffle_sfx option added * 0.5.0.1 From d6a56e645ecc741e782f1533ff7d0e0259b27587 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 12 Aug 2021 13:45:20 -0600 Subject: [PATCH 02/12] Bug with MutliClient that didn't register shop locations unless you were in one --- MultiClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiClient.py b/MultiClient.py index bf64a2d7..501630e0 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -846,7 +846,7 @@ async def track_locations(ctx : Context, roomid, roomdata): new_locations.append(Regions.lookup_name_to_id[location]) try: - if roomid in location_shop_ids: + if ctx.shop_mode or ctx.retro_mode: misc_data = await snes_read(ctx, SHOP_ADDR, SHOP_SRAM_LEN) for cnt, b in enumerate(misc_data): my_check = Regions.shop_table_by_location_id[0x400000 + cnt] From de6f3683d0ae9b7b9ac0bea7a8f95d1743a94326 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 12 Aug 2021 13:45:51 -0600 Subject: [PATCH 03/12] Fixed bug with sfx_shuffle in mystery --- Mystery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mystery.py b/Mystery.py index 306b5c27..c0666e6a 100644 --- a/Mystery.py +++ b/Mystery.py @@ -234,7 +234,7 @@ def roll_settings(weights): ret.heartbeep = get_choice('heartbeep', romweights) ret.ow_palettes = get_choice('ow_palettes', romweights) ret.uw_palettes = get_choice('uw_palettes', romweights) - ret.uw_palettes = get_choice('shuffle_sfx', romweights) == 'on' + ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on' return ret From cda70f783a375e642d61ac4fc08690fc620af59b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 13 Aug 2021 22:11:58 -0500 Subject: [PATCH 04/12] Removal of legacy ER shuffles --- BaseClasses.py | 10 +- ER_hint_reference.txt | 2 +- EntranceShuffle.py | 572 ------------------ ItemList.py | 6 +- resources/app/cli/args.json | 4 - resources/app/cli/lang/en.json | 4 +- .../app/gui/randomize/entrando/widgets.json | 4 - 7 files changed, 10 insertions(+), 592 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7e866f5d..32028111 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2553,11 +2553,11 @@ class Pot(object): self.flags = flags -# byte 0: DDOO EEEE (DR, OR, ER) +# byte 0: DDOO OEEE (DR, OR, ER) dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0} -or_mode = {"parallel": 1, "full": 2, "vanilla": 0} -er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, "restricted_legacy": 8, - "full_legacy": 9, "madness_legacy": 10, "insanity_legacy": 11, "dungeonsfull": 7, "dungeonssimple": 6} +or_mode = {"vanilla": 0, "parallel": 1, "full": 2} +orswap_mode = {"vanilla": 0, "mixed": 1, "crossed": 1} +er_mode = {"vanilla": 0, "simple": 1, "restricted": 3, "full": 3, "crossed": 4, "insanity": 5, "dungeonsfull": 7, "dungeonssimple": 7} # byte 1: LLLW WSSR (logic, mode, sword, retro) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} @@ -2592,7 +2592,7 @@ class Settings(object): @staticmethod def make_code(w, p): code = bytes([ - (dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 4) | er_mode[w.shuffle[p]], + (dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 4) | (orswap_mode[w.owSwap[p]] << 3) | er_mode[w.shuffle[p]], (logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3) | (sword_mode[w.swords[p]] << 1) | (1 if w.retro[p] else 0), diff --git a/ER_hint_reference.txt b/ER_hint_reference.txt index 999fb436..8bc73fc9 100644 --- a/ER_hint_reference.txt +++ b/ER_hint_reference.txt @@ -14,7 +14,7 @@ In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following ratios 8 hints for valuable items. 7 junk hints. -In the simple, restricted, and restricted legacy shuffles, these are the ratios: +In the simple, restricted shuffles, these are the ratios: 2 hints for inconvenient entrances. 1 hint for an inconvenient dungeon entrance. diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 9b31b652..dd98a9ed 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -374,61 +374,6 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif not invFlag and world.shuffle[player] == 'restricted_legacy': - simple_shuffle_dungeons(world, player) - - lw_entrances = list(LW_Entrances) - dw_entrances = list(DW_Entrances) - dw_must_exits = list(DW_Entrances_Must_Exit) - old_man_entrances = list(Old_Man_Entrances) - caves = list(Cave_Exits) - three_exit_caves = list(Cave_Three_Exits) - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - # only use two exit caves to do mandatory dw connections - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - # add three exit doors to pool for remainder - caves.extend(three_exit_caves) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - lw_entrances.extend(old_man_entrances) - random.shuffle(lw_entrances) - old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - - # place Old Man House in Light World - connect_caves(world, lw_entrances, [], Old_Man_House, player) - - # connect rest. There's 2 dw entrances remaining, so we will not run into parity issue placing caves - connect_caves(world, lw_entrances, dw_entrances, caves, player) - - # scramble holes - scramble_holes(world, player) - - # place blacksmith, has limited options - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place dam and pyramid fairy, have limited options - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) elif world.shuffle[player] == 'full': skull_woods_shuffle(world, player) @@ -769,324 +714,6 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, entrances, door_targets, player) - elif not invFlag and world.shuffle[player] == 'full_legacy': - skull_woods_shuffle(world, player) - - lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances) - dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances) - dw_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) - lw_must_exits = list(LW_Dungeon_Entrances_Must_Exit) - old_man_entrances = list(Old_Man_Entrances + ['Tower of Hera']) - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - else: - caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3))) - lw_entrances.append('Hyrule Castle Entrance (South)') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - else: - dw_entrances.append('Ganons Tower') - caves.append('Ganons Tower Exit') - - # we randomize which world requirements we fulfill first so we get better dungeon distribution - if random.randint(0, 1) == 0: - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - else: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - if world.mode[player] == 'standard': - # rest of hyrule castle must be in light world - connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [door for door in old_man_entrances if door in lw_entrances] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - lw_entrances.remove(old_man_exit) - - random.shuffle(lw_entrances) - old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - - # place Old Man House in Light World - connect_caves(world, lw_entrances, [], list(Old_Man_House), player) #need this to avoid badness with multiple seeds - - # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves, player) - - # scramble holes - scramble_holes(world, player) - - # place blacksmith, has limited options - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place bomb shop, has limited options - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) - elif not invFlag and world.shuffle[player] == 'madness_legacy': - # here lie dragons, connections are no longer two way - lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances) - dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances) - dw_entrances_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) - - lw_doors = list(LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit) + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', - 'Lumberjack Tree Cave'] + list(Old_Man_Entrances) - dw_doors = list(DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] - - random.shuffle(lw_doors) - random.shuffle(dw_doors) - - dw_entrances_must_exits.append('Skull Woods Second Section Door (West)') - dw_entrances.append('Skull Woods Second Section Door (East)') - dw_entrances.append('Skull Woods First Section Door') - - lw_entrances.extend(['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']) - - lw_entrances_must_exits = list(LW_Dungeon_Entrances_Must_Exit) - - old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera'] - - mandatory_light_world = ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)'] - mandatory_dark_world = [] - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) - - # shuffle up holes - - lw_hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave'] - dw_hole_entrances = ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] - - hole_targets = [('Kakariko Well Exit', 'Kakariko Well (top)'), - ('Bat Cave Exit', 'Bat Cave (right)'), - ('North Fairy Cave Exit', 'North Fairy Cave'), - ('Lost Woods Hideout Exit', 'Lost Woods Hideout (top)'), - ('Lumberjack Tree Exit', 'Lumberjack Tree (top)'), - (('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Back Drop')] - - if world.mode[player] == 'standard': - # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) - connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) - else: - lw_hole_entrances.append('Hyrule Castle Secret Entrance Drop') - hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) - lw_doors.append('Hyrule Castle Secret Entrance Stairs') - lw_entrances.append('Hyrule Castle Secret Entrance Stairs') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) - else: - dw_entrances.append('Ganons Tower') - caves.append('Ganons Tower Exit') - dw_hole_entrances.append('Pyramid Hole') - hole_targets.append(('Pyramid Exit', 'Pyramid')) - dw_entrances_must_exits.append('Pyramid Entrance') - dw_doors.extend(['Ganons Tower', 'Pyramid Entrance']) - - random.shuffle(lw_hole_entrances) - random.shuffle(dw_hole_entrances) - random.shuffle(hole_targets) - - # decide if skull woods first section should be in light or dark world - sw_light = random.randint(0, 1) == 0 - if sw_light: - sw_hole_pool = lw_hole_entrances - mandatory_light_world.append('Skull Woods First Section Exit') - else: - sw_hole_pool = dw_hole_entrances - mandatory_dark_world.append('Skull Woods First Section Exit') - for target in ['Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle']: - connect_entrance(world, sw_hole_pool.pop(), target, player) - - # sanctuary has to be in light world - connect_entrance(world, lw_hole_entrances.pop(), 'Sewer Drop', player) - mandatory_light_world.append('Sanctuary Exit') - - # fill up remaining holes - for hole in dw_hole_entrances: - exits, target = hole_targets.pop() - mandatory_dark_world.append(exits) - connect_entrance(world, hole, target, player) - - for hole in lw_hole_entrances: - exits, target = hole_targets.pop() - mandatory_light_world.append(exits) - connect_entrance(world, hole, target, player) - - # hyrule castle handling - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) - mandatory_light_world.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - else: - lw_doors.append('Hyrule Castle Entrance (South)') - lw_entrances.append('Hyrule Castle Entrance (South)') - caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - - # now let's deal with mandatory reachable stuff - def extract_reachable_exit(cavelist): - random.shuffle(cavelist) - candidate = None - for cave in cavelist: - if isinstance(cave, tuple) and len(cave) > 1: - # special handling: TRock and Spectracle Rock cave have two entries that we should consider entrance only - # ToDo this should be handled in a more sensible manner - if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2: - continue - candidate = cave - break - if candidate is None: - raise RuntimeError('No suitable cave.') - cavelist.remove(candidate) - return candidate - - def connect_reachable_exit(entrance, general, worldspecific, worldoors): - # select which one is the primary option - if random.randint(0, 1) == 0: - primary = general - secondary = worldspecific - else: - primary = worldspecific - secondary = general - - try: - cave = extract_reachable_exit(primary) - except RuntimeError: - cave = extract_reachable_exit(secondary) - - exit = cave[-1] - cave = cave[:-1] - connect_exit(world, exit, entrance, player) - connect_entrance(world, worldoors.pop(), exit, player) - # rest of cave now is forced to be in this world - worldspecific.append(cave) - - # we randomize which world requirements we fulfill first so we get better dungeon distribution - if random.randint(0, 1) == 0: - for entrance in lw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_light_world, lw_doors) - for entrance in dw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_dark_world, dw_doors) - else: - for entrance in dw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_dark_world, dw_doors) - for entrance in lw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_light_world, lw_doors) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [entrance for entrance in old_man_entrances if entrance in lw_entrances] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - lw_entrances.remove(old_man_exit) - - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) - connect_entrance(world, lw_doors.pop(), 'Old Man Cave Exit (East)', player) - mandatory_light_world.append('Old Man Cave Exit (West)') - - # we connect up the mandatory associations we have found - for mandatory in mandatory_light_world: - if not isinstance(mandatory, tuple): - mandatory = (mandatory,) - for exit in mandatory: - # point out somewhere - connect_exit(world, exit, lw_entrances.pop(), player) - # point in from somewhere - connect_entrance(world, lw_doors.pop(), exit, player) - - for mandatory in mandatory_dark_world: - if not isinstance(mandatory, tuple): - mandatory = (mandatory,) - for exit in mandatory: - # point out somewhere - connect_exit(world, exit, dw_entrances.pop(), player) - # point in from somewhere - connect_entrance(world, dw_doors.pop(), exit, player) - - # handle remaining caves - while caves: - # connect highest exit count caves first, prevent issue where we have 2 or 3 exits across worlds left to fill - cave_candidate = (None, 0) - for i, cave in enumerate(caves): - if isinstance(cave, str): - cave = (cave,) - if len(cave) > cave_candidate[1]: - cave_candidate = (i, len(cave)) - cave = caves.pop(cave_candidate[0]) - - place_lightworld = random.randint(0, 1) == 0 - if place_lightworld: - target_doors = lw_doors - target_entrances = lw_entrances - else: - target_doors = dw_doors - target_entrances = dw_entrances - - if isinstance(cave, str): - cave = (cave,) - - # check if we can still fit the cave into our target group - if len(target_doors) < len(cave): - if not place_lightworld: - target_doors = lw_doors - target_entrances = lw_entrances - else: - target_doors = dw_doors - target_entrances = dw_entrances - - for exit in cave: - connect_exit(world, exit, target_entrances.pop(), player) - connect_entrance(world, target_doors.pop(), exit, player) - - # handle simple doors - - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - # place blacksmith, has limited options - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place dam and pyramid fairy, have limited options - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) elif world.shuffle[player] == 'insanity': # beware ye who enter here @@ -1304,205 +931,6 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'insanity_legacy': - world.fix_fake_world[player] = False - # beware ye who enter here - - if not invFlag: - entrances_must_exits = DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit + ['Skull Woods Second Section Door (West)'] - - doors = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] - else: - entrances_must_exits = Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit - - doors = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Entrances_Must_Exit + Inverted_LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Secret Entrance Stairs'] + Inverted_Old_Man_Entrances +\ - Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] +\ - Inverted_LW_Single_Cave_Doors + Inverted_DW_Single_Cave_Doors + ['Desert Palace Entrance (West)', 'Desert Palace Entrance (North)'] - - exit_pool = list(doors) - - # randomize which desert ledge door is a must-exit - if random.randint(0, 1) == 0: - entrances_must_exits.append('Desert Palace Entrance (North)') - else: - entrances_must_exits.append('Desert Palace Entrance (West)') - - doors = LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] + Old_Man_Entrances +\ - DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] - - random.shuffle(doors) - - old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera'] - - caves = Cave_Exits + Dungeon_Exits + Cave_Three_Exits + ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', - 'Kakariko Well Exit', 'Bat Cave Exit', 'North Fairy Cave Exit', 'Lost Woods Hideout Exit', 'Lumberjack Tree Exit', 'Sanctuary Exit'] - - # shuffle up holes - - hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', - 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] - - hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Back Drop', - 'Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle'] - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - if world.mode[player] == 'standard': - # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) - connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) - else: - hole_entrances.append('Hyrule Castle Secret Entrance Drop') - hole_targets.append('Hyrule Castle Secret Entrance') - doors.append('Hyrule Castle Secret Entrance Stairs') - caves.append('Hyrule Castle Secret Entrance Exit') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - if invFlag == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) - else: - connect_two_way(world, 'Inverted Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Inverted Pyramid Hole', 'Pyramid', player) - else: - caves.extend(['Ganons Tower Exit', 'Pyramid Exit']) - hole_targets.append('Pyramid') - - if not invFlag: - doors.extend(['Ganons Tower']) - exit_pool.extend(['Ganons Tower']) - else: - doors.extend(['Agahnims Tower']) - exit_pool.extend(['Agahnims Tower']) - - if invFlag == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): - hole_entrances.append('Pyramid Hole') - doors.extend(['Pyramid Entrance']) - exit_pool.extend(['Pyramid Entrance']) - else: - hole_entrances.append('Inverted Pyramid Hole') - doors.extend(['Inverted Pyramid Entrance']) - exit_pool.extend(['Inverted Pyramid Entrance']) - - random.shuffle(hole_entrances) - random.shuffle(hole_targets) - random.shuffle(exit_pool) - - # fill up holes - for hole in hole_entrances: - connect_entrance(world, hole, hole_targets.pop(), player) - - # hyrule castle handling - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) - caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - else: - doors.append('Hyrule Castle Entrance (South)') - caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - - if not world.shufflelinks[player]: - if not invFlag: - links_house = 'Links House' - else: - links_house = 'Big Shop Shop' - else: - if not invFlag: - links_house_doors = [i for i in doors if i not in Sanctuary_Doors + Isolated_LH_Doors] - else: - links_house_doors = [i for i in doors if i not in Inverted_Dark_Sanctuary_Doors + Isolated_LH_Doors] - links_house = random.choice(links_house_doors) - connect_two_way(world, links_house, 'Links House Exit', player) - connect_exit(world, 'Chris Houlihan Room Exit', links_house, player) # should always match link's house, except for plandos - doors.remove(links_house) - exit_pool.remove(links_house) - - if not invFlag: - sanc_doors = [door for door in exit_pool] #[door for door in Sanctuary_Doors if door in exit_pool] - else: - sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in exit_pool] - sanc_door = random.choice(sanc_doors) - exit_pool.remove(sanc_door) - doors.remove(sanc_door) - connect_entrance(world, sanc_door, 'Dark Sanctuary Hint', player) - world.get_entrance('Dark Sanctuary Hint Exit', player).connect(world.get_entrance(sanc_door, player).parent_region) - - # now let's deal with mandatory reachable stuff - def extract_reachable_exit(cavelist): - random.shuffle(cavelist) - candidate = None - for cave in cavelist: - if isinstance(cave, tuple) and len(cave) > 1: - # special handling: TRock has two entries that we should consider entrance only - # ToDo this should be handled in a more sensible manner - if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2: - continue - candidate = cave - break - if candidate is None: - raise RuntimeError('No suitable cave.') - cavelist.remove(candidate) - return candidate - - def connect_reachable_exit(entrance, caves, doors, exit_pool): - cave = extract_reachable_exit(caves) - - exit = cave[-1] - cave = cave[:-1] - connect_exit(world, exit, entrance, player) - connect_entrance(world, doors.pop(), exit, player) - # rest of cave now is forced to be in this world - exit_pool.remove(entrance) - caves.append(cave) - - # connect mandatory exits - for entrance in entrances_must_exits: - connect_reachable_exit(entrance, caves, doors, exit_pool) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [entrance for entrance in old_man_entrances if entrance in exit_pool] - random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - exit_pool.remove(old_man_exit) - - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) - connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player) - caves.append('Old Man Cave Exit (West)') - - # handle simple doors - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - # place blacksmith, has limited options - random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - doors.remove(blacksmith_hut) - - # place dam and pyramid fairy, have limited options - random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - doors.remove(bomb_shop) - - # handle remaining caves - for cave in caves: - if isinstance(cave, str): - cave = (cave,) - - for exit in cave: - connect_exit(world, exit, exit_pool.pop(), player) - connect_entrance(world, doors.pop(), exit, player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) else: raise NotImplementedError('Shuffling not supported yet') diff --git a/ItemList.py b/ItemList.py index 44b8edc0..3d5c3a15 100644 --- a/ItemList.py +++ b/ItemList.py @@ -986,12 +986,12 @@ def test(): for mode in ['open', 'standard', 'inverted', 'retro']: for swords in ['random', 'assured', 'swordless', 'vanilla']: for progressive in ['on', 'off']: - for shuffle in ['full', 'insanity_legacy']: + for shuffle in ['vanilla', 'full', 'crossed', 'insanity']: for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: for retro in [True, False]: for door_shuffle in ['basic', 'crossed', 'vanilla']: - for owShuffle in ['full', 'vanilla']: - for owSwap in ['mixed', 'vanilla']: + for owShuffle in ['full', 'parallel', 'vanilla']: + for owSwap in ['vanilla', 'mixed', 'crossed']: out = get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic) count = len(out[0]) + len(out[1]) diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 7724ae9e..fe83b648 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -142,10 +142,6 @@ "full", "crossed", "insanity", - "restricted_legacy", - "full_legacy", - "madness_legacy", - "insanity_legacy", "dungeonsfull", "dungeonssimple" ] diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index b1691e53..9a2a647c 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -189,10 +189,8 @@ " which they are entered.", "Vanilla: All entrances are in the same locations they were", " in the base game.", - "Legacy shuffles preserve behavior from older versions of the", - "entrance randomizer including significant technical limitations.", "The dungeon variants only mix up dungeons and keep the rest of", - "the overworld vanilla." + "the entrances vanilla." ], "ow_shuffle": [ "This shuffles the layout of the overworld.", diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index 7701936b..ffeeb976 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -12,10 +12,6 @@ "full", "crossed", "insanity", - "restricted_legacy", - "full_legacy", - "madness_legacy", - "insanity_legacy", "dungeonsfull", "dungeonssimple" ] From 181e6e6f846e788ab877ee9e6b1aaa0cdb73c5f4 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 Aug 2021 00:38:52 -0500 Subject: [PATCH 05/12] Minor fix Pyramid Entrance hint in Insanity ER --- Rom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index bc4a9f02..217582c1 100644 --- a/Rom.py +++ b/Rom.py @@ -2177,10 +2177,10 @@ def write_strings(rom, world, player, team): if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']: entrances_to_hint.update(InsanityEntrances) if world.shuffle_ganon: - if world.mode[player] == 'inverted': + if world.mode[player] == 'inverted' != (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: - entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) + entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: From 7b81c5d9da3284c2b20aa673edc7e9cb2edccab2 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 Aug 2021 00:55:07 -0500 Subject: [PATCH 06/12] Minor code cleanup --- OverworldShuffle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 881cffdd..d1a3784f 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -152,8 +152,7 @@ def link_overworld(world, player): # layout shuffle if world.owShuffle[player] == 'vanilla': - for grouping in (trimmed_groups,): - groups = list(trimmed_groups.values()) + groups = list(trimmed_groups.values()) for (forward_edge_sets, back_edge_sets) in groups: assert len(forward_edge_sets) == len(back_edge_sets) for (forward_set, back_set) in zip(forward_edge_sets, back_edge_sets): From 71079269fa1159f793e8cce2e520aaeb35d54eb3 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 12 Aug 2021 15:59:01 -0600 Subject: [PATCH 07/12] Total keys fix for key logic (Basic reductions do not affect amount of keys placed while crossed does) --- DoorShuffle.py | 4 +++- DungeonGenerator.py | 1 + KeyDoorShuffle.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 67e376f6..918d6192 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1071,6 +1071,7 @@ def assign_cross_keys(dungeon_builders, world, player): # Step 3: Initial valid combination find - reduce flex if needed for name, builder in dungeon_builders.items(): suggested = builder.key_doors_num - builder.key_drop_cnt + builder.total_keys = builder.key_doors_num find_valid_combination(builder, start_regions_map[name], world, player) actual_chest_keys = builder.key_doors_num - builder.key_drop_cnt if actual_chest_keys < suggested: @@ -1087,6 +1088,7 @@ def assign_cross_keys(dungeon_builders, world, player): name = builder.name logger.debug('Cross Dungeon: Increasing key count by 1 for %s', name) builder.key_doors_num += 1 + builder.total_keys = builder.key_doors_num result = find_valid_combination(builder, start_regions_map[name], world, player, drop_keys=False) if result: remaining -= 1 @@ -1380,7 +1382,7 @@ def shuffle_key_doors(builder, world, player): skips.append(world.get_door(dp.door_a, player)) break num_key_doors += 1 - builder.key_doors_num = num_key_doors + builder.key_doors_num = builder.total_keys = num_key_doors find_small_key_door_candidates(builder, start_regions, world, player) find_valid_combination(builder, start_regions, world, player) reassign_key_doors(builder, world, player) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 1c49cba0..06af10da 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1175,6 +1175,7 @@ class DungeonBuilder(object): self.pre_open_stonewalls = set() # used by stonewall system self.candidates = None + self.total_keys = None self.key_doors_num = None self.combo_size = None self.flex = 0 diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 3ce9132f..ca4a7f10 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -1387,7 +1387,7 @@ def create_key_counters(key_layout, world, player): state.key_locations = default_key_counts[key_layout.sector.name] else: builder = world.dungeon_layouts[player][key_layout.sector.name] - state.key_locations = builder.key_doors_num - builder.key_drop_cnt + state.key_locations = builder.total_keys - builder.key_drop_cnt state.big_key_special, special_region = False, None for region in key_layout.sector.regions: for location in region.locations: From be6203e3f042f37817fcff567e0a9c366b14b654 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 Aug 2021 22:46:26 -0500 Subject: [PATCH 08/12] Moved Crossed OW Shuffle to its own setting, Renamed Tile Swap to Mixed --- BaseClasses.py | 16 +- CLI.py | 5 +- DoorShuffle.py | 2 +- EntranceShuffle.py | 24 +- ItemList.py | 31 +- Main.py | 6 +- Mystery.py | 4 +- OverworldShuffle.py | 570 ++++++++++++++---- README.md | 28 +- Rom.py | 137 ++--- Rules.py | 82 +-- asm/owrando.asm | 2 +- data/base2current.bps | Bin 141167 -> 141164 bytes mystery_example.yml | 10 +- resources/app/cli/args.json | 13 +- resources/app/cli/lang/en.json | 17 +- resources/app/gui/lang/en.json | 7 +- .../app/gui/randomize/overworld/widgets.json | 17 +- source/classes/constants.py | 3 +- source/gui/randomize/overworld.py | 16 +- 20 files changed, 645 insertions(+), 345 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 32028111..a1d522e8 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -19,13 +19,14 @@ from RoomData import Room class World(object): - def __init__(self, players, owShuffle, owSwap, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, + def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): self.players = players self.teams = 1 self.owShuffle = owShuffle.copy() - self.owSwap = owSwap.copy() + self.owCrossed = owCrossed.copy() self.owKeepSimilar = {} + self.owMixed = owMixed.copy() self.owFluteShuffle = {} self.shuffle = shuffle.copy() self.doorShuffle = doorShuffle.copy() @@ -2308,8 +2309,9 @@ class Spoiler(object): 'weapons': self.world.swords, 'goal': self.world.goal, 'ow_shuffle': self.world.owShuffle, - 'ow_swap': self.world.owSwap, + 'ow_crossed': self.world.owCrossed, 'ow_keepsimilar': self.world.owKeepSimilar, + 'ow_mixed': self.world.owMixed, 'ow_fluteshuffle': self.world.owFluteShuffle, 'shuffle': self.world.shuffle, 'door_shuffle': self.world.doorShuffle, @@ -2390,9 +2392,10 @@ class Spoiler(object): outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player]) outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player]) - outfile.write('Overworld Tile Swap:'.ljust(line_width) + '%s\n' % self.metadata['ow_swap'][player]) if self.metadata['ow_shuffle'][player] != 'vanilla': + outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_crossed'][player] else 'No')) outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No')) + outfile.write('Mixed OW:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No')) outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player]) 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]) @@ -2555,8 +2558,7 @@ class Pot(object): # byte 0: DDOO OEEE (DR, OR, ER) dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0} -or_mode = {"vanilla": 0, "parallel": 1, "full": 2} -orswap_mode = {"vanilla": 0, "mixed": 1, "crossed": 1} +or_mode = {"vanilla": 0, "parallel": 1, "full": 1} er_mode = {"vanilla": 0, "simple": 1, "restricted": 3, "full": 3, "crossed": 4, "insanity": 5, "dungeonsfull": 7, "dungeonssimple": 7} # byte 1: LLLW WSSR (logic, mode, sword, retro) @@ -2592,7 +2594,7 @@ class Settings(object): @staticmethod def make_code(w, p): code = bytes([ - (dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 4) | (orswap_mode[w.owSwap[p]] << 3) | er_mode[w.shuffle[p]], + (dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owCrossed[p] else 0) | (0x08 if w.owMixed[p] else 0) | er_mode[w.shuffle[p]], (logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3) | (sword_mode[w.swords[p]] << 1) | (1 if w.retro[p] else 0), diff --git a/CLI.py b/CLI.py index fb9367c3..ccc66e00 100644 --- a/CLI.py +++ b/CLI.py @@ -94,7 +94,7 @@ def parse_cli(argv, no_defaults=False): playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', - 'ow_shuffle', 'ow_swap', 'ow_keepsimilar', 'ow_fluteshuffle', + 'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_fluteshuffle', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'bombbag', @@ -146,8 +146,9 @@ def parse_settings(): "openpyramid": False, "shuffleganon": True, "ow_shuffle": "vanilla", - "ow_swap": "vanilla", + "ow_crossed": False, "ow_keepsimilar": False, + "ow_mixed": False, "ow_fluteshuffle": "vanilla", "shuffle": "vanilla", "shufflelinks": False, diff --git a/DoorShuffle.py b/DoorShuffle.py index 8ad99965..0a8b33fc 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -214,7 +214,7 @@ def vanilla_key_logic(world, player): analyze_dungeon(key_layout, world, player) world.key_logic[player][builder.name] = key_layout.key_logic log_key_logic(builder.name, key_layout.key_logic) - if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and world.owSwap[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: + if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and not world.owCrossed[player] and world.owMixed[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]: validate_vanilla_key_logic(world, player) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index dd98a9ed..0863010b 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -50,18 +50,18 @@ def link_entrances(world, player): # inverted entrance mods for owid in swapped_connections.keys(): - if invFlag != (owid in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if invFlag != (owid in world.owswaps[player][0] and world.owMixed[player]): for (entrancename, exitname) in swapped_connections[owid]: try: connect_two_way(world, entrancename, exitname, player) except RuntimeError: connect_entrance(world, entrancename, exitname, player) - if invFlag != (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed') and \ - invFlag == (0x0a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if invFlag != (0x03 in world.owswaps[player][0] and world.owMixed[player]) and \ + invFlag == (0x0a in world.owswaps[player][0] and world.owMixed[player]): connect_entrance(world, 'Death Mountain Return Cave (West)', 'Dark Death Mountain Healer Fairy', player) - elif invFlag != (0x0a in world.owswaps[player][0] and world.owSwap[player] == 'mixed') and \ - invFlag == (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + elif invFlag != (0x0a in world.owswaps[player][0] and world.owMixed[player]) and \ + invFlag == (0x03 in world.owswaps[player][0] and world.owMixed[player]): connect_two_way(world, 'Bumper Cave (Top)', 'Death Mountain Return Cave Exit (West)', player) # dungeon entrance shuffle @@ -225,7 +225,7 @@ def link_entrances(world, player): random.shuffle(remaining_entrances) old_man_entrance = remaining_entrances.pop() - connect_two_way(world, old_man_entrance if invFlag == (0x0a in world.owswaps[player][0] and world.owSwap[player] == 'mixed') else 'Bumper Cave (Bottom)', 'Old Man Cave Exit (West)', player) + connect_two_way(world, old_man_entrance if invFlag == (0x0a in world.owswaps[player][0] and world.owMixed[player]) else 'Bumper Cave (Bottom)', 'Old Man Cave Exit (West)', player) connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) if invFlag and old_man_exit == 'Spike Cave': bomb_shop_doors.remove('Spike Cave') @@ -791,8 +791,8 @@ def link_entrances(world, player): if not world.shuffle_ganon: connect_two_way(world, 'Ganons Tower' if not invFlag else 'Agahnims Tower', 'Ganons Tower Exit', player) - connect_two_way(world, 'Pyramid Entrance' if invFlag == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed') else 'Inverted Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole' if invFlag == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed') else 'Inverted Pyramid Hole', 'Pyramid', player) + connect_two_way(world, 'Pyramid Entrance' if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]) else 'Inverted Pyramid Entrance', 'Pyramid Exit', player) + connect_entrance(world, 'Pyramid Hole' if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]) else 'Inverted Pyramid Hole', 'Pyramid', player) else: caves.extend(['Ganons Tower Exit', 'Pyramid Exit']) hole_targets.append('Pyramid') @@ -804,7 +804,7 @@ def link_entrances(world, player): exit_pool.extend(['Agahnims Tower']) doors.extend(['Agahnims Tower']) - if invFlag == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]): exit_pool.extend(['Pyramid Entrance']) hole_entrances.append('Pyramid Hole') entrances_must_exits.append('Pyramid Entrance') @@ -943,7 +943,7 @@ def link_entrances(world, player): world.powder_patch_required[player] = True # check for ganon location - if world.get_entrance('Pyramid Hole' if invFlag == (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed') else 'Inverted Pyramid Hole', player).connected_region.name != 'Pyramid': + if world.get_entrance('Pyramid Hole' if invFlag == (0x03 in world.owswaps[player][0] and world.owMixed[player]) else 'Inverted Pyramid Hole', player).connected_region.name != 'Pyramid': world.ganon_at_pyramid[player] = False # check for Ganon's Tower location @@ -1030,7 +1030,7 @@ def scramble_holes(world, player): ('Lumberjack Tree Exit', 'Lumberjack Tree (top)')] if not world.shuffle_ganon: - if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owMixed[player]): connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: @@ -1053,7 +1053,7 @@ def scramble_holes(world, player): if world.shuffle_ganon: random.shuffle(hole_targets) exit, target = hole_targets.pop() - if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owMixed[player]): connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: diff --git a/ItemList.py b/ItemList.py index 3d5c3a15..c07da745 100644 --- a/ItemList.py +++ b/ItemList.py @@ -258,7 +258,7 @@ def generate_itempool(world, player): (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999) else: - (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.owShuffle[player], world.owSwap[player], world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.doorShuffle[player], world.logic[player]) + (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.doorShuffle[player], world.logic[player]) if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] @@ -727,7 +727,7 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)' 'Rupees (100)': 100, 'Rupees (300)': 300} -def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic): +def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic): pool = [] placed_items = {} precollected_items = [] @@ -990,21 +990,20 @@ def test(): for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: for retro in [True, False]: for door_shuffle in ['basic', 'crossed', 'vanilla']: - for owShuffle in ['full', 'parallel', 'vanilla']: - for owSwap in ['vanilla', 'mixed', 'crossed']: - out = get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic) - count = len(out[0]) + len(out[1]) + for bombbag in [True, False]: + out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic) + count = len(out[0]) + len(out[1]) - correct_count = total_items_to_place - if goal == 'pedestal' and swords != 'vanilla': - # pedestal goals generate one extra item - correct_count += 1 - if retro: - correct_count += 28 - try: - assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag)) - except AssertionError as e: - print(e) + correct_count = total_items_to_place + if goal == 'pedestal' and swords != 'vanilla': + # pedestal goals generate one extra item + correct_count += 1 + if retro: + correct_count += 28 + try: + assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag)) + except AssertionError as e: + print(e) if __name__ == '__main__': test() diff --git a/Main.py b/Main.py index 84d97fb9..b7f0c295 100644 --- a/Main.py +++ b/Main.py @@ -61,7 +61,7 @@ def main(args, seed=None, fish=None): for player, code in args.code.items(): if code: Settings.adjust_args_from_code(code, player, args) - world = World(args.multi, args.ow_shuffle, args.ow_swap, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, + 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.retro, args.custom, args.customitemarray, args.hints) logger = logging.getLogger('') @@ -263,7 +263,7 @@ def main(args, seed=None, fish=None): customize_shops(world, player) balance_money_progression(world) - if world.owShuffle[1] != 'vanilla' or world.owSwap[1] != 'vanilla' or str(world.seed).startswith('M'): + if world.owShuffle[1] != 'vanilla' or world.owCrossed[1] or world.owMixed[1] or str(world.seed).startswith('M'): outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' else: outfilebase = f'DR_{args.outputname if args.outputname else world.seed}' @@ -369,7 +369,7 @@ def main(args, seed=None, fish=None): def copy_world(world): # ToDo: Not good yet - ret = World(world.players, world.owShuffle, world.owSwap, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, + ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints) ret.teams = world.teams diff --git a/Mystery.py b/Mystery.py index 88f5d7f0..f92764d9 100644 --- a/Mystery.py +++ b/Mystery.py @@ -133,9 +133,9 @@ def roll_settings(weights): overworld_shuffle = get_choice('overworld_shuffle') ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla' - overworld_swap = get_choice('overworld_swap') - ret.ow_swap = overworld_swap if overworld_swap != 'none' else 'vanilla' + ret.ow_crossed = get_choice('overworld_crossed') ret.ow_keepsimilar = get_choice('overworld_keepsimilar') + ret.ow_mixed = get_choice('overworld_mixed') overworld_flute = get_choice('flute_shuffle') ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla' entrance_shuffle = get_choice('entrance_shuffle') diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d1a3784f..5d1d9669 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -11,9 +11,25 @@ def link_overworld(world, player): for exitname, destname in temporary_mandatory_connections: connect_two_way(world, exitname, destname, player) - # tile shuffle trimmed_groups = copy.deepcopy(OWEdgeGroups) - if world.owSwap[player] != 'vanilla': + + # adjust Frog/Dig Game swap manually due to NP/P relationship with LW + if world.owShuffle[player] == 'parallel' and not world.owKeepSimilar[player]: + for group in trimmed_groups.keys(): + (std, region, axis, terrain, _, _) = 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']) + 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']) + trimmed_groups[group] = (forward_edges, back_edges) + + # tile shuffle + if world.owMixed[player]: tile_groups = {} for (name, groupType) in OWTileGroups.keys(): if world.mode[player] != 'standard' or name not in ['Castle', 'Links', 'Central Bonk Rocks']: @@ -44,7 +60,7 @@ def link_overworld(world, player): exist_dw_regions.extend(OWTileRegions.inverse[owid]) tile_groups[(name, groupType)] = (exist_owids, exist_lw_regions, exist_dw_regions) - #tile shuffle happens here, the groups that remain in the list are the tiles that get swapped + # tile shuffle happens here, the groups that remain in the list are the tiles that get swapped removed = list() for group in tile_groups.keys(): if random.randint(0, 1): @@ -52,7 +68,7 @@ def link_overworld(world, player): for group in removed: tile_groups.pop(group, None) - #save shuffled tiles to world object + # save shuffled tiles to world object for group in tile_groups.keys(): (owids, lw_regions, dw_regions) = tile_groups[group] (exist_owids, exist_lw_regions, exist_dw_regions) = world.owswaps[player] @@ -61,7 +77,7 @@ def link_overworld(world, player): exist_dw_regions.extend(dw_regions) world.owswaps[player] = [exist_owids, exist_lw_regions, exist_dw_regions] - #replace LW edges with DW + # replace LW edges with DW ignore_list = list() #TODO: Remove ignore_list when special OW areas are included in pool for edgeset in temporary_mandatory_connections: for edge in edgeset: @@ -108,7 +124,7 @@ def link_overworld(world, player): #TODO: Figure out a way to handle index changes on the fly when removing items logging.getLogger('').warning('OW Tile Swap encountered minor IndexError... retrying') - if 0x28 in world.owswaps[player][0]: #handle Frog/Dig Game swap manually due to NP/P relationship with LW + if world.owShuffle[player] != 'parallel' and 0x28 in world.owswaps[player][0]: # handle Frog/Dig Game swap manually due to NP/P relationship with LW trimmed_groups[(OpenStd.Open, WorldType.Dark, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][0].append(['Maze Race ES']) trimmed_groups[(OpenStd.Open, WorldType.Dark, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][1].append(['Kakariko Suburb WS']) trimmed_groups[(OpenStd.Open, WorldType.Light, PolSlot.EastWest, Terrain.Land, IsParallel.Yes, 1)][0].remove(['Maze Race ES']) @@ -139,7 +155,7 @@ def link_overworld(world, player): # 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'): + if (world.mode[player] == 'inverted') == (owid in world.owswaps[player][0] and world.owMixed[player]): for (exitname, regionname) in ow_connections[owid][0]: connect_simple(world, exitname, regionname, player) else: @@ -151,66 +167,99 @@ def link_overworld(world, player): connect_custom(world, connected_edges, player) # layout shuffle - if world.owShuffle[player] == 'vanilla': + if world.owShuffle[player] == 'vanilla' and not world.owCrossed[player]: + # vanilla transitions groups = list(trimmed_groups.values()) for (forward_edge_sets, back_edge_sets) in groups: assert len(forward_edge_sets) == len(back_edge_sets) for (forward_set, back_set) in zip(forward_edge_sets, back_edge_sets): 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.append(forward_edge) - connected_edges.append(back_edge) + connect_two_way(world, forward_edge, back_edge, player, connected_edges) assert len(connected_edges) == len(default_connections) * 2, connected_edges else: if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel': for exitname, destname in parallelsimilar_connections: - connect_two_way(world, exitname, destname, player) - connected_edges.append(exitname) - connected_edges.append(destname) + connect_two_way(world, exitname, destname, player, connected_edges) + + if world.owShuffle[player] == 'vanilla' and world.owCrossed[player]: + if world.mode[player] == 'standard': + # connect vanilla std + for group in trimmed_groups.keys(): + (std, _, _, _, _, _) = group + if std == OpenStd.Standard: + (forward_set, back_set) = trimmed_groups[group] + for (forward_edges, back_edges) in zip(forward_set, back_set): + for (forward_edge, back_edge) in zip(forward_edges, back_edges): + connect_two_way(world, forward_edge, back_edge, player, connected_edges) + + # connect non-parallel edges + for group in trimmed_groups.keys(): + (_, _, _, _, parallel, _) = group + if parallel == IsParallel.No: + (forward_set, back_set) = trimmed_groups[group] + for (forward_edges, back_edges) in zip(forward_set, back_set): + for (forward_edge, back_edge) in zip(forward_edges, back_edges): + if forward_edge not in connected_edges and back_edge not in connected_edges: + connect_two_way(world, forward_edge, back_edge, player, connected_edges) #TODO: Remove, just for testing for exitname, destname in test_connections: - connect_two_way(world, exitname, destname, player) - connected_edges.append(exitname) - connected_edges.append(destname) + connect_two_way(world, exitname, destname, player, connected_edges) trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) groups = reorganize_groups(world, trimmed_groups, player) - #all shuffling occurs here - random.shuffle(groups) - for (forward_edge_sets, back_edge_sets) in groups: - assert len(forward_edge_sets) == len(back_edge_sets) - random.shuffle(back_edge_sets) - for (forward_set, back_set) in zip(forward_edge_sets, back_edge_sets): - 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.append(forward_edge) - connected_edges.append(back_edge) - if world.owShuffle[player] == 'parallel': - if forward_edge in parallel_links.keys() or forward_edge in parallel_links.inverse.keys(): - try: - parallel_forward_edge = parallel_links[forward_edge] if forward_edge in parallel_links.keys() else parallel_links.inverse[forward_edge][0] - parallel_back_edge = parallel_links[back_edge] if back_edge in parallel_links.keys() else parallel_links.inverse[back_edge][0] - connect_two_way(world, parallel_forward_edge, parallel_back_edge, player) - connected_edges.append(parallel_forward_edge) - connected_edges.append(parallel_back_edge) - except KeyError: - # TODO: Figure out why non-parallel edges are getting into parallel groups - raise KeyError('No parallel edge for edge %d' % back_edge) + # all layout shuffling occurs here + if world.owShuffle[player] != 'vanilla': + # layout shuffle + random.shuffle(groups) + for (forward_edge_sets, back_edge_sets) in groups: + assert len(forward_edge_sets) == len(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] + f += 1 + while back_set[0] in connected_edges: + b += 1 + if b < len(back_edge_sets): + back_set = back_edge_sets[b] + b += 1 + 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) + else: + # vanilla/crossed shuffle + for (forward_edge_sets, back_edge_sets) in groups: + assert len(forward_edge_sets) == len(back_edge_sets) + for (forward_set, back_set) in zip(forward_edge_sets, back_edge_sets): + assert len(forward_set) == len(back_set) + swapped = random.randint(0, 1) + for (forward_edge, back_edge) in zip(forward_set, back_set): + if forward_edge not in connected_edges and back_edge not in connected_edges: + if swapped: + forward_edge = parallel_links[forward_edge] if forward_edge in parallel_links else parallel_links.inverse[forward_edge][0] + connect_two_way(world, forward_edge, back_edge, player, connected_edges) assert len(connected_edges) == len(default_connections) * 2, connected_edges - + # flute shuffle def connect_flutes(flute_destinations): for o in range(0, len(flute_destinations)): owslot = flute_destinations[o] regions = flute_data[owslot][0] - if (world.mode[player] == 'inverted') == (flute_data[owslot][1] in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (flute_data[owslot][1] in world.owswaps[player][0] and world.owMixed[player]): connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player) else: connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player) @@ -232,7 +281,7 @@ def link_overworld(world, player): new_ignored.add(exit.connected_region.name) getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name]) - if (world.mode[player] == 'inverted') == (flute_data[owid][1] in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (flute_data[owid][1] in world.owswaps[player][0] and world.owMixed[player]): new_region = flute_data[owid][0][0] else: new_region = flute_data[owid][0][1] @@ -269,18 +318,15 @@ def link_overworld(world, player): world.owflutespots[player] = new_spots connect_flutes(new_spots) - 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]: - connect_two_way(world, edgename1, edgename2, player) - connected_edges.append(edgename1) - connected_edges.append(edgename2) + connect_two_way(world, edgename1, edgename2, player, connected_edges) def connect_simple(world, exitname, regionname, player): world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) -def connect_two_way(world, edgename1, edgename2, player): +def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): edge1 = world.get_entrance(edgename1, player) edge2 = world.get_entrance(edgename2, player) @@ -302,23 +348,41 @@ def connect_two_way(world, edgename1, edgename2, player): x.dest = y y.dest = x - if world.owShuffle[player] != 'vanilla' or world.owSwap[player] != 'vanilla': + if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player]: world.spoiler.set_overworld(edgename2, edgename1, 'both', player) + if connected_edges is not None: + connected_edges.append(edgename1) + connected_edges.append(edgename2) + + # connecting parallel connections + if world.owShuffle[player] == 'parallel' or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player]): + if (edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys()): + 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] + 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: + # TODO: Figure out why non-parallel edges are getting into parallel groups + raise KeyError('No parallel edge for edge %s' % edgename2) + def remove_reserved(world, groupedlist, connected_edges, player): new_grouping = {} for group in groupedlist.keys(): new_grouping[group] = ([], []) for group in groupedlist.keys(): - (std, region, axis, terrain, parallel, count) = group + (_, region, _, _, _, _) = group (forward_edges, back_edges) = groupedlist[group] + # remove edges already connected (thru plando and other forced connections) for edge in connected_edges: forward_edges = list(list(filter((edge).__ne__, i)) for i in forward_edges) back_edges = list(list(filter((edge).__ne__, i)) for i in back_edges) - - if world.owShuffle[player] == 'parallel' and region == WorldType.Dark: + + # remove parallel edges from pool, since they get added during shuffle + if (not world.owCrossed[player] and world.owShuffle[player] == 'parallel') and region == WorldType.Dark: for edge in parallel_links: forward_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in forward_edges) back_edges = list(list(filter((parallel_links[edge]).__ne__, i)) for i in back_edges) @@ -329,8 +393,6 @@ def remove_reserved(world, groupedlist, connected_edges, player): forward_edges = list(filter(([]).__ne__, forward_edges)) back_edges = list(filter(([]).__ne__, back_edges)) - #TODO: Remove edges set in connect_custom. The lists above can be left with invalid counts if connect_custom removes entries, they need to get put into their appropriate group - (exist_forward_edges, exist_back_edges) = new_grouping[group] exist_forward_edges.extend(forward_edges) exist_back_edges.extend(back_edges) @@ -340,11 +402,324 @@ def remove_reserved(world, groupedlist, connected_edges, player): return new_grouping def reorganize_groups(world, groups, player): + # predefined shuffle groups get reorganized here + # this restructures the candidate pool based on the chosen settings if world.owShuffle[player] == 'full': - #predefined shuffle groups get reorganized here + if world.owCrossed[player]: + if world.owKeepSimilar[player]: + if world.mode[player] == 'standard': + # tuple goes to (A,_,C,D,_,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, _, axis, terrain, _, count) = group + new_grouping[(std, axis, terrain, count)] = ([], []) + + for group in grouping.keys(): + (std, _, axis, terrain, _, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,_,C,D,_,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, _, axis, terrain, _, count) = group + new_grouping[(axis, terrain, count)] = ([], []) + + for group in grouping.keys(): + (_, _, axis, terrain, _, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(axis, terrain, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + if world.mode[player] == 'standard': + # tuple goes to (A,_,C,D,_,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, _, axis, terrain, _, _) = group + new_grouping[(std, axis, terrain)] = ([], []) + + for group in grouping.keys(): + (std, _, axis, terrain, _, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, axis, terrain)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,_,C,D,_,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, _, axis, terrain, _, _) = group + new_grouping[(axis, terrain)] = ([], []) + + for group in grouping.keys(): + (_, _, axis, terrain, _, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(axis, terrain)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + if world.owKeepSimilar[player]: + if world.mode[player] == 'standard': + # tuple goes to (A,B,C,D,_,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, region, axis, terrain, _, count) = group + new_grouping[(std, region, axis, terrain, count)] = ([], []) + + for group in grouping.keys(): + (std, region, axis, terrain, _, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,B,C,D,_,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, region, axis, terrain, _, count) = group + new_grouping[(region, axis, terrain, count)] = ([], []) + + for group in grouping.keys(): + (_, region, axis, terrain, _, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(region, axis, terrain, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + if world.mode[player] == 'standard': + # tuple goes to (A,B,C,D,_,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, region, axis, terrain, _, _) = group + new_grouping[(std, region, axis, terrain)] = ([], []) + + for group in grouping.keys(): + (std, region, axis, terrain, _, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, region, axis, terrain)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,B,C,D,_,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, region, axis, terrain, _, _) = group + new_grouping[(region, axis, terrain)] = ([], []) + + for group in grouping.keys(): + (_, region, axis, terrain, _, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(region, axis, terrain)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + elif world.owShuffle[player] == 'parallel': + if world.owCrossed[player]: + if world.owKeepSimilar[player]: + if world.mode[player] == 'standard': + # tuple goes to (A,_,C,D,E,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, _, axis, terrain, parallel, count) = group + new_grouping[(std, axis, terrain, parallel, count)] = ([], []) + + for group in grouping.keys(): + (std, _, axis, terrain, parallel, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain, parallel, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,_,C,D,E,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, _, axis, terrain, parallel, count) = group + new_grouping[(axis, terrain, parallel, count)] = ([], []) + + for group in grouping.keys(): + (_, _, axis, terrain, parallel, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain, parallel, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + if world.mode[player] == 'standard': + # tuple goes to (A,_,C,D,E,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, _, axis, terrain, parallel, _) = group + new_grouping[(std, axis, terrain, parallel)] = ([], []) + + for group in grouping.keys(): + (std, _, axis, terrain, parallel, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(std, axis, terrain, parallel)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,_,C,D,E,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, _, axis, terrain, parallel, _) = group + new_grouping[(axis, terrain, parallel)] = ([], []) + + for group in grouping.keys(): + (_, _, axis, terrain, parallel, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(axis, terrain, parallel)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + if world.owKeepSimilar[player]: + if world.mode[player] == 'standard': + # tuple stays (A,B,C,D,E,F) + for grouping in (groups,): + return list(grouping.values()) + else: + # tuple goes to (_,B,C,D,E,F) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, region, axis, terrain, parallel, count) = group + new_grouping[(region, axis, terrain, parallel, count)] = ([], []) + + for group in grouping.keys(): + (_, region, axis, terrain, parallel, count) = group + (forward_edges, back_edges) = grouping[group] + (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, parallel, count)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(region, axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + if world.mode[player] == 'standard': + # tuple goes to (A,B,C,D,E,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (std, region, axis, terrain, parallel, _) = group + new_grouping[(std, region, axis, terrain, parallel)] = ([], []) + + for group in grouping.keys(): + (std, region, axis, terrain, parallel, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, parallel)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(std, region, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + else: + # tuple goes to (_,B,C,D,E,_) + for grouping in (groups,): + new_grouping = {} + + for group in grouping.keys(): + (_, region, axis, terrain, parallel, _) = group + new_grouping[(region, axis, terrain, parallel)] = ([], []) + + for group in grouping.keys(): + (_, region, axis, terrain, parallel, _) = group + (forward_edges, back_edges) = grouping[group] + forward_edges = [[i] for l in forward_edges for i in l] + back_edges = [[i] for l in back_edges for i in l] + + (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, parallel)] + exist_forward_edges.extend(forward_edges) + exist_back_edges.extend(back_edges) + new_grouping[(region, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) + + return list(new_grouping.values()) + elif world.owShuffle[player] == 'vanilla' and world.owCrossed[player]: if world.owKeepSimilar[player]: if world.mode[player] == 'standard': - #tuple stays (A,B,C,D,_,F) + # tuple goes to (A,B,C,D,_,F) for grouping in (groups,): new_grouping = {} @@ -362,7 +737,7 @@ def reorganize_groups(world, groups, player): return list(new_grouping.values()) else: - #tuple goes to (_,B,C,D,_,F) + # tuple goes to (_,B,C,D,_,F) for grouping in (groups,): new_grouping = {} @@ -381,7 +756,7 @@ def reorganize_groups(world, groups, player): return list(new_grouping.values()) else: if world.mode[player] == 'standard': - #tuple stays (A,B,C,D,_,_) + # tuple goes to (A,B,C,D,_,_) for grouping in (groups,): new_grouping = {} @@ -402,7 +777,7 @@ def reorganize_groups(world, groups, player): return list(new_grouping.values()) else: - #tuple goes to (_,B,C,D,_,_) + # tuple goes to (_,B,C,D,_,_) for grouping in (groups,): new_grouping = {} @@ -421,82 +796,14 @@ def reorganize_groups(world, groups, player): exist_back_edges.extend(back_edges) new_grouping[(region, axis, terrain)] = (exist_forward_edges, exist_back_edges) - return list(new_grouping.values()) - elif world.owShuffle[player] == 'parallel': - #predefined shuffle groups get reorganized here - if world.owKeepSimilar[player]: - if world.mode[player] == 'standard': - #tuple stays (A,B,C,D,E,F) - for grouping in (groups,): - return list(grouping.values()) - else: - #tuple goes to (_,B,C,D,E,F) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (_, region, axis, terrain, parallel, count) = group - new_grouping[(region, axis, terrain, parallel, count)] = ([], []) - - for group in grouping.keys(): - (_, region, axis, terrain, parallel, count) = group - (forward_edges, back_edges) = grouping[group] - (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, parallel, count)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(region, axis, terrain, parallel, count)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - if world.mode[player] == 'standard': - #tuple stays (A,B,C,D,E,_) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (std, region, axis, terrain, parallel, _) = group - new_grouping[(std, region, axis, terrain, parallel)] = ([], []) - - for group in grouping.keys(): - (std, region, axis, terrain, parallel, _) = group - (forward_edges, back_edges) = grouping[group] - forward_edges = [[i] for l in forward_edges for i in l] - back_edges = [[i] for l in back_edges for i in l] - - (exist_forward_edges, exist_back_edges) = new_grouping[(std, region, axis, terrain, parallel)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(std, region, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) - - return list(new_grouping.values()) - else: - #tuple goes to (_,B,C,D,E,_) - for grouping in (groups,): - new_grouping = {} - - for group in grouping.keys(): - (_, region, axis, terrain, parallel, _) = group - new_grouping[(region, axis, terrain, parallel)] = ([], []) - - for group in grouping.keys(): - (_, region, axis, terrain, parallel, _) = group - (forward_edges, back_edges) = grouping[group] - forward_edges = [[i] for l in forward_edges for i in l] - back_edges = [[i] for l in back_edges for i in l] - - (exist_forward_edges, exist_back_edges) = new_grouping[(region, axis, terrain, parallel)] - exist_forward_edges.extend(forward_edges) - exist_back_edges.extend(back_edges) - new_grouping[(region, axis, terrain, parallel)] = (exist_forward_edges, exist_back_edges) - return list(new_grouping.values()) else: raise NotImplementedError('Shuffling not supported yet') def create_flute_exits(world, player): 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 (world.owSwap[player] != 'mixed' and region.type == RegionType.LightWorld) \ - or (world.owSwap[player] == 'mixed' and region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ + if (not world.owMixed[player] and region.type == RegionType.LightWorld) \ + or (world.owMixed[player] and region.type in [RegionType.LightWorld, RegionType.DarkWorld] \ and (region.name not in world.owswaps[player][1] or region.name in world.owswaps[player][2])): exitname = 'Flute From ' + region.name exit = Entrance(region.player, exitname, region) @@ -506,7 +813,7 @@ def create_flute_exits(world, player): world.initialize_regions() def update_world_regions(world, player): - if world.owSwap[player] == 'mixed': + if world.owMixed[player]: for name in world.owswaps[player][1]: world.get_region(name, player).type = RegionType.DarkWorld for name in world.owswaps[player][2]: @@ -1110,9 +1417,8 @@ ow_connections = { } parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'), - ('Dig Game EC', 'Frog WC'), - ('Dig Game ES', 'Frog WS') - ] + ('Dig Game EC', 'Frog WC') + ] # non shuffled overworld default_connections = [#('Lost Woods NW', 'Master Sword Meadow SC'), diff --git a/README.md b/README.md index 3ecdb94f..7bf01573 100644 --- a/README.md +++ b/README.md @@ -66,19 +66,9 @@ OW Transitions are shuffled, but both worlds will have a matching layout. OW Transitions are shuffled within each world separately. -## Overworld Tile Swap (--ow_swap) +## Crossed (--ow_crossed) -### Vanilla - -OW tiles remain in their original worlds. - -### Mixed - -OW tiles are randomly chosen to become a part of the opposite world - -### Crossed - -OW tiles remain in their original world, but transitions can now be travel cross-world. +This allows OW connections to be shuffled cross-world. ## Visual Representation of Main OW Shuffle Settings @@ -88,6 +78,10 @@ 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 +## Mixed Overworld (--ow_mixed) + +OW tiles are randomly chosen to become a part of the opposite world + ## Flute Shuffle (--ow_fluteshuffle) When enabled, new flute spots are generated and gives the player the option to cancel out of the flute menu by pressing X. @@ -120,10 +114,10 @@ Show the help message and exit. For specifying the overworld layout shuffle you want as above. (default: vanilla) ``` ---ow_swap +--ow_crossed ``` -For specifying the overworld tile swap you want as above. (default: vanilla) +This allows cross-world connections on the overworld ``` --ow_keepsimilar @@ -131,6 +125,12 @@ For specifying the overworld tile swap you want as above. (default: vanilla) This keeps similar edge transitions paired together with other pairs of transitions +``` +--ow_mixed +``` + +This gives each OW tile a random chance to be swapped to the opposite world + ``` --ow_fluteshuffle ``` diff --git a/Rom.py b/Rom.py index 217582c1..26911e9f 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'cc8fc59caa0bbe6d26ac64b9d2893709' +RANDOMIZERBASEHASH = '99f3f57ab2c9449172cade4927a462d6' class JsonRom(object): @@ -612,71 +612,20 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.pot_contents[player]: write_pots_to_rom(rom, world.pot_contents[player]) - # patch overworld edges - inverted_buffer = [0] * 0x82 - if world.owShuffle[player] != 'vanilla' or world.owSwap[player] != 'vanilla': - owMode = 0 - if world.owShuffle[player] == 'parallel': - owMode = 1 - elif world.owShuffle[player] == 'full': - owMode = 2 - - if world.owSwap[player] == 'mixed': - owMode |= 0x100 - world.fix_fake_world[player] = True - elif world.owSwap[player] == 'crossed': - owMode |= 0x200 - world.fix_fake_world[player] = True - - write_int16(rom, 0x150002, owMode) - - owFlags = 0 - if world.owKeepSimilar[player]: - owFlags |= 0x1 - if world.owFluteShuffle[player] != 'vanilla': - owFlags |= 0x100 - - write_int16(rom, 0x150004, owFlags) - - rom.write_byte(0x18004C, 0x01) # patch for allowing Frogsmith to enter multi-entrance caves - - # patches map data specific for OW Shuffle - #inverted_buffer[0x03] = inverted_buffer[0x03] | 0x2 # convenient portal on WDM - inverted_buffer[0x1A] = inverted_buffer[0x1A] | 0x2 # rocks added to prevent OWG hardlock - inverted_buffer[0x1B] = inverted_buffer[0x1B] | 0x2 # rocks added to prevent OWG hardlock - inverted_buffer[0x22] = inverted_buffer[0x22] | 0x2 # rocks added to prevent OWG hardlock - inverted_buffer[0x3F] = inverted_buffer[0x3F] | 0x2 # added C to terrain - #inverted_buffer[0x43] = inverted_buffer[0x43] | 0x2 # convenient portal on WDDM - inverted_buffer[0x5A] = inverted_buffer[0x5A] | 0x2 # rocks added to prevent OWG hardlock - inverted_buffer[0x5B] = inverted_buffer[0x5B] | 0x2 # rocks added to prevent OWG hardlock - inverted_buffer[0x62] = inverted_buffer[0x62] | 0x2 # rocks added to prevent OWG hardlock - inverted_buffer[0x7F] = inverted_buffer[0x7F] | 0x2 # added C to terrain - - if world.owSwap[player] == 'mixed': - for b in world.owswaps[player][0]: - # load inverted maps - inverted_buffer[b] = (inverted_buffer[b] & 0xFE) | ((inverted_buffer[b] + 1) % 2) - - # set world flag - rom.write_byte(0x153A00 + b, 0x00 if b >= 0x40 else 0x40) - - for edge in world.owedges: - if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player: - write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc) - write_int16(rom, edge.getAddress() + 0x0e, edge.getTarget()) - # patch flute spots + owFlags = 0 if world.owFluteShuffle[player] == 'vanilla': flute_spots = default_flute_connections else: flute_spots = world.owflutespots[player] + owFlags |= 0x100 for o in range(0, len(flute_spots)): owslot = flute_spots[o] offset = 0 data = flute_data[owslot] - if (world.mode[player] == 'inverted') != (data[1] in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (data[1] in world.owswaps[player][0] and world.owMixed[player]): offset = 0x40 write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), data[1] + offset) # owid @@ -695,7 +644,55 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(snes_to_pc(0x0AB78B + o), data[12] // 0x100) # X high byte rom.write_byte(snes_to_pc(0x0AB793 + o), data[11] & 0xff) # Y low byte rom.write_byte(snes_to_pc(0x0AB79B + o), data[11] // 0x100) # Y high byte + + # patch overworld edges + inverted_buffer = [0] * 0x82 + if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] or world.owMixed[player]: + owMode = 0 + if world.owShuffle[player] == 'parallel': + owMode = 1 + elif world.owShuffle[player] == 'full': + owMode = 2 + + if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player]): + owMode |= 0x100 + if world.owCrossed[player]: + owMode |= 0x200 + world.fix_fake_world[player] = True + if world.owMixed[player]: + owMode |= 0x400 + write_int16(rom, 0x150002, owMode) + + write_int16(rom, 0x150004, owFlags) + + rom.write_byte(0x18004C, 0x01) # patch for allowing Frogsmith to enter multi-entrance caves + + # patches map data specific for OW Shuffle + #inverted_buffer[0x03] = inverted_buffer[0x03] | 0x2 # convenient portal on WDM + inverted_buffer[0x1A] = inverted_buffer[0x1A] | 0x2 # rocks added to prevent OWG hardlock + inverted_buffer[0x1B] = inverted_buffer[0x1B] | 0x2 # rocks added to prevent OWG hardlock + inverted_buffer[0x22] = inverted_buffer[0x22] | 0x2 # rocks added to prevent OWG hardlock + inverted_buffer[0x3F] = inverted_buffer[0x3F] | 0x2 # added C to terrain + #inverted_buffer[0x43] = inverted_buffer[0x43] | 0x2 # convenient portal on WDDM + inverted_buffer[0x5A] = inverted_buffer[0x5A] | 0x2 # rocks added to prevent OWG hardlock + inverted_buffer[0x5B] = inverted_buffer[0x5B] | 0x2 # rocks added to prevent OWG hardlock + inverted_buffer[0x62] = inverted_buffer[0x62] | 0x2 # rocks added to prevent OWG hardlock + inverted_buffer[0x7F] = inverted_buffer[0x7F] | 0x2 # added C to terrain + + if world.owMixed[player]: + for b in world.owswaps[player][0]: + # load inverted maps + inverted_buffer[b] = (inverted_buffer[b] & 0xFE) | ((inverted_buffer[b] + 1) % 2) + + # set world flag + rom.write_byte(0x153A00 + b, 0x00 if b >= 0x40 else 0x40) + + for edge in world.owedges: + if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player: + write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc) + write_int16(rom, edge.getAddress() + 0x0e, edge.getTarget()) + # patch entrance/exits/holes for region in world.regions: @@ -2177,7 +2174,7 @@ def write_strings(rom, world, player, team): if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']: entrances_to_hint.update(InsanityEntrances) if world.shuffle_ganon: - if world.mode[player] == 'inverted' != (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if world.mode[player] == 'inverted' != (0x1b in world.owswaps[player][0] and world.owMixed[player]): entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) @@ -2247,7 +2244,7 @@ def write_strings(rom, world, player, team): tt[hint_locations.pop(0)] = this_hint # Adding a guaranteed hint for the Flute in overworld shuffle. - if world.owShuffle[player] in ['parallel','full']: + if world.owShuffle[player] != 'vanilla' or world.owMixed[player]: this_location = world.find_items_not_key_only('Ocarina', player) if this_location: this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.' @@ -2255,7 +2252,7 @@ def write_strings(rom, world, player, team): # Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well. items_to_hint = RelevantItems.copy() - if world.owShuffle[player] in ['parallel','full']: + if world.owShuffle[player] != 'vanilla' or world.owMixed[player]: items_to_hint.remove('Ocarina') if world.keyshuffle[player]: items_to_hint.extend(SmallKeys) @@ -2264,7 +2261,7 @@ def write_strings(rom, world, player, team): random.shuffle(items_to_hint) hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 hint_count += 2 if world.doorShuffle[player] == 'crossed' else 0 - hint_count += 1 if world.owShuffle[player] in ['parallel', 'full'] else 0 + hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owMixed[player] else 0 while hint_count > 0: this_item = items_to_hint.pop(0) this_location = world.find_items_not_key_only(this_item, player) @@ -2475,7 +2472,7 @@ def set_inverted_mode(world, player, rom, inverted_buffer): if world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3: write_int16(rom, 0x15AEE + 2*0x38, 0x00E0) write_int16(rom, 0x15AEE + 2*0x25, 0x000C) - if (world.mode[player] == 'inverted') != (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (0x03 in world.owswaps[player][0] and world.owMixed[player]): if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']: rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) # mountain cave starts on OW @@ -2498,18 +2495,18 @@ def set_inverted_mode(world, player, rom, inverted_buffer): rom.write_byte(snes_to_pc(0x02D9B8), 0x12) rom.write_bytes(0x180247, [0x00, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00]) #indicates the overworld door being used for the single entrance spawn point - if (world.mode[player] == 'inverted') != (0x05 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (0x05 in world.owswaps[player][0] and world.owMixed[player]): rom.write_bytes(snes_to_pc(0x1BC655), [0x4A, 0x1D, 0x82]) # add warp under rock - if (world.mode[player] == 'inverted') != (0x07 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (0x07 in world.owswaps[player][0] and world.owMixed[player]): rom.write_bytes(snes_to_pc(0x1BC387), [0xDD, 0xD1]) # add warps under rocks rom.write_bytes(snes_to_pc(0x1BD1DD), [0xA4, 0x06, 0x82, 0x9E, 0x06, 0x82, 0xFF, 0xFF]) # add warps under rocks rom.write_byte(0x180089, 0x01) # open TR after exit rom.write_bytes(0x0086E, [0x5C, 0x00, 0xA0, 0xA1]) # TR tail if world.shuffle[player] in ['vanilla']: world.fix_trock_doors[player] = True - if (world.mode[player] == 'inverted') != (0x10 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (0x10 in world.owswaps[player][0] and world.owMixed[player]): rom.write_bytes(snes_to_pc(0x1BC67A), [0x2E, 0x0B, 0x82]) # add warp under rock - if (world.mode[player] == 'inverted') != (0x1B in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (0x1B in world.owswaps[player][0] and world.owMixed[player]): write_int16(rom, 0x15AEE + 2 * 0x06, 0x0020) # post aga hyrule castle spawn rom.write_byte(0x15B8C + 0x06, 0x1B) write_int16(rom, 0x15BDB + 2 * 0x06, 0x00AE) @@ -2597,21 +2594,21 @@ def set_inverted_mode(world, player, rom, inverted_buffer): write_int16(rom, 0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door write_int16(rom, 0xDBA71 + 2 * 0x35, 0x011C) - if (world.mode[player] == 'inverted') != (0x29 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') != (0x29 in world.owswaps[player][0] and world.owMixed[player]): 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'): + if (world.mode[player] == 'inverted') != (0x2C in world.owswaps[player][0] and world.owMixed[player]): 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'): + if (world.mode[player] == 'inverted') != (0x2F in world.owswaps[player][0] and world.owMixed[player]): 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'): + if (world.mode[player] == 'inverted') != (0x30 in world.owswaps[player][0] and world.owMixed[player]): 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'): + if (world.mode[player] == 'inverted') != (0x33 in world.owswaps[player][0] and world.owMixed[player]): 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'): + if (world.mode[player] == 'inverted') != (0x35 in world.owswaps[player][0] and world.owMixed[player]): rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) # add warp under rock # apply inverted map changes diff --git a/Rules.py b/Rules.py index 3cd00a14..3e50c6d4 100644 --- a/Rules.py +++ b/Rules.py @@ -844,7 +844,7 @@ def ow_rules(world, player): else: set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) - if (world.mode[player] == 'inverted') == (0x00 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x00 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Lost Woods East Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lost Woods Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lost Woods Pedestal Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -859,12 +859,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Skull Woods Forgotten (Middle) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Skull Woods Front Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x02 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x02 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Lumberjack Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Lumberjack Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('West Death Mountain (Top) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Spectacle Rock Mirror Spot', player), lambda state: state.has_Mirror(player)) else: @@ -872,7 +872,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Bubble Boy Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('West Dark Death Mountain (Bottom) Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x05 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x05 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('East Death Mountain (Top West) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('East Death Mountain (Top East) Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Mimic Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -894,7 +894,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Dark Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Death Mountain Teleporter (East)', player), lambda state: state.can_lift_heavy_rocks(player)) - if (world.mode[player] == 'inverted') == (0x07 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x07 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('TR Pegs Area Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('TR Pegs Teleporter', player), lambda state: state.has('Hammer', player)) else: @@ -902,7 +902,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Turtle Rock Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Turtle Rock Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) - if (world.mode[player] == 'inverted') == (0x0a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x0a in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Mountain Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Mountain Entry Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Mountain Entry Entrance Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -911,12 +911,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Bumper Cave Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bumper Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x0f in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x0f in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Zora Waterfall Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Catfish Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x10 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x10 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Lost Woods Pass West Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lost Woods Pass East Top Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lost Woods Pass East Bottom Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -929,24 +929,24 @@ def ow_rules(world, player): set_rule(world.get_entrance('West Dark World Teleporter (Hammer)', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) set_rule(world.get_entrance('West Dark World Teleporter (Rock)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) # bunny cannot lift bushes - if (world.mode[player] == 'inverted') == (0x11 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x11 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Kakariko Fortune Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Outcast Fortune Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x12 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x12 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Kakariko Pond Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Outcast Pond Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x13 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x13 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Sanctuary Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bonk Rock Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Chapel Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Chapel Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x14 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x14 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Graveyard Ledge Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) set_rule(world.get_entrance('Kings Grave Mirror Spot', player), lambda state: state.has_Pearl(player) and state.has_Mirror(player)) else: @@ -956,28 +956,28 @@ def ow_rules(world, player): set_rule(world.get_entrance('Dark Graveyard Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Graveyard Grave Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x15 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x15 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('River Bend Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('River Bend East Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Qirn Jump Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Qirn Jump East Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x16 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x16 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Potion Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Potion Shop Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Witch Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Witch Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x17 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x17 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Zora Approach Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Zora Approach Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Catfish Approach Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Catfish Approach Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x18 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x18 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Kakariko Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Kakariko Grass Mirror Spot', player), lambda state: state.has_Mirror(player)) else: @@ -985,13 +985,13 @@ def ow_rules(world, player): set_rule(world.get_entrance('Village of Outcasts Southwest Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer House Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x1a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x1a in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Forgotton Forest Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Forgotton Forest Fence Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Shield Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x1b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x1b in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False) set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) @@ -1017,7 +1017,7 @@ def ow_rules(world, player): set_rule(world.get_entrance('Pyramid Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Post Aga Inverted Teleporter', player), lambda state: state.has('Beat Agahnim 1', player)) - if (world.mode[player] == 'inverted') == (0x1d in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x1d in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Wooden Bridge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Wooden Bridge Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Wooden Bridge West Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1026,12 +1026,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Broken Bridge East Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Broken Bridge Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x1e in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x1e in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Eastern Palace Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Palace of Darkness Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x22 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x22 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Blacksmith Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Blacksmith Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Bat Cave Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1039,19 +1039,19 @@ def ow_rules(world, player): set_rule(world.get_entrance('Hammer Pegs Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hammer Pegs Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x25 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x25 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Sand Dunes Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Dunes Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x28 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x28 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Maze Race Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Maze Race Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dig Game Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dig Game Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x29 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x29 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Kakariko Suburb Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Kakariko Suburb South Mirror Spot', player), lambda state: state.has_Mirror(player)) else: @@ -1059,24 +1059,24 @@ def ow_rules(world, player): set_rule(world.get_entrance('Frog Prison Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Archery Game Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x2a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x2a in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Flute Boy Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Flute Boy Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Stumpy Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Stumpy Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x2b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x2b in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Central Bonk Rocks Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Bonk Rocks Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x2c in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x2c in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Links House Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Big Bomb Shop Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x2d in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x2d in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Stone Bridge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Stone Bridge South Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hobo Mirror Spot', player), lambda state: state.has_Mirror(player) and state.has_Pearl(player) and state.has('Flippers', player)) @@ -1085,19 +1085,19 @@ def ow_rules(world, player): set_rule(world.get_entrance('Hammer Bridge South Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Dark Hobo Mirror Spot', player), lambda state: state.has_Mirror(player) and state.has_Pearl(player) and state.has('Flippers', player)) - if (world.mode[player] == 'inverted') == (0x2e in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x2e in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Tree Line Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark Tree Line Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x2f in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x2f in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Eastern Nook Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('East Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer else: set_rule(world.get_entrance('Darkness Nook Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('East Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) - if (world.mode[player] == 'inverted') == (0x30 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x30 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Checkerboard Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Desert Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Desert Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -1112,14 +1112,14 @@ def ow_rules(world, player): set_rule(world.get_entrance('Misery Mire Main Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Misery Mire Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - if (world.mode[player] == 'inverted') == (0x32 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x32 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Cave 45 Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Flute Boy Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Stumpy Approach Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Stumpy Bush Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x33 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x33 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('C Whirlpool Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('C Whirlpool Outer Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('South Hyrule Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot use hammer @@ -1130,12 +1130,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('South Dark World Teleporter', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) set_rule(world.get_entrance('South Teleporter Cliff Ledge Drop', player), lambda state: state.can_lift_rocks(player) and state.has_Pearl(player)) - if (world.mode[player] == 'inverted') == (0x34 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x34 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Statues Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Hype Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x35 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x35 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Lake Hylia Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lake Hylia Northeast Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Lake Hylia Island Mirror Spot', player), lambda state: state.has_Mirror(player) and state.has_Pearl(player) and state.has('Flippers', player)) @@ -1152,12 +1152,12 @@ def ow_rules(world, player): set_rule(world.get_entrance('Ice Palace Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Ice Palace Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) - if (world.mode[player] == 'inverted') == (0x37 in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x37 in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Ice Cave Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Shopping Mall Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x3a in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x3a in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Desert Pass Ledge Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Desert Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) else: @@ -1165,17 +1165,17 @@ def ow_rules(world, player): set_rule(world.get_entrance('Swamp Nook Southeast Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Swamp Nook Pegs Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x3b in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x3b in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Dam Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Swamp Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x3c in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x3c in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('South Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Dark South Pass Mirror Spot', player), lambda state: state.has_Mirror(player)) - if (world.mode[player] == 'inverted') == (0x3f in world.owswaps[player][0] and world.owSwap[player] == 'mixed'): + if (world.mode[player] == 'inverted') == (0x3f in world.owswaps[player][0] and world.owMixed[player]): set_rule(world.get_entrance('Octoballoon Mirror Spot', player), lambda state: state.has_Mirror(player)) else: set_rule(world.get_entrance('Bomber Corner Mirror Spot', player), lambda state: state.has_Mirror(player)) @@ -2050,7 +2050,7 @@ def set_inverted_big_bomb_rules(world, player): else: raise Exception('No logic found for routing from %s to the pyramid.' % bombshop_entrance.name) - if world.owShuffle[player] != 'vanilla' or world.owSwap[player] != 'vanilla': + if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player]: add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: False) #temp disable progression until routing to Pyramid get be guaranteed diff --git a/asm/owrando.asm b/asm/owrando.asm index 8cd7bcf1..98be93c0 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -341,7 +341,7 @@ OWNewDestination: sep #$30 : lda OWOppSlotOffset,y : !add $04 : asl : and #$7f : sta $700 ; crossed OW shuffle - lda.l OWMode+1 : and #$ff : cmp #$02 : bne .return + lda.l OWMode+1 : and.b #$02 : beq .return ldx $05 : lda.l OWTileWorldAssoc,x : sta.l $7ef3ca ; change world ; toggle bunny mode diff --git a/data/base2current.bps b/data/base2current.bps index 6a4a34e265eac6877f69ae5e8469811901ebcee0..24256d1451f20acfbeb2557b8a911cb00c387721 100644 GIT binary patch delta 111 zcmV-#0FeLh&Is(z2(U>31cfhp(6dbesPzq(U6GXr&;ektZJSR$vjg`#4*><2KL!CF z0s`=tVFm#g5(*Kftw})1K+qATUC9^l7Na$niUt8113%y%x2FaH&QJjtmv&bHGyyrc Rt5*TM&j|HQVkzZ8KJ-`mDcJx3 delta 134 zcmV;10D1rH&Is?$2(U>31m7+o(X&kfsPzt$d%>9i1JD6ruWg$z4YLCGI}ZU3mp%po z9t8i%0??OU1_2i@4iToUNkGX!&=I9w$rtbzqd9n{oq!9aUC9^F1(OGWnWY7Vio!5p oqaRB2H{c?-qy_=bPyrK{b5{X00X?^ Date: Mon, 16 Aug 2021 22:47:43 -0500 Subject: [PATCH 09/12] Fixed issue with Sanc in TR start as bunny if TR tile is swapped --- OWEdges.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OWEdges.py b/OWEdges.py index b8c5ceab..4d231070 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -697,7 +697,6 @@ OWTileRegions = bidict({ 'Spectacle Rock Ledge': 0x03, 'West Death Mountain (Bottom)': 0x03, - 'Death Mountain Floating Island': 0x05, 'East Death Mountain (Top West)': 0x05, 'East Death Mountain (Top East)': 0x05, 'Spiral Cave Ledge': 0x05, @@ -706,6 +705,7 @@ OWTileRegions = bidict({ 'Fairy Ascension Plateau': 0x05, 'East Death Mountain (Bottom Left)': 0x05, 'East Death Mountain (Bottom)': 0x05, + 'Death Mountain Floating Island': 0x05, 'Death Mountain TR Pegs': 0x07, 'Death Mountain TR Pegs Ledge': 0x07, @@ -846,6 +846,9 @@ OWTileRegions = bidict({ 'East Dark Death Mountain (Top)': 0x45, 'East Dark Death Mountain (Bottom Left)': 0x45, 'East Dark Death Mountain (Bottom)': 0x45, + 'Dark Death Mountain Ledge': 0x45, + 'Dark Death Mountain Isolated Ledge': 0x45, + 'Dark Death Mountain Floating Island': 0x45, 'Turtle Rock Area': 0x47, 'Turtle Rock Ledge': 0x47, From 8ff463b5c745ae5e3f5bc765075f34fb5c66da01 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 16 Aug 2021 22:50:03 -0500 Subject: [PATCH 10/12] Plando support for OW edges --- OverworldShuffle.py | 12 ++- Plando.py | 175 ++++++++++++++++++++++++--------------- Plandomizer_Template.txt | 7 ++ 3 files changed, 123 insertions(+), 71 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 5d1d9669..b4da2cd5 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -164,8 +164,6 @@ def link_overworld(world, player): connected_edges = [] - connect_custom(world, connected_edges, player) - # layout shuffle if world.owShuffle[player] == 'vanilla' and not world.owCrossed[player]: # vanilla transitions @@ -208,6 +206,8 @@ def link_overworld(world, player): for exitname, destname in test_connections: connect_two_way(world, exitname, destname, player, connected_edges) + connect_custom(world, connected_edges, player) + trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player) groups = reorganize_groups(world, trimmed_groups, player) @@ -321,7 +321,15 @@ def link_overworld(world, 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 + continue def connect_simple(world, exitname, regionname, player): world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) diff --git a/Plando.py b/Plando.py index 2a4b911d..c1cd8c23 100755 --- a/Plando.py +++ b/Plando.py @@ -9,6 +9,7 @@ import sys from BaseClasses import World from Regions import create_regions +from OverworldShuffle import link_overworld from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit from Rom import patch_rom, LocalRom, write_string_to_rom, apply_rom_settings, get_sprite_from_name from Rules import set_rules @@ -42,6 +43,11 @@ def main(args): create_regions(world, 1) create_dungeons(world, 1) + text_patches = [] + prefill_world(world, args.plando, text_patches) + + link_overworld(world, 1) + link_entrances(world, 1) logger.info('Calculating Access Rules.') @@ -50,9 +56,7 @@ def main(args): logger.info('Fill the world.') - text_patches = [] - - fill_world(world, args.plando, text_patches) + fill_world(world, args.plando) if world.get_entrance('Dam', 1).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', 1).connected_region.name != 'Swamp Palace (Entrance)': world.swamp_patch_required[1] = True @@ -92,75 +96,14 @@ def main(args): return world -def fill_world(world, plando, text_patches): - mm_medallion = 'Ether' - tr_medallion = 'Quake' +def fill_world(world, plando): logger = logging.getLogger('') with open(plando, 'r') as plandofile: for line in plandofile.readlines(): + line = line.lstrip() if line.startswith('#'): continue - if ':' in line: - line = line.lstrip() - - if line.startswith('!'): - if line.startswith('!mm_medallion'): - _, medallionstr = line.split(':', 1) - mm_medallion = medallionstr.strip() - elif line.startswith('!tr_medallion'): - _, medallionstr = line.split(':', 1) - tr_medallion = medallionstr.strip() - elif line.startswith('!mode'): - _, modestr = line.split(':', 1) - world.mode = {1: modestr.strip()} - elif line.startswith('!logic'): - _, logicstr = line.split(':', 1) - world.logic = {1: logicstr.strip()} - elif line.startswith('!goal'): - _, goalstr = line.split(':', 1) - world.goal = {1: goalstr.strip()} - elif line.startswith('!light_cone_sewers'): - _, sewerstr = line.split(':', 1) - world.sewer_light_cone = {1: sewerstr.strip().lower() == 'true'} - elif line.startswith('!light_cone_lw'): - _, lwconestr = line.split(':', 1) - world.light_world_light_cone = lwconestr.strip().lower() == 'true' - elif line.startswith('!light_cone_dw'): - _, dwconestr = line.split(':', 1) - world.dark_world_light_cone = dwconestr.strip().lower() == 'true' - elif line.startswith('!fix_trock_doors'): - _, trdstr = line.split(':', 1) - world.fix_trock_doors = {1: trdstr.strip().lower() == 'true'} - elif line.startswith('!fix_trock_exit'): - _, trfstr = line.split(':', 1) - world.fix_trock_exit = {1: trfstr.strip().lower() == 'true'} - elif line.startswith('!fix_gtower_exit'): - _, gtfstr = line.split(':', 1) - world.fix_gtower_exit = gtfstr.strip().lower() == 'true' - elif line.startswith('!fix_pod_exit'): - _, podestr = line.split(':', 1) - world.fix_palaceofdarkness_exit = {1: podestr.strip().lower() == 'true'} - elif line.startswith('!fix_skullwoods_exit'): - _, swestr = line.split(':', 1) - world.fix_skullwoods_exit = {1: swestr.strip().lower() == 'true'} - elif line.startswith('!check_beatable_only'): - _, chkbtstr = line.split(':', 1) - world.check_beatable_only = chkbtstr.strip().lower() == 'true' - elif line.startswith('!ganon_death_pyramid_respawn'): - _, gnpstr = line.split(':', 1) - world.ganon_at_pyramid = gnpstr.strip().lower() == 'true' - elif line.startswith('!save_quit_boss'): - _, sqbstr = line.split(':', 1) - world.save_and_quite_from_boss = sqbstr.strip().lower() == 'true' - elif line.startswith('!text_'): - textname, text = line.split(':', 1) - text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()]) - #temporarilly removed. New credits system not ready to handle this. - #elif line.startswith('!credits_'): - # textname, text = line.split(':', 1) - # text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()]) - continue - + elif ':' in line: locationstr, itemstr = line.split(':', 1) location = world.get_location(locationstr.strip(), 1) if location is None: @@ -172,6 +115,7 @@ def fill_world(world, plando, text_patches): world.push_item(location, item) if item.smallkey or item.bigkey: location.event = True + #TODO: move entrance stuff to prefill_world to work like OW stuff elif '<=>' in line: entrance, exit = line.split('<=>', 1) connect_two_way(world, entrance.strip(), exit.strip(), 1) @@ -182,14 +126,107 @@ def fill_world(world, plando, text_patches): entrance, exit = line.split('<=', 1) connect_exit(world, exit.strip(), entrance.strip(), 1) - world.required_medallions[1] = (mm_medallion, tr_medallion) - # set up Agahnim Events world.get_location('Agahnim 1', 1).event = True world.get_location('Agahnim 1', 1).item = ItemFactory('Beat Agahnim 1', 1) world.get_location('Agahnim 2', 1).event = True world.get_location('Agahnim 2', 1).item = ItemFactory('Beat Agahnim 2', 1) +def prefill_world(world, plando, text_patches): + mm_medallion = 'Ether' + tr_medallion = 'Quake' + logger = logging.getLogger('') + with open(plando, 'r') as plandofile: + for line in plandofile.readlines(): + line = line.lstrip() + if line.startswith('#'): + continue + elif line.startswith('!'): + if line.startswith('!mm_medallion'): + _, medallionstr = line.split(':', 1) + mm_medallion = medallionstr.strip() + elif line.startswith('!tr_medallion'): + _, medallionstr = line.split(':', 1) + tr_medallion = medallionstr.strip() + elif line.startswith('!mode'): + _, modestr = line.split(':', 1) + world.mode = {1: modestr.strip()} + elif line.startswith('!logic'): + _, logicstr = line.split(':', 1) + world.logic = {1: logicstr.strip()} + elif line.startswith('!goal'): + _, goalstr = line.split(':', 1) + world.goal = {1: goalstr.strip()} + elif line.startswith('!owShuffle'): + _, modestr = line.split(':', 1) + world.owShuffle = {1: modestr.strip()} + elif line.startswith('!owCrossed'): + _, modestr = line.split(':', 1) + modestr = modestr.strip().lower() + world.owCrossed = {1: True if modestr in ('true', 'yes', 'on', 'enabled') else False} + elif line.startswith('!owKeepSimilar'): + _, modestr = line.split(':', 1) + modestr = modestr.strip().lower() + world.owKeepSimilar = {1: True if modestr in ('true', 'yes', 'on', 'enabled') else False} + elif line.startswith('!light_cone_sewers'): + _, sewerstr = line.split(':', 1) + world.sewer_light_cone = {1: sewerstr.strip().lower() == 'true'} + elif line.startswith('!light_cone_lw'): + _, lwconestr = line.split(':', 1) + world.light_world_light_cone = lwconestr.strip().lower() == 'true' + elif line.startswith('!light_cone_dw'): + _, dwconestr = line.split(':', 1) + world.dark_world_light_cone = dwconestr.strip().lower() == 'true' + elif line.startswith('!fix_trock_doors'): + _, trdstr = line.split(':', 1) + world.fix_trock_doors = {1: trdstr.strip().lower() == 'true'} + elif line.startswith('!fix_trock_exit'): + _, trfstr = line.split(':', 1) + world.fix_trock_exit = {1: trfstr.strip().lower() == 'true'} + elif line.startswith('!fix_gtower_exit'): + _, gtfstr = line.split(':', 1) + world.fix_gtower_exit = gtfstr.strip().lower() == 'true' + elif line.startswith('!fix_pod_exit'): + _, podestr = line.split(':', 1) + world.fix_palaceofdarkness_exit = {1: podestr.strip().lower() == 'true'} + elif line.startswith('!fix_skullwoods_exit'): + _, swestr = line.split(':', 1) + world.fix_skullwoods_exit = {1: swestr.strip().lower() == 'true'} + elif line.startswith('!check_beatable_only'): + _, chkbtstr = line.split(':', 1) + world.check_beatable_only = chkbtstr.strip().lower() == 'true' + elif line.startswith('!ganon_death_pyramid_respawn'): + _, gnpstr = line.split(':', 1) + world.ganon_at_pyramid = gnpstr.strip().lower() == 'true' + elif line.startswith('!save_quit_boss'): + _, sqbstr = line.split(':', 1) + world.save_and_quite_from_boss = sqbstr.strip().lower() == 'true' + elif line.startswith('!text_'): + textname, text = line.split(':', 1) + text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()]) + #temporarilly removed. New credits system not ready to handle this. + #elif line.startswith('!credits_'): + # textname, text = line.split(':', 1) + # text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()]) + continue + elif line.startswith('$'): + edge1, edge2 = line.split('=', 1) + if world.custom_overworld is None: + world.custom_overworld = {1: []} + world.custom_overworld[1].append(edge1.strip(), edge2.strip()) + #TODO: Do entrances similar to OW + elif '<=>' in line: + entrance, exit = line.split('<=>', 1) + #connect_two_way(world, entrance.strip(), exit.strip(), 1) + elif '=>' in line: + entrance, exit = line.split('=>', 1) + #connect_entrance(world, entrance.strip(), exit.strip(), 1) + elif '<=' in line: + entrance, exit = line.split('<=', 1) + #connect_exit(world, exit.strip(), entrance.strip(), 1) + + world.required_medallions[1] = (mm_medallion, tr_medallion) + def start(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) diff --git a/Plandomizer_Template.txt b/Plandomizer_Template.txt index 6c94cc76..dce3f005 100644 --- a/Plandomizer_Template.txt +++ b/Plandomizer_Template.txt @@ -242,3 +242,10 @@ Ganons Tower - Mini Helmasaur Room - Right: Nothing Ganons Tower - Pre-Moldorm Chest: Small Key (Ganons Tower) Ganons Tower - Validation Chest: Nothing Ganon: Triforce + +# set Overworld connections (lines starting with $, separate edges with =) +!owShuffle: parallel +#!owMixed: true # Mixed OW not supported yet +!owCrossed: true +!owKeepSimilar: true +$Links House NE = Kakariko Village SE From 5fb394aba1bef9e931cb0320f1e55bab2e1160dc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 17 Aug 2021 10:26:05 -0500 Subject: [PATCH 11/12] Fixed issue with Pyramid Hole not shuffling --- EntranceShuffle.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 0863010b..6d3e0099 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1029,16 +1029,6 @@ def scramble_holes(world, player): ('Lost Woods Hideout Exit', 'Lost Woods Hideout (top)'), ('Lumberjack Tree Exit', 'Lumberjack Tree (top)')] - if not world.shuffle_ganon: - if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owMixed[player]): - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) - else: - connect_two_way(world, 'Inverted Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Inverted Pyramid Hole', 'Pyramid', player) - else: - hole_targets.append(('Pyramid Exit', 'Pyramid')) - if world.mode[player] == 'standard': # cannot move uncle cave connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) @@ -1050,18 +1040,31 @@ def scramble_holes(world, player): # do not shuffle sanctuary into pyramid hole unless shuffle is crossed if world.shuffle[player] == 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) - if world.shuffle_ganon: - random.shuffle(hole_targets) - exit, target = hole_targets.pop() + + # determine pyramid hole + if not world.shuffle_ganon: if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owMixed[player]): connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) else: connect_two_way(world, 'Inverted Pyramid Entrance', 'Pyramid Exit', player) connect_entrance(world, 'Inverted Pyramid Hole', 'Pyramid', player) + else: + hole_targets.append(('Pyramid Exit', 'Pyramid')) + + random.shuffle(hole_targets) + exit, target = hole_targets.pop() + if (world.mode[player] == 'inverted') == (0x03 in world.owswaps[player][0] and world.owMixed[player]): + connect_two_way(world, 'Pyramid Entrance', exit, player) + connect_entrance(world, 'Pyramid Hole', target, player) + else: + connect_two_way(world, 'Inverted Pyramid Entrance', exit, player) + connect_entrance(world, 'Inverted Pyramid Hole', target, player) + if world.shuffle[player] != 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) + # shuffle the rest random.shuffle(hole_targets) for entrance, drop in hole_entrances: exit, target = hole_targets.pop() From ec327ca5a9adb30176523cd688b136c5cd27dec8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 17 Aug 2021 15:07:50 -0500 Subject: [PATCH 12/12] Version bump 0.1.8.0 --- CHANGELOG.md | 8 ++++++++ OverworldShuffle.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97665ce..2a113cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### 0.1.8.0 +- Moved Crossed to its own checkbox option +- Removed Legacy ER shuffles +- Added OW Shuffle support for Plando module (needs user testing) +- Fixed issue with Sanc start at TR as bunny when it is LW +- Fixed issue with Pyramid Hole not getting shuffled +- ~~Merged DR v0.5.0.3 - Minor DR fixes~~ + ### 0.1.7.4 - Fixed issue with Mixed OW failing to generate when HC/Pyramid is swapped - Various fixes to improve generation rates for Mixed OW Shuffle diff --git a/OverworldShuffle.py b/OverworldShuffle.py index b4da2cd5..c41f9097 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -2,7 +2,7 @@ import RaceRandom as random, logging, copy from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OpenStd, parallel_links, IsParallel -__version__ = '0.1.7.4-u' +__version__ = '0.1.8.0-u' def link_overworld(world, player): # setup mandatory connections