LttP: implement dungeonscrossed ER

This commit is contained in:
Fabian Dill 2021-05-08 12:04:03 +02:00
parent e5bbcb8d27
commit 5c9aa09c80
4 changed files with 104 additions and 17 deletions

View File

@ -212,7 +212,7 @@ def main(args, seed=None):
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
elif world.open_pyramid[player] == 'auto': elif world.open_pyramid[player] == 'auto':
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \ 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: else:
world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(world.open_pyramid[player], world.open_pyramid[player]) world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(world.open_pyramid[player], world.open_pyramid[player])

View File

@ -172,7 +172,7 @@ def parse_arguments(argv, no_defaults=False):
slightly biased to placing progression items with slightly biased to placing progression items with
less restrictions. 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='''\ help='''\
Select Entrance Shuffling Algorithm. (default: %(default)s) Select Entrance Shuffling Algorithm. (default: %(default)s)
Full: Mix cave and dungeon entrances freely while limiting Full: Mix cave and dungeon entrances freely while limiting

View File

@ -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, 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_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, 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': elif world.shuffle[player] == 'simple':
simple_shuffle_dungeons(world, player) simple_shuffle_dungeons(world, player)
@ -1117,7 +1119,7 @@ def link_inverted_entrances(world, player):
dw_entrances = list(Inverted_DW_Dungeon_Entrances) dw_entrances = list(Inverted_DW_Dungeon_Entrances)
# randomize which desert ledge door is a must-exit # 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_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)')
lw_entrances.append('Desert Palace Entrance (West)') lw_entrances.append('Desert Palace Entrance (West)')
else: else:
@ -1152,7 +1154,7 @@ def link_inverted_entrances(world, player):
if aga_door in lw_entrances: if aga_door in lw_entrances:
lw_entrances.remove(aga_door) lw_entrances.remove(aga_door)
elif aga_door in dw_entrances: else:
dw_entrances.remove(aga_door) dw_entrances.remove(aga_door)
connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player) 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_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player)
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, 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': elif world.shuffle[player] == 'simple':
simple_shuffle_dungeons(world, player) 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 # 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) caves.sort(key=lambda cave: 1 if isinstance(cave, str) else len(cave), reverse=True)
for cave in caves: 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): if isinstance(cave, str):
cave = (cave,) 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 (West)', 'Turtle Rock Ledge Exit (West)', player)
connect_two_way(world, 'Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)', 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 unbias_some_entrances(world, Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits):
def shuffle_lists_in_list(ls): def shuffle_lists_in_list(ls):

View File

@ -2080,7 +2080,7 @@ def write_strings(rom, world, player, team):
tt.removeUnwantedText() tt.removeUnwantedText()
# Let's keep this guy's text accurate to the shuffle setting. # 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_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.' 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 break
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
entrances_to_hint.update(InconvenientOtherEntrances) 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 hint_count = 0
elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']: elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']:
hint_count = 2 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'}) entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else: else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) 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: for entrance in all_entrances:
if entrance.name in entrances_to_hint: if entrance.name in entrances_to_hint:
if hint_count: 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. # 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() 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) locations_to_hint.extend(InconvenientVanillaLocations)
local_random.shuffle(locations_to_hint) 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]: for location in locations_to_hint[:hint_count]:
if location == 'Swamp Left': if location == 'Swamp Left':
if local_random.randint(0, 1): if local_random.randint(0, 1):
@ -2256,7 +2256,7 @@ def write_strings(rom, world, player, team):
if world.bigkeyshuffle[player]: if world.bigkeyshuffle[player]:
items_to_hint.extend(BigKeys) items_to_hint.extend(BigKeys)
local_random.shuffle(items_to_hint) 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: while hint_count > 0 and items_to_hint:
this_item = items_to_hint.pop(0) this_item = items_to_hint.pop(0)
this_location = world.find_items(this_item, player) 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_byte(0xDBB73 + 0x36, 0x24)
rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0) rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0)
rom.write_int16(0x15AEE + 2 * 0x25, 0x000C) 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(0x15B8C, 0x6C)
rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house
rom.write_byte(0xDBB73 + 0x52, 0x01) 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_int16(snes_to_pc(0x02D9A6), 0x005A)
rom.write_byte(snes_to_pc(0x02D9B3), 0x12) rom.write_byte(snes_to_pc(0x02D9B3), 0x12)
# keep the old man spawn point at old man house unless shuffle is vanilla # 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_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01])
rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1) rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1)
rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03]) 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_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(0x308300), 0x0140) # new pyramid hole entrance
rom.write_int16(snes_to_pc(0x308320), 0x001B) 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_byte(snes_to_pc(0x308340), 0x7B)
rom.write_int16(snes_to_pc(0x1af504), 0x148B) rom.write_int16(snes_to_pc(0x1af504), 0x148B)
rom.write_int16(snes_to_pc(0x1af50c), 0x149B) 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_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82])
rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door
rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4) 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(0xDBB73 + 0x35, 0x36)
rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp 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_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area
rom.write_byte(0x15B8C + 0x37, 0x1B) rom.write_byte(0x15B8C + 0x37, 0x1B)
rom.write_int16(0x15BDB + 2 * 0x37, 0x0418) rom.write_int16(0x15BDB + 2 * 0x37, 0x0418)