From 5c9aa09c80d4229ec1b1bee6c9a54c3950f21841 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 8 May 2021 12:04:03 +0200 Subject: [PATCH] LttP: implement dungeonscrossed ER --- Main.py | 2 +- worlds/alttp/EntranceRandomizer.py | 2 +- worlds/alttp/EntranceShuffle.py | 95 ++++++++++++++++++++++++++++-- worlds/alttp/Rom.py | 22 +++---- 4 files changed, 104 insertions(+), 17 deletions(-) diff --git a/Main.py b/Main.py index 9960b288..0993777f 100644 --- a/Main.py +++ b/Main.py @@ -212,7 +212,7 @@ def main(args, seed=None): world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} elif world.open_pyramid[player] == 'auto': world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \ - (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull'} or not world.shuffle_ganon) + (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'} or not world.shuffle_ganon) else: world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(world.open_pyramid[player], world.open_pyramid[player]) diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 1801a8c0..0e8d744a 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -172,7 +172,7 @@ def parse_arguments(argv, no_defaults=False): slightly biased to placing progression items with less restrictions. ''') - parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'], + parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed'], help='''\ Select Entrance Shuffling Algorithm. (default: %(default)s) Full: Mix cave and dungeon entrances freely while limiting diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index 92957409..fdf3750b 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -60,6 +60,8 @@ def link_entrances(world, player): connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) + elif world.shuffle[player] == 'dungeonscrossed': + crossed_shuffle_dungeons(world, player) elif world.shuffle[player] == 'simple': simple_shuffle_dungeons(world, player) @@ -1117,7 +1119,7 @@ def link_inverted_entrances(world, player): dw_entrances = list(Inverted_DW_Dungeon_Entrances) # randomize which desert ledge door is a must-exit - if world.random.randint(0, 1) == 0: + if world.random.randint(0, 1): lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') lw_entrances.append('Desert Palace Entrance (West)') else: @@ -1152,7 +1154,7 @@ def link_inverted_entrances(world, player): if aga_door in lw_entrances: lw_entrances.remove(aga_door) - elif aga_door in dw_entrances: + else: dw_entrances.remove(aga_door) connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) @@ -1161,7 +1163,8 @@ def link_inverted_entrances(world, player): connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) - + elif world.shuffle[player] == 'dungeonscrossed': + inverted_crossed_shuffle_dungeons(world, player) elif world.shuffle[player] == 'simple': simple_shuffle_dungeons(world, player) @@ -2030,7 +2033,7 @@ def connect_caves(world, lw_entrances, dw_entrances, caves, player): # connect highest exit count caves first, prevent issue where we have 2 or 3 exits accross worlds left to fill caves.sort(key=lambda cave: 1 if isinstance(cave, str) else len(cave), reverse=True) for cave in caves: - target = lw_entrances if world.random.randint(0, 1) == 0 else dw_entrances + target = lw_entrances if world.random.randint(0, 1) else dw_entrances if isinstance(cave, str): cave = (cave,) @@ -2194,6 +2197,90 @@ def simple_shuffle_dungeons(world, player): connect_two_way(world, 'Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)', player) connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', player) +def crossed_shuffle_dungeons(world, player: int): + lw_entrances = LW_Dungeon_Entrances.copy() + dw_entrances = DW_Dungeon_Entrances.copy() + + for exitname, regionname in default_connections: + connect_simple(world, exitname, regionname, player) + + skull_woods_shuffle(world, player) + + dungeon_exits = Dungeon_Exits_Base.copy() + dungeon_entrances = lw_entrances+dw_entrances + + if not world.shuffle_ganon: + connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) + else: + dungeon_entrances.append('Ganons Tower') + dungeon_exits.append('Ganons Tower Exit') + + 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: + dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) + dungeon_entrances.append('Hyrule Castle Entrance (South)') + + connect_mandatory_exits(world, dungeon_entrances, dungeon_exits, + LW_Dungeon_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit, player) + + if world.mode[player] == 'standard': + connect_caves(world, dungeon_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) + + connect_caves(world, dungeon_entrances, [], dungeon_exits, player) + assert not dungeon_exits # make sure all exits are accounted for + +def inverted_crossed_shuffle_dungeons(world, player: int): + + lw_entrances = Inverted_LW_Dungeon_Entrances.copy() + dw_entrances = Inverted_DW_Dungeon_Entrances.copy() + lw_dungeon_entrances_must_exit = list(Inverted_LW_Dungeon_Entrances_Must_Exit) + for exitname, regionname in inverted_default_connections: + connect_simple(world, exitname, regionname, player) + + skull_woods_shuffle(world, player) + + dungeon_exits = Inverted_Dungeon_Exits_Base.copy() + dungeon_entrances = lw_entrances+dw_entrances + + # randomize which desert ledge door is a must-exit + if world.random.randint(0, 1): + lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)') + dungeon_entrances.append('Desert Palace Entrance (West)') + else: + lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)') + dungeon_entrances.append('Desert Palace Entrance (North)') + + dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) + dungeon_entrances.append('Hyrule Castle Entrance (South)') + + if not world.shuffle_ganon: + connect_two_way(world, 'Inverted Ganons Tower', 'Inverted Ganons Tower Exit', player) + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'] + else: + dungeon_entrances.append('Inverted Ganons Tower') + dungeon_exits.append('Inverted Ganons Tower Exit') + hc_ledge_entrances = ['Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower'] + + # shuffle aga door first. If it's on HC ledge, remaining HC ledge door must be must-exit + world.random.shuffle(dungeon_entrances) + aga_door = dungeon_entrances.pop() + + if aga_door in hc_ledge_entrances: + hc_ledge_entrances.remove(aga_door) + world.random.shuffle(hc_ledge_entrances) + hc_ledge_must_exit = hc_ledge_entrances.pop() + dungeon_entrances.remove(hc_ledge_must_exit) + lw_dungeon_entrances_must_exit.append(hc_ledge_must_exit) + + connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) + dungeon_exits.remove('Inverted Agahnims Tower Exit') + + connect_mandatory_exits(world, dungeon_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) + + connect_caves(world, dungeon_entrances, [], dungeon_exits, player) + assert not dungeon_exits # make sure all exits are accounted for def unbias_some_entrances(world, Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits): def shuffle_lists_in_list(ls): diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index f99a8ef7..85676555 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -2080,7 +2080,7 @@ def write_strings(rom, world, player, team): tt.removeUnwantedText() # Let's keep this guy's text accurate to the shuffle setting. - if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']: + if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed']: tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' @@ -2138,7 +2138,7 @@ def write_strings(rom, world, player, team): break # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. entrances_to_hint.update(InconvenientOtherEntrances) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: hint_count = 0 elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']: hint_count = 2 @@ -2180,7 +2180,7 @@ def write_strings(rom, world, player, team): entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) - hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0 + hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'] else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: if hint_count: @@ -2194,10 +2194,10 @@ def write_strings(rom, world, player, team): # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: locations_to_hint.extend(InconvenientVanillaLocations) local_random.shuffle(locations_to_hint) - hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5 + hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'] else 5 for location in locations_to_hint[:hint_count]: if location == 'Swamp Left': if local_random.randint(0, 1): @@ -2256,7 +2256,7 @@ def write_strings(rom, world, player, team): if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) local_random.shuffle(items_to_hint) - hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 + hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'] else 8 while hint_count > 0 and items_to_hint: this_item = items_to_hint.pop(0) this_location = world.find_items(this_item, player) @@ -2473,7 +2473,7 @@ def set_inverted_mode(world, player, rom): rom.write_byte(0xDBB73 + 0x36, 0x24) rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0) rom.write_int16(0x15AEE + 2 * 0x25, 0x000C) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: rom.write_byte(0x15B8C, 0x6C) rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house rom.write_byte(0xDBB73 + 0x52, 0x01) @@ -2531,7 +2531,7 @@ def set_inverted_mode(world, player, rom): rom.write_int16(snes_to_pc(0x02D9A6), 0x005A) rom.write_byte(snes_to_pc(0x02D9B3), 0x12) # keep the old man spawn point at old man house unless shuffle is vanilla - if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']: + if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed']: rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1) rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03]) @@ -2594,7 +2594,7 @@ def set_inverted_mode(world, player, rom): rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B]) rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance rom.write_int16(snes_to_pc(0x308320), 0x001B) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: rom.write_byte(snes_to_pc(0x308340), 0x7B) rom.write_int16(snes_to_pc(0x1af504), 0x148B) rom.write_int16(snes_to_pc(0x1af50c), 0x149B) @@ -2631,10 +2631,10 @@ def set_inverted_mode(world, player, rom): rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: rom.write_byte(0xDBB73 + 0x35, 0x36) rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: + if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area rom.write_byte(0x15B8C + 0x37, 0x1B) rom.write_int16(0x15BDB + 2 * 0x37, 0x0418)