Ice Palace added

Dynamic logical doors added for ice cross w/ push block
Improved crystal switch pathing
Minor update to PoD
This commit is contained in:
aerinon
2019-11-01 16:13:23 -06:00
parent 49cfe923b6
commit e08bf3776a
14 changed files with 442 additions and 703 deletions

View File

@@ -5,11 +5,11 @@ import logging
import operator as op
from functools import reduce
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, Polarity, pol_idx, pol_inc
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, Polarity
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions
from Dungeons import dungeon_regions, region_starts, split_region_starts, dungeon_keys, dungeon_bigs
from RoomData import DoorKind, PairedDoor
from DungeonGenerator import ExplorationState, extend_reachable_state, convert_regions, generate_dungeon
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon
def link_doors(world, player):
@@ -112,33 +112,6 @@ oppositemap = {
Direction.Down: Direction.Up,
}
similar_directions = {
Direction.South: [Direction.South],
Direction.North: [Direction.North],
Direction.West: [Direction.West],
Direction.East: [Direction.East],
Direction.Up: [Direction.Down, Direction.Up],
Direction.Down: [Direction.Down, Direction.Up],
}
paired_directions = {
Direction.South: [Direction.North],
Direction.North: [Direction.South],
Direction.West: [Direction.East],
Direction.East: [Direction.West],
Direction.Up: [Direction.Down, Direction.Up],
Direction.Down: [Direction.Down, Direction.Up],
}
allied_directions = {
Direction.South: [Direction.North, Direction.South],
Direction.North: [Direction.North, Direction.South],
Direction.West: [Direction.East, Direction.West],
Direction.East: [Direction.East, Direction.West],
Direction.Up: [Direction.Down, Direction.Up],
Direction.Down: [Direction.Down, Direction.Up],
}
def switch_dir(direction):
return oppositemap[direction]
@@ -235,8 +208,41 @@ def pair_existing_key_doors(world, player, door_a, door_b):
# for paired_door in world.paired_doors[player]:
# paired_door.pair = False
def within_dungeon(world, player):
fix_big_key_doors_with_ugly_smalls(world, player)
overworld_prep(world, player)
dungeon_sectors = []
for key in dungeon_regions.keys():
sector_list = convert_to_sectors(dungeon_regions[key], world, player)
if key in split_region_starts.keys():
split_sectors = split_up_sectors(sector_list, split_region_starts[key])
for idx, sub_sector_list in enumerate(split_sectors):
dungeon_sectors.append((key, sub_sector_list, split_region_starts[key][idx]))
# todo: shuffable entrances like pinball, left pit need to be added to entrance list
else:
dungeon_sectors.append((key, sector_list, region_starts[key]))
dungeon_layouts = []
for key, sector_list, entrance_list in dungeon_sectors:
ds = generate_dungeon(sector_list, entrance_list, world, player)
ds.name = key
dungeon_layouts.append((ds, entrance_list))
combine_layouts(dungeon_layouts)
world.dungeon_layouts[player] = {}
for sector, entrances in dungeon_layouts:
world.dungeon_layouts[player][sector.name] = (sector, entrances)
remove_inaccessible_entrances(world, player)
paths = determine_required_paths(world)
check_required_paths(paths, world, player)
# shuffle_key_doors for dungeons
for sector, entrances in world.dungeon_layouts[player].values():
shuffle_key_doors(sector, entrances, world, player)
def within_dungeon_legacy(world, player):
# TODO: The "starts" regions need access logic
# Aerinon's note: I think this is handled already by ER Rules - may need to check correct requirements
dungeon_region_starts_es = ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Secret Room']
@@ -260,6 +266,7 @@ def within_dungeon(world, player):
for key in dungeon_regions.keys():
world.dungeon_layouts[player][key] = (key, region_starts[key])
def shuffle_dungeon(world, player, start_region_names, dungeon_region_names):
logger = logging.getLogger('')
# Part one - generate a random layout
@@ -422,8 +429,8 @@ def doors_fit_mandatory_pair(pair_list, a, b):
# goals:
# 1. have enough chests to be interesting (2 more than dungeon items)
# 2. have a balanced amount of regions added (check)
# 3. prevent soft locks due to key usage
# 4. rules in place to affect item placement (lamp, keys, etc.)
# 3. prevent soft locks due to key usage (algorithm written)
# 4. rules in place to affect item placement (lamp, keys, etc. -- in rules)
# 5. to be complete -- all doors linked (check, somewhat)
# 6. avoid deadlocks/dead end dungeon (check)
# 7. certain paths through dungeon must be possible - be able to reach goals (check)
@@ -445,10 +452,10 @@ def cross_dungeon(world, player):
dungeon_sectors.append((name, sector_list, region_starts[name]))
# todo - adjust dungeon item pools -- ?
dungeon_layouts = []
for key, sector_list, entrance_list in dungeon_sectors:
ds = shuffle_dungeon_no_repeats_new(world, player, sector_list, entrance_list)
ds.name = key
dungeon_layouts.append((ds, entrance_list))
# for key, sector_list, entrance_list in dungeon_sectors:
# ds = shuffle_dungeon_no_repeats_new(world, player, sector_list, entrance_list)
# ds.name = key
# dungeon_layouts.append((ds, entrance_list))
combine_layouts(dungeon_layouts)
@@ -490,13 +497,6 @@ def experiment(world, player):
shuffle_key_doors(sector, entrances, world, player)
def convert_regions(region_names, world, player):
region_list = []
for name in region_names:
region_list.append(world.get_region(name, player))
return region_list
def convert_to_sectors(region_names, world, player):
region_list = convert_regions(region_names, world, player)
sectors = []
@@ -523,7 +523,7 @@ def convert_to_sectors(region_names, world, player):
new_sector = False
else:
door = world.check_for_door(ext.name, player)
if door is not None:
if door is not None and door.controller is None:
outstanding_doors.append(door)
if new_sector:
sector = Sector()
@@ -686,495 +686,10 @@ def find_proposal(proposal, buckets, candidates):
return proposal
def extend_state_backward(search_regions, state, world, player):
local_state = state.copy()
for region in search_regions:
local_state.visit_region(region)
local_state.add_all_entrance_doors_check_unattached(region, world, player)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
entrance = world.get_entrance(explorable_door.door.name, player)
connect_region = entrance.parent_region
if connect_region is not None:
if valid_region_to_explore(connect_region, world) and not local_state.visited(connect_region):
local_state.visit_region(connect_region)
local_state.add_all_entrance_doors_check_unattached(connect_region, world, player)
return local_state
# code below is for an algorithm without restarts
# todo: this sometimes generates two independent parts - that could be valid if the entrances are accessible
# todo: prevent crystal barrier dead ends
def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_region_names):
logger = logging.getLogger('')
random.shuffle(available_sectors)
for sector in available_sectors:
random.shuffle(sector.outstanding_doors)
entrance_regions = convert_regions(entrance_region_names, world, player)
state = extend_reachable_state(entrance_regions, ExplorationState(), world, player)
# Loop until all available doors are used
while len(state.unattached_doors) > 0:
# Pick a random available door to connect
explorable_door = random.choice(state.unattached_doors)
door = explorable_door.door
sector = find_sector_for_door(door, available_sectors)
if sector is None:
state.unattached_doors.remove(explorable_door)
continue
sector.outstanding_doors.remove(door)
# door_connected = False
logger.debug('Linking %s', door.name)
# Find an available region that has a compatible door
reachable_doors = [d.door for d in state.unattached_doors]
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors)
while len(compatibles) > 0:
connect_sector, connect_door = compatibles.pop()
logger.debug(' Found possible new sector via %s', connect_door.name)
# Check if valid
if is_valid(door, connect_door, sector, connect_sector, available_sectors, reachable_doors, state, world, player):
# Apply connection and add the new region's doors to the available list
maybe_connect_two_way(world, door, connect_door, player)
state.unattached_doors.remove(explorable_door)
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)
if not door.blocked:
connect_region = world.get_entrance(door.dest.name, player).parent_region
state = extend_reachable_state([connect_region], state, world, player)
break # skips else block below
logger.debug(' Not Linking %s to %s', door.name, connect_door.name)
if len(compatibles) == 0: # time to try again
if len(state.unattached_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
compatibles = find_all_compatible_door_in_list(door, reachable_doors)
while len(compatibles) > 0:
connect_door = compatibles.pop()
logger.debug(' Adding loop via %s', connect_door.name)
connect_sector = find_sector_for_door(connect_door, available_sectors)
# Check if valid
if is_loop_valid(door, connect_door, sector, connect_sector, available_sectors):
maybe_connect_two_way(world, door, connect_door, player)
state.unattached_doors.remove(explorable_door)
state.unattached_doors[:] = [ed for ed in state.unattached_doors if ed.door != 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)
break # skips else block with exception
else:
logger.debug(' Not Linking %s to %s', door.name, connect_door.name)
sector.outstanding_doors.insert(0, door)
if len(state.unattached_doors) <= 2:
raise Exception('Rejected last option due to likely improper loops...')
else:
raise Exception('Nothing is apparently compatible with %s' % door.name)
# Check that we used everything, we failed otherwise
if len(available_sectors) != 1:
logger.warning('Failed to add all regions/doors to dungeon, generation will likely fail.')
return available_sectors[0]
def valid_region_to_explore(region, world):
return region.type == RegionType.Dungeon or region.name in world.inaccessible_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) and proposed_door not in reachable_doors:
result.append((sector, proposed_door))
return result
def find_all_compatible_door_in_list(door, doors):
result = []
for proposed_door in doors:
if proposed_door != door and doors_compatible_ignore_keys(door, proposed_door):
result.append(proposed_door)
return result
# this method also assumes that sectors have been built appropriately - so only randomizable doors get here
def doors_compatible_ignore_keys(a, b):
if a.type != b.type:
return False
if a.type == DoorType.SpiralStairs and a != b:
return True
return a.direction == switch_dir(b.direction)
# todo: path checking needed?
def is_valid(door_a, door_b, sector_a, sector_b, available_sectors, reachable_doors, state, world, player):
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 early_loop_dies(door_a, sector_a, sector_b, available_sectors):
return False
elif logical_dead_end_3(door_a, door_b, state, world, player, available_sectors, reachable_doors):
return False
elif door_a.blocked and door_b.blocked: # I can't see this going well unless we are in loop generation...
return False
elif pinball_exception(door_a, door_b, sector_a):
return True
elif not door_a.blocked and not door_b.blocked and sector_a != sector_b:
return sector_a.outflow() + sector_b.outflow() - 1 > 0 # door_a has been removed already, so a.outflow is reduced by one
elif door_a.blocked or door_b.blocked and sector_a != sector_b:
return sector_a.outflow() + sector_b.outflow() > 0
elif sector_a == sector_b and not door_a.blocked and not door_b.blocked:
return sector_a.outflow() - 1 > 0
elif sector_a == sector_b and door_a.blocked or door_b.blocked:
return sector_a.outflow() > 0
return False # not sure how we got here, but it's a bad idea
def is_loop_valid(door_a, door_b, sector_a, sector_b, available_sectors):
if len(available_sectors) == 1:
return True
if len(available_sectors) == 2 and door_b not in sector_a.outstanding_doors:
return True
elif not door_a.blocked and not door_b.blocked and sector_a != sector_b:
return sector_a.outflow() + sector_b.outflow() - 1 > 0 # door_a has been removed already, so a.outflow is reduced by one
elif door_a.blocked or door_b.blocked and sector_a != sector_b:
return sector_a.outflow() + sector_b.outflow() > 0
elif sector_a == sector_b and not door_a.blocked and not door_b.blocked:
return sector_a.outflow() - 1 > 0
elif sector_a == sector_b and door_a.blocked or door_b.blocked:
return sector_a.outflow() > 0
return True # I think this is always true. Both blocked but we're connecting loops now, so dead end?
def are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):
idx = pol_idx[door_a.direction][0]
diff = sum_vector(available_sectors, lambda x: x.magnitude())[idx]-sector_b.magnitude()[idx]
if diff == 0:
return True # this case will cover all the doors of that DirectionPair
only_neutral_left = True
for sector in available_sectors:
if sector != sector_b and sector.polarity()[idx] != 0:
only_neutral_left = False
break
if only_neutral_left:
hooks_left = False
for door in sector_a.outstanding_doors:
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 door != door_a and pol_idx[door.direction][0] == idx:
hooks_left = True
break
return hooks_left
return True
def early_loop_dies(door_a, sector_a, sector_b, available_sectors):
other_sectors = list(available_sectors)
other_sectors.remove(sector_a)
if sector_a != sector_b:
other_sectors.remove(sector_b)
current_pol = sector_a.polarity() + sector_b.polarity()
current_mag = sum_vector([sector_a, sector_b], lambda x: x.magnitude())
else:
current_pol = sector_a.polarity()
current_mag = sector_a.magnitude()
idx, inc = pol_idx[door_a.direction]
current_pol.vector[idx] = pol_inc[inc](current_pol[idx])
current_mag[idx] -= 1
other_mag = sum_vector(other_sectors, lambda x: x.magnitude())
# other_polarity = reduce(lambda x, y: x+y, map(lambda x: x.polarity(), other_sectors))
ttl_magnitude = 0
for i in range(len(current_mag)):
ttl_magnitude += 0 if current_pol[i] == 0 and other_mag[i] == 0 else current_mag[i]
if ttl_magnitude == 0:
return True
return False
def logical_dead_end(door_a, door_b, state, world, player, available_sectors, reachable_doors):
region = world.get_entrance(door_b.name, player).parent_region
new_state = extend_reachable_state([region], state, world, player)
new_state.unattached_doors[:] = [x for x in new_state.unattached_doors if x.door not in [door_a, door_b]]
if len(new_state.unattached_doors) == 0:
return True
d_type = door_a.type
directions = paired_directions[door_a.direction]
hook_directions = similar_directions[door_a.direction]
number_of_hooks = 0
opposing_hooks = 0
for door in new_state.unattached_doors:
if door.door.type == d_type and door.door.direction in hook_directions:
number_of_hooks += 1
if door.door.type == d_type and door.door.direction in directions:
opposing_hooks += 1
hooks_needed = 0
visited_regions = set()
outstanding_doors_of_type = 0
outstanding_hooks = 0
only_dead_ends = True
for sector in available_sectors:
for door in sector.outstanding_doors:
if door_of_interest(door, door_b, d_type, directions, hook_directions, new_state):
needed = True
if door.direction in directions:
outstanding_doors_of_type += 1
region = world.get_entrance(door.name, player).parent_region
local_state = extend_state_backward([region], ExplorationState(), world, player)
if len(local_state.non_door_entrances) > 0 and not door.blocked:
needed = False
else:
for exp_d in local_state.unattached_doors:
if different_direction(exp_d.door, d_type, directions, hook_directions, reachable_doors):
needed = False
break
region_set = set(local_state.visited_orange+local_state.visited_blue)
if needed and len(visited_regions.intersection(region_set)) == 0 and door.direction in directions:
hooks_needed += 1
visited_regions.update(region_set)
elif door.direction in hook_directions and not door.blocked:
if opposing_hooks > 0 and more_than_one_hook(local_state, hook_directions):
needed = False
if not needed:
outstanding_hooks += 1
visited_regions.update(region_set)
if not needed:
only_dead_ends = False
if outstanding_doors_of_type > 0 and ((number_of_hooks == 0 and only_dead_ends) or hooks_needed > number_of_hooks + outstanding_hooks):
return True
return False
def logical_dead_end_3(door_a, door_b, state, world, player, available_sectors, reachable_doors):
region = world.get_entrance(door_b.name, player).parent_region
new_state = extend_reachable_state([region], state, world, player)
new_state.unattached_doors[:] = [x for x in new_state.unattached_doors if x.door not in [door_a, door_b]]
if len(new_state.unattached_doors) == 0:
return True
current_hooks = defaultdict(lambda: 0)
hooks_needed = defaultdict(set)
outstanding_hooks = defaultdict(lambda: 0)
outstanding_total = defaultdict(lambda: 0)
potential_hooks = []
only_dead_ends_vector = [True, True, True]
avail_dirs = set()
for exp_d in new_state.unattached_doors:
hook_key = hook_id(exp_d.door)
current_hooks[hook_key] += 1
avail_dirs.update(allied_directions[exp_d.door.direction])
for sector in available_sectors:
for door in sector.outstanding_doors:
if door != door_b and not state.in_door_list_ic(door, new_state.unattached_doors):
opp_hook_key = opp_hook_id(door)
outstanding_total[opp_hook_key] += 1
if door.blocked:
hooks_needed[opp_hook_key].add(door)
else:
dead_end, cross_interaction = True, False
region = world.get_entrance(door.name, player).parent_region
local_state = extend_state_backward([region], ExplorationState(), world, player)
if len(local_state.non_door_entrances) > 0:
dead_end = False # not a dead end
cross_interaction = True
elif len(local_state.unattached_doors) > 1:
dead_end = False
for exp_d in local_state.unattached_doors:
if cross_door_interaction(exp_d.door, door, reachable_doors, avail_dirs):
cross_interaction = True
break
if dead_end:
hooks_needed[opp_hook_key].add(door)
else:
door_set = set([x.door for x in local_state.unattached_doors if x.door != door])
potential_hooks.append((door, door_set))
if cross_interaction:
only_dead_ends_vector[pol_idx[door.direction][0]] = False
logically_valid = False
satisfying_hooks = []
while not logically_valid:
check_good = True
for key in [Direction.North, Direction.South, Direction.East, Direction.West, DoorType.SpiralStairs]:
dir = key if isinstance(key, Direction) else Direction.Up
vector_idx = pol_idx[dir][0]
ttl_hooks = current_hooks[key] + current_hooks[switch_dir(dir) if isinstance(key, Direction) else DoorType.SpiralStairs]
if outstanding_total[key] > 0 and ttl_hooks == 0 and only_dead_ends_vector[vector_idx]:
return True # no way to get to part of the dungeon
hooks_wanted = hooks_needed[key]
if outstanding_total[key] > 0 and len(hooks_wanted) > current_hooks[key] + outstanding_hooks[key]:
check_good = False
fixer = find_fixer(potential_hooks, key, hooks_wanted, [])
if fixer is None:
return True # couldn't find a fix
else:
apply_fix(fixer, potential_hooks, outstanding_hooks, hooks_needed, satisfying_hooks, key)
if check_good and len(satisfying_hooks) > 0:
check_good = False
votes = defaultdict(lambda: 0) # I really don't like this as other votes could lead to a valid configuration
door_dict = {}
for hook_set in satisfying_hooks:
if len(hook_set) == 0:
return True # unsatisfiable condition
for hookable in hook_set:
votes[hookable.name] += 1
door_dict[hookable.name] = hookable
winner = None
for door_name in votes.keys():
if winner is None or votes[door_name] > votes[winner]:
winner = door_name
winning_hook = door_dict[winner]
key = opp_hook_id(winning_hook)
hooks_needed[key].add(winning_hook)
satisfying_hooks[:] = [x for x in satisfying_hooks if winning_hook not in x]
logically_valid = check_good
return False # no logical dead ends!
# potential_fixers = []
# skip = False
# for hookable in hook_set:
# key = opp_hook_id(hookable)
# if len(hooks_needed[key]) < current_hooks[key] + outstanding_hooks[key]:
# hooks_needed[key].add(hookable)
# hooks_to_remove.append(hook_set)
# hooks_to_remove.extend([x for x in satisfying_hooks if hookable in x])
# skip = True
# break
# fixer = find_fixer(potential_hooks, key, hooks_needed[key], hook_set)
# if fixer is not None:
# potential_fixers.append(fixer)
# if skip:
# break
# if len(potential_fixers) == 0:
# return True # can't find fixers for this set
# elif len(potential_fixers) >= 1:
# check_good = False
# fixer = potential_fixers[0] # just pick the first for now
# apply_fix(fixer, potential_hooks, outstanding_hooks, hooks_needed, satisfying_hooks, hook_id(fixer[0]))
# hooks_to_remove.append(hook_set)
# # what's left - multiple set with multiple available fixes
# if len(hooks_to_remove) == 0:
# return True # can't seem to make progress yet
# satisfying_hooks[:] = [x for x in satisfying_hooks if x not in hooks_to_remove]
# logically_valid = check_good
# return False # no logical dead ends!
def hook_id(door):
if door.type == DoorType.Normal:
return door.direction
if door.type == DoorType.SpiralStairs:
return door.type
return 'Some new door type'
def opp_hook_id(door):
if door.type == DoorType.Normal:
return switch_dir(door.direction)
if door.type == DoorType.SpiralStairs:
return door.type
return 'Some new door type'
def find_fixer(potential_hooks, key, hooks_wanted, invalid_options):
fixer = None
for door, door_set in potential_hooks:
if match_hook_key(door, key) and (len(hooks_wanted) > 1 or len(hooks_wanted.union(door_set)) > 1) and door not in invalid_options:
if fixer is None or len(door_set) > len(fixer[1]): # choose the one with most options
fixer = (door, door_set)
return fixer
def apply_fix(fixer, potential_hooks, outstanding_hooks, hooks_needed, satisfying_hooks, key):
outstanding_hooks[key] += 1
potential_hooks.remove(fixer)
winnow_potential_hooks(potential_hooks, fixer[0]) #
winnow_satisfying_hooks(satisfying_hooks, fixer[0])
if len(fixer[1]) == 1:
new_need = fixer[1].pop()
hooks_needed[opp_hook_id(new_need)].add(new_need)
# removes any hooks that are now fulfilled
satisfying_hooks[:] = [x for x in satisfying_hooks if new_need not in x]
else:
satisfying_hooks.append(fixer[1])
def match_hook_key(door, key):
if isinstance(key, DoorType):
return door.type == key
if isinstance(key, Direction):
return door.direction == key
return False
def winnow_potential_hooks(hooks, door_removal):
for door, door_set in hooks:
if door_removal in door_set:
door_set.remove(door_removal)
hooks[:] = [x for x in hooks if len(x[1]) > 0]
def winnow_satisfying_hooks(satisfying, door_removal):
for hook_set in satisfying:
if door_removal in hook_set:
hook_set.remove(door_removal)
def door_of_interest(door, door_b, d_type, directions, hook_directions, state):
if door == door_b or door.type != d_type:
return False
if door.direction not in directions and door.direction not in hook_directions:
return False
return not state.in_door_list_ic(door, state.unattached_doors)
def different_direction(door, d_type, directions, hook_directions, reachable_doors):
if door in reachable_doors:
return False
return door.type != d_type or (door.direction not in directions and door.direction not in hook_directions)
def cross_door_interaction(door, original_door, reachable_doors, avail_dir):
if door in reachable_doors or door == original_door:
return False
if door.type == original_door.type and door.direction in allied_directions[original_door.direction]: # revisit if cross-type linking ever happens
return False
return door.direction in avail_dir
def more_than_one_hook(state, hook_directions):
cnt = 0
for exp_d in state.unattached_doors:
if exp_d.door.direction in hook_directions:
cnt += 1
return cnt > 1
def pinball_exception(door_a, door_b, sector_a):
if door_a.name == 'Skull Pot Prison SE' and door_b.name == 'Skull Pinball NE':
for r in sector_a.regions:
if r.name == 'Skull 2 East Lobby':
return True
return False
def shuffle_key_doors(dungeon_sector, entrances, world, player):
start_regions = convert_regions(entrances, world, player)
# count number of key doors - this could be a table?
@@ -1381,8 +896,9 @@ def validate_key_layout_r(state, flat_proposal, checked_states, key_logic, world
for exp_door in state.small_doors:
state_copy = state.copy()
state_copy.opened_doors.append(exp_door.door)
state_copy.avail_doors.append(exp_door)
state_copy.small_doors.remove(exp_door)
doors_to_open = [x for x in state_copy.small_doors if x.door == exp_door.door]
state_copy.small_doors[:] = [x for x in state_copy.small_doors if x.door != exp_door.door]
state_copy.avail_doors.extend(doors_to_open)
dest_door = exp_door.door.dest
if dest_door in flat_proposal:
state_copy.opened_doors.append(dest_door)
@@ -1521,6 +1037,7 @@ def determine_required_paths(world):
'Swamp Palace': ['Swamp Boss'],
'Skull Woods': ['Skull Boss'],
'Thieves Town': ['Thieves Boss', ('Thieves Blind\'s Cell', 'Thieves Boss')],
'Ice Palace': ['Ice Boss'],
}
if world.shuffle == 'vanilla':
# paths['Skull Woods'].remove('Skull Boss') # is this necessary?
@@ -1647,6 +1164,8 @@ logical_connections = [
('Hera Big Chest Landing Exit', 'Hera 4F'),
('PoD Arena Main Crystal Path', 'PoD Arena Crystal'),
('PoD Arena Crystal Path', 'PoD Arena Main'),
('PoD Arena Main Orange Barrier', 'PoD Arena North'),
('PoD Arena North Drop Down', 'PoD Arena Main'),
('PoD Arena Bridge Drop Down', 'PoD Arena Main'),
('PoD Map Balcony Drop Down', 'PoD Sexy Statue'),
('PoD Basement Ledge Drop Down', 'PoD Stalfos Basement'),
@@ -1694,6 +1213,24 @@ logical_connections = [
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
('Thieves Conveyor Block Path', 'Thieves Conveyor Bridge'),
# ('Ice Cross Left Push Block', ''), # todo: vanilla connections
# ('Ice Cross Right Push Block Bottom', ''),
# ('Ice Cross Bottom Push Block Right', ''),
# ('Ice Cross Top Push Block Right', ''),
('Ice Cross Bottom Push Block Left', 'Ice Floor Switch'),
('Ice Cross Right Push Block Top', 'Ice Bomb Drop'),
('Ice Cross Top Push Block Left', 'Ice Floor Switch'),
('Ice Big Key Push Block', 'Ice Dead End'),
('Ice Bomb Jump Ledge Orange Barrier', 'Ice Bomb Jump Catwalk'),
('Ice Bomb Jump Catwalk Orange Barrier', 'Ice Bomb Jump Ledge'),
('Ice Hookshot Ledge Path', 'Ice Hookshot Balcony'),
('Ice Hookshot Balcony Path', 'Ice Hookshot Ledge'),
('Ice Crystal Right Orange Barrier', 'Ice Crystal Left'),
('Ice Crystal Left Orange Barrier', 'Ice Crystal Right'),
('Ice Crystal Left Blue Barrier', 'Ice Crystal Block'),
('Ice Crystal Block Exit', 'Ice Crystal Left'),
('Ice Big Chest Landing Push Blocks', 'Ice Big Chest View'),
# ('', ''),
# ('', ''),
]
@@ -1728,6 +1265,12 @@ spiral_staircases = [
('Swamp Behind Waterfall Up Stairs', 'Swamp C Down Stairs'),
('Thieves Spike Switch Up Stairs', 'Thieves Attic Down Stairs'),
('Thieves Conveyor Maze Down Stairs', 'Thieves Basement Block Up Stairs'),
('Ice Jelly Key Down Stairs', 'Ice Floor Switch Up Stairs'),
('Ice Narrow Corridor Down Stairs', 'Ice Pengator Trap Up Stairs'),
('Ice Spike Room Up Stairs', 'Ice Hammer Block Down Stairs'),
('Ice Spike Room Down Stairs', 'Ice Spikeball Up Stairs'),
('Ice Lonely Freezor Down Stairs', 'Iced T Up Stairs'),
('Ice Backwards Room Down Stairs', 'Ice Anti-Fairy Up Stairs'),
# ('', ''),
]
@@ -1779,6 +1322,15 @@ falldown_pits = [
('Swamp Attic Right Pit', 'Swamp Barrier Ledge'),
('Swamp Attic Left Pit', 'Swamp West Ledge'),
('Skull Final Drop Hole', 'Skull Boss'),
('Ice Bomb Drop Hole', 'Ice Stalfos Hint'),
('Ice Falling Square Hole', 'Ice Tall Hint'),
('Ice Freezors Hole', 'Ice Big Chest View'),
('Ice Freezors Ledge Hole', 'Ice Big Chest View'),
('Ice Freezors Bomb Hole', 'Ice Big Chest Landing'),
('Ice Crystal Block Hole', 'Ice Switch Room'),
('Ice Crystal Right Blue Hole', 'Ice Switch Room'),
('Ice Backwards Room Hole', 'Ice Fairy'),
('Ice Antechamber Hole', 'Ice Boss'),
# ('', ''),
]
@@ -1789,11 +1341,16 @@ dungeon_warps = [
('PoD Warp Room Warp', 'PoD Warp Hint'),
('PoD Stalfos Basement Warp', 'PoD Warp Room'),
('PoD Callback Warp', 'PoD Dark Alley'),
('Ice Fairy Warp', 'Ice Anti-Fairy'),
# ('', ''),
]
ladders = [
('PoD Bow Statue Down Ladder', 'PoD Dark Pegs Up Ladder')
('PoD Bow Statue Down Ladder', 'PoD Dark Pegs Up Ladder'),
('Ice Big Key Down Ladder', 'Ice Tongue Pull Up Ladder'),
('Ice Firebar Down Ladder', 'Ice Freezors Up Ladder'),
# ('', ''),
# ('', ''),
]
interior_doors = [
@@ -1878,6 +1435,20 @@ interior_doors = [
('Thieves Lonely Zazak ES', 'Thieves Blind\'s Cell WS'),
('Thieves Conveyor Bridge WS', 'Thieves Big Chest Room ES'),
('Thieves Conveyor Block WN', 'Thieves Trap EN'),
('Ice Lobby WS', 'Ice Jelly Key ES'),
('Ice Floor Switch ES', 'Ice Cross Left WS'),
('Ice Cross Top NE', 'Ice Bomb Drop SE'),
('Ice Pengator Switch ES', 'Ice Dead End WS'),
('Ice Stalfos Hint SE', 'Ice Conveyor NE'),
('Ice Bomb Jump EN', 'Ice Narrow Corridor WN'),
('Ice Spike Cross WS', 'Ice Firebar ES'),
('Ice Spike Cross NE', 'Ice Falling Square SE'),
('Ice Hammer Block ES', 'Ice Tongue Pull WS'),
('Ice Freezors Ledge ES', 'Ice Tall Hint WS'),
('Ice Hookshot Balcony SW', 'Ice Spikeball NW'),
('Ice Crystal Right NE', 'Ice Backwards Room SE'),
('Ice Crystal Left WS', 'Ice Big Chest View ES'),
('Ice Anti-Fairy SE', 'Ice Switch Room NE'),
# ('', ''),
]
@@ -1965,8 +1536,18 @@ default_door_connections = [
('Thieves Triple Bypass EN', 'Thieves Conveyor Maze WN'),
('Thieves Basement Block WN', 'Thieves Conveyor Bridge EN'),
('Thieves Lonely Zazak WS', 'Thieves Conveyor Bridge ES'),
# ('', ''),
# ('', ''),
('Ice Cross Bottom SE', 'Ice Compass Room NE'),
('Ice Cross Right ES', 'Ice Pengator Switch WS'),
('Ice Conveyor SW', 'Ice Bomb Jump NW'),
('Ice Pengator Trap NE', 'Ice Spike Cross SE'),
('Ice Spike Cross ES', 'Ice Spike Room WS'),
('Ice Tall Hint SE', 'Ice Lonely Freezor NW'),
('Ice Tall Hint EN', 'Ice Hookshot Ledge WN'),
('Iced T EN', 'Ice Catwalk WN'),
('Ice Catwalk NW', 'Ice Many Pots SW'),
('Ice Many Pots WS', 'Ice Crystal Right ES'),
('Ice Switch Room ES', 'Ice Refill WS'),
('Ice Switch Room SE', 'Ice Antechamber NE'),
# ('', ''),
]
@@ -2026,5 +1607,8 @@ dungeon_x_idx_to_name = {
4: 'Agahnims Tower',
5: 'Palace of Darkness',
6: 'Swamp Palace',
7: 'Skull Woods',
8: 'Thieves Town',
9: 'Ice Palace'
# etc
}