Fixed some trap doors in logic

Added the one way after validation chest (can't get to the chest from the door)
Started refining some key logic to relax it / tighten it
This commit is contained in:
aerinon
2019-12-02 16:36:15 -07:00
parent c64b2269c0
commit 43ba391df1
6 changed files with 93 additions and 34 deletions

View File

@@ -1565,7 +1565,8 @@ logical_connections = [
('GT Firesnake Room Hook Path', 'GT Firesnake Room Ledge'), ('GT Firesnake Room Hook Path', 'GT Firesnake Room Ledge'),
('GT Left Moldorm Ledge Drop Down', 'GT Moldorm'), ('GT Left Moldorm Ledge Drop Down', 'GT Moldorm'),
('GT Right Moldorm Ledge Drop Down', 'GT Moldorm'), ('GT Right Moldorm Ledge Drop Down', 'GT Moldorm'),
('GT Moldorm Gap', 'GT Validation') ('GT Moldorm Gap', 'GT Validation'),
('GT Validation Block Path', 'GT Validation Door')
] ]
vanilla_logical_connections = [ vanilla_logical_connections = [

View File

@@ -660,7 +660,7 @@ def create_doors(world, player):
create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(1), create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(1),
create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(1), create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(1),
create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0), create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0),
create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(We, 0x7f, Top, High).trap(0x4).pos(0), create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(We, 0x7f, Top, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Ice Hookshot Ledge Path', Lgcl), create_door(player, 'Ice Hookshot Ledge Path', Lgcl),
create_door(player, 'Ice Hookshot Balcony Path', Lgcl), create_door(player, 'Ice Hookshot Balcony Path', Lgcl),
create_door(player, 'Ice Hookshot Balcony SW', Intr).dir(So, 0x7f, Left, High).pos(1), create_door(player, 'Ice Hookshot Balcony SW', Intr).dir(So, 0x7f, Left, High).pos(1),
@@ -1040,10 +1040,11 @@ def create_doors(world, player):
create_door(player, 'GT Right Moldorm Ledge Drop Down', Lgcl), create_door(player, 'GT Right Moldorm Ledge Drop Down', Lgcl),
create_door(player, 'GT Moldorm Gap', Lgcl), create_door(player, 'GT Moldorm Gap', Lgcl),
create_door(player, 'GT Moldorm Hole', Hole), create_door(player, 'GT Moldorm Hole', Hole),
create_door(player, 'GT Validation Block Path', Lgcl),
create_door(player, 'GT Validation WS', Nrml).dir(We, 0x4d, Bot, High).pos(1), create_door(player, 'GT Validation WS', Nrml).dir(We, 0x4d, Bot, High).pos(1),
create_door(player, 'GT Right Moldorm Ledge Down Stairs', Sprl).dir(Dn, 0x4d, 0, HTH).ss(S, 0x12, 0x80), create_door(player, 'GT Right Moldorm Ledge Down Stairs', Sprl).dir(Dn, 0x4d, 0, HTH).ss(S, 0x12, 0x80),
create_door(player, 'GT Moldorm Pit Up Stairs', Sprl).dir(Up, 0xa6, 0, HTH).ss(S, 0x1b, 0x6c), create_door(player, 'GT Moldorm Pit Up Stairs', Sprl).dir(Up, 0xa6, 0, HTH).ss(S, 0x1b, 0x6c),
create_door(player, 'GT Frozen Over ES', Nrml).dir(Ea, 0x4c, Bot, High).trap(0x4).pos(0), create_door(player, 'GT Frozen Over ES', Nrml).dir(Ea, 0x4c, Bot, High).no_exit().trap(0x4).pos(0),
create_door(player, 'GT Frozen Over Up Stairs', Sprl).dir(Up, 0x4c, 0, HTH).ss(S, 0x1a, 0x6c, True), create_door(player, 'GT Frozen Over Up Stairs', Sprl).dir(Up, 0x4c, 0, HTH).ss(S, 0x1a, 0x6c, True),
create_door(player, 'GT Brightly Lit Hall Down Stairs', Sprl).dir(Dn, 0x1d, 0, HTH).ss(S, 0x11, 0x80, False, True), create_door(player, 'GT Brightly Lit Hall Down Stairs', Sprl).dir(Dn, 0x1d, 0, HTH).ss(S, 0x11, 0x80, False, True),
create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0), create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0),

