Key logic overhaul - hope to have fixed many key locks
Specific keysanity fix Fixed issue with crossed that created extra keys
This commit is contained in:
@@ -711,6 +711,7 @@ def cross_dungeon(world, player):
|
|||||||
queue = collections.deque(sorted(queue, key=lambda b: b.combo_size))
|
queue = collections.deque(sorted(queue, key=lambda b: b.combo_size))
|
||||||
else:
|
else:
|
||||||
logger.info('Cross Dungeon: Increase failed for %s', name)
|
logger.info('Cross Dungeon: Increase failed for %s', name)
|
||||||
|
builder.key_doors_num -= 1
|
||||||
builder.flex = 0
|
builder.flex = 0
|
||||||
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
original_key_counter = find_counter({}, False, key_layout)
|
original_key_counter = find_counter({}, False, key_layout)
|
||||||
queue = deque([(None, original_key_counter)])
|
queue = deque([(None, original_key_counter)])
|
||||||
doors_completed = set()
|
doors_completed = set()
|
||||||
|
visited_cid = set()
|
||||||
|
visited_cid.add(cid(original_key_counter, key_layout))
|
||||||
|
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
queue = deque(sorted(queue, key=queue_sorter))
|
queue = deque(sorted(queue, key=queue_sorter))
|
||||||
@@ -131,25 +133,28 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
for child in key_counter.child_doors.keys():
|
for child in key_counter.child_doors.keys():
|
||||||
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
|
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
|
||||||
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world)
|
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world)
|
||||||
if child not in doors_completed:
|
empty_flag = empty_counter(odd_counter)
|
||||||
child_queue.append((child, odd_counter))
|
child_queue.append((child, odd_counter, empty_flag))
|
||||||
if child in doors_completed and child in key_logic.door_rules.keys():
|
if child in doors_completed and child in key_logic.door_rules.keys():
|
||||||
rule = key_logic.door_rules[child]
|
rule = key_logic.door_rules[child]
|
||||||
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
||||||
smallest_rule = rule.small_key_num
|
smallest_rule = rule.small_key_num
|
||||||
while len(child_queue) > 0:
|
while len(child_queue) > 0:
|
||||||
child, odd_counter = child_queue.popleft()
|
child, odd_counter, empty_flag = child_queue.popleft()
|
||||||
if not child.bigKey:
|
if not child.bigKey and child not in doors_completed:
|
||||||
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, False)
|
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, False, empty_flag)
|
||||||
rule = create_rule(best_counter, key_counter, key_layout, world)
|
rule = create_rule(best_counter, key_counter, key_layout, world)
|
||||||
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
||||||
smallest_rule = rule.small_key_num
|
smallest_rule = rule.small_key_num
|
||||||
check_for_self_lock_key(rule, child, best_counter, key_layout, world)
|
check_for_self_lock_key(rule, child, best_counter, key_layout, world)
|
||||||
bk_restricted_rules(rule, child, odd_counter, key_counter, key_layout, world)
|
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world)
|
||||||
key_logic.door_rules[child.name] = rule
|
key_logic.door_rules[child.name] = rule
|
||||||
doors_completed.add(child)
|
doors_completed.add(child)
|
||||||
next_counter = find_next_counter(child, key_counter, key_layout)
|
next_counter = find_next_counter(child, key_counter, key_layout)
|
||||||
queue.append((child, next_counter))
|
ctr_id = cid(next_counter, key_layout)
|
||||||
|
if ctr_id not in visited_cid:
|
||||||
|
queue.append((child, next_counter))
|
||||||
|
visited_cid.add(ctr_id)
|
||||||
possible_smalls_collected = len(key_counter.key_only_locations) + non_big_locs
|
possible_smalls_collected = len(key_counter.key_only_locations) + non_big_locs
|
||||||
if not key_counter.big_key_opened:
|
if not key_counter.big_key_opened:
|
||||||
if smallest_rule is not None and smallest_rule >= possible_smalls_collected and not avail_bigs:
|
if smallest_rule is not None and smallest_rule >= possible_smalls_collected and not avail_bigs:
|
||||||
@@ -183,6 +188,8 @@ def queue_sorter_2(queue_item):
|
|||||||
|
|
||||||
|
|
||||||
def find_bk_locked_sections(key_layout, world):
|
def find_bk_locked_sections(key_layout, world):
|
||||||
|
if key_layout.big_key_special:
|
||||||
|
return
|
||||||
key_counters = key_layout.key_counters
|
key_counters = key_layout.key_counters
|
||||||
key_logic = key_layout.key_logic
|
key_logic = key_layout.key_logic
|
||||||
|
|
||||||
@@ -230,7 +237,7 @@ def unique_child_door(child, key_counter):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk): # try to waste as many keys as possible?
|
def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk, empty_flag): # try to waste as many keys as possible?
|
||||||
ignored_doors = {door, door.dest} if door is not None else {}
|
ignored_doors = {door, door.dest} if door is not None else {}
|
||||||
finished = False
|
finished = False
|
||||||
opened_doors = dict(key_counter.open_doors)
|
opened_doors = dict(key_counter.open_doors)
|
||||||
@@ -248,15 +255,14 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk
|
|||||||
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
||||||
bk_open = new_counter.big_key_opened
|
bk_open = new_counter.big_key_opened
|
||||||
# this means the new_door invalidates the door / leads to the same stuff
|
# this means the new_door invalidates the door / leads to the same stuff
|
||||||
if relative_empty_counter(odd_counter, new_counter):
|
if not empty_flag and relative_empty_counter(odd_counter, new_counter):
|
||||||
ignored_doors.add(new_door)
|
ignored_doors.add(new_door)
|
||||||
|
elif empty_flag or key_wasted(new_door, door, last_counter, new_counter, key_layout, world):
|
||||||
|
last_counter = new_counter
|
||||||
|
opened_doors = proposed_doors
|
||||||
|
bk_opened = bk_open
|
||||||
else:
|
else:
|
||||||
if not key_wasted(new_door, door, last_counter, new_counter, key_layout, world):
|
ignored_doors.add(new_door)
|
||||||
ignored_doors.add(new_door)
|
|
||||||
else:
|
|
||||||
last_counter = new_counter
|
|
||||||
opened_doors = proposed_doors
|
|
||||||
bk_opened = bk_open
|
|
||||||
return last_counter
|
return last_counter
|
||||||
|
|
||||||
|
|
||||||
@@ -441,10 +447,10 @@ def available_chest_small_keys_logic(key_counter, world, sm_restricted):
|
|||||||
return key_counter.max_chests
|
return key_counter.max_chests
|
||||||
|
|
||||||
|
|
||||||
def bk_restricted_rules(rule, door, odd_counter, key_counter, key_layout, world):
|
def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_layout, world):
|
||||||
if key_counter.big_key_opened:
|
if key_counter.big_key_opened:
|
||||||
return
|
return
|
||||||
best_counter = find_best_counter(door, odd_counter, key_counter, key_layout, world, True)
|
best_counter = find_best_counter(door, odd_counter, key_counter, key_layout, world, True, empty_flag)
|
||||||
bk_number = create_rule(best_counter, key_counter, key_layout, world).small_key_num
|
bk_number = create_rule(best_counter, key_counter, key_layout, world).small_key_num
|
||||||
if bk_number == rule.small_key_num:
|
if bk_number == rule.small_key_num:
|
||||||
return
|
return
|
||||||
@@ -521,6 +527,9 @@ def exist_relevant_big_doors(key_counter, key_layout):
|
|||||||
diff = dict_difference(bk_counter.key_only_locations, key_counter.key_only_locations)
|
diff = dict_difference(bk_counter.key_only_locations, key_counter.key_only_locations)
|
||||||
if len(diff) > 0:
|
if len(diff) > 0:
|
||||||
return True
|
return True
|
||||||
|
diff = dict_difference(bk_counter.child_doors, key_counter.child_doors)
|
||||||
|
if len(diff) > 0:
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -659,6 +668,7 @@ def adjust_key_location_mins(key_layout, min_rules, getter, setter):
|
|||||||
|
|
||||||
def check_rules_deep(original_counter, key_layout, world):
|
def check_rules_deep(original_counter, key_layout, world):
|
||||||
key_logic = key_layout.key_logic
|
key_logic = key_layout.key_logic
|
||||||
|
big_locations = {x for x in key_layout.all_chest_locations if x not in key_logic.bk_restricted}
|
||||||
queue = deque([original_counter])
|
queue = deque([original_counter])
|
||||||
completed = set()
|
completed = set()
|
||||||
completed.add(cid(original_counter, key_layout))
|
completed.add(cid(original_counter, key_layout))
|
||||||
@@ -670,19 +680,28 @@ def check_rules_deep(original_counter, key_layout, world):
|
|||||||
bail += 1
|
bail += 1
|
||||||
if bail > 10:
|
if bail > 10:
|
||||||
raise Exception('Key logic issue, during deep rule check: %s' % key_layout.sector.name)
|
raise Exception('Key logic issue, during deep rule check: %s' % key_layout.sector.name)
|
||||||
|
else:
|
||||||
|
bail = 0
|
||||||
last_counter = counter
|
last_counter = counter
|
||||||
chest_keys = available_chest_small_keys_logic(counter, world, key_logic.sm_restricted)
|
chest_keys = available_chest_small_keys_logic(counter, world, key_logic.sm_restricted)
|
||||||
big_avail = counter.big_key_opened
|
big_avail = counter.big_key_opened
|
||||||
|
big_maybe_not_found = not counter.big_key_opened
|
||||||
if not key_layout.big_key_special and not big_avail:
|
if not key_layout.big_key_special and not big_avail:
|
||||||
for location in counter.free_locations:
|
for location in counter.free_locations:
|
||||||
if location not in key_logic.bk_restricted:
|
if location not in key_logic.bk_restricted:
|
||||||
big_avail = True
|
big_avail = True
|
||||||
break
|
break
|
||||||
|
outstanding_big_locs = {x for x in big_locations if x not in counter.free_locations}
|
||||||
|
if big_maybe_not_found:
|
||||||
|
if len(outstanding_big_locs) == 0:
|
||||||
|
big_maybe_not_found = False
|
||||||
big_uses_chest = big_avail and not key_layout.big_key_special
|
big_uses_chest = big_avail and not key_layout.big_key_special
|
||||||
|
collected_alt = len(counter.key_only_locations) + chest_keys
|
||||||
if big_uses_chest and chest_keys == count_locations_big_optional(counter.free_locations, counter.big_key_opened):
|
if big_uses_chest and chest_keys == count_locations_big_optional(counter.free_locations, counter.big_key_opened):
|
||||||
chest_keys -= 1
|
chest_keys -= 1
|
||||||
collected = len(counter.key_only_locations) + chest_keys
|
collected = len(counter.key_only_locations) + chest_keys
|
||||||
can_progress = len(counter.child_doors) == 0
|
can_progress = len(counter.child_doors) == 0
|
||||||
|
smalls_opened = False
|
||||||
small_rules = []
|
small_rules = []
|
||||||
for door in counter.child_doors.keys():
|
for door in counter.child_doors.keys():
|
||||||
can_open = False
|
can_open = False
|
||||||
@@ -691,33 +710,62 @@ def check_rules_deep(original_counter, key_layout, world):
|
|||||||
elif door.name in key_logic.door_rules.keys():
|
elif door.name in key_logic.door_rules.keys():
|
||||||
rule = key_logic.door_rules[door.name]
|
rule = key_logic.door_rules[door.name]
|
||||||
small_rules.append(rule)
|
small_rules.append(rule)
|
||||||
# todo: satisfies alternate big key rule?
|
if rule_satisfied(rule, collected, collected_alt, outstanding_big_locs, chest_keys, key_layout):
|
||||||
if collected >= rule.small_key_num or \
|
|
||||||
(rule.allow_small and collected >= rule.small_key_num-1 and chest_keys < key_layout.max_chests):
|
|
||||||
can_open = True
|
can_open = True
|
||||||
|
smalls_opened = True
|
||||||
elif not door.bigKey:
|
elif not door.bigKey:
|
||||||
can_open = True
|
can_open = True
|
||||||
if can_open:
|
if can_open:
|
||||||
can_progress = True
|
can_progress = smalls_opened or not big_maybe_not_found
|
||||||
next_counter = find_next_counter(door, counter, key_layout)
|
next_counter = find_next_counter(door, counter, key_layout)
|
||||||
c_id = cid(next_counter, key_layout)
|
c_id = cid(next_counter, key_layout)
|
||||||
if c_id not in completed:
|
if c_id not in completed:
|
||||||
completed.add(c_id)
|
completed.add(c_id)
|
||||||
queue.append(next_counter)
|
queue.append(next_counter)
|
||||||
if not can_progress:
|
if not can_progress:
|
||||||
smallest_rules = []
|
if len(small_rules) > 0: # zero could be indicative of a problem, but also, the big key is now required
|
||||||
min_num = None
|
reduce_rules(small_rules, collected, collected_alt)
|
||||||
for rule in small_rules:
|
queue.append(counter) # run it through again
|
||||||
if min_num is None or rule.small_key_num <= min_num:
|
else:
|
||||||
if min_num is not None and rule.small_key_num < min_num:
|
raise Exception('Possible problem with generation or bk rules')
|
||||||
min_num = rule.small_key_num
|
|
||||||
smallest_rules.clear()
|
|
||||||
elif min_num is None:
|
def rule_satisfied(rule, collected, collected_alt, outstanding_big_locs, chest_keys, key_layout):
|
||||||
min_num = rule.small_key_num
|
if collected >= rule.small_key_num:
|
||||||
smallest_rules.append(rule)
|
return True
|
||||||
for rule in smallest_rules:
|
if rule.allow_small and collected >= rule.small_key_num-1 and chest_keys < key_layout.max_chests:
|
||||||
rule.small_key_num = collected
|
return True
|
||||||
queue.append(counter) # run it through again
|
rule_diff = outstanding_big_locs.difference(rule.alternate_big_key_loc)
|
||||||
|
if rule.alternate_small_key is not None and len(rule_diff) == 0 and collected >= rule.alternate_small_key:
|
||||||
|
return True
|
||||||
|
if collected_alt > collected:
|
||||||
|
if collected_alt >= rule.small_key_num:
|
||||||
|
return True
|
||||||
|
if rule.allow_small and collected_alt >= rule.small_key_num-1 and chest_keys+1 < key_layout.max_chests:
|
||||||
|
return True
|
||||||
|
if rule.alternate_small_key is not None and len(rule_diff) == 0 and collected_alt >= rule.alternate_small_key:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def reduce_rules(small_rules, collected, collected_alt):
|
||||||
|
smallest_rules = []
|
||||||
|
min_num = None
|
||||||
|
for rule in small_rules:
|
||||||
|
if min_num is None or rule.small_key_num <= min_num:
|
||||||
|
if min_num is not None and rule.small_key_num < min_num:
|
||||||
|
min_num = rule.small_key_num
|
||||||
|
smallest_rules.clear()
|
||||||
|
elif min_num is None:
|
||||||
|
min_num = rule.small_key_num
|
||||||
|
smallest_rules.append(rule)
|
||||||
|
for rule in smallest_rules:
|
||||||
|
if rule.allow_small: # we are already reducing it
|
||||||
|
rule.allow_small = False
|
||||||
|
if min_num > collected_alt > collected:
|
||||||
|
rule.small_key_num = collected_alt
|
||||||
|
else:
|
||||||
|
rule.small_key_num = collected
|
||||||
|
|
||||||
|
|
||||||
# Soft lock stuff
|
# Soft lock stuff
|
||||||
@@ -776,7 +824,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
def cnt_avail_small_locations(key_layout, ttl_locations, state, world):
|
def cnt_avail_small_locations(key_layout, ttl_locations, state, world):
|
||||||
if not world.keysanity and world.mode != 'retro':
|
if not world.keysanity and world.mode != 'retro':
|
||||||
return min(ttl_locations - state.used_locations, state.key_locations - state.used_smalls)
|
return min(ttl_locations - state.used_locations, state.key_locations - state.used_smalls)
|
||||||
return key_layout.max_chests + state.key_locations - state.used_smalls
|
return state.key_locations - state.used_smalls
|
||||||
|
|
||||||
|
|
||||||
def cnt_avail_big_locations(ttl_locations, state, world):
|
def cnt_avail_big_locations(ttl_locations, state, world):
|
||||||
@@ -1073,9 +1121,9 @@ def val_ganons(key_logic, world, player):
|
|||||||
compass_room = {'Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'}
|
compass_room = {'Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'}
|
||||||
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'], 6, False, None, 4, rando_room.union({'Ganons Tower - Firesnake Room'}))
|
val_rule(key_logic.door_rules['GT Double Switch EN'], 6, False, None, 4, rando_room.union({'Ganons Tower - Firesnake Room'}))
|
||||||
val_rule(key_logic.door_rules['GT Hookshot ES'], 8, True, 'Ganons Tower - Map Chest', 5, {'Ganons Tower - Map Chest'})
|
val_rule(key_logic.door_rules['GT Hookshot ES'], 7, False, 'Ganons Tower - Map Chest', 5, {'Ganons Tower - Map Chest'})
|
||||||
val_rule(key_logic.door_rules['GT Tile Room EN'], 6, 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, 5, rando_room)
|
val_rule(key_logic.door_rules['GT Firesnake Room SW'], 7, False, None, 5, rando_room)
|
||||||
val_rule(key_logic.door_rules['GT Conveyor Star Pits EN'], 6, False, None, 5, gt_middle) # should be 7?
|
val_rule(key_logic.door_rules['GT Conveyor Star Pits EN'], 6, False, None, 5, gt_middle) # should be 7?
|
||||||
val_rule(key_logic.door_rules['GT Mini Helmasaur Room WN'], 6) # not sure about this 6...
|
val_rule(key_logic.door_rules['GT Mini Helmasaur Room WN'], 6) # not sure about this 6...
|
||||||
val_rule(key_logic.door_rules['GT Crystal Circles SW'], 8)
|
val_rule(key_logic.door_rules['GT Crystal Circles SW'], 8)
|
||||||
|
|||||||
4
Rules.py
4
Rules.py
@@ -1739,13 +1739,13 @@ def create_key_rule_allow_small(small_key_name, player, keys, location):
|
|||||||
|
|
||||||
def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs):
|
def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs):
|
||||||
chest_names = [x.name for x in bk_locs]
|
chest_names = [x.name for x in bk_locs]
|
||||||
return lambda state: state.has_key(small_key_name, player, keys) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
|
return lambda state: (state.has_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
|
||||||
|
|
||||||
|
|
||||||
def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs):
|
def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs):
|
||||||
loc = location.name
|
loc = location.name
|
||||||
chest_names = [x.name for x in bk_locs]
|
chest_names = [x.name for x in bk_locs]
|
||||||
return lambda state: state.has_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
|
return lambda state: (state.has_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
|
||||||
|
|
||||||
|
|
||||||
def create_advanced_key_rule(key_logic, player, rule):
|
def create_advanced_key_rule(key_logic, player, rule):
|
||||||
|
|||||||
Reference in New Issue
Block a user