Merge branch 'DoorDevUnstable' into Sandbox
# Conflicts: # ItemList.py # Items.py # Main.py # Rom.py # data/base2current.bps
This commit is contained in:
490
DoorShuffle.py
490
DoorShuffle.py
@@ -6,7 +6,7 @@ from enum import unique, Flag
|
||||
from typing import DefaultDict, Dict, List
|
||||
from itertools import chain
|
||||
|
||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||
from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||
from BaseClasses import PotFlags, LocationType, Direction
|
||||
from Doors import reset_portals
|
||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||
@@ -15,13 +15,12 @@ from Items import ItemFactory
|
||||
from RoomData import DoorKind, PairedDoor, reset_rooms
|
||||
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
|
||||
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
|
||||
# from DungeonGenerator import generate_dungeon
|
||||
from DungeonGenerator import ExplorationState, convert_regions, pre_validate, determine_required_paths, drop_entrances
|
||||
from DungeonGenerator import ExplorationState, convert_regions, determine_required_paths, drop_entrances
|
||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||
from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations
|
||||
from DungeonGenerator import valid_region_to_explore
|
||||
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
||||
from KeyDoorShuffle import validate_bk_layout, check_bk_special
|
||||
from KeyDoorShuffle import validate_bk_layout
|
||||
from Utils import ncr, kth_combination
|
||||
|
||||
|
||||
@@ -129,7 +128,7 @@ def link_doors_prep(world, player):
|
||||
vanilla_key_logic(world, player)
|
||||
|
||||
|
||||
def link_doors_main(world, player):
|
||||
def create_dungeon_pool(world, player):
|
||||
pool = None
|
||||
if world.doorShuffle[player] == 'basic':
|
||||
pool = [([name], regions) for name, regions in dungeon_regions.items()]
|
||||
@@ -143,6 +142,11 @@ def link_doors_main(world, player):
|
||||
elif world.doorShuffle[player] != 'vanilla':
|
||||
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||
return pool
|
||||
|
||||
|
||||
def link_doors_main(world, player):
|
||||
pool = create_dungeon_pool(world, player)
|
||||
if pool:
|
||||
main_dungeon_pool(pool, world, player)
|
||||
if world.doorShuffle[player] != 'vanilla':
|
||||
@@ -226,21 +230,37 @@ def vanilla_key_logic(world, player):
|
||||
|
||||
add_inaccessible_doors(world, player)
|
||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||
for builder in builders:
|
||||
enabled_entrances = world.enabled_entrances[player] = {}
|
||||
builder_queue = deque(builders)
|
||||
last_key, loops = None, 0
|
||||
while len(builder_queue) > 0:
|
||||
builder = builder_queue.popleft()
|
||||
origin_list = entrances_map[builder.name]
|
||||
start_regions = convert_regions(origin_list, world, player)
|
||||
doors = convert_key_doors(default_small_key_doors[builder.name], world, player)
|
||||
key_layout = build_key_layout(builder, start_regions, doors, world, player)
|
||||
valid = validate_key_layout(key_layout, world, player)
|
||||
if not valid:
|
||||
logging.getLogger('').info('Vanilla key layout not valid %s', builder.name)
|
||||
builder.key_door_proposal = doors
|
||||
if player not in world.key_logic.keys():
|
||||
world.key_logic[player] = {}
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
world.key_layout[player][builder.name] = key_layout
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name)
|
||||
if len(origin_list) <= 0:
|
||||
if last_key == builder.name or loops > 1000:
|
||||
origin_name = (world.get_region(origin_list[0], player).entrances[0].parent_region.name
|
||||
if len(origin_list) > 0 else 'no origin')
|
||||
raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}')
|
||||
builder_queue.append(builder)
|
||||
last_key = builder.name
|
||||
loops += 1
|
||||
else:
|
||||
find_new_entrances(builder.master_sector, entrances_map, connections, potentials,
|
||||
enabled_entrances, world, player)
|
||||
start_regions = convert_regions(origin_list, world, player)
|
||||
doors = convert_key_doors(default_small_key_doors[builder.name], world, player)
|
||||
key_layout = build_key_layout(builder, start_regions, doors, {}, world, player)
|
||||
valid = validate_key_layout(key_layout, world, player)
|
||||
if not valid:
|
||||
logging.getLogger('').info('Vanilla key layout not valid %s', builder.name)
|
||||
builder.key_door_proposal = doors
|
||||
if player not in world.key_logic.keys():
|
||||
world.key_logic[player] = {}
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
world.key_layout[player][builder.name] = key_layout
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
# if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
|
||||
# validate_vanilla_key_logic(world, player)
|
||||
|
||||
@@ -477,14 +497,14 @@ def choose_portals(world, player):
|
||||
info.required_passage = {x: y for x, y in info.required_passage.items() if len(y) > 0}
|
||||
for target_region, possible_portals in info.required_passage.items():
|
||||
candidates = find_portal_candidates(master_door_list, dungeon, custom, allowed, need_passage=True,
|
||||
bk_shuffle=bk_shuffle, rupee_bow=rupee_bow_flag)
|
||||
bk_shuffle=bk_shuffle, standard=std_flag, rupee_bow=rupee_bow_flag)
|
||||
choice, portal = assign_portal(candidates, possible_portals, custom, world, player)
|
||||
portal.destination = True
|
||||
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
|
||||
dead_end_choices = info.total - 1 - len(portal_assignment[dungeon])
|
||||
for i in range(0, dead_end_choices):
|
||||
candidates = find_portal_candidates(master_door_list, dungeon, custom, allowed, dead_end_allowed=True,
|
||||
bk_shuffle=bk_shuffle, rupee_bow=rupee_bow_flag)
|
||||
bk_shuffle=bk_shuffle, standard=std_flag, rupee_bow=rupee_bow_flag)
|
||||
possible_portals = outstanding_portals if not info.sole_entrance else [x for x in outstanding_portals if x != info.sole_entrance]
|
||||
choice, portal = assign_portal(candidates, possible_portals, custom, world, player)
|
||||
if choice.deadEnd:
|
||||
@@ -542,7 +562,9 @@ def customizer_portals(master_door_list, world, player):
|
||||
custom_doors = world.customizer.get_doors()[player]
|
||||
if custom_doors and 'lobbies' in custom_doors:
|
||||
for portal, assigned_door in custom_doors['lobbies'].items():
|
||||
door = next(x for x in master_door_list if x.name == assigned_door)
|
||||
door = next((x for x in master_door_list if x.name == assigned_door), None)
|
||||
if door is None:
|
||||
raise Exception(f'{assigned_door} not found. Check for typos')
|
||||
custom_portals[portal] = door
|
||||
assigned_doors.add(door)
|
||||
if custom_doors and 'doors' in custom_doors:
|
||||
@@ -552,9 +574,27 @@ def customizer_portals(master_door_list, world, player):
|
||||
if isinstance(dest, str):
|
||||
door = world.get_door(dest, player)
|
||||
assigned_doors.add(door)
|
||||
else:
|
||||
elif 'dest' in dest:
|
||||
door = world.get_door(dest['dest'], player)
|
||||
assigned_doors.add(door)
|
||||
# restricts connected doors to the customized portals
|
||||
if assigned_doors:
|
||||
pool = create_dungeon_pool(world, player)
|
||||
if pool:
|
||||
pool_map = {}
|
||||
for pool, region_list in pool:
|
||||
sector_pool = convert_to_sectors(region_list, world, player)
|
||||
merge_sectors(sector_pool, world, player)
|
||||
for p in pool:
|
||||
pool_map[p] = sector_pool
|
||||
for portal, assigned_door in custom_portals.items():
|
||||
portal_region = world.get_door(assigned_door, player).entrance.parent_region
|
||||
portal_dungeon = world.get_region(f'{portal} Portal', player).dungeon.name
|
||||
sector_pool = pool_map[portal_dungeon]
|
||||
sector = next((s for s in sector_pool if portal_region in s.regions), None)
|
||||
for door in sector.outstanding_doors:
|
||||
if door.portalAble:
|
||||
door.dungeonLink = portal_dungeon
|
||||
return custom_portals, assigned_doors
|
||||
|
||||
|
||||
@@ -798,15 +838,18 @@ def main_dungeon_pool(dungeon_pool, world, player):
|
||||
hc = world.get_dungeon('Hyrule Castle', player)
|
||||
hc_compass = ItemFactory('Compass (Escape)', player)
|
||||
hc_compass.advancement = world.restrict_boss_items[player] != 'none'
|
||||
hc.dungeon_items.append(hc_compass)
|
||||
if hc.dungeon_items.count(hc_compass) < 1:
|
||||
hc.dungeon_items.append(hc_compass)
|
||||
if 'Agahnims Tower' in pool:
|
||||
at = world.get_dungeon('Agahnims Tower', player)
|
||||
at_compass = ItemFactory('Compass (Agahnims Tower)', player)
|
||||
at_compass.advancement = world.restrict_boss_items[player] != 'none'
|
||||
at.dungeon_items.append(at_compass)
|
||||
if at.dungeon_items.count(at_compass) < 1:
|
||||
at.dungeon_items.append(at_compass)
|
||||
at_map = ItemFactory('Map (Agahnims Tower)', player)
|
||||
at_map.advancement = world.restrict_boss_items[player] != 'none'
|
||||
at.dungeon_items.append(at_map)
|
||||
if at.dungeon_items.count(at_map) < 1:
|
||||
at.dungeon_items.append(at_map)
|
||||
sector_pool = convert_to_sectors(region_list, world, player)
|
||||
merge_sectors(sector_pool, world, player)
|
||||
# todo: which dungeon to create
|
||||
@@ -831,7 +874,8 @@ def main_dungeon_pool(dungeon_pool, world, player):
|
||||
for name in pool:
|
||||
builder = world.dungeon_layouts[player][name]
|
||||
region_set = builder.master_sector.region_set()
|
||||
builder.bk_required = builder.bk_door_proposal or any(x in region_set for x in special_bk_regions)
|
||||
builder.bk_required = (builder.bk_door_proposal or any(x in region_set for x in special_bk_regions)
|
||||
or len(world.key_logic[player][name].bk_chests) > 0)
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if not builder.bk_required or builder.bk_provided:
|
||||
dungeon.big_key = None
|
||||
@@ -1018,7 +1062,8 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
||||
find_standard_origins(builder, recombinant_builders, origin_list)
|
||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
||||
split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player)
|
||||
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
|
||||
# todo: figure out pre-validate, ensure all needed origins are enabled?
|
||||
if len(origin_list) <= 0: # or not pre_validate(builder, origin_list, split_dungeon, world, player):
|
||||
if last_key == builder.name or loops > 1000:
|
||||
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
|
||||
raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}')
|
||||
@@ -1068,14 +1113,14 @@ def determine_entrance_list(world, player):
|
||||
connections = {}
|
||||
for key, portal_list in dungeon_portals.items():
|
||||
entrance_map[key] = []
|
||||
r_names = {}
|
||||
r_names = []
|
||||
if key in dungeon_drops.keys():
|
||||
for drop in dungeon_drops[key]:
|
||||
r_names[drop] = None
|
||||
r_names.append((drop, None))
|
||||
for portal_name in portal_list:
|
||||
portal = world.get_portal(portal_name, player)
|
||||
r_names[portal.door.entrance.parent_region.name] = portal
|
||||
for region_name, portal in r_names.items():
|
||||
r_names.append((portal.door.entrance.parent_region.name, portal))
|
||||
for region_name, portal in r_names:
|
||||
if portal:
|
||||
region = world.get_region(portal.name + ' Portal', player)
|
||||
else:
|
||||
@@ -1236,10 +1281,13 @@ def cross_dungeon(world, player):
|
||||
if world.restrict_boss_items[player] != 'none':
|
||||
hc_compass.advancement = at_compass.advancement = at_map.advancement = True
|
||||
hc = world.get_dungeon('Hyrule Castle', player)
|
||||
hc.dungeon_items.append(hc_compass)
|
||||
if hc.dungeon_items.count(hc_compass) < 1:
|
||||
hc.dungeon_items.append(hc_compass)
|
||||
at = world.get_dungeon('Agahnims Tower', player)
|
||||
at.dungeon_items.append(at_compass)
|
||||
at.dungeon_items.append(at_map)
|
||||
if at.dungeon_items.count(at_compass) < 1:
|
||||
at.dungeon_items.append(at_compass)
|
||||
if at.dungeon_items.count(at_map) < 1:
|
||||
at.dungeon_items.append(at_map)
|
||||
|
||||
setup_custom_door_types(world, player)
|
||||
assign_cross_keys(dungeon_builders, world, player)
|
||||
@@ -1695,7 +1743,6 @@ def setup_custom_door_types(world, player):
|
||||
custom_doors = custom_doors[player]
|
||||
if 'doors' not in custom_doors:
|
||||
return
|
||||
# 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():
|
||||
@@ -1707,8 +1754,8 @@ def setup_custom_door_types(world, player):
|
||||
if d.type == DoorType.SpiralStairs:
|
||||
type_map[door_kind][dungeon.name].append(d)
|
||||
else:
|
||||
# check if the
|
||||
if d.dest.type in [DoorType.Interior, DoorType.Normal]:
|
||||
# check if the dest is paired
|
||||
if d.dest and d.dest.type in [DoorType.Interior, DoorType.Normal] and door_kind != 'Trap Door':
|
||||
type_map[door_kind][dungeon.name].append((d, d.dest))
|
||||
else:
|
||||
type_map[door_kind][dungeon.name].append(d)
|
||||
@@ -1737,12 +1784,12 @@ class DoorTypePool:
|
||||
self.tricky += counts[6]
|
||||
|
||||
def chaos_shuffle(self, counts):
|
||||
weights = [1, 2, 4, 3, 2, 1]
|
||||
weights = [1, 2, 4, 3, 2]
|
||||
return [random.choices(self.get_choices(counts[i]), weights=weights)[0] for i, c in enumerate(counts)]
|
||||
|
||||
@staticmethod
|
||||
def get_choices(number):
|
||||
return [max(number-i, 0) for i in range(-1, 5)]
|
||||
return [max(number+i, 0) for i in range(-1, 4)]
|
||||
|
||||
|
||||
class BuilderDoorCandidates:
|
||||
@@ -1756,88 +1803,103 @@ class BuilderDoorCandidates:
|
||||
def shuffle_door_types(door_type_pools, paths, world, player):
|
||||
start_regions_map = {}
|
||||
for name, builder in world.dungeon_layouts[player].items():
|
||||
start_regions = convert_regions(builder.path_entrances, world, player)
|
||||
start_regions = convert_regions(find_possible_entrances(world, player, builder), world, player)
|
||||
start_regions_map[name] = start_regions
|
||||
builder.candidates = BuilderDoorCandidates()
|
||||
|
||||
all_custom = defaultdict(list)
|
||||
if player in world.custom_door_types:
|
||||
for custom_dict in world.custom_door_types[player].values():
|
||||
for dungeon, doors in custom_dict.items():
|
||||
all_custom[dungeon].extend(doors)
|
||||
|
||||
world.paired_doors[player].clear()
|
||||
used_doors = shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player)
|
||||
used_doors = shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, world, player)
|
||||
# big keys
|
||||
used_doors = shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world, player)
|
||||
used_doors = shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player)
|
||||
# small keys
|
||||
used_doors = shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player)
|
||||
used_doors = shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player)
|
||||
# bombable / dashable
|
||||
used_doors = shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, world, player)
|
||||
used_doors = shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player)
|
||||
# handle paired list
|
||||
|
||||
|
||||
def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player):
|
||||
def shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, 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)
|
||||
if ttl == 0:
|
||||
continue
|
||||
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,
|
||||
drop=True)
|
||||
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]
|
||||
random.shuffle(builder_order)
|
||||
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:
|
||||
if world.trap_door_mode[player] != 'oneway':
|
||||
ttl = 0
|
||||
suggestion_map, trap_map, flex_map = {}, {}, {}
|
||||
remaining = door_type_pool.traps
|
||||
if player in world.custom_door_types and 'Trap Door' in world.custom_door_types[player]:
|
||||
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]
|
||||
if 'Mire Warping Pool' in builder.master_sector.region_set():
|
||||
custom_trap_doors[dungeon].append(world.get_door('Mire Warping Pool ES', player))
|
||||
world.custom_door_types[player]['Trap Door'] = custom_trap_doors
|
||||
find_trappable_candidates(builder, world, player)
|
||||
if all_custom[dungeon]:
|
||||
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon])
|
||||
remaining -= len(custom_trap_doors[dungeon])
|
||||
ttl += len(builder.candidates.trap)
|
||||
if ttl == 0:
|
||||
continue
|
||||
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,
|
||||
drop=True)
|
||||
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
|
||||
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]
|
||||
random.shuffle(builder_order)
|
||||
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
|
||||
else:
|
||||
trap_map = {dungeon: [] for dungeon in pool}
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
if 'Mire Warping Pool' in builder.master_sector.region_set():
|
||||
trap_map[dungeon].append(world.get_door('Mire Warping Pool ES', player))
|
||||
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):
|
||||
def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, 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:
|
||||
if player in world.custom_door_types and 'Big Key Door' in world.custom_door_types[player]:
|
||||
custom_bk_doors = world.custom_door_types[player]['Big Key Door']
|
||||
else:
|
||||
custom_bk_doors = defaultdict(list)
|
||||
@@ -1845,16 +1907,17 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
find_big_key_candidates(builder, start_regions_map[dungeon], used_doors, world, player)
|
||||
if custom_bk_doors[dungeon]:
|
||||
builder.candidates.big = filter_key_door_pool(builder.candidates.big, custom_bk_doors[dungeon])
|
||||
if all_custom[dungeon]:
|
||||
builder.candidates.big = filter_key_door_pool(builder.candidates.big, all_custom[dungeon])
|
||||
remaining -= len(custom_bk_doors[dungeon])
|
||||
ttl += len(builder.candidates.big)
|
||||
if ttl == 0:
|
||||
continue
|
||||
remaining = max(0, remaining)
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
proportion = len(builder.candidates.big)
|
||||
calc = int(round(proportion * door_type_pool.bigs/ttl))
|
||||
calc = int(round(proportion * remaining/ttl))
|
||||
suggested = min(proportion, calc)
|
||||
remaining -= suggested
|
||||
suggestion_map[dungeon] = suggested
|
||||
@@ -1891,48 +1954,56 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
|
||||
return used_doors
|
||||
|
||||
|
||||
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
|
||||
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player):
|
||||
max_computation = 11 # this is around 6 billion worse case factorial don't want to exceed this much
|
||||
for pool, door_type_pool in door_type_pools:
|
||||
ttl = 0
|
||||
suggestion_map, small_map, flex_map = {}, {}, {}
|
||||
remaining = door_type_pool.smalls
|
||||
total_keys = remaining
|
||||
if player in world.custom_door_types:
|
||||
if player in world.custom_door_types and 'Key Door' in world.custom_door_types[player]:
|
||||
custom_key_doors = world.custom_door_types[player]['Key Door']
|
||||
else:
|
||||
custom_key_doors = defaultdict(list)
|
||||
|
||||
total_adjustable = len(pool) > 1
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
if not total_adjustable:
|
||||
builder.total_keys = total_keys
|
||||
find_small_key_door_candidates(builder, start_regions_map[dungeon], used_doors, world, player)
|
||||
if custom_key_doors[dungeon]:
|
||||
builder.candidates.small = filter_key_door_pool(builder.candidates.small, custom_key_doors[dungeon])
|
||||
remaining -= len(custom_key_doors[dungeon])
|
||||
builder.key_doors_num = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
||||
custom_doors = 0
|
||||
if all_custom[dungeon]:
|
||||
builder.candidates.small = filter_key_door_pool(builder.candidates.small, all_custom[dungeon])
|
||||
custom_doors = len(custom_key_doors[dungeon])
|
||||
remaining -= custom_doors
|
||||
builder.key_doors_num = max(0, len(builder.candidates.small) - builder.key_drop_cnt) + custom_doors
|
||||
total_keys -= builder.key_drop_cnt
|
||||
ttl += builder.key_doors_num
|
||||
remaining = max(0, remaining)
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
||||
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
||||
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
||||
limit = min(max_keys, cand_len)
|
||||
limit = min(max_keys, cand_len, max_computation)
|
||||
suggested = min(calculated, limit)
|
||||
combo_size = ncr(len(builder.candidates.small), suggested + builder.key_drop_cnt)
|
||||
while combo_size > 500000 and suggested > 0:
|
||||
suggested -= 1
|
||||
combo_size = ncr(len(builder.candidates.small), suggested + builder.key_drop_cnt)
|
||||
suggestion_map[dungeon] = builder.key_doors_num = suggested + builder.key_drop_cnt
|
||||
remaining -= suggested + builder.key_drop_cnt
|
||||
key_door_num = min(suggested + builder.key_drop_cnt, max_computation)
|
||||
combo_size = ncr(len(builder.candidates.small), key_door_num)
|
||||
suggestion_map[dungeon] = builder.key_doors_num = key_door_num
|
||||
remaining -= key_door_num + builder.key_drop_cnt
|
||||
builder.combo_size = combo_size
|
||||
flex_map[dungeon] = (limit - suggested) if suggested < limit else 0
|
||||
flex_map[dungeon] = (limit - key_door_num) if key_door_num < limit else 0
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
if total_adjustable:
|
||||
builder.total_keys = max(suggestion_map[dungeon], builder.key_drop_cnt)
|
||||
valid_doors, small_number = find_valid_combination(builder, suggestion_map[dungeon],
|
||||
start_regions_map[dungeon], world, player)
|
||||
small_map[dungeon] = valid_doors
|
||||
actual_chest_keys = small_number - builder.key_drop_cnt
|
||||
if actual_chest_keys < suggestion_map[dungeon]:
|
||||
if total_adjustable:
|
||||
builder.total_keys = actual_chest_keys + builder.key_drop_cnt
|
||||
flex_map[dungeon] = 0
|
||||
remaining += suggestion_map[dungeon] - actual_chest_keys
|
||||
suggestion_map[dungeon] = small_number
|
||||
@@ -1943,6 +2014,8 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
||||
builder = queue.popleft()
|
||||
dungeon = builder.name
|
||||
increased = suggestion_map[dungeon] + 1
|
||||
if increased > max_computation:
|
||||
continue
|
||||
builder.key_doors_num = increased
|
||||
valid_doors, small_number = find_valid_combination(builder, increased, start_regions_map[dungeon],
|
||||
world, player)
|
||||
@@ -1951,6 +2024,8 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
||||
remaining -= 1
|
||||
suggestion_map[dungeon] = increased
|
||||
flex_map[dungeon] -= 1
|
||||
if total_adjustable:
|
||||
builder.total_keys = max(increased, builder.key_drop_cnt)
|
||||
if flex_map[dungeon] > 0:
|
||||
builder.combo_size = ncr(len(builder.candidates.small), builder.key_doors_num)
|
||||
queue.append(builder)
|
||||
@@ -1976,14 +2051,14 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
||||
return used_doors
|
||||
|
||||
|
||||
def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, world, player):
|
||||
def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player):
|
||||
for pool, door_type_pool in door_type_pools:
|
||||
ttl = 0
|
||||
suggestion_map, bd_map = {}, {}
|
||||
remaining_bomb = door_type_pool.bombable
|
||||
remaining_dash = door_type_pool.dashable
|
||||
|
||||
if player in world.custom_door_types:
|
||||
if player in world.custom_door_types and 'Bomb Door' in world.custom_door_types[player]:
|
||||
custom_bomb_doors = world.custom_door_types[player]['Bomb Door']
|
||||
custom_dash_doors = world.custom_door_types[player]['Dash Door']
|
||||
else:
|
||||
@@ -1993,11 +2068,9 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, worl
|
||||
for dungeon in pool:
|
||||
builder = world.dungeon_layouts[player][dungeon]
|
||||
find_bd_candidates(builder, start_regions_map[dungeon], used_doors, world, player)
|
||||
if custom_bomb_doors[dungeon]:
|
||||
builder.candidates.bomb_dash = filter_key_door_pool(builder.candidates.bomb_dash, custom_bomb_doors[dungeon])
|
||||
if all_custom[dungeon]:
|
||||
builder.candidates.bomb_dash = filter_key_door_pool(builder.candidates.bomb_dash, all_custom[dungeon])
|
||||
remaining_bomb -= len(custom_bomb_doors[dungeon])
|
||||
if custom_dash_doors[dungeon]:
|
||||
builder.candidates.bomb_dash = filter_key_door_pool(builder.candidates.bomb_dash, custom_dash_doors[dungeon])
|
||||
remaining_dash -= len(custom_dash_doors[dungeon])
|
||||
ttl += len(builder.candidates.bomb_dash)
|
||||
if ttl == 0:
|
||||
@@ -2094,7 +2167,7 @@ def find_trappable_candidates(builder, world, player):
|
||||
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:
|
||||
if 0 <= d.doorListPos < 3 and not d.entranceFlag and d.name != 'Skull Small Hall WS':
|
||||
room = world.get_room(d.roomIndex, player)
|
||||
kind = room.kind(d)
|
||||
if d.type == DoorType.Interior:
|
||||
@@ -2115,20 +2188,21 @@ def find_trappable_candidates(builder, world, player):
|
||||
for ext in world.get_region(r, player).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:
|
||||
if d.blocked and d.trapFlag != 0 and exclude_boss_traps(d):
|
||||
builder.candidates.trap.append(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:
|
||||
if player in world.custom_door_types and 'Trap Door' in world.custom_door_types[player]:
|
||||
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)
|
||||
trap_doors_needed = max(0, trap_doors_needed)
|
||||
if len(trap_door_pool) < trap_doors_needed:
|
||||
if not drop:
|
||||
return None, 0
|
||||
@@ -2139,7 +2213,7 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world,
|
||||
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)
|
||||
start_regions, event_starts = 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):
|
||||
@@ -2160,14 +2234,32 @@ def find_valid_trap_combination(builder, suggested, start_regions, paths, world,
|
||||
# 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 = {}
|
||||
excluded = {} # todo: drop lobbies, might be better to white list instead (two entrances per region)
|
||||
event_doors = {}
|
||||
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
|
||||
# make sure that a drop is not accessible for this "destination"
|
||||
drop_region = next((x.parent_region for x in region.entrances
|
||||
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
|
||||
or x.parent_region.name == 'Sewer Drop'), None)
|
||||
if not drop_region:
|
||||
excluded[region] = None
|
||||
if portal and not portal.destination:
|
||||
portal_entrance_region = portal.door.entrance.parent_region.name
|
||||
if portal_entrance_region not in builder.path_entrances:
|
||||
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()]
|
||||
if portal is None:
|
||||
entrance = next((x for x in region.entrances
|
||||
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
|
||||
or x.parent_region.name == 'Sewer Drop'), None)
|
||||
event_doors[entrance] = None
|
||||
else:
|
||||
event_doors[portal.find_portal_entrance()] = None
|
||||
|
||||
return [x for x in start_regions if x not in excluded.keys()], event_doors
|
||||
|
||||
|
||||
def validate_trap_layout(proposal, builder, start_regions, paths, world, player):
|
||||
@@ -2193,6 +2285,7 @@ def find_bk_special_location(builder, world, player):
|
||||
return loc
|
||||
return None
|
||||
|
||||
|
||||
def check_required_paths_with_traps(paths, proposal, dungeon_name, start_regions, world, player):
|
||||
cached_initial_state = None
|
||||
if len(paths[dungeon_name]) > 0:
|
||||
@@ -2238,7 +2331,7 @@ 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))
|
||||
queue = deque(find_current_trap_doors(builder, world, player))
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.type is DoorType.Interior and d not in traps:
|
||||
@@ -2257,16 +2350,21 @@ def reassign_trap_doors(trap_map, world, player):
|
||||
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('Trap Door: %s', d.name)
|
||||
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player)
|
||||
logger.debug(f'Trap Door: {d.name} ({d.dungeon_name()})')
|
||||
|
||||
|
||||
def find_current_trap_doors(builder):
|
||||
def exclude_boss_traps(d):
|
||||
return ' Boss ' not in d.name and ' Agahnim ' not in d.name and d.name not in ['Skull Spike Corner SW']
|
||||
|
||||
|
||||
def find_current_trap_doors(builder, world, player):
|
||||
checker = exclude_boss_traps if world.trap_door_mode[player] in ['vanilla', 'optional'] else (lambda x: True)
|
||||
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:
|
||||
if d and d.blocked and d.trapFlag != 0 and checker(d):
|
||||
current_doors.append(d)
|
||||
return current_doors
|
||||
|
||||
@@ -2276,7 +2374,9 @@ def change_door_to_trap(d, world, player):
|
||||
if d.type is DoorType.Interior:
|
||||
kind = room.kind(d)
|
||||
new_kind = None
|
||||
if kind == DoorKind.TrapTriggerable and d.direction in [Direction.South, Direction.East]:
|
||||
if kind == DoorKind.Trap:
|
||||
new_kind = DoorKind.Trap
|
||||
elif kind == DoorKind.TrapTriggerable and d.direction in [Direction.South, Direction.East]:
|
||||
new_kind = DoorKind.Trap
|
||||
elif kind == DoorKind.Trap2 and d.direction in [Direction.North, Direction.West]:
|
||||
new_kind = DoorKind.Trap
|
||||
@@ -2356,21 +2456,16 @@ def find_big_key_door_candidates(region, checked, used, world, player):
|
||||
if valid and d.dest not in candidates: # interior doors are not separable yet
|
||||
candidates.append(d.dest)
|
||||
elif d.type == DoorType.Normal:
|
||||
if decoupled:
|
||||
valid = kind in okay_normals
|
||||
else:
|
||||
valid = kind in okay_normals
|
||||
if valid and not decoupled:
|
||||
d2 = d.dest
|
||||
if d2 not in candidates and d2 not in used:
|
||||
if d2.type == DoorType.Normal:
|
||||
room_b = world.get_room(d2.roomIndex, player)
|
||||
pos_b, kind_b = room_b.doorList[d2.doorListPos]
|
||||
valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||
else:
|
||||
valid = kind in okay_normals
|
||||
valid &= kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||
if valid and 0 <= d2.doorListPos < 4:
|
||||
candidates.append(d2)
|
||||
else:
|
||||
valid = True
|
||||
if valid and d not in candidates:
|
||||
candidates.append(d)
|
||||
connected = ext.connected_region
|
||||
@@ -2384,13 +2479,14 @@ def find_big_key_door_candidates(region, checked, used, world, player):
|
||||
def find_valid_bk_combination(builder, suggested, start_regions, world, player, drop=True):
|
||||
bk_door_pool = builder.candidates.big
|
||||
bk_doors_needed = suggested
|
||||
if player in world.custom_door_types:
|
||||
if player in world.custom_door_types and 'Big Key Door' in world.custom_door_types[player]:
|
||||
custom_bk_doors = world.custom_door_types[player]['Big Key Door'][builder.name]
|
||||
else:
|
||||
custom_bk_doors = []
|
||||
if custom_bk_doors:
|
||||
bk_door_pool = filter_key_door_pool(bk_door_pool, custom_bk_doors)
|
||||
bk_doors_needed -= len(custom_bk_doors)
|
||||
bk_doors_needed = max(0, bk_doors_needed)
|
||||
if len(bk_door_pool) < bk_doors_needed:
|
||||
if not drop:
|
||||
return None, 0
|
||||
@@ -2401,7 +2497,7 @@ def find_valid_bk_combination(builder, suggested, start_regions, world, player,
|
||||
proposal = kth_combination(sample_list[itr], bk_door_pool, bk_doors_needed)
|
||||
proposal.extend(custom_bk_doors)
|
||||
|
||||
start_regions = filter_start_regions(builder, start_regions, world, player)
|
||||
start_regions, event_starts = filter_start_regions(builder, start_regions, world, player)
|
||||
while not validate_bk_layout(proposal, builder, start_regions, world, player):
|
||||
itr += 1
|
||||
if itr >= len(sample_list):
|
||||
@@ -2452,17 +2548,20 @@ def reassign_big_key_doors(bk_map, world, player):
|
||||
if d1.type is DoorType.Interior:
|
||||
change_door_to_big_key(d1, world, player)
|
||||
d2.bigKey = True # ensure flag is set
|
||||
if d2.smallKey:
|
||||
d2.smallKey = False
|
||||
else:
|
||||
world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
|
||||
change_door_to_big_key(d1, world, player)
|
||||
change_door_to_big_key(d2, world, player)
|
||||
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Big Key Door', player)
|
||||
logger.debug(f'Big Key Door: {d1.name} <-> {d2.name}')
|
||||
world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', 'Big Key Door', player)
|
||||
logger.debug(f'Big Key Door: {d1.name} <-> {d2.name} ({d1.dungeon_name()})')
|
||||
else:
|
||||
d = obj
|
||||
if d.type is DoorType.Interior:
|
||||
change_door_to_big_key(d, world, player)
|
||||
d.dest.bigKey = True # ensure flag is set
|
||||
if world.door_type_mode[player] != 'original':
|
||||
d.dest.bigKey = True # ensure flag is set when bk doors are double sided
|
||||
elif d.type is DoorType.SpiralStairs:
|
||||
pass # we don't have spiral stairs candidates yet that aren't already key doors
|
||||
elif d.type is DoorType.Normal:
|
||||
@@ -2473,12 +2572,14 @@ def reassign_big_key_doors(bk_map, world, player):
|
||||
if stateful_door(d.dest, dest_room.kind(d.dest)):
|
||||
change_door_to_big_key(d.dest, world, player)
|
||||
add_pair(d, d.dest, world, player)
|
||||
world.spoiler.set_door_type(d.name, 'Big Key Door', player)
|
||||
logger.debug(f'Big Key Door: {d.name}')
|
||||
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Big Key Door', player)
|
||||
logger.debug(f'Big Key Door: {d.name} ({d.dungeon_name()})')
|
||||
|
||||
|
||||
def change_door_to_big_key(d, world, player):
|
||||
d.bigKey = True
|
||||
if d.smallKey:
|
||||
d.smallKey = False
|
||||
room = world.get_room(d.roomIndex, player)
|
||||
if room.doorList[d.doorListPos][1] != DoorKind.BigKey:
|
||||
verify_door_list_pos(d, room, world, player)
|
||||
@@ -2522,14 +2623,14 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k
|
||||
logger = logging.getLogger('')
|
||||
key_door_pool = list(builder.candidates.small)
|
||||
key_doors_needed = target
|
||||
if player in world.custom_door_types:
|
||||
if player in world.custom_door_types and 'Key Door' in world.custom_door_types[player]:
|
||||
custom_key_doors = world.custom_door_types[player]['Key Door'][builder.name]
|
||||
else:
|
||||
custom_key_doors = []
|
||||
if custom_key_doors: # could validate that each custom item is in the candidates
|
||||
key_door_pool = filter_key_door_pool(key_door_pool, custom_key_doors)
|
||||
key_doors_needed -= len(custom_key_doors)
|
||||
|
||||
key_doors_needed = max(0, key_doors_needed)
|
||||
# find valid combination of candidates
|
||||
if len(key_door_pool) < key_doors_needed:
|
||||
if not drop_keys:
|
||||
@@ -2544,9 +2645,10 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k
|
||||
sample_list = build_sample_list(combinations)
|
||||
proposal = kth_combination(sample_list[itr], key_door_pool, key_doors_needed)
|
||||
proposal.extend(custom_key_doors)
|
||||
start_regions = filter_start_regions(builder, start_regions, world, player)
|
||||
builder.key_doors_num = len(proposal)
|
||||
start_regions, event_starts = filter_start_regions(builder, start_regions, world, player)
|
||||
|
||||
key_layout = build_key_layout(builder, start_regions, proposal, world, player)
|
||||
key_layout = build_key_layout(builder, start_regions, proposal, event_starts, world, player)
|
||||
determine_prize_lock(key_layout, world, player)
|
||||
while not validate_key_layout(key_layout, world, player):
|
||||
itr += 1
|
||||
@@ -2572,12 +2674,11 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k
|
||||
# make changes
|
||||
if player not in world.key_logic.keys():
|
||||
world.key_logic[player] = {}
|
||||
builder.total_keys = builder.key_doors_num
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
builder.key_door_proposal = proposal
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
world.key_layout[player][builder.name] = key_layout
|
||||
return builder.key_door_proposal, key_doors_needed
|
||||
return builder.key_door_proposal, key_doors_needed + len(custom_key_doors)
|
||||
|
||||
|
||||
def find_bd_candidates(builder, start_regions, used, world, player):
|
||||
@@ -2618,25 +2719,21 @@ def find_bd_door_candidates(region, checked, used, world, player):
|
||||
room = world.get_room(d.roomIndex, player)
|
||||
position, kind = room.doorList[d.doorListPos]
|
||||
if d.type == DoorType.Interior:
|
||||
valid = kind in okay_interiors
|
||||
if valid and d.dest not in candidates: # interior doors are not separable yet
|
||||
# interior doors are not separable yet
|
||||
valid = kind in okay_interiors and d.dest not in used
|
||||
if valid and d.dest not in candidates:
|
||||
candidates.append(d.dest)
|
||||
elif d.type == DoorType.Normal:
|
||||
if decoupled:
|
||||
valid = kind in okay_normals
|
||||
else:
|
||||
valid = kind in okay_normals
|
||||
if valid and not decoupled:
|
||||
d2 = d.dest
|
||||
if d2 not in candidates and d2 not in used:
|
||||
if d2.type == DoorType.Normal:
|
||||
room_b = world.get_room(d2.roomIndex, player)
|
||||
pos_b, kind_b = room_b.doorList[d2.doorListPos]
|
||||
valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||
else:
|
||||
valid = kind in okay_normals
|
||||
valid &= kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||
if valid and 0 <= d2.doorListPos < 4:
|
||||
candidates.append(d2)
|
||||
else:
|
||||
valid = True
|
||||
if valid and d not in candidates:
|
||||
candidates.append(d)
|
||||
connected = ext.connected_region
|
||||
@@ -2654,7 +2751,7 @@ def find_valid_bd_combination(builder, suggested, world, player):
|
||||
bd_door_pool = builder.candidates.bomb_dash
|
||||
bomb_doors_needed, dash_doors_needed = suggested
|
||||
ttl_needed = bomb_doors_needed + dash_doors_needed
|
||||
if player in world.custom_door_types:
|
||||
if player in world.custom_door_types and 'Bomb Door' in world.custom_door_types[player]:
|
||||
custom_bomb_doors = world.custom_door_types[player]['Bomb Door'][builder.name]
|
||||
custom_dash_doors = world.custom_door_types[player]['Dash Door'][builder.name]
|
||||
else:
|
||||
@@ -2667,8 +2764,15 @@ def find_valid_bd_combination(builder, suggested, world, player):
|
||||
bd_door_pool = filter_key_door_pool(bd_door_pool, custom_dash_doors)
|
||||
dash_doors_needed -= len(custom_dash_doors)
|
||||
while len(bd_door_pool) < bomb_doors_needed + dash_doors_needed:
|
||||
bomb_doors_needed = round(len(bd_door_pool) * bomb_doors_needed/ttl_needed)
|
||||
dash_doors_needed = round(len(bd_door_pool) * dash_doors_needed/ttl_needed)
|
||||
test = random.choice([True, False])
|
||||
if test:
|
||||
bomb_doors_needed -= 1
|
||||
if bomb_doors_needed < 0:
|
||||
bomb_doors_needed = 0
|
||||
else:
|
||||
dash_doors_needed -= 1
|
||||
if dash_doors_needed < 0:
|
||||
dash_doors_needed = 0
|
||||
bomb_proposal = random.sample(bd_door_pool, k=bomb_doors_needed)
|
||||
bomb_proposal.extend(custom_bomb_doors)
|
||||
dash_pool = [x for x in bd_door_pool if x not in bomb_proposal]
|
||||
@@ -2696,8 +2800,8 @@ def reassign_bd_doors(bd_map, world, player):
|
||||
elif d.type is DoorType.Normal and not_in_proposal(d):
|
||||
if not d.entranceFlag:
|
||||
world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal)
|
||||
do_bombable_dashable(flat_bomb_proposal, DoorKind.Bombable, world, player)
|
||||
do_bombable_dashable(flat_dash_proposal, DoorKind.Dashable, world, player)
|
||||
do_bombable_dashable(pair[0], DoorKind.Bombable, world, player)
|
||||
do_bombable_dashable(pair[1], DoorKind.Dashable, world, player)
|
||||
|
||||
|
||||
def do_bombable_dashable(proposal, kind, world, player):
|
||||
@@ -2723,7 +2827,7 @@ def do_bombable_dashable(proposal, kind, world, player):
|
||||
change_door_to_kind(d1, kind, world, player)
|
||||
change_door_to_kind(d2, kind, world, player)
|
||||
spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, spoiler_type, player)
|
||||
world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', spoiler_type, player)
|
||||
else:
|
||||
d = obj
|
||||
if d.type is DoorType.Interior:
|
||||
@@ -2737,7 +2841,7 @@ def do_bombable_dashable(proposal, kind, world, player):
|
||||
change_door_to_kind(d.dest, kind, world, player)
|
||||
add_pair(d, d.dest, world, player)
|
||||
spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(d.name, spoiler_type, player)
|
||||
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', spoiler_type, player)
|
||||
|
||||
|
||||
def find_current_bd_doors(builder, world):
|
||||
@@ -2852,27 +2956,23 @@ def find_key_door_candidates(region, checked, used, world, player):
|
||||
room = world.get_room(d.roomIndex, player)
|
||||
position, kind = room.doorList[d.doorListPos]
|
||||
if d.type == DoorType.Interior:
|
||||
valid = kind in okay_interiors
|
||||
if valid and d.dest not in candidates: # interior doors are not separable yet
|
||||
valid = kind in okay_interiors and d.dest not in used
|
||||
# interior doors are not separable yet
|
||||
if valid and d.dest not in candidates:
|
||||
candidates.append(d.dest)
|
||||
elif d.type == DoorType.SpiralStairs:
|
||||
valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow]
|
||||
elif d.type == DoorType.Normal:
|
||||
if decoupled:
|
||||
valid = kind in okay_normals
|
||||
else:
|
||||
valid = kind in okay_normals
|
||||
if valid and not decoupled:
|
||||
d2 = d.dest
|
||||
if d2 not in candidates and d2 not in used:
|
||||
if d2.type == DoorType.Normal:
|
||||
room_b = world.get_room(d2.roomIndex, player)
|
||||
pos_b, kind_b = room_b.doorList[d2.doorListPos]
|
||||
valid = kind in okay_normals and kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||
else:
|
||||
valid = kind in okay_normals
|
||||
valid &= kind_b in okay_normals and valid_key_door_pair(d, d2)
|
||||
if valid and 0 <= d2.doorListPos < 4:
|
||||
candidates.append(d2)
|
||||
else:
|
||||
valid = True
|
||||
if valid and d not in candidates:
|
||||
candidates.append(d)
|
||||
connected = ext.connected_region
|
||||
@@ -2944,8 +3044,8 @@ def reassign_key_doors(small_map, world, player):
|
||||
world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
|
||||
change_door_to_small_key(d1, world, player)
|
||||
change_door_to_small_key(d2, world, player)
|
||||
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Key Door', player)
|
||||
logger.debug('Key Door: %s', d1.name+' <-> '+d2.name)
|
||||
world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', 'Key Door', player)
|
||||
logger.debug(f'Key Door: {d1.name} <-> {d2.name} ({d1.dungeon_name()})')
|
||||
else:
|
||||
d = obj
|
||||
if d.type is DoorType.Interior:
|
||||
@@ -2961,8 +3061,8 @@ def reassign_key_doors(small_map, world, player):
|
||||
if stateful_door(d.dest, dest_room.kind(d.dest)):
|
||||
change_door_to_small_key(d.dest, world, player)
|
||||
add_pair(d, d.dest, world, player)
|
||||
world.spoiler.set_door_type(d.name, 'Key Door', player)
|
||||
logger.debug('Key Door: %s', d.name)
|
||||
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Key Door', player)
|
||||
logger.debug(f'Key Door: {d.name} ({d.dungeon_name()})')
|
||||
|
||||
|
||||
def change_door_to_small_key(d, world, player):
|
||||
@@ -3152,7 +3252,7 @@ def change_pair_type(door, new_type, world, player):
|
||||
room_b.change(door.dest.doorListPos, new_type)
|
||||
add_pair(door, door.dest, world, player)
|
||||
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(door.name + ' <-> ' + door.dest.name, spoiler_type, player)
|
||||
world.spoiler.set_door_type(f'{door.name} <-> {door.dest.name} ({door.dungeon_name()})', spoiler_type, player)
|
||||
|
||||
|
||||
def remove_pair_type_if_present(door, world, player):
|
||||
@@ -3245,6 +3345,14 @@ def find_accessible_entrances(world, player, builder):
|
||||
return visited_entrances
|
||||
|
||||
|
||||
def find_possible_entrances(world, player, builder):
|
||||
entrances = [region.name for region in
|
||||
(portal.door.entrance.parent_region for portal in world.dungeon_portals[player])
|
||||
if region.dungeon.name == builder.name]
|
||||
entrances.extend(drop_entrances[builder.name])
|
||||
return entrances
|
||||
|
||||
|
||||
def valid_inaccessible_region(r):
|
||||
return r.type is not RegionType.Cave or (len(r.exits) > 0 and r.name not in ['Links House', 'Chris Houlihan Room'])
|
||||
|
||||
@@ -4216,7 +4324,7 @@ default_door_connections = [
|
||||
('Eastern Map Valley SW', 'Eastern Dark Square NW'),
|
||||
('Eastern Attic Start WS', 'Eastern False Switches ES'),
|
||||
('Eastern Cannonball Hell WS', 'Eastern Single Eyegore ES'),
|
||||
('Desert Compass NW', 'Desert Cannonball S'),
|
||||
('Desert Compass NE', 'Desert Cannonball S'),
|
||||
('Desert Beamos Hall NE', 'Desert Tiles 2 SE'),
|
||||
('PoD Middle Cage N', 'PoD Pit Room S'),
|
||||
('PoD Pit Room NW', 'PoD Arena Main SW'),
|
||||
@@ -4503,7 +4611,7 @@ door_type_counts = {
|
||||
'Swamp Palace': (6, 0, 0, 2, 0, 0, 0),
|
||||
'Palace of Darkness': (6, 1, 1, 3, 2, 0, 0),
|
||||
'Misery Mire': (6, 3, 5, 2, 0, 0, 0),
|
||||
'Skull Woods': (5, 0, 2, 2, 0, 1, 0),
|
||||
'Skull Woods': (5, 0, 1, 2, 0, 1, 0),
|
||||
'Ice Palace': (6, 1, 3, 0, 0, 0, 0),
|
||||
'Tower of Hera': (1, 1, 0, 0, 0, 0, 0),
|
||||
'Thieves Town': (3, 1, 2, 1, 1, 0, 0),
|
||||
|
||||
Reference in New Issue
Block a user