Aga Tower preliminary check in

This commit is contained in:
aerinon
2019-09-20 21:24:46 -06:00
parent 1aa2d904d3
commit 5eb0e6e379
13 changed files with 287 additions and 121 deletions

View File

@@ -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']
]