Merging in base er changes

This commit is contained in:
aerinon
2021-02-25 15:26:29 -07:00
35 changed files with 2157 additions and 82 deletions

View File

@@ -1,6 +1,7 @@
import random
import RaceRandom as random
# ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave.
from collections import defaultdict
def link_entrances(world, player):
@@ -1117,11 +1118,9 @@ def link_inverted_entrances(world, player):
# randomize which desert ledge door is a must-exit
if random.randint(0, 1) == 0:
lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (North)')
dp_must_exit = 'Desert Palace Entrance (North)'
lw_entrances.append('Desert Palace Entrance (West)')
else:
lw_dungeon_entrances_must_exit.append('Desert Palace Entrance (West)')
dp_must_exit = 'Desert Palace Entrance (West)'
lw_entrances.append('Desert Palace Entrance (North)')
dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
@@ -1157,13 +1156,10 @@ def link_inverted_entrances(world, player):
connect_two_way(world, aga_door, 'Inverted Agahnims Tower Exit', player)
dungeon_exits.remove('Inverted Agahnims Tower Exit')
all_dungeon_entrances = dw_entrances + lw_entrances
connect_mandatory_exits(world, all_dungeon_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player, dp_must_exit)
remaining_dw_entrances = [i for i in all_dungeon_entrances if i in dw_entrances]
remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances]
connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, 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] == 'simple':
simple_shuffle_dungeons(world, player)
@@ -1271,7 +1267,7 @@ def link_inverted_entrances(world, player):
caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House)
single_doors = list(Single_Cave_Doors)
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors)
door_targets = list(Inverted_Single_Cave_Targets)
# place links house
@@ -1354,18 +1350,16 @@ def link_inverted_entrances(world, player):
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', '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
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors)
door_targets = list(Inverted_Single_Cave_Targets)
old_man_house = list(Old_Man_House)
# randomize which desert ledge door is a must-exit
if random.randint(0, 1) == 0:
lw_must_exits.append('Desert Palace Entrance (North)')
dp_must_exit = 'Desert Palace Entrance (North)'
lw_entrances.append('Desert Palace Entrance (West)')
else:
lw_must_exits.append('Desert Palace Entrance (West)')
dp_must_exit = 'Desert Palace Entrance (West)'
lw_entrances.append('Desert Palace Entrance (North)')
# tavern back door cannot be shuffled yet
@@ -1428,7 +1422,7 @@ def link_inverted_entrances(world, player):
# no dw must exits in inverted, but we randomize whether cave is in light or dark world
if random.randint(0, 1) == 0:
caves += old_man_house
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player, dp_must_exit)
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
try:
caves.remove(old_man_house[0])
except ValueError:
@@ -1437,7 +1431,7 @@ def link_inverted_entrances(world, player):
connect_caves(world, lw_entrances, [], old_man_house, player)
else:
connect_caves(world, dw_entrances, [], old_man_house, player)
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player, dp_must_exit)
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
# place old man, has limited options
# exit has to come from specific set of doors, the entrance is free to move about
@@ -1506,17 +1500,15 @@ def link_inverted_entrances(world, player):
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors)
door_targets = list(Inverted_Single_Cave_Targets)
# randomize which desert ledge door is a must-exit
if random.randint(0, 1) == 0:
must_exits.append('Desert Palace Entrance (North)')
dp_must_exit = 'Desert Palace Entrance (North)'
entrances.append('Desert Palace Entrance (West)')
else:
must_exits.append('Desert Palace Entrance (West)')
dp_must_exit = 'Desert Palace Entrance (West)'
entrances.append('Desert Palace Entrance (North)')
caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3)))
@@ -1567,7 +1559,7 @@ def link_inverted_entrances(world, player):
#place must-exit caves
connect_mandatory_exits(world, entrances, caves, must_exits, player, dp_must_exit)
connect_mandatory_exits(world, entrances, caves, must_exits, player)
# place old man, has limited options
@@ -1630,8 +1622,8 @@ def link_inverted_entrances(world, player):
# and rentering to find bomb shop. However appended list here is all those that we currently have
# bomb shop logic for.
# Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance'])
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Turtle Rock Isolated Ledge Entrance', 'Hookshot Cave Back Entrance'])
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Inverted_Blacksmith_Multi_Cave_Doors)
door_targets = list(Inverted_Single_Cave_Targets)
random.shuffle(doors)
@@ -1940,17 +1932,33 @@ def connect_random(world, exitlist, targetlist, player, two_way=False):
connect_entrance(world, exit, target, player)
def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_must_exit=None):
def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
"""This works inplace"""
random.shuffle(entrances)
random.shuffle(caves)
# Keeps track of entrances that cannot be used to access each exit / cave
if world.mode == 'inverted':
invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy()
else:
invalid_connections = Must_Exit_Invalid_Connections.copy()
invalid_cave_connections = defaultdict(set)
# Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge
if world.mode == 'inverted':
for entrance in invalid_connections:
if world.get_entrance(entrance, player).connected_region == world.get_region('Inverted Agahnims Tower', player):
for exit in invalid_connections[entrance]:
invalid_connections[exit] = invalid_connections[exit].union({'Inverted Ganons Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'})
break
used_caves = []
required_entrances = 0 # Number of entrances reserved for used_caves
while must_be_exits:
exit = must_be_exits.pop()
# find multi exit cave
cave = None
for candidate in caves:
if not isinstance(candidate, str):
if not isinstance(candidate, str) and (candidate in used_caves or len(candidate) < len(entrances) - required_entrances - 1):
cave = candidate
break
@@ -1959,30 +1967,47 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_m
# all caves are sorted so that the last exit is always reachable
connect_two_way(world, exit, cave[-1], player)
if len(cave) == 2:
entrance = entrances.pop()
# ToDo Better solution, this is a hot fix. Do not connect both sides of trock/desert ledge only to each other
if world.mode[player] != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)':
new_entrance = entrances.pop()
entrances.append(entrance)
entrance = new_entrance
if world.mode[player] == 'inverted' and entrance == dp_must_exit:
new_entrance = entrances.pop()
entrances.append(entrance)
entrance = new_entrance
if len(cave) == 2:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in invalid_cave_connections[tuple(cave)])
entrances.remove(entrance)
connect_two_way(world, entrance, cave[0], player)
if cave in used_caves:
required_entrances -= 2
used_caves.remove(cave)
if entrance in invalid_connections:
for exit2 in invalid_connections[entrance]:
invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]).union(invalid_cave_connections[tuple(cave)])
elif cave[-1] == 'Spectacle Rock Cave Exit': #Spectacle rock only has one exit
for exit in cave[:-1]:
connect_two_way(world,entrances.pop(),exit, player)
cave_entrances = []
for cave_exit in cave[:-1]:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit])
cave_entrances.append(entrance)
entrances.remove(entrance)
connect_two_way(world,entrance,cave_exit, player)
if entrance not in invalid_connections:
invalid_connections[exit] = set()
if all(entrance in invalid_connections for entrance in cave_entrances):
new_invalid_connections = invalid_connections[cave_entrances[0]].intersection(invalid_connections[cave_entrances[1]])
for exit2 in new_invalid_connections:
invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit])
else:#save for later so we can connect to multiple exits
if cave in used_caves:
required_entrances -= 1
used_caves.remove(cave)
else:
required_entrances += len(cave)-1
caves.append(cave[0:-1])
random.shuffle(caves)
used_caves.append(cave[0:-1])
invalid_cave_connections[tuple(cave[0:-1])] = invalid_cave_connections[tuple(cave)].union(invalid_connections[exit])
caves.remove(cave)
for cave in used_caves:
if cave in caves: #check if we placed multiple entrances from this 3 or 4 exit
for exit in cave:
connect_two_way(world, entrances.pop(), exit, player)
for cave_exit in cave:
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)])
invalid_cave_connections[tuple(cave)] = set()
entrances.remove(entrance)
connect_two_way(world, entrance, cave_exit, player)
caves.remove(cave)
@@ -2685,6 +2710,8 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
'Desert Palace Entrance (West)',
'Desert Palace Entrance (North)']
Inverted_Blacksmith_Multi_Cave_Doors = Blacksmith_Multi_Cave_Doors # same as non-inverted
Inverted_LW_Single_Cave_Doors = LW_Single_Cave_Doors + ['Inverted Big Bomb Shop']
Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)',
@@ -2755,6 +2782,37 @@ Inverted_Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing',
'Inverted Links House',
'Inverted Big Bomb Shop']
Inverted_Blacksmith_Single_Cave_Doors = ['Blinds Hideout',
'Lake Hylia Fairy',
'Light Hype Fairy',
'Desert Fairy',
'Chicken House',
'Aginahs Cave',
'Sahasrahlas Hut',
'Cave Shop (Lake Hylia)',
'Blacksmiths Hut',
'Sick Kids House',
'Lost Woods Gamble',
'Fortune Teller (Light)',
'Snitch Lady (East)',
'Snitch Lady (West)',
'Bush Covered House',
'Tavern (Front)',
'Light World Bomb Hut',
'Kakariko Shop',
'Mini Moldorm Cave',
'Long Fairy Cave',
'Good Bee Cave',
'20 Rupee Cave',
'50 Rupee Cave',
'Ice Rod Cave',
'Library',
'Potion Shop',
'Dam',
'Lumberjack House',
'Lake Hylia Fortune Teller',
'Kakariko Gamble Game',
'Inverted Big Bomb Shop']
Inverted_Single_Cave_Targets = ['Blinds Hideout',
'Bonk Fairy (Light)',
@@ -2844,6 +2902,27 @@ Isolated_LH_Doors = ['Kings Grave',
'Dark World Hammer Peg Cave',
'Turtle Rock Isolated Ledge Entrance']
# Entrances that cannot be used to access a must_exit entrance - symmetrical to allow reverse lookups
Must_Exit_Invalid_Connections = defaultdict(set, {
'Dark Death Mountain Ledge (East)': {'Dark Death Mountain Ledge (West)', 'Mimic Cave'},
'Dark Death Mountain Ledge (West)': {'Dark Death Mountain Ledge (East)', 'Mimic Cave'},
'Mimic Cave': {'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)'},
'Bumper Cave (Top)': {'Death Mountain Return Cave (West)'},
'Death Mountain Return Cave (West)': {'Bumper Cave (Top)'},
'Skull Woods Second Section Door (West)': {'Skull Woods Final Section'},
'Skull Woods Final Section': {'Skull Woods Second Section Door (West)'},
})
Inverted_Must_Exit_Invalid_Connections = defaultdict(set, {
'Bumper Cave (Top)': {'Death Mountain Return Cave (West)'},
'Death Mountain Return Cave (West)': {'Bumper Cave (Top)'},
'Desert Palace Entrance (North)': {'Desert Palace Entrance (West)'},
'Desert Palace Entrance (West)': {'Desert Palace Entrance (North)'},
'Inverted Ganons Tower': {'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)'},
'Hyrule Castle Entrance (West)': {'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower'},
'Hyrule Castle Entrance (East)': {'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower'},
})
# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
mandatory_connections = [('Links House S&Q', 'Links House'),
('Sanctuary S&Q', 'Sanctuary'),
@@ -2963,8 +3042,10 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
('Old Man S&Q', 'Old Man House'),
('Castle Ledge S&Q', 'Hyrule Castle Ledge'),
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia Island', 'Lake Hylia Island'),
('Zoras River', 'Zoras River'),
('Lake Hylia Island Pier', 'Lake Hylia Island'),
('Lake Hylia Warp', 'Northeast Light World'),
('Northeast Light World Warp', 'Light World'),
('Zoras River', 'Zoras River'),
('Kings Grave Outer Rocks', 'Kings Grave Area'),
('Kings Grave Inner Rocks', 'Light World'),
('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'),