View File

@@ -251,7 +251,9 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
if len(dungeon.keys()) <= 1 and len(proposed_map.keys()) < len(doors_to_connect): if len(dungeon.keys()) <= 1 and len(proposed_map.keys()) < len(doors_to_connect):
return False return False
# origin has no more hooks, but not all doors have been proposed # origin has no more hooks, but not all doors have been proposed
if len(dungeon['Origin'].hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect): 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]
if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
return False return False
for key in hangers.keys(): for key in hangers.keys():
if len(hooks[key]) > 0 and len(hangers[key]) == 0: if len(hooks[key]) > 0 and len(hangers[key]) == 0:
@@ -551,6 +553,7 @@ class ExplorationState(object):
return ret return ret
def next_avail_door(self): def next_avail_door(self):
self.avail_doors.sort(key=lambda x: 0 if x.flag else 1)
exp_door = self.avail_doors.pop() exp_door = self.avail_doors.pop()
self.crystal = exp_door.crystal self.crystal = exp_door.crystal
return exp_door return exp_door

View File

@@ -294,8 +294,8 @@ gt_regions = [
'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1', 'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1',
'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches', 'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches',
'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Left Moldorm Ledge', 'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Left Moldorm Ledge',
'GT Right Moldorm Ledge', 'GT Moldorm', 'GT Moldorm Pit', 'GT Validation', 'GT Frozen Over', 'GT Brightly Lit Hall', 'GT Right Moldorm Ledge', 'GT Moldorm', 'GT Moldorm Pit', 'GT Validation', 'GT Validation Door', 'GT Frozen Over',
'GT Agahnim 2' 'GT Brightly Lit Hall', 'GT Agahnim 2'
] ]

View File

@@ -146,24 +146,35 @@ def analyze_dungeon(key_layout, world, player):
# logic min? # logic min?
if not key_sphere.bk_locked and big_chest_in_locations(key_counter.free_locations): if not key_sphere.bk_locked and big_chest_in_locations(key_counter.free_locations):
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations)) key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
minimal_keys = None
# todo: this feels like big key doors aren't accounted for - you may or may not find the big_key door at this point # todo: this feels like big key doors aren't accounted for - you may or may not find the big_key door at this point
if available + key_counter.used_keys <= possible_smalls: minimal_keys = available + key_counter.used_keys
minimal_keys = available + key_counter.used_keys minimal_satisfied = False
# todo: detect forced subsequent keys - see keypuzzles # todo: detect forced subsequent keys - see keypuzzles
# try to relax the rules here? # try to relax the rules here? - smallest requirement that doesn't force a softlock
for child in key_sphere.child_doors: childqueue = collections.deque()
for child in sorted(list(key_sphere.child_doors), key=lambda x: x.name):
next_sphere = key_layout.key_spheres[child.name] next_sphere = key_layout.key_spheres[child.name]
if not empty_sphere(next_sphere) and child not in doors_completed: if not empty_sphere(next_sphere) and child not in doors_completed:
if not child.bigKey: childqueue.append((child, next_sphere))
expanded_counter = expand_counter_to_last_door(child, key_counter, key_layout, set()) while len(childqueue) > 0:
rule = create_rule(expanded_counter, key_layout, minimal_keys, world) child, next_sphere = childqueue.popleft()
check_for_self_lock_key(rule, next_sphere, key_layout, world) if not child.bigKey:
bk_restricted_rules(rule, next_sphere, key_counter, key_layout, minimal_keys, world) expanded_counter = expand_counter_to_last_door(child, key_counter, key_layout, set())
key_logic.door_rules[child.name] = rule parent_rule = find_best_parent_rule(key_layout, child)
doors_completed.add(next_sphere.access_door) if parent_rule is not None:
next_counter = increment_key_counter(child, next_sphere, key_counter, key_layout.flat_prop) true_min = max(minimal_keys, parent_rule.small_key_num + 1)
queue.append((next_sphere, next_counter)) else:
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)
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)
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)
queue.append((next_sphere, next_counter))
return key_layout return key_layout
@@ -191,6 +202,17 @@ def empty_sphere(sphere):
return not sphere.prize_region return not sphere.prize_region
def find_best_parent_rule(key_layout, child):
best = None
for door_name, sphere in key_layout.key_spheres.items():
if sphere.access_door is not None and child in sphere.child_doors:
if door_name in key_layout.key_logic.door_rules.keys():
rule = key_layout.key_logic.door_rules[door_name]
if best is None or rule.small_key_num < best.small_key_num:
best = rule
return best
def relative_empty_sphere(sphere, key_counter): def relative_empty_sphere(sphere, key_counter):
if len(sphere.key_only_locations.difference(key_counter.key_only_locations)) > 0: if len(sphere.key_only_locations.difference(key_counter.key_only_locations)) > 0:
return False return False
@@ -198,7 +220,7 @@ def relative_empty_sphere(sphere, key_counter):
return False return False
new_child_door = False new_child_door = False
for child in sphere.child_doors: for child in sphere.child_doors:
if child not in key_counter.child_doors and child not in key_counter.open_doors and (not child.bigKey or not key_counter.big_key_opened): if unique_child_door(child, key_counter):
new_child_door = True new_child_door = True
break break
if new_child_door: if new_child_door:
@@ -206,6 +228,36 @@ def relative_empty_sphere(sphere, key_counter):
return True return True
def unique_child_door(child, key_counter):
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
return False
if child in key_counter.open_doors or child.dest in key_counter.child_doors:
return False
if child.bigKey and key_counter.big_key_opened:
return False
return True
# def relative_empty_sphere2(expanded_sphere, key_counter):
# return len(expanded_sphere.free_locations.difference(key_counter.free_locations)) == 0
#
#
# def expand_sphere(sphere, key_layout):
# counter = KeyCounter(key_layout.max_chests)
# counter.update(sphere)
# queue = collections.deque(counter.child_doors)
# already_queued = set(counter.child_doors)
# while len(queue) > 0:
# child = queue.popleft()
# if child not in counter.open_doors:
# counter = increment_key_counter(child, key_layout.key_spheres[child.name], counter, key_layout.flat_prop)
# for new_door in counter.child_doors:
# if new_door not in already_queued:
# queue.append(new_door)
# already_queued.add(new_door)
# return counter
def increment_key_counter(door, sphere, key_counter, flat_proposal): def increment_key_counter(door, sphere, key_counter, flat_proposal):
new_counter = key_counter.copy() new_counter = key_counter.copy()
new_counter.open_door(door, flat_proposal) new_counter.open_door(door, flat_proposal)
@@ -246,12 +298,12 @@ def expand_counter_to_last_door(door, key_counter, key_layout, ignored_doors):
return new_counter return new_counter
def create_rule(key_counter, key_layout, minimal_keys, world): def create_rule(key_counter, key_layout, minimal_keys, force_min, world):
chest_keys = available_chest_small_keys(key_counter, key_counter.big_key_opened, 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 available = chest_keys + len(key_counter.key_only_locations) - key_counter.used_keys
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
required_keys = min(available, possible_smalls) + key_counter.used_keys required_keys = min(available, possible_smalls) + key_counter.used_keys
if minimal_keys is None or required_keys <= minimal_keys: if not force_min or required_keys <= minimal_keys:
return DoorRules(required_keys) return DoorRules(required_keys)
else: else:
return DoorRules(minimal_keys) return DoorRules(minimal_keys)
@@ -295,11 +347,11 @@ def available_chest_small_keys(key_counter, bk, world):
return key_counter.max_chests return key_counter.max_chests
def bk_restricted_rules(rule, sphere, key_counter, key_layout, minimal_keys, world): def bk_restricted_rules(rule, sphere, key_counter, key_layout, minimal_keys, force_min, world):
if sphere.bk_locked: if sphere.bk_locked:
return return
expanded_counter = expand_counter_no_big_doors(sphere.access_door, key_counter, key_layout, set()) 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, world).small_key_num bk_number = create_rule(expanded_counter, key_layout, minimal_keys, force_min, world).small_key_num
if bk_number == rule.small_key_num: if bk_number == rule.small_key_num:
return return
post_counter = KeyCounter(key_layout.max_chests) post_counter = KeyCounter(key_layout.max_chests)
@@ -521,9 +573,10 @@ def validate_vanilla_key_logic(world, player):
def val_hyrule(key_logic, world, player): def val_hyrule(key_logic, world, player):
val_rule(key_logic.door_rules['Sewers Secret Room Key Door S'], 2) val_rule(key_logic.door_rules['Sewers Secret Room Key Door S'], 2)
val_rule(key_logic.door_rules['Sewers Dark Cross Key Door N'], 2) val_rule(key_logic.door_rules['Sewers Dark Cross Key Door N'], 3)
val_rule(key_logic.door_rules['Hyrule Dungeon Map Room Key Door S'], 2) val_rule(key_logic.door_rules['Hyrule Dungeon Map Room Key Door S'], 3)
val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 3, True, 'Hyrule Castle - Zelda\'s Chest') val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4, True, 'Hyrule Castle - Zelda\'s Chest')
# why is allow_small actually false?
# val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4) # val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4)
@@ -537,7 +590,7 @@ def val_eastern(key_logic, world, player):
def val_desert(key_logic, world, player): def val_desert(key_logic, world, player):
val_rule(key_logic.door_rules['Desert East Wing Key Door EN'], 2) val_rule(key_logic.door_rules['Desert East Wing Key Door EN'], 4)
val_rule(key_logic.door_rules['Desert Tiles 1 Up Stairs'], 2) val_rule(key_logic.door_rules['Desert Tiles 1 Up Stairs'], 2)
val_rule(key_logic.door_rules['Desert Beamos Hall NE'], 3) val_rule(key_logic.door_rules['Desert Beamos Hall NE'], 3)
val_rule(key_logic.door_rules['Desert Tiles 2 NE'], 4) val_rule(key_logic.door_rules['Desert Tiles 2 NE'], 4)
@@ -610,7 +663,7 @@ def val_ice(key_logic, world, player):
def val_mire(key_logic, world, player): def val_mire(key_logic, world, player):
mire_west_wing = {'Misery Mire - Big Key Chest', 'Misery Mire - Compass Chest'} mire_west_wing = {'Misery Mire - Big Key Chest', 'Misery Mire - Compass Chest'}
val_rule(key_logic.door_rules['Mire Spikes NW'], 5) val_rule(key_logic.door_rules['Mire Spikes NW'], 4) # todo: crystal state in key door analysis
val_rule(key_logic.door_rules['Mire Hub WS'], 5, False, None, 4, mire_west_wing) val_rule(key_logic.door_rules['Mire Hub WS'], 5, False, None, 4, mire_west_wing)
val_rule(key_logic.door_rules['Mire Conveyor Crystal WS'], 6, False, None, 5, mire_west_wing) val_rule(key_logic.door_rules['Mire Conveyor Crystal WS'], 6, False, None, 5, mire_west_wing)
assert world.get_location('Misery Mire - Boss', player) in key_logic.bk_restricted assert world.get_location('Misery Mire - Boss', player) in key_logic.bk_restricted
@@ -641,9 +694,9 @@ def val_ganons(key_logic, world, player):
gt_middle = {'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Chest', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest'} gt_middle = {'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Chest', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest'}
val_rule(key_logic.door_rules['GT Double Switch EN'], 7, False, None, 5, rando_room.union({'Ganons Tower - Firesnake Room'})) val_rule(key_logic.door_rules['GT Double Switch EN'], 7, False, None, 5, rando_room.union({'Ganons Tower - Firesnake Room'}))
val_rule(key_logic.door_rules['GT Hookshot ES'], 8, True, 'Ganons Tower - Map Chest', 6, {'Ganons Tower - Map Chest'}) val_rule(key_logic.door_rules['GT Hookshot ES'], 8, True, 'Ganons Tower - Map Chest', 6, {'Ganons Tower - Map Chest'})
val_rule(key_logic.door_rules['GT Tile Room EN'], 7, False, None, 5, compass_room) val_rule(key_logic.door_rules['GT Tile Room EN'], 6, False, None, 5, compass_room)
val_rule(key_logic.door_rules['GT Firesnake Room SW'], 8, False, None, 6, rando_room) val_rule(key_logic.door_rules['GT Firesnake Room SW'], 8, False, None, 6, rando_room)
val_rule(key_logic.door_rules['GT Conveyor Star Pits EN'], 8, False, None, 6, gt_middle) val_rule(key_logic.door_rules['GT Conveyor Star Pits EN'], 7, False, None, 6, gt_middle)
val_rule(key_logic.door_rules['GT Mini Helmasaur Room WN'], 7) val_rule(key_logic.door_rules['GT Mini Helmasaur Room WN'], 7)
val_rule(key_logic.door_rules['GT Crystal Circles SW'], 8) val_rule(key_logic.door_rules['GT Crystal Circles SW'], 8)
assert world.get_location('Ganons Tower - Mini Helmasaur Room - Left', player) in key_logic.bk_restricted assert world.get_location('Ganons Tower - Mini Helmasaur Room - Left', player) in key_logic.bk_restricted

