Merge branch 'master' into Dev-owg
This commit is contained in:
@@ -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):
|
||||
@@ -1103,11 +1104,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)'))
|
||||
@@ -1143,13 +1142,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 == 'simple':
|
||||
simple_shuffle_dungeons(world, player)
|
||||
@@ -1253,7 +1249,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(Inverted_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
|
||||
@@ -1335,18 +1331,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(Inverted_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
|
||||
@@ -1408,7 +1402,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:
|
||||
@@ -1417,7 +1411,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
|
||||
@@ -1486,17 +1480,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(Inverted_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)))
|
||||
@@ -1546,7 +1538,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
|
||||
@@ -1609,8 +1601,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 + ['Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance'])
|
||||
blacksmith_doors = list(Inverted_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)
|
||||
@@ -1909,17 +1901,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
|
||||
|
||||
@@ -1928,30 +1936,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 != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)':
|
||||
new_entrance = entrances.pop()
|
||||
entrances.append(entrance)
|
||||
entrance = new_entrance
|
||||
if world.mode == '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)
|
||||
|
||||
|
||||
@@ -2632,8 +2657,6 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
||||
'Death Mountain Return Cave (East)',
|
||||
'Death Mountain Return Cave (West)',
|
||||
'Spectacle Rock Cave Peak',
|
||||
'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave (Bottom)',
|
||||
'Paradox Cave (Bottom)',
|
||||
'Paradox Cave (Middle)',
|
||||
'Paradox Cave (Top)',
|
||||
@@ -2648,7 +2671,7 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
||||
'Desert Palace Entrance (West)',
|
||||
'Desert Palace Entrance (North)']
|
||||
|
||||
Inverted_Blacksmith_Multi_Cave_Doors = [] # same as non-inverted
|
||||
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']
|
||||
|
||||
@@ -2840,6 +2863,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 = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
|
||||
('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'),
|
||||
@@ -3057,7 +3101,10 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
|
||||
('Cave 45 Clip Spot', 'Cave 45 Ledge'),
|
||||
]
|
||||
|
||||
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
|
||||
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
|
||||
('Lake Hylia Island Pier', 'Lake Hylia Island'),
|
||||
('Lake Hylia Warp', 'Northeast Light World'),
|
||||
('Northeast Light World Warp', 'Light World'),
|
||||
('Zoras River', 'Zoras River'),
|
||||
('Graveyard Ledge Clip Spot', 'Kings Grave Area'),
|
||||
('Kings Grave Outer Rocks', 'Kings Grave Area'),
|
||||
@@ -3305,7 +3352,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
|
||||
('Lumberjack House', 'Lumberjack House'),
|
||||
("Hyrule Castle Secret Entrance Drop", "Hyrule Castle Secret Entrance"),
|
||||
("Hyrule Castle Secret Entrance Stairs", "Hyrule Castle Secret Entrance"),
|
||||
("Hyrule Castle Secret Entrance Exit", "Light World"),
|
||||
("Hyrule Castle Secret Entrance Exit", "Hyrule Castle Courtyard"),
|
||||
('Bonk Fairy (Light)', 'Bonk Fairy (Light)'),
|
||||
('Lake Hylia Fairy', 'Lake Hylia Healer Fairy'),
|
||||
('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'),
|
||||
@@ -3616,7 +3663,7 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace
|
||||
('Hyrule Castle Entrance (South)', 'Hyrule Castle'),
|
||||
('Hyrule Castle Entrance (West)', 'Hyrule Castle'),
|
||||
('Hyrule Castle Entrance (East)', 'Hyrule Castle'),
|
||||
('Hyrule Castle Exit (South)', 'Light World'),
|
||||
('Hyrule Castle Exit (South)', 'Hyrule Castle Courtyard'),
|
||||
('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'),
|
||||
('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'),
|
||||
('Agahnims Tower', 'Agahnims Tower'),
|
||||
|
||||
Reference in New Issue
Block a user