More trap doors (mostly interior)
DungeonGen refinements --More Big Key door considerations --Backtracks earlier when hook candidates comes up empty Minor work on key shuffling - lots of bad rules still Playthrough gen doesn't flood swamp keys now
This commit is contained in:
@@ -12,7 +12,7 @@ from Dungeons import dungeon_regions, region_starts, split_region_starts, dungeo
|
||||
from Dungeons import drop_entrances
|
||||
from RoomData import DoorKind, PairedDoor
|
||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon
|
||||
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic
|
||||
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, validate_key_layout_ex
|
||||
|
||||
|
||||
def link_doors(world, player):
|
||||
|
||||
10
Doors.py
10
Doors.py
@@ -299,7 +299,7 @@ def create_doors(world, player):
|
||||
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 NW', Intr).dir(No, 0x30, Left, High).pos(1),
|
||||
create_door(player, 'Tower Altar SW', Intr).dir(So, 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 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),
|
||||
|
||||
@@ -481,7 +481,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Swamp I S', Intr).dir(So, 0x16, Mid, High).pos(0),
|
||||
create_door(player, 'Swamp T SW', Intr).dir(So, 0x16, Left, High).small_key().pos(1),
|
||||
create_door(player, 'Swamp T NW', Nrml).dir(No, 0x16, Left, High).pos(3),
|
||||
create_door(player, 'Swamp Boss SW', Nrml).dir(So, 0x06, Left, High).trap(0x4).pos(0),
|
||||
create_door(player, 'Swamp Boss SW', Nrml).dir(So, 0x06, Left, High).no_exit().trap(0x4).pos(0),
|
||||
|
||||
create_door(player, 'Skull 1 Lobby WS', Nrml).dir(We, 0x58, Bot, High).small_key().pos(1),
|
||||
create_door(player, 'Skull 1 Lobby ES', Intr).dir(Ea, 0x58, Bot, High).pos(5),
|
||||
@@ -643,7 +643,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 Down Ladder', Lddr),
|
||||
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).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 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 Down Stairs', Sprl).dir(Dn, 0x5f, 3, HTH).ss(Z, 0x11, 0x48, True, True),
|
||||
@@ -980,7 +980,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 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 Four Torches ES', Intr).dir(Ea, 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 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 Four Torches Up Stairs', Sprl).dir(Up, 0x1c, 0, HTH).ss(Z, 0x1b, 0x2c, True, True),
|
||||
@@ -1012,7 +1012,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 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 Quad Pot SW', Intr).dir(So, 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 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 SW', Intr).dir(So, 0xa5, Left, High).pos(2),
|
||||
|
||||
@@ -52,7 +52,7 @@ def generate_dungeon(available_sectors, entrance_region_names, split_dungeon, wo
|
||||
if itr > 5000:
|
||||
raise Exception('Generation taking too long. Ref %s' % entrance_region_names[0])
|
||||
if depth not in dungeon_cache.keys():
|
||||
dungeon, hangers, hooks = gen_dungeon_info(available_sectors, entrance_regions, proposed_map, doors_to_connect, world, player)
|
||||
dungeon, hangers, hooks = gen_dungeon_info(available_sectors, entrance_regions, proposed_map, doors_to_connect, bk_needed, world, player)
|
||||
dungeon_cache[depth] = dungeon, hangers, hooks
|
||||
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed)
|
||||
else:
|
||||
@@ -109,10 +109,10 @@ def determine_if_bk_needed(sector, split_dungeon, world, player):
|
||||
return False
|
||||
|
||||
|
||||
def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, world, player):
|
||||
def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, bk_needed, world, player):
|
||||
# step 1 create dungeon: Dict<DoorName|Origin, GraphPiece>
|
||||
dungeon = {}
|
||||
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, world, player)
|
||||
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, bk_needed, world, player)
|
||||
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
|
||||
doors_to_connect = set()
|
||||
hanger_set = set()
|
||||
@@ -123,7 +123,7 @@ def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_do
|
||||
if not door.stonewall and door not in proposed_map.keys():
|
||||
hanger_set.add(door)
|
||||
parent = parent_region(door, world, player).parent_region
|
||||
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, world, player)
|
||||
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, False, world, player)
|
||||
o_state_cache[door.name] = o_state
|
||||
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
|
||||
dungeon[door.name] = piece
|
||||
@@ -183,7 +183,7 @@ def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_do
|
||||
def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player):
|
||||
parent = parent_region(door, world, player).parent_region
|
||||
blue_start = ExplorationState(CrystalBarrier.Blue)
|
||||
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, world, player)
|
||||
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, False, world, player)
|
||||
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
|
||||
|
||||
|
||||
@@ -234,6 +234,8 @@ def make_a_choice(dungeon, hangers, avail_hooks, prev_choices):
|
||||
if len(hook_candidates) > 0:
|
||||
hook_candidates.sort(key=lambda x: x.name) # sort for deterministic seeds
|
||||
hook = random.choice(tuple(hook_candidates))
|
||||
else:
|
||||
return None, None
|
||||
|
||||
return next_hanger, hook
|
||||
|
||||
@@ -252,7 +254,7 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
|
||||
return False
|
||||
# origin has no more hooks, but not all doors have been proposed
|
||||
possible_bks = len(dungeon['Origin'].possible_bk_locations)
|
||||
true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0]
|
||||
true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0 or not bk_needed]
|
||||
if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
|
||||
return False
|
||||
for key in hangers.keys():
|
||||
@@ -553,7 +555,7 @@ class ExplorationState(object):
|
||||
return ret
|
||||
|
||||
def next_avail_door(self):
|
||||
self.avail_doors.sort(key=lambda x: 0 if x.flag else 1)
|
||||
self.avail_doors.sort(key=lambda x: 0 if x.flag else 1 if x.door.bigKey else 2)
|
||||
exp_door = self.avail_doors.pop()
|
||||
self.crystal = exp_door.crystal
|
||||
return exp_door
|
||||
@@ -784,13 +786,16 @@ def extend_reachable_state(search_regions, state, world, player):
|
||||
return local_state
|
||||
|
||||
|
||||
def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, world, player):
|
||||
def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, isOrigin, world, player):
|
||||
local_state = state.copy()
|
||||
for region in search_regions:
|
||||
local_state.visit_region(region)
|
||||
local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, False, world, player)
|
||||
while len(local_state.avail_doors) > 0:
|
||||
explorable_door = local_state.next_avail_door()
|
||||
if explorable_door.door.bigKey:
|
||||
if isOrigin and local_state.count_locations_exclude_specials() == 0:
|
||||
continue # we can't open this door
|
||||
if explorable_door.door in proposed_map:
|
||||
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
|
||||
else:
|
||||
|
||||
@@ -138,7 +138,8 @@ def analyze_dungeon(key_layout, world, player):
|
||||
key_sphere, key_counter = queue.popleft()
|
||||
chest_keys = available_chest_small_keys(key_counter, False, world)
|
||||
# chest_keys_bk = available_chest_small_keys(key_counter, True, world)
|
||||
available = chest_keys + len(key_counter.key_only_locations) - key_counter.used_keys
|
||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||
available = raw_avail - key_counter.used_keys
|
||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||
if not key_counter.big_key_opened:
|
||||
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls:
|
||||
@@ -167,10 +168,10 @@ def analyze_dungeon(key_layout, world, player):
|
||||
true_min = minimal_keys
|
||||
last_small_child = len([x for x in childqueue if not x[0].bigKey]) == 0
|
||||
force_min = not minimal_satisfied and last_small_child
|
||||
rule = create_rule(expanded_counter, key_layout, true_min, force_min, world)
|
||||
rule = create_rule(expanded_counter, key_layout, true_min, force_min, raw_avail, world)
|
||||
minimal_satisfied = minimal_satisfied or rule.small_key_num <= minimal_keys
|
||||
check_for_self_lock_key(rule, next_sphere, key_layout, world)
|
||||
bk_restricted_rules(rule, next_sphere, key_counter, key_layout, true_min, force_min, world)
|
||||
bk_restricted_rules(rule, next_sphere, key_counter, key_layout, true_min, force_min, raw_avail, world)
|
||||
key_logic.door_rules[child.name] = rule
|
||||
doors_completed.add(next_sphere.access_door)
|
||||
next_counter = increment_key_counter(child, next_sphere, key_counter, key_layout.flat_prop)
|
||||
@@ -298,10 +299,12 @@ def expand_counter_to_last_door(door, key_counter, key_layout, ignored_doors):
|
||||
return new_counter
|
||||
|
||||
|
||||
def create_rule(key_counter, key_layout, minimal_keys, force_min, world):
|
||||
def create_rule(key_counter, key_layout, minimal_keys, force_min, prev_avail, world):
|
||||
chest_keys = available_chest_small_keys(key_counter, key_counter.big_key_opened, world)
|
||||
available = chest_keys + len(key_counter.key_only_locations) - key_counter.used_keys
|
||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||
available = raw_avail - key_counter.used_keys
|
||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||
# key_gain = max(raw_avail - prev_avail, 0)
|
||||
required_keys = min(available, possible_smalls) + key_counter.used_keys
|
||||
if not force_min or required_keys <= minimal_keys:
|
||||
return DoorRules(required_keys)
|
||||
@@ -347,11 +350,11 @@ def available_chest_small_keys(key_counter, bk, world):
|
||||
return key_counter.max_chests
|
||||
|
||||
|
||||
def bk_restricted_rules(rule, sphere, key_counter, key_layout, minimal_keys, force_min, world):
|
||||
def bk_restricted_rules(rule, sphere, key_counter, key_layout, minimal_keys, force_min, prev_avail, world):
|
||||
if sphere.bk_locked:
|
||||
return
|
||||
expanded_counter = expand_counter_no_big_doors(sphere.access_door, key_counter, key_layout, set())
|
||||
bk_number = create_rule(expanded_counter, key_layout, minimal_keys, force_min, world).small_key_num
|
||||
bk_number = create_rule(expanded_counter, key_layout, minimal_keys, force_min, prev_avail, world).small_key_num
|
||||
if bk_number == rule.small_key_num:
|
||||
return
|
||||
post_counter = KeyCounter(key_layout.max_chests)
|
||||
@@ -491,6 +494,15 @@ def unique_doors(doors):
|
||||
return unique_d_set
|
||||
|
||||
|
||||
# does not allow dest doors
|
||||
def count_unique_doors(doors):
|
||||
unique_d_set = set()
|
||||
for d in doors:
|
||||
if d not in unique_d_set and d.dest not in unique_d_set:
|
||||
unique_d_set.add(d)
|
||||
return len(unique_d_set)
|
||||
|
||||
|
||||
# doesn't count dest doors
|
||||
def count_unique_small_doors(key_counter, proposal):
|
||||
cnt = 0
|
||||
@@ -545,6 +557,71 @@ def flatten_pair_list(paired_list):
|
||||
|
||||
|
||||
# Soft lock stuff
|
||||
def validate_key_layout_ex(key_layout, world, player):
|
||||
key_layout = KeyLayout(key_layout.sector, key_layout.start_regions, key_layout.proposal)
|
||||
key_layout.flat_prop = flatten_pair_list(key_layout.proposal)
|
||||
key_layout.max_chests = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
counters = create_key_counters(key_layout, world, player)
|
||||
pass
|
||||
|
||||
|
||||
def create_key_counters(key_layout, world, player):
|
||||
key_counters = {}
|
||||
flat_proposal = key_layout.flat_prop
|
||||
state = ExplorationState()
|
||||
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
|
||||
for region in key_layout.start_regions:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
expand_key_state(state, flat_proposal, world, player)
|
||||
code = state_id(state, key_layout.flat_prop)
|
||||
key_counters[code] = create_key_counter_x(state, key_layout.max_chests, world, player)
|
||||
queue = collections.deque([(key_counters[code], state)])
|
||||
while len(queue) > 0:
|
||||
next_key_sphere, parent_state = queue.popleft()
|
||||
for door in next_key_sphere.child_doors:
|
||||
child_state = parent_state.copy()
|
||||
# open the door
|
||||
open_a_door(door, child_state, flat_proposal)
|
||||
expand_key_state(child_state, flat_proposal, world, player)
|
||||
code = state_id(child_state, key_layout.flat_prop)
|
||||
if code not in key_counters.keys():
|
||||
child_kr = create_key_counter_x(child_state, key_layout.max_chests, world, player)
|
||||
key_counters[code] = child_kr
|
||||
queue.append((child_kr, child_state))
|
||||
return key_counters
|
||||
|
||||
|
||||
def create_key_counter_x(state, max_chests, world, player):
|
||||
key_sphere = KeyCounter(max_chests)
|
||||
key_sphere.child_doors.update(unique_doors(state.small_doors+state.big_doors))
|
||||
for loc in state.found_locations:
|
||||
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
|
||||
key_sphere.important_location = True
|
||||
# todo: zelda's cell is special in standard, and probably crossed too
|
||||
elif loc.name in ['Attic Cracked Floor', 'Suspicious Maiden']:
|
||||
key_sphere.important_location = True
|
||||
elif loc.event and 'Small Key' in loc.item.name:
|
||||
key_sphere.key_only_locations.add(loc)
|
||||
elif loc.name not in dungeon_events:
|
||||
key_sphere.free_locations.add(loc)
|
||||
key_sphere.open_doors.update(state.opened_doors)
|
||||
key_sphere.used_keys = count_unique_doors(state.opened_doors)
|
||||
if state.big_key_special:
|
||||
key_sphere.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player))
|
||||
else:
|
||||
key_sphere.big_key_opened = state.big_key_opened
|
||||
return key_sphere
|
||||
|
||||
|
||||
def state_id(state, flat_proposal):
|
||||
s_id = '1' if state.big_key_opened else '0'
|
||||
for d in flat_proposal:
|
||||
s_id += '1' if d in state.opened_doors else '0'
|
||||
return s_id
|
||||
|
||||
|
||||
class SoftLockException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
2
Main.py
2
Main.py
@@ -381,7 +381,7 @@ def create_playthrough(world):
|
||||
if not world.keysanity:
|
||||
state.sweep_for_events(key_only=True)
|
||||
|
||||
sphere = list(filter(state.can_reach, required_locations))
|
||||
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
||||
|
||||
for location in sphere:
|
||||
required_locations.remove(location)
|
||||
|
||||
Reference in New Issue
Block a user