Large refactor underway.

Wrote new main method
Implemented trap door shuffle to some degree
Still needs the other types
This commit is contained in:
aerinon
2022-06-28 13:05:38 -06:00
parent b13e15f999
commit 2e9d132985
5 changed files with 648 additions and 79 deletions

View File

@@ -6,13 +6,14 @@ from enum import unique, Flag
from typing import DefaultDict, Dict, List from typing import DefaultDict, Dict, List
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
from BaseClasses import PotFlags, LocationType from BaseClasses import PotFlags, LocationType, Direction
from Doors import reset_portals from Doors import reset_portals
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
from Dungeons import dungeon_bigs, dungeon_hints from Dungeons import dungeon_bigs, dungeon_hints
from Items import ItemFactory from Items import ItemFactory
from RoomData import DoorKind, PairedDoor, reset_rooms from RoomData import DoorKind, PairedDoor, reset_rooms
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
# from DungeonGenerator import generate_dungeon # from DungeonGenerator import generate_dungeon
from DungeonGenerator import ExplorationState, convert_regions, pre_validate, determine_required_paths, drop_entrances from DungeonGenerator import ExplorationState, convert_regions, pre_validate, determine_required_paths, drop_entrances
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
@@ -759,6 +760,131 @@ def find_entrance_region(portal):
return None return None
# each dungeon_pool members is a pair of lists: dungeon names and regions in those dungeons
def main_dungeon_pool(dungeon_pool, world, player):
add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list(world, player)
connections_tuple = (entrances_map, potentials, connections)
entrances, splits = create_dungeon_entrances(world, player)
dungeon_builders = {}
door_type_pools = []
for pool, region_list in dungeon_pool:
if len(pool) == 1:
dungeon_key = next(pool)
sector_pool = convert_to_sectors(region_list, world, player)
merge_sectors(sector_pool, world, player)
dungeon_builders[dungeon_key] = simple_dungeon_builder(dungeon_key, sector_pool)
dungeon_builders[dungeon_key].entrance_list = list(entrances_map[dungeon_key])
else:
if 'Hyrule Castle' in pool:
hc = world.get_dungeon('Hyrule Castle', player)
hc.dungeon_items.append(ItemFactory('Compass (Escape)', player))
if 'Agahnims Tower' in pool:
at = world.get_dungeon('Agahnims Tower', player)
at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player))
at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
sector_pool = convert_to_sectors(region_list, world, player)
merge_sectors(sector_pool, world, player)
# todo: which dungeon to create
dungeon_builders.update(create_dungeon_builders(sector_pool, connections_tuple,
world, player, pool, entrances, splits))
door_type_pools.append((pool, DoorTypePool(sector_pool, world, player)))
update_forced_keys(dungeon_builders, entrances_map, world, player)
recombinant_builders = {}
builder_info = entrances, splits, connections_tuple, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
setup_custom_door_types(world, player)
paths = determine_required_paths(world, player)
shuffle_door_types(door_type_pools, paths, world, player)
check_required_paths(paths, world, player)
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
target_items = 34
if world.retro[player]:
target_items += 1 if world.dropshuffle[player] else 0 # the hc big key
else:
target_items += 29 # small keys in chests
if world.dropshuffle[player]:
target_items += 14 # 13 dropped smalls + 1 big
if world.pottery[player] not in ['none', 'cave']:
target_items += 19 # 19 pot keys
d_items = target_items - all_dungeon_items_cnt
world.pool_adjustment[player] = d_items
if not world.decoupledoors[player]:
smooth_door_pairs(world, player)
def update_forced_keys(dungeon_builders, entrances_map, world, player):
for builder in dungeon_builders.values():
builder.entrance_list = list(entrances_map[builder.name])
dungeon_obj = world.get_dungeon(builder.name, player)
for sector in builder.sectors:
for region in sector.regions:
region.dungeon = dungeon_obj
for loc in region.locations:
if loc.forced_item:
key_name = (dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop'
else dungeon_bigs[builder.name])
loc.forced_item = loc.item = ItemFactory(key_name, player)
def finish_up_work(world, player):
dungeon_builders = world.dungeon_layouts[player]
# Re-assign dungeon bosses
gt = world.get_dungeon('Ganons Tower', player)
for name, builder in dungeon_builders.items():
reassign_boss('GT Ice Armos', 'bottom', builder, gt, world, player)
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
sanctuary = world.get_region('Sanctuary', player)
d_name = sanctuary.dungeon.name
if d_name != 'Hyrule Castle':
possible_portals = []
for portal_name in dungeon_portals[d_name]:
portal = world.get_portal(portal_name, player)
if portal.door.name == 'Sanctuary S':
possible_portals.clear()
possible_portals.append(portal)
break
if not portal.destination and not portal.deadEnd:
possible_portals.append(portal)
if len(possible_portals) == 1:
world.sanc_portal[player] = possible_portals[0]
else:
reachable_portals = []
for portal in possible_portals:
start_area = portal.door.entrance.parent_region
state = ExplorationState(dungeon=d_name)
state.visit_region(start_area)
state.add_all_doors_check_unattached(start_area, world, player)
explore_state(state, world, player)
if state.visited_at_all(sanctuary):
reachable_portals.append(portal)
world.sanc_portal[player] = random.choice(reachable_portals)
if world.intensity[player] >= 3:
if player in world.sanc_portal:
portal = world.sanc_portal[player]
else:
portal = world.get_portal('Sanctuary', player)
target = portal.door.entrance.parent_region
connect_simple_door(world, 'Sanctuary Mirror Route', target, player)
check_entrance_fixes(world, player)
if world.standardize_palettes[player] == 'standardize' and world.doorShuffle[player] not in ['basic']:
palette_assignment(world, player)
refine_hints(dungeon_builders)
refine_boss_exits(world, player)
# def unpair_all_doors(world, player): # def unpair_all_doors(world, player):
# for paired_door in world.paired_doors[player]: # for paired_door in world.paired_doors[player]:
# paired_door.pair = False # paired_door.pair = False
@@ -1522,7 +1648,9 @@ def setup_custom_door_types(world, player):
custom_doors = custom_doors[player] custom_doors = custom_doors[player]
if 'doors' not in custom_doors: if 'doors' not in custom_doors:
return return
world.custom_door_types[player] = type_map = {'Key Door': defaultdict(list), 'Dash Door': [], 'Bomb Door': []} # todo: dash/bomb door pool specific
customizeable_types = ['Key Door', 'Dash Door', 'Bomb Door', 'Trap Door', 'Big Key Door']
world.custom_door_types[player] = type_map = {x: defaultdict(list) for x in customizeable_types}
for door, dest in custom_doors['doors'].items(): for door, dest in custom_doors['doors'].items():
if isinstance(dest, dict): if isinstance(dest, dict):
if 'type' in dest: if 'type' in dest:
@@ -1531,17 +1659,206 @@ def setup_custom_door_types(world, player):
dungeon = d.entrance.parent_region.dungeon dungeon = d.entrance.parent_region.dungeon
if d.type == DoorType.SpiralStairs: if d.type == DoorType.SpiralStairs:
type_map[door_kind][dungeon.name].append(d) type_map[door_kind][dungeon.name].append(d)
elif door_kind == 'Key Door': else:
# check if the # check if the
if d.dest.type in [DoorType.Interior, DoorType.Normal]: if d.dest.type in [DoorType.Interior, DoorType.Normal]:
type_map[door_kind][dungeon.name].append((d, d.dest)) type_map[door_kind][dungeon.name].append((d, d.dest))
else: else:
type_map[door_kind][dungeon.name].append(d) type_map[door_kind][dungeon.name].append(d)
else:
if d.dest.type in [DoorType.Interior, DoorType.Normal]:
type_map[door_kind].append((d, d.dest)) class DoorTypePool:
else: def __init__(self, sectors, world, player):
type_map[door_kind].append(d) self.smalls = 0
self.bombable = 0
self.dashable = 0
self.bigs = 0
self.traps = 0
# self.tricky = 0
# self.hidden = 0
# todo: custom pools?
self.count_via_sectors(sectors, world, player)
def count_via_sectors(self, sectors, world, player):
skips = set()
for sector in sectors:
for region in sector.regions:
for ext in region.exits:
if ext.door:
d = ext.door
if d.name not in skips and d.type in [DoorType.Normal, DoorType.Interior]:
if d.smallKey:
self.smalls += 1
elif d.bigKey:
self.bigs += 1
elif d.blocked and d.trapFlag and 'Boss' not in d.name and 'Agahnim' not in d.name:
self.traps += 1
elif d.name == 'TR Compass Room NW':
self.tricky += 1
elif d.name in ['Skull Vines NW', 'Tower Altar NW']:
self.hidden += 1
else:
kind = world.get_room(d.roomIndex, player).kind(d)
if kind == DoorKind.Bombable:
self.bombable += 1
elif kind == DoorKind.Dashable:
self.dashable += 1
if d.type == DoorType.Interior:
skips.add(d.dest.name) ## lookup a different way for interior door shuffle
elif d.type == DoorType.Normal:
for dp in world.paired_doors[player]:
if d.name == dp.door_a or d.name == dp.door_b:
skips.add(dp.door_b if d.name == dp.door_a else dp.door_a)
break
def chaos_shuffle(self):
weights = [1, 2, 4, 3, 2, 1]
self.smalls = random.choices(self.get_choices(self.smalls), weights=weights)
self.bombable = random.choices(self.get_choices(self.bombable), weights=weights)
self.dashable = random.choices(self.get_choices(self.dashable), weights=weights)
self.bigs = random.choices(self.get_choices(self.bigs), weights=weights)
self.traps = random.choices(self.get_choices(self.traps), weights=weights)
# self.tricky = random.choices(self.get_choices(self.tricky), weights=weights)
# self.hidden = random.choices(self.get_choices(self.hidden), weights=weights)
@staticmethod
def get_choices(number):
return [max(number-i, 0) for i in range(-1, 5)]
class BuilderDoorCandidates:
def __init__(self):
self.small = set()
self.big = set()
self.trap = set()
self.bombable = set()
self.dashable = set()
self.checked = set()
def shuffle_door_types(door_type_pools, paths, world, player):
start_regions_map = {}
for name, builder in world.dungeon_layouts[player]:
start_regions = convert_regions(builder.path_entrances, world, player)
start_regions_map[name] = start_regions
used_doors = shuffle_door_traps(door_type_pools, paths, start_regions_map, world, player)
# big keys
# small keys
# bombable / dashable
# tricky / hidden
def shuffle_door_traps(door_type_pools, paths, start_regions_map, world, player):
used_doors = set()
for pool, door_type_pool in door_type_pools:
ttl = 0
suggestion_map, trap_map, flex_map = {}, {}, {}
remaining = door_type_pool.traps
if player in world.custom_door_types:
custom_trap_doors = world.custom_door_types[player]['Trap Door']
else:
custom_trap_doors = defaultdict(list)
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
find_trappable_candidates(builder, world, player)
if custom_trap_doors[dungeon]:
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, custom_trap_doors[dungeon])
remaining -= len(custom_trap_doors[dungeon])
ttl += len(builder.candidates.trap)
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
proportion = len(builder.candidates.trap)
calc = int(round(proportion * door_type_pool.traps/ttl))
suggested = min(proportion, calc)
remaining -= suggested
suggestion_map[dungeon] = suggested
flex_map[dungeon] = (proportion - suggested) if suggested < proportion else 0
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
valid_traps, trap_number = find_valid_trap_combination(builder, suggestion_map[dungeon],
start_regions_map[dungeon], paths, world, player)
trap_map[dungeon] = valid_traps
if trap_number < suggestion_map[dungeon]:
flex_map[dungeon] = 0
remaining += suggestion_map[dungeon] - trap_number
suggestion_map[dungeon] = trap_number
builder_order = [x for x in pool if flex_map[x] > 0]
queue = deque(builder_order)
while len(queue) > 0 and remaining > 0:
dungeon = queue.popleft()
builder = world.dungeon_layouts[player][dungeon]
increased = suggestion_map[dungeon] + 1
valid_traps, trap_number = find_valid_trap_combination(builder, increased, start_regions_map[dungeon],
paths, world, player)
if valid_traps:
trap_map[dungeon] = valid_traps
remaining -= 1
suggestion_map[dungeon] = increased
flex_map[dungeon] -= 1
if flex_map[dungeon] > 0:
queue.append(dungeon)
# time to re-assign
reassign_trap_doors(trap_map, world, player)
for name, traps in trap_map.items():
used_doors.update(traps)
return used_doors
def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
for pool, door_type_pool in door_type_pools:
ttl = 0
suggestion_map, bk_map, flex_map = {}, {}, {}
remaining = door_type_pool.bigs
if player in world.custom_door_types:
custom_bk_doors = world.custom_door_types[player]['Big Key Door']
else:
custom_bk_doors = defaultdict(list)
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
find_big_key_candidates(builder, used_doors, world, player)
if custom_bk_doors[dungeon]:
builder.candidates.trap = filter_key_door_pool(builder.candidates.big, custom_bk_doors[dungeon])
remaining -= len(custom_bk_doors[dungeon])
ttl += len(builder.candidates.big)
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
proportion = len(builder.candidates.big)
calc = int(round(proportion * door_type_pool.big/ttl))
suggested = min(proportion, calc)
remaining -= suggested
suggestion_map[dungeon] = suggested
flex_map[dungeon] = (proportion - suggested) if suggested < proportion else 0
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
valid_doors, bk_number = find_valid_bk_combination(builder, suggestion_map[dungeon],
start_regions_map[dungeon], world, player)
bk_map[dungeon] = valid_doors
if bk_number < suggestion_map[dungeon]:
flex_map[dungeon] = 0
remaining += suggestion_map[dungeon] - bk_number
suggestion_map[dungeon] = bk_number
builder_order = [x for x in pool if flex_map[x] > 0]
queue = deque(builder_order)
while len(queue) > 0 and remaining > 0:
dungeon = queue.popleft()
builder = world.dungeon_layouts[player][dungeon]
increased = suggestion_map[dungeon] + 1
valid_doors, bk_number = find_valid_bk_combination(builder, increased, start_regions_map[dungeon],
paths, world, player)
if valid_doors:
bk_map[dungeon] = valid_doors
remaining -= 1
suggestion_map[dungeon] = increased
flex_map[dungeon] -= 1
if flex_map[dungeon] > 0:
queue.append(dungeon)
# time to re-assign
reassign_big_key_doors(bk_map, world, player)
def shuffle_key_doors(builder, world, player): def shuffle_key_doors(builder, world, player):
@@ -1582,6 +1899,223 @@ def find_current_key_doors(builder):
return current_doors return current_doors
def find_trappable_candidates(builder, world, player):
if world.door_type_mode[player] != 'original': # all, chaos
r_set = builder.master_sector.region_set()
filtered_doors = [ext.door for r in r_set for ext in r.exits
if ext.door and ext.door.type in [DoorType.Interior, DoorType.Normal]]
for d in filtered_doors:
# I only support the first 3 due to the trapFlag right now
if 0 <= d.doorListPos < 3 and not d.entranceFlag:
room = world.get_room(d.roomIndex, player)
kind = room.kind(d)
if d.type == DoorType.Interior:
if (kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable,
DoorKind.BigKey]
or (d.blocked and d.trapFlag != 0 and 'Boss' not in d.name and 'Agahnim' not in d.name)
or (kind == DoorKind.TrapTriggerable and d.direction in [Direction.North, Direction.East])
or (kind == DoorKind.Trap2 and d.direction in [Direction.South, Direction.West])):
builder.candidates.trap.add(d)
elif d.type == DoorType.Normal:
if (kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable,
DoorKind.BigKey]
or (d.blocked and d.trapFlag != 0 and 'Boss' not in d.name and 'Agahnim' not in d.name)):
builder.candidates.trap.add(d)
else:
r_set = builder.master_sector.region_set()
for r in r_set:
for ext in r.exits:
if ext.door:
d = ext.door
if d.blocked and d.trapFlag != 0 and 'Boss' not in d.name and 'Agahnim' not in d.name:
builder.candidates.trap.add(d)
def find_valid_trap_combination(builder, suggested, start_regions, paths, world, player, drop=True):
trap_door_pool = builder.candidates.trap
trap_doors_needed = suggested
if player in world.custom_door_types:
custom_trap_doors = world.custom_door_types[player]['Trap Door'][builder.name]
else:
custom_trap_doors = []
if custom_trap_doors:
trap_door_pool = filter_key_door_pool(trap_door_pool, custom_trap_doors)
trap_doors_needed -= len(custom_trap_doors)
if len(trap_door_pool) < trap_doors_needed:
if not drop:
return None, 0
trap_doors_needed = len(trap_door_pool)
combinations = ncr(len(trap_door_pool), trap_doors_needed)
itr = 0
sample_list = build_sample_list(combinations, 1000)
proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed)
proposal.extend(custom_trap_doors)
start_regions = filter_start_regions(builder, start_regions, world, player)
while not validate_trap_layout(proposal, builder, start_regions, paths, world, player):
itr += 1
if itr >= len(sample_list):
if not drop:
return None, 0
trap_doors_needed -= 1
if trap_doors_needed < 0:
raise Exception(f'Bad dungeon {builder.name} - maybe custom trap doors are bad')
combinations = ncr(len(trap_door_pool), trap_doors_needed)
sample_list = build_sample_list(combinations, 1000)
itr = 0
proposal = kth_combination(sample_list[itr], trap_door_pool, trap_doors_needed)
proposal.extend(custom_trap_doors)
return proposal, trap_doors_needed
# eliminate start region if portal marked as destination
def filter_start_regions(builder, start_regions, world, player):
std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle'
excluded = {}
for region in start_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination:
excluded[region] = None
if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'):
excluded[region] = None
return [x for x in start_regions if x not in excluded.keys()]
def validate_trap_layout(proposal, builder, start_regions, paths, world, player):
return check_required_paths_with_traps(paths, proposal, builder.name, start_regions, world, player)
def check_required_paths_with_traps(paths, proposal, dungeon_name, start_regions, world, player):
if len(paths[dungeon_name]) > 0:
states_to_explore = {}
common_starts = tuple(start_regions)
for path in paths[dungeon_name]:
if type(path) is tuple:
states_to_explore[tuple([path[0]])] = (path[1], 'any')
else:
if common_starts not in states_to_explore:
states_to_explore[common_starts] = ([], 'all')
states_to_explore[common_starts][0].append(path)
cached_initial_state = None
for start_regs, info in states_to_explore.items():
dest_regs, path_type = info
if type(dest_regs) is not list:
dest_regs = [dest_regs]
check_paths = convert_regions(dest_regs, world, player)
start_regions = convert_regions(start_regs, world, player)
initial = start_regs == common_starts
if not initial or cached_initial_state is None:
init = determine_init_crystal(initial, cached_initial_state, start_regions)
state = ExplorationState2(init, dungeon_name)
for region in start_regions:
state.visit_region(region)
state.add_all_doors_check_proposed_traps(region, proposal, world, player)
explore_state_proposed_traps(state, world, player)
if initial and cached_initial_state is None:
cached_initial_state = state
else:
state = cached_initial_state
if path_type == 'any':
valid, bad_region = check_if_any_regions_visited(state, check_paths)
else:
valid, bad_region = check_if_all_regions_visited(state, check_paths)
if not valid:
return False
return True
def reassign_trap_doors(trap_map, world, player):
logger = logging.getLogger('')
for name, traps in trap_map.items():
builder = world.dungeon_layouts[player][name]
queue = deque(find_current_trap_doors(builder))
while len(queue) > 0:
d = queue.pop()
if d.type is DoorType.Interior and d not in traps:
room = world.get_room(d.roomIndex, player)
kind = room.kind(d)
if kind == DoorKind.Trap:
new_type = (DoorKind.TrapTriggerable if d.direction in [Direction.South, Direction.East] else
DoorKind.Trap2)
room.change(d.doorListPos, new_type)
elif kind in [DoorKind.Trap2, DoorKind.TrapTriggerable]:
room.change(d.doorListPos, DoorKind.Normal)
d.blocked = False
elif d.type is DoorType.Normal and d not in traps:
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
d.blocked = False
for d in traps:
change_door_to_trap(d, world, player)
world.spoiler.set_door_type(d.name, 'Trap Door', player)
logger.debug('Key Door: %s', d.name)
def find_current_trap_doors(builder):
current_doors = []
for region in builder.master_sector.regions:
for ext in region.exits:
d = ext.door
if d and d.blocked and d.trapFlag != 0:
current_doors.append(d)
return current_doors
def change_door_to_trap(d, world, player):
room = world.get_room(d.roomIndex, player)
if d.type is DoorType.Interior:
kind = room.kind(d)
new_kind = None
if kind == DoorKind.TrapTriggerable and d.direction in [Direction.North, Direction.West]:
new_kind = DoorKind.Trap
elif kind == DoorKind.Trap2 and d.direction in [Direction.South, Direction.East]:
new_kind = DoorKind.Trap
elif d.direction in [Direction.North, Direction.West]:
new_kind = DoorKind.Trap2
elif d.direction in [Direction.South, Direction.East]:
new_kind = DoorKind.TrapTriggerable
if new_kind:
d.blocked = True
verify_door_list_pos(d, room, world, player, pos=3)
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos]
room.change(d.doorListPos, new_kind)
elif d.type is DoorType.Normal:
d.blocked = True
verify_door_list_pos(d, room, world, player, pos=3)
d.trapFlag = {0: 0x4, 1: 0x2, 2: 0x1}[d.doorListPos]
room.change(d.doorListPos, DoorKind.Trap)
def find_big_key_candidates(builder, used, world, player):
if world.door_type_mode[player] != 'original': # all, chaos
r_set = builder.master_sector.region_set()
filtered_doors = [ext.door for r in r_set for ext in r.exits
if ext.door and ext.door.type in [DoorType.Interior, DoorType.Normal]]
for d in filtered_doors:
if 0 <= d.doorListPos < 4 and not d.entranceFlag:
room = world.get_room(d.roomIndex, player)
kind = room.kind(d)
if d.type in [DoorType.Interior, DoorType.Normal]:
if (kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable,
DoorKind.BigKey] and d not in used)
or (d.blocked and d.trapFlag != 0 and 'Boss' not in d.name and 'Agahnim' not in d.name)
or (kind == DoorKind.TrapTriggerable and d.direction in [Direction.North, Direction.East])
or (kind == DoorKind.Trap2 and d.direction in [Direction.South, Direction.West])):
builder.candidates.big.add(d)
elif d.type == DoorType.Normal:
if (kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable,
DoorKind.BigKey]
or (d.blocked and d.trapFlag != 0 and 'Boss' not in d.name and 'Agahnim' not in d.name)):
builder.candidates.trap.add(d)
else:
r_set = builder.master_sector.region_set()
for r in r_set:
for ext in r.exits:
if ext.door:
d = ext.door
if d.bigKey and d.type in [DoorType.Normal, DoorType.Interior]:
builder.candidates.big.add(d)
def find_small_key_door_candidates(builder, start_regions, world, player): def find_small_key_door_candidates(builder, start_regions, world, player):
# traverse dungeon and find candidates # traverse dungeon and find candidates
candidates = [] candidates = []
@@ -1638,16 +2172,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
sample_list = build_sample_list(combinations) sample_list = build_sample_list(combinations)
proposal = kth_combination(sample_list[itr], key_door_pool, key_doors_needed) proposal = kth_combination(sample_list[itr], key_door_pool, key_doors_needed)
proposal.extend(custom_key_doors) proposal.extend(custom_key_doors)
# eliminate start region if portal marked as destination start_regions = filter_start_regions(builder, start_regions, world, player)
std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle'
excluded = {}
for region in start_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination:
excluded[region] = None
if std_flag and (not portal or portal.find_portal_entrance().parent_region.name != 'Hyrule Castle Courtyard'):
excluded[region] = None
start_regions = [x for x in start_regions if x not in excluded.keys()]
key_layout = build_key_layout(builder, start_regions, proposal, world, player) key_layout = build_key_layout(builder, start_regions, proposal, world, player)
determine_prize_lock(key_layout, world, player) determine_prize_lock(key_layout, world, player)
@@ -1682,13 +2207,12 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
return True return True
def build_sample_list(combinations): def build_sample_list(combinations, max_combinations=1000000):
if combinations <= 1000000: if combinations <= max_combinations:
sample_list = list(range(0, int(combinations))) sample_list = list(range(0, int(combinations)))
else: else:
num_set = set() num_set = set()
while len(num_set) < 1000000: while len(num_set) < max_combinations:
num_set.add(random.randint(0, combinations)) num_set.add(random.randint(0, combinations))
sample_list = list(num_set) sample_list = list(num_set)
sample_list.sort() sample_list.sort()
@@ -1885,9 +2409,9 @@ def change_door_to_small_key(d, world, player):
room.change(d.doorListPos, DoorKind.SmallKey) room.change(d.doorListPos, DoorKind.SmallKey)
def verify_door_list_pos(d, room, world, player): def verify_door_list_pos(d, room, world, player, pos=4):
if d.doorListPos >= 4: if d.doorListPos >= pos:
new_index = room.next_free() new_index = room.next_free(pos)
if new_index is not None: if new_index is not None:
room.swap(new_index, d.doorListPos) room.swap(new_index, d.doorListPos)
other = next(x for x in world.doors if x.player == player and x.roomIndex == d.roomIndex other = next(x for x in world.doors if x.player == player and x.roomIndex == d.roomIndex
@@ -1895,7 +2419,7 @@ def verify_door_list_pos(d, room, world, player):
other.doorListPos = d.doorListPos other.doorListPos = d.doorListPos
d.doorListPos = new_index d.doorListPos = new_index
else: else:
raise Exception(f'Invalid stateful door: {d.name}. Only 4 stateful doors per supertile') raise Exception(f'Invalid stateful door: {d.name}. Only {pos} stateful doors per supertile')
def smooth_door_pairs(world, player): def smooth_door_pairs(world, player):
@@ -2261,6 +2785,15 @@ def explore_state(state, world, player):
state.add_all_doors_check_unattached(connect_region, world, player) state.add_all_doors_check_unattached(connect_region, world, player)
def explore_state_proposed_traps(state, proposed_traps, world, player):
while len(state.avail_doors) > 0:
door = state.next_avail_door().door
connect_region = world.get_entrance(door.name, player).connected_region
if not state.visited(connect_region) and valid_region_to_explore(connect_region, world, player):
state.visit_region(connect_region)
state.add_all_doors_check_proposed_traps(connect_region, proposed_traps, world, player)
def explore_state_not_inaccessible(state, world, player): def explore_state_not_inaccessible(state, world, player):
while len(state.avail_doors) > 0: while len(state.avail_doors) > 0:
door = state.next_avail_door().door door = state.next_avail_door().door

View File

@@ -348,7 +348,7 @@ def create_doors(world, player):
create_door(player, 'Tower Catwalk North Stairs', StrS).dir(No, 0x40, Left, High), create_door(player, 'Tower Catwalk North Stairs', StrS).dir(No, 0x40, Left, High),
create_door(player, 'Tower Antechamber South Stairs', StrS).dir(So, 0x30, Left, High), create_door(player, 'Tower Antechamber South Stairs', StrS).dir(So, 0x30, Left, High),
create_door(player, 'Tower Antechamber NW', Intr).dir(No, 0x30, Left, High).pos(1), create_door(player, 'Tower Antechamber NW', Intr).dir(No, 0x30, Left, High).pos(1),
create_door(player, 'Tower Altar SW', Intr).dir(So, 0x30, Left, High).no_exit().pos(1), create_door(player, 'Tower Altar SW', Intr).dir(So, 0x30, Left, High).no_exit().trap(0x2).pos(1),
create_door(player, 'Tower Altar NW', Nrml).dir(No, 0x30, Left, High).pos(0), create_door(player, 'Tower Altar NW', Nrml).dir(No, 0x30, Left, High).pos(0),
create_door(player, 'Tower Agahnim 1 SW', Nrml).dir(So, 0x20, Left, High).no_exit().trap(0x4).pos(0), create_door(player, 'Tower Agahnim 1 SW', Nrml).dir(So, 0x20, Left, High).no_exit().trap(0x4).pos(0),
@@ -667,7 +667,7 @@ def create_doors(world, player):
create_door(player, 'Thieves Conveyor Maze SW', Intr).dir(So, 0xbc, Left, High).pos(6), create_door(player, 'Thieves Conveyor Maze SW', Intr).dir(So, 0xbc, Left, High).pos(6),
create_door(player, 'Thieves Pot Alcove Top NW', Intr).dir(No, 0xbc, Left, High).pos(6), create_door(player, 'Thieves Pot Alcove Top NW', Intr).dir(No, 0xbc, Left, High).pos(6),
create_door(player, 'Thieves Conveyor Maze EN', Intr).dir(Ea, 0xbc, Top, High).pos(2), create_door(player, 'Thieves Conveyor Maze EN', Intr).dir(Ea, 0xbc, Top, High).pos(2),
create_door(player, 'Thieves Hallway WN', Intr).dir(We, 0xbc, Top, High).no_exit().pos(2), create_door(player, 'Thieves Hallway WN', Intr).dir(We, 0xbc, Top, High).no_exit().trap(0x1).pos(2),
create_door(player, 'Thieves Conveyor Maze Down Stairs', Sprl).dir(Dn, 0xbc, 0, HTH).ss(A, 0x11, 0x80, True, True), create_door(player, 'Thieves Conveyor Maze Down Stairs', Sprl).dir(Dn, 0xbc, 0, HTH).ss(A, 0x11, 0x80, True, True),
create_door(player, 'Thieves Boss SE', Nrml).dir(So, 0xac, Right, High).no_exit().trap(0x4).pos(0), create_door(player, 'Thieves Boss SE', Nrml).dir(So, 0xac, Right, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Thieves Spike Track ES', Nrml).dir(Ea, 0xbb, Bot, High).pos(5), create_door(player, 'Thieves Spike Track ES', Nrml).dir(Ea, 0xbb, Bot, High).pos(5),
@@ -742,7 +742,7 @@ def create_doors(world, player):
create_door(player, 'Ice Big Key Push Block', Lgcl), create_door(player, 'Ice Big Key Push Block', Lgcl),
create_door(player, 'Ice Big Key Down Ladder', Lddr).dir(So, 0x1f, 3, High), create_door(player, 'Ice Big Key Down Ladder', Lddr).dir(So, 0x1f, 3, High),
create_door(player, 'Ice Stalfos Hint SE', Intr).dir(So, 0x3e, Right, High).pos(0), create_door(player, 'Ice Stalfos Hint SE', Intr).dir(So, 0x3e, Right, High).pos(0),
create_door(player, 'Ice Conveyor NE', Intr).dir(No, 0x3e, Right, High).no_exit().pos(0), create_door(player, 'Ice Conveyor NE', Intr).dir(No, 0x3e, Right, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Ice Conveyor to Crystal', Lgcl), create_door(player, 'Ice Conveyor to Crystal', Lgcl),
create_door(player, 'Ice Conveyor Crystal Exit', Lgcl), create_door(player, 'Ice Conveyor Crystal Exit', Lgcl),
create_door(player, 'Ice Conveyor SW', Nrml).dir(So, 0x3e, Left, High).small_key().pos(1).portal(Z, 0x20), create_door(player, 'Ice Conveyor SW', Nrml).dir(So, 0x3e, Left, High).small_key().pos(1).portal(Z, 0x20),
@@ -760,7 +760,7 @@ def create_doors(world, player):
create_door(player, 'Ice Firebar ES', Intr).dir(Ea, 0x5e, Bot, High).pos(3), create_door(player, 'Ice Firebar ES', Intr).dir(Ea, 0x5e, Bot, High).pos(3),
create_door(player, 'Ice Firebar Down Ladder', Lddr).dir(So, 0x5e, 5, High), create_door(player, 'Ice Firebar Down Ladder', Lddr).dir(So, 0x5e, 5, High),
create_door(player, 'Ice Spike Cross NE', Intr).dir(No, 0x5e, Right, High).pos(1), create_door(player, 'Ice Spike Cross NE', Intr).dir(No, 0x5e, Right, High).pos(1),
create_door(player, 'Ice Falling Square SE', Intr).dir(So, 0x5e, Right, High).no_exit().pos(1), create_door(player, 'Ice Falling Square SE', Intr).dir(So, 0x5e, Right, High).no_exit().trap(0x1).pos(1),
create_door(player, 'Ice Falling Square Hole', Hole), create_door(player, 'Ice Falling Square Hole', Hole),
create_door(player, 'Ice Spike Room WS', Nrml).dir(We, 0x5f, Bot, High).small_key().pos(0), create_door(player, 'Ice Spike Room WS', Nrml).dir(We, 0x5f, Bot, High).small_key().pos(0),
create_door(player, 'Ice Spike Room Down Stairs', Sprl).dir(Dn, 0x5f, 3, HTH).ss(Z, 0x11, 0x48, True, True), create_door(player, 'Ice Spike Room Down Stairs', Sprl).dir(Dn, 0x5f, 3, HTH).ss(Z, 0x11, 0x48, True, True),
@@ -840,12 +840,12 @@ def create_doors(world, player):
create_door(player, 'Mire Hub Top NW', Nrml).dir(No, 0xc2, Left, High).pos(2), create_door(player, 'Mire Hub Top NW', Nrml).dir(No, 0xc2, Left, High).pos(2),
create_door(player, 'Mire Lone Shooter WS', Nrml).dir(We, 0xc3, Bot, High).pos(6), create_door(player, 'Mire Lone Shooter WS', Nrml).dir(We, 0xc3, Bot, High).pos(6),
create_door(player, 'Mire Lone Shooter ES', Intr).dir(Ea, 0xc3, Bot, High).pos(3), create_door(player, 'Mire Lone Shooter ES', Intr).dir(Ea, 0xc3, Bot, High).pos(3),
create_door(player, 'Mire Falling Bridge WS', Intr).dir(We, 0xc3, Bot, High).no_exit().pos(3), create_door(player, 'Mire Falling Bridge WS', Intr).dir(We, 0xc3, Bot, High).no_exit().trap(0x8).pos(3),
create_door(player, 'Mire Falling Bridge W', Intr).dir(We, 0xc3, Mid, High).pos(2), create_door(player, 'Mire Falling Bridge W', Intr).dir(We, 0xc3, Mid, High).pos(2),
create_door(player, 'Mire Failure Bridge E', Intr).dir(Ea, 0xc3, Mid, High).no_exit().pos(2), create_door(player, 'Mire Failure Bridge E', Intr).dir(Ea, 0xc3, Mid, High).no_exit().trap(0x1).pos(2),
create_door(player, 'Mire Failure Bridge W', Nrml).dir(We, 0xc3, Mid, High).pos(5), create_door(player, 'Mire Failure Bridge W', Nrml).dir(We, 0xc3, Mid, High).pos(5),
create_door(player, 'Mire Falling Bridge WN', Intr).dir(We, 0xc3, Top, High).pos(1), create_door(player, 'Mire Falling Bridge WN', Intr).dir(We, 0xc3, Top, High).pos(1),
create_door(player, 'Mire Map Spike Side EN', Intr).dir(Ea, 0xc3, Top, High).no_exit().pos(1), create_door(player, 'Mire Map Spike Side EN', Intr).dir(Ea, 0xc3, Top, High).no_exit().trap(0x2).pos(1),
create_door(player, 'Mire Map Spot WN', Nrml).dir(We, 0xc3, Top, High).small_key().pos(0), create_door(player, 'Mire Map Spot WN', Nrml).dir(We, 0xc3, Top, High).small_key().pos(0),
create_door(player, 'Mire Crystal Dead End NW', Nrml).dir(No, 0xc3, Left, High).pos(4), create_door(player, 'Mire Crystal Dead End NW', Nrml).dir(No, 0xc3, Left, High).pos(4),
create_door(player, 'Mire Map Spike Side Drop Down', Lgcl), create_door(player, 'Mire Map Spike Side Drop Down', Lgcl),
@@ -903,7 +903,7 @@ def create_doors(world, player):
create_door(player, 'Mire Tile Room NW', Intr).dir(No, 0xc1, Left, High).pos(3), create_door(player, 'Mire Tile Room NW', Intr).dir(No, 0xc1, Left, High).pos(3),
create_door(player, 'Mire Compass Room SW', Intr).dir(So, 0xc1, Left, High).pos(3), create_door(player, 'Mire Compass Room SW', Intr).dir(So, 0xc1, Left, High).pos(3),
create_door(player, 'Mire Compass Room EN', Intr).dir(Ea, 0xc1, Top, High).pos(2), create_door(player, 'Mire Compass Room EN', Intr).dir(Ea, 0xc1, Top, High).pos(2),
create_door(player, 'Mire Wizzrobe Bypass WN', Intr).dir(We, 0xc1, Top, High).no_exit().pos(2), create_door(player, 'Mire Wizzrobe Bypass WN', Intr).dir(We, 0xc1, Top, High).no_exit().trap(0x1).pos(2),
create_door(player, 'Mire Compass Blue Barrier', Lgcl), create_door(player, 'Mire Compass Blue Barrier', Lgcl),
create_door(player, 'Mire Compass Chest Exit', Lgcl), create_door(player, 'Mire Compass Chest Exit', Lgcl),
create_door(player, 'Mire Neglected Room NE', Nrml).dir(No, 0xd1, Right, High).pos(2), create_door(player, 'Mire Neglected Room NE', Nrml).dir(No, 0xd1, Right, High).pos(2),
@@ -912,7 +912,7 @@ def create_doors(world, player):
create_door(player, 'Mire Neglected Room SE', Intr).dir(So, 0xd1, Right, High).pos(3), create_door(player, 'Mire Neglected Room SE', Intr).dir(So, 0xd1, Right, High).pos(3),
create_door(player, 'Mire Chest View NE', Intr).dir(No, 0xd1, Right, High).pos(3), create_door(player, 'Mire Chest View NE', Intr).dir(No, 0xd1, Right, High).pos(3),
create_door(player, 'Mire BK Chest Ledge WS', Intr).dir(We, 0xd1, Bot, High).pos(0), create_door(player, 'Mire BK Chest Ledge WS', Intr).dir(We, 0xd1, Bot, High).pos(0),
create_door(player, 'Mire Warping Pool ES', Intr).dir(Ea, 0xd1, Bot, High).no_exit().pos(0), create_door(player, 'Mire Warping Pool ES', Intr).dir(Ea, 0xd1, Bot, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Mire Warping Pool Warp', Warp), create_door(player, 'Mire Warping Pool Warp', Warp),
create_door(player, 'Mire Torches Top Down Stairs', Sprl).dir(Dn, 0x97, 0, HTH).ss(A, 0x11, 0xb0, True).kill(), create_door(player, 'Mire Torches Top Down Stairs', Sprl).dir(Dn, 0x97, 0, HTH).ss(A, 0x11, 0xb0, True).kill(),
create_door(player, 'Mire Torches Top SW', Intr).dir(So, 0x97, Left, High).pos(1), create_door(player, 'Mire Torches Top SW', Intr).dir(So, 0x97, Left, High).pos(1),
@@ -1011,7 +1011,7 @@ def create_doors(world, player):
create_door(player, 'TR Big Chest Entrance SE', Nrml).dir(So, 0x24, Right, High).pos(4).kill().portal(X, 0x00), create_door(player, 'TR Big Chest Entrance SE', Nrml).dir(So, 0x24, Right, High).pos(4).kill().portal(X, 0x00),
create_door(player, 'TR Big Chest Entrance Gap', Lgcl), create_door(player, 'TR Big Chest Entrance Gap', Lgcl),
create_door(player, 'TR Big Chest NE', Intr).dir(No, 0x24, Right, High).pos(3), create_door(player, 'TR Big Chest NE', Intr).dir(No, 0x24, Right, High).pos(3),
create_door(player, 'TR Dodgers SE', Intr).dir(So, 0x24, Right, High).no_exit().pos(3), create_door(player, 'TR Dodgers SE', Intr).dir(So, 0x24, Right, High).no_exit().trap(0x8).pos(3),
create_door(player, 'TR Dodgers NE', Nrml).dir(No, 0x24, Right, High).big_key().pos(0), create_door(player, 'TR Dodgers NE', Nrml).dir(No, 0x24, Right, High).big_key().pos(0),
create_door(player, 'TR Lazy Eyes SE', Nrml).dir(So, 0x23, Right, High).pos(0).portal(X, 0x00), create_door(player, 'TR Lazy Eyes SE', Nrml).dir(So, 0x23, Right, High).pos(0).portal(X, 0x00),
create_door(player, 'TR Lazy Eyes ES', Nrml).dir(Ea, 0x23, Bot, High).pos(1), create_door(player, 'TR Lazy Eyes ES', Nrml).dir(Ea, 0x23, Bot, High).pos(1),
@@ -1073,7 +1073,7 @@ def create_doors(world, player):
create_door(player, 'GT Hope Room EN', Nrml).dir(Ea, 0x8c, Top, High).trap(0x4).pos(0), create_door(player, 'GT Hope Room EN', Nrml).dir(Ea, 0x8c, Top, High).trap(0x4).pos(0),
create_door(player, 'GT Torch EN', Intr).dir(Ea, 0x8c, Top, High).small_key().pos(2), create_door(player, 'GT Torch EN', Intr).dir(Ea, 0x8c, Top, High).small_key().pos(2),
create_door(player, 'GT Hope Room WN', Intr).dir(We, 0x8c, Top, High).small_key().pos(2), create_door(player, 'GT Hope Room WN', Intr).dir(We, 0x8c, Top, High).small_key().pos(2),
create_door(player, 'GT Torch SW', Intr).dir(So, 0x8c, Left, High).no_exit().pos(1), create_door(player, 'GT Torch SW', Intr).dir(So, 0x8c, Left, High).no_exit().trap(0x2).pos(1),
create_door(player, 'GT Big Chest NW', Intr).dir(No, 0x8c, Left, High).pos(1), create_door(player, 'GT Big Chest NW', Intr).dir(No, 0x8c, Left, High).pos(1),
create_door(player, 'GT Blocked Stairs Down Stairs', Sprl).dir(Dn, 0x8c, 3, HTH).ss(Z, 0x12, 0x40, True, True).kill(), create_door(player, 'GT Blocked Stairs Down Stairs', Sprl).dir(Dn, 0x8c, 3, HTH).ss(Z, 0x12, 0x40, True, True).kill(),
create_door(player, 'GT Blocked Stairs Block Path', Lgcl), create_door(player, 'GT Blocked Stairs Block Path', Lgcl),
@@ -1179,7 +1179,7 @@ def create_doors(world, player):
create_door(player, 'GT Ice Armos NE', Intr).dir(No, 0x1c, Right, High).pos(0), create_door(player, 'GT Ice Armos NE', Intr).dir(No, 0x1c, Right, High).pos(0),
create_door(player, 'GT Big Key Room SE', Intr).dir(So, 0x1c, Right, High).pos(0), create_door(player, 'GT Big Key Room SE', Intr).dir(So, 0x1c, Right, High).pos(0),
create_door(player, 'GT Ice Armos WS', Intr).dir(We, 0x1c, Bot, High).pos(1), create_door(player, 'GT Ice Armos WS', Intr).dir(We, 0x1c, Bot, High).pos(1),
create_door(player, 'GT Four Torches ES', Intr).dir(Ea, 0x1c, Bot, High).no_exit().pos(1), create_door(player, 'GT Four Torches ES', Intr).dir(Ea, 0x1c, Bot, High).no_exit().trap(0x2).pos(1),
create_door(player, 'GT Four Torches NW', Intr).dir(No, 0x1c, Left, High).pos(2), create_door(player, 'GT Four Torches NW', Intr).dir(No, 0x1c, Left, High).pos(2),
create_door(player, 'GT Fairy Abyss SW', Intr).dir(So, 0x1c, Left, High).pos(2), create_door(player, 'GT Fairy Abyss SW', Intr).dir(So, 0x1c, Left, High).pos(2),
create_door(player, 'GT Four Torches Up Stairs', Sprl).dir(Up, 0x1c, 0, HTH).ss(Z, 0x1b, 0x2c, True, True), create_door(player, 'GT Four Torches Up Stairs', Sprl).dir(Up, 0x1c, 0, HTH).ss(Z, 0x1b, 0x2c, True, True),
@@ -1211,7 +1211,7 @@ def create_doors(world, player):
create_door(player, 'GT Beam Dash WS', Intr).dir(We, 0x6c, Bot, High).pos(0), create_door(player, 'GT Beam Dash WS', Intr).dir(We, 0x6c, Bot, High).pos(0),
create_door(player, 'GT Lanmolas 2 ES', Intr).dir(Ea, 0x6c, Bot, High).pos(0), create_door(player, 'GT Lanmolas 2 ES', Intr).dir(Ea, 0x6c, Bot, High).pos(0),
create_door(player, 'GT Lanmolas 2 NW', Intr).dir(No, 0x6c, Left, High).pos(1), create_door(player, 'GT Lanmolas 2 NW', Intr).dir(No, 0x6c, Left, High).pos(1),
create_door(player, 'GT Quad Pot SW', Intr).dir(So, 0x6c, Left, High).no_exit().pos(1), create_door(player, 'GT Quad Pot SW', Intr).dir(So, 0x6c, Left, High).no_exit().trap(0x2).pos(1),
create_door(player, 'GT Quad Pot Up Stairs', Sprl).dir(Up, 0x6c, 0, HTH).ss(A, 0x1b, 0x6c, True, True), create_door(player, 'GT Quad Pot Up Stairs', Sprl).dir(Up, 0x6c, 0, HTH).ss(A, 0x1b, 0x6c, True, True),
create_door(player, 'GT Wizzrobes 1 Down Stairs', Sprl).dir(Dn, 0xa5, 0, HTH).ss(A, 0x12, 0x80, True, True), create_door(player, 'GT Wizzrobes 1 Down Stairs', Sprl).dir(Dn, 0xa5, 0, HTH).ss(A, 0x12, 0x80, True, True),
create_door(player, 'GT Wizzrobes 1 SW', Intr).dir(So, 0xa5, Left, High).pos(2), create_door(player, 'GT Wizzrobes 1 SW', Intr).dir(So, 0xa5, Left, High).pos(2),

View File

@@ -1262,7 +1262,7 @@ def simple_dungeon_builder(name, sector_list):
return builder return builder
def create_dungeon_builders(all_sectors, connections_tuple, world, player, def create_dungeon_builders(all_sectors, connections_tuple, world, player, dungeon_pool,
dungeon_entrances=None, split_dungeon_entrances=None): dungeon_entrances=None, split_dungeon_entrances=None):
logger = logging.getLogger('') logger = logging.getLogger('')
logger.info('Shuffling Dungeon Sectors') logger.info('Shuffling Dungeon Sectors')
@@ -1278,7 +1278,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
global_pole = GlobalPolarity(candidate_sectors) global_pole = GlobalPolarity(candidate_sectors)
dungeon_map = {} dungeon_map = {}
for key in dungeon_regions.keys(): for key in dungeon_pool:
dungeon_map[key] = DungeonBuilder(key) dungeon_map[key] = DungeonBuilder(key)
for key in dungeon_boss_sectors.keys(): for key in dungeon_boss_sectors.keys():
current_dungeon = dungeon_map[key] current_dungeon = dungeon_map[key]

View File

@@ -316,9 +316,9 @@ class Room(object):
byte_array.append(kind.value) byte_array.append(kind.value)
return byte_array return byte_array
def next_free(self): def next_free(self, pos=4):
for i, door in enumerate(self.doorList): for i, door in enumerate(self.doorList):
if i >= 4: if i >= pos:
return None return None
pos, kind = door pos, kind = door
if kind not in [DoorKind.SmallKey, DoorKind.Dashable, DoorKind.Bombable, DoorKind.TrapTriggerable, if kind not in [DoorKind.SmallKey, DoorKind.Dashable, DoorKind.Bombable, DoorKind.TrapTriggerable,
@@ -395,8 +395,8 @@ class DoorKind(Enum):
Bombable = 0x2E Bombable = 0x2E
BlastWall = 0x30 BlastWall = 0x30
Hidden = 0x32 Hidden = 0x32
TrapTriggerable = 0x36 # right side trap or south side trap TrapTriggerable = 0x36 # right side trap or south side trap (West, South)
Trap2 = 0x38 # left side trap or north side trap Trap2 = 0x38 # left side trap or north side trap (East, North)
NormalLow2 = 0x40 NormalLow2 = 0x40
TrapTriggerableLow = 0x44 TrapTriggerableLow = 0x44
Warp = 0x46 Warp = 0x46

View File

@@ -68,16 +68,11 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect, idx = {}, 0 doors_to_connect, idx = {}, 0
all_regions = set() all_regions = set()
bk_special = False
for sector in builder.sectors: for sector in builder.sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
doors_to_connect[door.name] = door, idx doors_to_connect[door.name] = door, idx
idx += 1 idx += 1
all_regions.update(sector.regions) all_regions.update(sector.regions)
bk_special |= check_for_special(sector.regions)
bk_needed = False
for sector in builder.sectors:
bk_needed |= determine_if_bk_needed(sector, split_dungeon, bk_special, world, player)
finished = False finished = False
# flag if standard and this is hyrule castle # flag if standard and this is hyrule castle
paths = determine_paths_for_dungeon(world, player, all_regions, name) paths = determine_paths_for_dungeon(world, player, all_regions, name)
@@ -96,9 +91,9 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
if hash_code not in hash_code_set: if hash_code not in hash_code_set:
hash_code_set.add(hash_code) hash_code_set.add(hash_code)
explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect, explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect,
bk_needed, bk_special, world, player) world, player)
if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions, if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions,
bk_needed, bk_special, paths, entrance_regions, world, player): paths, entrance_regions, world, player):
finished = True finished = True
else: else:
proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect, proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect,
@@ -217,29 +212,21 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
return proposed_map, hash_code return proposed_map, hash_code
def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player):
bk_needed, bk_special, world, player):
start = ExplorationState(dungeon=name) start = ExplorationState(dungeon=name)
start.big_key_special = bk_special original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map,
all_regions, valid_doors, world, player)
bk_flag = False if world.bigkeyshuffle[player] and not bk_special else bk_needed
def exception(d):
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
return original_state return original_state
def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions, def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions,
bk_needed, bk_special, paths, entrance_regions, world, player): paths, entrance_regions, world, player):
all_visited = set() all_visited = set()
all_visited.update(exploration_state.visited_blue) all_visited.update(exploration_state.visited_blue)
all_visited.update(exploration_state.visited_orange) all_visited.update(exploration_state.visited_orange)
if len(all_regions.difference(all_visited)) > 0: if len(all_regions.difference(all_visited)) > 0:
return False return False
if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player):
bk_needed, bk_special, world, player):
return False return False
return True return True
@@ -262,8 +249,7 @@ def check_for_special(regions):
return False return False
def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player):
bk_needed, bk_special, world, player):
for path in paths: for path in paths:
if type(path) is tuple: if type(path) is tuple:
target = path[1] target = path[1]
@@ -275,14 +261,12 @@ def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, propose
else: else:
target = path target = path
start_regions = entrance_regions start_regions = entrance_regions
if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player):
bk_needed, bk_special, world, player):
return False return False
return True return True
def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player):
bk_needed, bk_special, world, player):
target_regions = set() target_regions = set()
if type(target) is not list: if type(target) is not list:
for region in all_regions: for region in all_regions:
@@ -295,16 +279,11 @@ def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_re
target_regions.add(region) target_regions.add(region)
start = ExplorationState(dungeon=name) start = ExplorationState(dungeon=name)
start.big_key_special = bk_special original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions,
bk_flag = False if world.bigkeyshuffle[player] and not bk_special else bk_needed valid_doors, world, player)
def exception(d):
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
original_state = extend_reachable_state_improved(starting_regions, start, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
for exp_door in original_state.unattached_doors: for exp_door in original_state.unattached_doors:
if not exp_door.door.blocked: if not exp_door.door.blocked or exp_door.door.trapFlag != 0:
return True # outstanding connection possible return True # outstanding connection possible
for target in target_regions: for target in target_regions:
if original_state.visited_at_all(target): if original_state.visited_at_all(target):
@@ -637,6 +616,37 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors): elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag) self.append_door_to_list(door, self.avail_doors, flag)
def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, flag, world, player):
for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door)
if self.can_traverse_ignore_traps(door):
if door.controller is not None:
door = door.controller
if door.dest is None and door not in proposed_map.keys() and door.name in valid_doors:
if not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors, flag)
else:
other = self.find_door_in_list(door, self.unattached_doors)
if self.crystal != other.crystal:
other.crystal = CrystalBarrier.Either
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors, flag)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag)
def add_all_doors_check_proposed_traps(self, region, proposed_traps, world, player):
for door in get_doors(world, region, player):
if self.can_traverse_ignore_traps(door) and door not in proposed_traps:
if door.controller is not None:
door = door.controller
if door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors, False)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, False)
def add_all_doors_check_key_region(self, region, key_region, world, player): def add_all_doors_check_key_region(self, region, key_region, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if self.can_traverse(door): if self.can_traverse(door):
@@ -693,6 +703,13 @@ class ExplorationState(object):
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True return True
def can_traverse_ignore_traps(self, door):
if door.blocked and door.trapFlag == 0:
return False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def count_locations_exclude_specials(self, world, player): def count_locations_exclude_specials(self, world, player):
return count_locations_exclude_big_chest(self.found_locations, world, player) return count_locations_exclude_big_chest(self.found_locations, world, player)
@@ -801,6 +818,25 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
return local_state return local_state
def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player):
local_state = state.copy()
for region in search_regions:
local_state.visit_region(region)
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
if explorable_door.door in proposed_map:
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
else:
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None:
if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player)
and not local_state.visited(connect_region)):
local_state.visit_region(connect_region)
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player)
return local_state
def special_big_key_found(state): def special_big_key_found(state):
for location in state.found_locations: for location in state.found_locations:
if location.forced_item and location.forced_item.bigkey: if location.forced_item and location.forced_item.bigkey: