Aga Tower preliminary check in
This commit is contained in:
256
DoorShuffle.py
256
DoorShuffle.py
@@ -3,7 +3,8 @@ import collections
|
||||
import logging
|
||||
|
||||
from BaseClasses import RegionType, DoorType, Direction, Sector, pol_idx
|
||||
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions
|
||||
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions
|
||||
from Dungeons import dungeon_regions
|
||||
|
||||
def link_doors(world, player):
|
||||
|
||||
@@ -14,7 +15,7 @@ def link_doors(world, player):
|
||||
for edge_a, edge_b in interior_doors:
|
||||
connect_two_way(world, edge_a, edge_b, player)
|
||||
|
||||
# These connection are here because they are currently unable to be shuffled
|
||||
# These connections are here because they are currently unable to be shuffled
|
||||
for entrance, ext in straight_staircases:
|
||||
connect_two_way(world, entrance, ext, player)
|
||||
for entrance, ext in open_edges:
|
||||
@@ -161,11 +162,13 @@ def within_dungeon(world, player):
|
||||
dungeon_region_starts_ep = ['Eastern Lobby']
|
||||
dungeon_region_starts_dp = ['Desert Back Lobby', 'Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby']
|
||||
dungeon_region_starts_th = ['Hera Lobby']
|
||||
dungeon_region_starts_at = ['Tower Lobby']
|
||||
dungeon_region_lists = [
|
||||
(dungeon_region_starts_es, hyrule_castle_regions),
|
||||
(dungeon_region_starts_ep, eastern_regions),
|
||||
(dungeon_region_starts_dp, desert_regions),
|
||||
(dungeon_region_starts_th, hera_regions)
|
||||
(dungeon_region_starts_th, hera_regions),
|
||||
(dungeon_region_starts_at, tower_regions),
|
||||
]
|
||||
for start_list, region_list in dungeon_region_lists:
|
||||
shuffle_dungeon(world, player, start_list, region_list)
|
||||
@@ -199,7 +202,7 @@ def shuffle_dungeon(world, player, start_region_names, dungeon_region_names):
|
||||
# into the dungeon), or puts them late in the dungeon (so they probably are part
|
||||
# of a loop). Panic if neither of these happens.
|
||||
random.shuffle(available_doors)
|
||||
available_doors.sort(key=lambda door: 1 if door.blocked else 2 if door.ugly else 0)
|
||||
available_doors.sort(key=lambda door: 1 if door.blocked else 0 if door.ugly else 2)
|
||||
door = available_doors.pop()
|
||||
logger.info('Linking %s', door.name)
|
||||
# Find an available region that has a compatible door
|
||||
@@ -328,28 +331,39 @@ def doors_fit_mandatory_pair(pair_list, a, b):
|
||||
|
||||
|
||||
def cross_dungeon(world, player):
|
||||
hc = convert_to_sectors(hyrule_castle_regions, world, player)
|
||||
ep = convert_to_sectors(eastern_regions, world, player)
|
||||
dp = convert_to_sectors(desert_regions, world, player)
|
||||
th = convert_to_sectors(hera_regions, world, player)
|
||||
world_split = split_up_sectors(hc + ep + dp + th, default_dungeon_sets)
|
||||
dp_split = split_up_sectors(world_split.pop(2), desert_default_entrance_sets)
|
||||
world_split.extend(dp_split)
|
||||
hc = convert_to_sectors(dungeon_regions['Hyrule Castle'], world, player)
|
||||
ep = convert_to_sectors(dungeon_regions['Eastern'], world, player)
|
||||
dp = convert_to_sectors(dungeon_regions['Desert'], world, player)
|
||||
th = convert_to_sectors(dungeon_regions['Hera'], world, player)
|
||||
at = convert_to_sectors(dungeon_regions['Tower'], world, player)
|
||||
dungeon_split = split_up_sectors(hc + ep + dp + th + at, default_dungeon_sets)
|
||||
dp_split = split_up_sectors(dungeon_split.pop(2), desert_default_entrance_sets)
|
||||
dungeon_sectors = []
|
||||
# todo - adjust dungeon item pools
|
||||
for sector_list in world_split:
|
||||
shuffle_dungeon_no_repeats(world, player, sector_list)
|
||||
for idx, sector_list in enumerate(dungeon_split):
|
||||
dungeon_sectors.append((sector_list, entrance_sets[idx]))
|
||||
for idx, sector_list in enumerate(dp_split):
|
||||
dungeon_sectors.append((sector_list, desert_default_entrance_sets[idx]))
|
||||
|
||||
for sector_list, entrance_list in dungeon_sectors:
|
||||
shuffle_dungeon_no_repeats(world, player, sector_list, entrance_list)
|
||||
|
||||
|
||||
def experiment(world, player):
|
||||
hc = convert_to_sectors(hyrule_castle_regions, world, player)
|
||||
ep = convert_to_sectors(eastern_regions, world, player)
|
||||
dp = convert_to_sectors(desert_regions, world, player)
|
||||
th = convert_to_sectors(hera_regions, world, player)
|
||||
dungeon_sectors = [hc, ep, th]
|
||||
hc = convert_to_sectors(dungeon_regions['Hyrule Castle'], world, player)
|
||||
ep = convert_to_sectors(dungeon_regions['Eastern'], world, player)
|
||||
dp = convert_to_sectors(dungeon_regions['Desert'], world, player)
|
||||
th = convert_to_sectors(dungeon_regions['Hera'], world, player)
|
||||
at = convert_to_sectors(dungeon_regions['Tower'], world, player)
|
||||
dungeon_sectors = []
|
||||
for idx, sector_list in enumerate([hc, ep, th, at]):
|
||||
dungeon_sectors.append((sector_list, entrance_sets[idx]))
|
||||
dp_split = split_up_sectors(dp, desert_default_entrance_sets)
|
||||
dungeon_sectors.extend(dp_split)
|
||||
for sector_list in dungeon_sectors:
|
||||
shuffle_dungeon_no_repeats(world, player, sector_list)
|
||||
for idx, sector_list in enumerate(dp_split):
|
||||
dungeon_sectors.append((sector_list, desert_default_entrance_sets[idx]))
|
||||
|
||||
for sector_list, entrance_list in dungeon_sectors:
|
||||
shuffle_dungeon_no_repeats(world, player, sector_list, entrance_list)
|
||||
|
||||
|
||||
def convert_regions(region_names, world, player):
|
||||
@@ -378,7 +392,7 @@ def convert_to_sectors(region_names, world, player):
|
||||
exits.extend(connect_region.exits)
|
||||
else:
|
||||
door = world.check_for_door(ext.name, player)
|
||||
if door is not None and not door.landing:
|
||||
if door is not None:
|
||||
outstanding_doors.append(door)
|
||||
sector = Sector()
|
||||
sector.regions.extend(region_chunk)
|
||||
@@ -515,79 +529,111 @@ def find_proposal(proposal, buckets, candidates):
|
||||
|
||||
# code below is for an algorithm without restarts
|
||||
|
||||
# "simple" thing that would probably reduce the number of restarts:
|
||||
# When you pick a region check for all existing connections to other regions
|
||||
# first via region.exits (which is a list of non-door exits from the current region)
|
||||
# then for each Entrance in that list it may have a connected_region (or not)
|
||||
# but make sure the connected_region is a Dungeon type so the search doesn't venture out into the overworld.
|
||||
# Then, once you have this region chunk, add all the doors and do the normal loop.
|
||||
# Nuts, normal loop
|
||||
|
||||
|
||||
def shuffle_dungeon_no_repeats(world, player, available_sectors):
|
||||
def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region_names):
|
||||
logger = logging.getLogger('')
|
||||
|
||||
random.shuffle(available_sectors)
|
||||
for sector in available_sectors:
|
||||
random.shuffle(sector.outstanding_doors)
|
||||
|
||||
while len(available_sectors) > 0:
|
||||
# Pick a random region and make its doors the open set
|
||||
sector = available_sectors.pop()
|
||||
current_sector = sector
|
||||
entrance_regions = []
|
||||
# current_sector = None
|
||||
for region_name in entrance_region_names:
|
||||
entrance_regions.append(world.get_region(region_name, player))
|
||||
|
||||
# Loop until all available doors are used
|
||||
while len(current_sector.outstanding_doors) > 0:
|
||||
# Pick a random available door to connect
|
||||
random.shuffle(current_sector.outstanding_doors)
|
||||
door = current_sector.outstanding_doors.pop()
|
||||
logger.info('Linking %s', door.name)
|
||||
# Find an available region that has a compatible door
|
||||
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors)
|
||||
while len(compatibles) > 0:
|
||||
connect_sector, connect_door = compatibles.pop()
|
||||
logger.info(' Found possible new sector via %s', connect_door.name)
|
||||
# Check if valid
|
||||
if is_valid(door, connect_door, current_sector, connect_sector, available_sectors):
|
||||
# Apply connection and add the new region's doors to the available list
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
connect_sector.outstanding_doors.remove(connect_door)
|
||||
reachable_doors, visited_regions = extend_reachable(entrance_regions, [], world, player)
|
||||
# Loop until all available doors are used
|
||||
while len(reachable_doors) > 0:
|
||||
# Pick a random available door to connect
|
||||
door = random.choice(reachable_doors)
|
||||
sector = find_sector_for_door(door, available_sectors)
|
||||
sector.outstanding_doors.remove(door)
|
||||
# door_connected = False
|
||||
logger.info('Linking %s', door.name)
|
||||
# Find an available region that has a compatible door
|
||||
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors)
|
||||
while len(compatibles) > 0:
|
||||
connect_sector, connect_door = compatibles.pop()
|
||||
logger.info(' Found possible new sector via %s', connect_door.name)
|
||||
# Check if valid
|
||||
if is_valid(door, connect_door, sector, connect_sector, available_sectors):
|
||||
# Apply connection and add the new region's doors to the available list
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
reachable_doors.remove(door)
|
||||
connect_sector.outstanding_doors.remove(connect_door)
|
||||
if sector != connect_sector: # combine if not the same
|
||||
available_sectors.remove(connect_sector)
|
||||
current_sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||
current_sector.regions.extend(connect_sector.regions)
|
||||
break
|
||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
if len(compatibles) == 0: # time to try again
|
||||
current_sector.outstanding_doors.insert(0, door)
|
||||
if len(current_sector.outstanding_doors) <= 1:
|
||||
raise Exception('Rejected last option due to dead end... infinite loop ensues')
|
||||
else:
|
||||
# If there's no available region with a door, use an internal connection
|
||||
# todo: find all possibles for this door first
|
||||
connect_door = find_compatible_door_in_list_old(world, door, current_sector.outstanding_doors, player)
|
||||
if connect_door is not None:
|
||||
logger.info(' Adding loop via %s', connect_door.name)
|
||||
# Check if valid
|
||||
if is_loop_valid(door, connect_door, current_sector, len(available_sectors) == 0):
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
current_sector.outstanding_doors.remove(connect_door)
|
||||
else:
|
||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
current_sector.outstanding_doors.insert(0, door)
|
||||
if len(current_sector.outstanding_doors) <= 2:
|
||||
raise Exception('Rejected last option due to likely improper loops...')
|
||||
sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||
sector.regions.extend(connect_sector.regions)
|
||||
if not door.blocked:
|
||||
connect_region = world.get_entrance(door.dest.name, player).parent_region
|
||||
potential_new_doors, visited_regions = extend_reachable([connect_region], visited_regions, world, player)
|
||||
reachable_doors.extend(potential_new_doors)
|
||||
break # skips else block below
|
||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
if len(compatibles) == 0: # time to try again
|
||||
sector.outstanding_doors.insert(0, door)
|
||||
if len(reachable_doors) <= 1:
|
||||
raise Exception('Rejected last option due to dead end... infinite loop ensues')
|
||||
else:
|
||||
# If there's no available region with a door, use an internal connection
|
||||
# todo: find all possibles for this door first
|
||||
connect_door = find_compatible_door_in_list_old(world, door, reachable_doors, player)
|
||||
if connect_door is not None:
|
||||
logger.info(' Adding loop via %s', connect_door.name)
|
||||
# Check if valid
|
||||
if is_loop_valid(door, connect_door, sector, len(available_sectors) == 1):
|
||||
maybe_connect_two_way(world, door, connect_door, player)
|
||||
reachable_doors.remove(door)
|
||||
reachable_doors.remove(connect_door)
|
||||
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
||||
connect_sector.outstanding_doors.remove(connect_door)
|
||||
if sector != connect_sector: # combine if not the same
|
||||
available_sectors.remove(connect_sector)
|
||||
sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||
sector.regions.extend(connect_sector.regions)
|
||||
else:
|
||||
raise Exception('Something has gone terribly wrong')
|
||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||
sector.outstanding_doors.insert(0, door)
|
||||
if len(reachable_doors) <= 2:
|
||||
raise Exception('Rejected last option due to likely improper loops...')
|
||||
else:
|
||||
raise Exception('Something has gone terribly wrong')
|
||||
# Check that we used everything, we failed otherwise
|
||||
if len(available_sectors) > 0 or len(current_sector.outstanding_doors) > 0:
|
||||
if len(available_sectors) != 1:
|
||||
logger.warning('Failed to add all regions/doors to dungeon, generation will likely fail.')
|
||||
|
||||
|
||||
def find_all_compatible_door_in_sectors_ex(door, sectors):
|
||||
def extend_reachable(search_regions, visited_regions, world, player):
|
||||
region_list = list(search_regions)
|
||||
visited_regions = visited_regions
|
||||
reachable_doors = []
|
||||
while len(region_list) > 0:
|
||||
region = region_list.pop()
|
||||
visited_regions.append(region)
|
||||
for ext in region.exits:
|
||||
if ext.connected_region is not None:
|
||||
connect_region = ext.connected_region
|
||||
if connect_region not in visited_regions and connect_region not in region_list:
|
||||
region_list.append(connect_region)
|
||||
else:
|
||||
door = world.check_for_door(ext.name, player)
|
||||
if door is not None and door.dest is None: # make sure it isn't a weird blocked door
|
||||
reachable_doors.append(door)
|
||||
return reachable_doors, visited_regions
|
||||
|
||||
|
||||
def find_sector_for_door(door, sectors):
|
||||
for sector in sectors:
|
||||
if door in sector.outstanding_doors:
|
||||
return sector
|
||||
return None
|
||||
|
||||
|
||||
def find_all_compatible_door_in_sectors_ex(door, sectors, reachable_doors):
|
||||
result = []
|
||||
for sector in sectors:
|
||||
for proposed_door in sector.outstanding_doors:
|
||||
if doors_compatible_ignore_keys(door, proposed_door):
|
||||
if doors_compatible_ignore_keys(door, proposed_door) and proposed_door not in reachable_doors:
|
||||
result.append((sector, proposed_door))
|
||||
return result
|
||||
|
||||
@@ -603,8 +649,8 @@ def doors_compatible_ignore_keys(a, b):
|
||||
if a.type != b.type:
|
||||
return False
|
||||
# todo: test spirals linking to each other
|
||||
# if a.type == DoorType.SpiralStairs:
|
||||
# return True
|
||||
if a.type == DoorType.SpiralStairs:
|
||||
return True
|
||||
return a.direction == switch_dir(b.direction)
|
||||
|
||||
|
||||
@@ -612,6 +658,8 @@ def doors_compatible_ignore_keys(a, b):
|
||||
def is_valid(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||
if len(available_sectors) == 1:
|
||||
return True
|
||||
if len(available_sectors) <= 2 and sector_a != sector_b:
|
||||
return True
|
||||
elif not are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||
return False
|
||||
elif door_a.blocked and door_b.blocked: # I can't see this going well unless we are in loop generation...
|
||||
@@ -647,12 +695,12 @@ def are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, avai
|
||||
if only_neutral_left:
|
||||
hooks_left = False
|
||||
for door in sector_a.outstanding_doors:
|
||||
if door != door_a and pol_idx[door.direction][0] == idx:
|
||||
if door != door_a and door != door_b and pol_idx[door.direction][0] == idx:
|
||||
hooks_left = True
|
||||
break
|
||||
if not hooks_left:
|
||||
for door in sector_b.outstanding_doors:
|
||||
if door != door_b and pol_idx[door.direction][0] == idx:
|
||||
if door != door_b and door != door_a and pol_idx[door.direction][0] == idx:
|
||||
hooks_left = True
|
||||
break
|
||||
return hooks_left
|
||||
@@ -683,11 +731,16 @@ spiral_staircases = [
|
||||
('Hera Startile Wide Up Stairs', 'Hera 4F Down Stairs'),
|
||||
('Hera 4F Up Stairs', 'Hera 5F Down Stairs'),
|
||||
('Hera 5F Up Stairs', 'Hera Boss Down Stairs'),
|
||||
('Tower Room 03 Up Stairs', 'Tower Lone Statue Down Stairs'),
|
||||
('Tower Dark Chargers Up Stairs', 'Tower Dual Statues Down Stairs'),
|
||||
('Tower Dark Archers Up Stairs', 'Tower Red Spears Down Stairs'),
|
||||
('Tower Pacifist Run Up Stairs','Tower Push Statue Down Stairs'),
|
||||
]
|
||||
|
||||
straight_staircases = [
|
||||
('Hyrule Castle Lobby North Stairs', 'Hyrule Castle Throne Room South Stairs'),
|
||||
('Sewers Rope Room North Stairs', 'Sewers Dark Cross South Stairs')
|
||||
('Sewers Rope Room North Stairs', 'Sewers Dark Cross South Stairs'),
|
||||
('Tower Catwalk North Stairs', 'Tower Antechamber South Stairs')
|
||||
]
|
||||
|
||||
open_edges = [
|
||||
@@ -743,6 +796,17 @@ interior_doors = [
|
||||
('Hera Tridorm SE', 'Hera Torches NE'),
|
||||
('Hera Beetles WS', 'Hera Startile Corner ES'),
|
||||
('Hera Startile Corner NW', 'Hera Startile Wide SW'),
|
||||
('Tower Lobby NW', 'Tower Gold Knights SW'),
|
||||
('Tower Gold Knights EN', 'Tower Room 03 WN'),
|
||||
('Tower Lone Statue WN', 'Tower Dark Maze EN'),
|
||||
('Tower Dark Maze ES', 'Tower Dark Chargers WS'),
|
||||
('Tower Dual Statues WS', 'Tower Dark Pits ES'),
|
||||
('Tower Dark Pits EN', 'Tower Dark Archers WS'),
|
||||
('Tower Red Spears WN', 'Tower Red Guards EN'),
|
||||
('Tower Red Guards SW', 'Tower Circle of Pots NW'),
|
||||
('Tower Circle of Pots WS', 'Tower Pacifist Run ES'),
|
||||
('Tower Push Statue WS', 'Tower Catwalk ES'),
|
||||
('Tower Antechamber NW', 'Tower Altar SW'),
|
||||
]
|
||||
|
||||
key_doors = [
|
||||
@@ -795,7 +859,8 @@ default_door_connections = [
|
||||
default_one_way_connections = [
|
||||
('Sewers Pull Switch S', 'Sanctuary N'),
|
||||
('Eastern Eyegores NE', 'Eastern Boss SE'),
|
||||
('Desert Wall Slide NW', 'Desert Boss SW')
|
||||
('Desert Wall Slide NW', 'Desert Boss SW'),
|
||||
('Tower Altar NW', 'Tower Agahnim 1 SW')
|
||||
]
|
||||
|
||||
# todo: these path rules are more complicated I think...
|
||||
@@ -827,7 +892,8 @@ default_dungeon_sets = [
|
||||
'Hyrule Dungeon Cellblock'],
|
||||
['Eastern Lobby', 'Eastern Boss'],
|
||||
['Desert Back Lobby', 'Desert Boss', 'Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby'],
|
||||
['Hera Lobby', 'Hera Boss']
|
||||
['Hera Lobby', 'Hera Boss'],
|
||||
['Tower Lobby', 'Tower Agahnim 1']
|
||||
]
|
||||
|
||||
|
||||
@@ -835,3 +901,19 @@ desert_default_entrance_sets = [
|
||||
['Desert Back Lobby'],
|
||||
['Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby']
|
||||
]
|
||||
|
||||
# 'Skull': ['Skull 1 Lobby', 'Skull 2 Mummy Lobby', 'Skull 2 Key Lobby', 'Skull 3 Lobby'],
|
||||
|
||||
entrance_sets = [
|
||||
['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Secret Room'],
|
||||
['Eastern Lobby'],
|
||||
['Hera Lobby'],
|
||||
['Tower Lobby'],
|
||||
# ['PoD Lobby'],
|
||||
# ['Swamp Lobby'],
|
||||
# ['TT Lobby'],
|
||||
# ['Ice Lobby'],
|
||||
# ['Mire Lobby'],
|
||||
# ['TR Main Lobby', 'TR Eye Trap', 'TR Big Chest', 'TR Laser Bridge'],
|
||||
# ['GT Lobby']
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user