View File

@@ -698,7 +698,8 @@ def create_regions(world, player):
create_dungeon_region(player, 'GT Right Moldorm Ledge', 'Ganon\'s Tower', None, ['GT Right Moldorm Ledge Down Stairs', 'GT Right Moldorm Ledge Drop Down']), create_dungeon_region(player, 'GT Right Moldorm Ledge', 'Ganon\'s Tower', None, ['GT Right Moldorm Ledge Down Stairs', 'GT Right Moldorm Ledge Drop Down']),
create_dungeon_region(player, 'GT Moldorm', 'Ganon\'s Tower', None, ['GT Moldorm Hole', 'GT Moldorm Gap']), create_dungeon_region(player, 'GT Moldorm', 'Ganon\'s Tower', None, ['GT Moldorm Hole', 'GT Moldorm Gap']),
create_dungeon_region(player, 'GT Moldorm Pit', 'Ganon\'s Tower', None, ['GT Moldorm Pit Up Stairs']), create_dungeon_region(player, 'GT Moldorm Pit', 'Ganon\'s Tower', None, ['GT Moldorm Pit Up Stairs']),
create_dungeon_region(player, 'GT Validation', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest'], ['GT Validation WS']), create_dungeon_region(player, 'GT Validation', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest'], ['GT Validation Block Path']),
create_dungeon_region(player, 'GT Validation Door', 'Ganon\'s Tower', None, ['GT Validation WS']),
create_dungeon_region(player, 'GT Frozen Over', 'Ganon\'s Tower', None, ['GT Frozen Over ES', 'GT Frozen Over Up Stairs']), create_dungeon_region(player, 'GT Frozen Over', 'Ganon\'s Tower', None, ['GT Frozen Over ES', 'GT Frozen Over Up Stairs']),
create_dungeon_region(player, 'GT Brightly Lit Hall', 'Ganon\'s Tower', None, ['GT Brightly Lit Hall Down Stairs', 'GT Brightly Lit Hall NW']), create_dungeon_region(player, 'GT Brightly Lit Hall', 'Ganon\'s Tower', None, ['GT Brightly Lit Hall Down Stairs', 'GT Brightly Lit Hall NW']),
create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW']) create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW'])