Check for key locks after placing dungeon items.

This commit is contained in:
compiling
2020-02-05 18:54:57 +11:00
parent 2116bb6d0d
commit 8ad28542a4
4 changed files with 51 additions and 3 deletions

View File

@@ -2,7 +2,7 @@ import copy
from enum import Enum, unique, Flag from enum import Enum, unique, Flag
import logging import logging
import json import json
from collections import OrderedDict, deque from collections import OrderedDict, deque, defaultdict
from EntranceShuffle import door_addresses from EntranceShuffle import door_addresses
from _vendor.collections_extended import bag from _vendor.collections_extended import bag
@@ -70,6 +70,7 @@ class World(object):
self.inaccessible_regions = {} self.inaccessible_regions = {}
self.key_logic = {} self.key_logic = {}
self.pool_adjustment = {} self.pool_adjustment = {}
self.key_layout = defaultdict(dict)
for player in range(1, players + 1): for player in range(1, players + 1):
def set_player_attr(attr, val): def set_player_attr(attr, val):

View File

@@ -988,6 +988,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
analyze_dungeon(key_layout, world, player) analyze_dungeon(key_layout, world, player)
builder.key_door_proposal = proposal builder.key_door_proposal = proposal
world.key_logic[player][builder.name] = key_layout.key_logic world.key_logic[player][builder.name] = key_layout.key_logic
world.key_layout[player][builder.name] = key_layout
return True return True

View File

@@ -147,8 +147,9 @@ def analyze_dungeon(key_layout, world, player):
if not child.bigKey and child not in doors_completed: if not child.bigKey and child not in doors_completed:
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False, empty_flag) best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False, empty_flag)
rule = create_rule(best_counter, key_counter, key_layout, world, player) rule = create_rule(best_counter, key_counter, key_layout, world, player)
if not rule.is_valid: # todo: seems to be caused by best_counter not opening the big key door when that's logically required. Re-evaluate usage of this
logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name) # if not rule.is_valid:
# logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name)
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, player) check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
@@ -1233,3 +1234,42 @@ def val_rule(rule, skn, allow=False, loc=None, askn=None, setCheck=None):
assert len(setCheck) == len(rule.alternate_big_key_loc) assert len(setCheck) == len(rule.alternate_big_key_loc)
for loc in rule.alternate_big_key_loc: for loc in rule.alternate_big_key_loc:
assert loc.name in setCheck assert loc.name in setCheck
# Soft lock stuff
def validate_key_placement(key_layout, world, player):
if world.retro[player] or world.accessibility[player] == 'none':
return True # Can't keylock in retro. Expected if beatable only.
max_counter = find_max_counter(key_layout)
keys_outside = 0
big_key_outside = False
dungeon = world.get_dungeon(key_layout.sector.name, player)
smallkey_name = 'Small Key (%s)' % (key_layout.sector.name if key_layout.sector.name != 'Hyrule Castle' else 'Escape')
if world.keyshuffle[player]:
keys_outside = key_layout.max_chests - sum(1 for i in max_counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player)
if world.bigkeyshuffle[player]:
max_counter = find_max_counter(key_layout)
big_key_outside = dungeon.big_key not in (l.item for l in max_counter.free_locations)
for counter in key_layout.key_counters.values():
if len(counter.child_doors) == 0:
continue
found_keys = sum(1 for i in counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player) + \
len(counter.key_only_locations) + keys_outside
big_found = any(i.item == dungeon.big_key for i in counter.free_locations) or big_key_outside
can_progress = (not counter.big_key_opened and big_found) or found_keys > counter.used_keys and any(not d.bigKey for d in counter.child_doors)
if not can_progress:
missing_locations = set(max_counter.free_locations.keys()).difference(counter.free_locations.keys())
missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys())
if world.accessibility[player] == 'items':
# Maybe ok if only locking keys
missing_key_only = []
missing_locations = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key)]
if len(missing_locations) > 0 or len(missing_key_only) > 0:
logging.getLogger('').error("Keylock - can't open locations: ")
for i in missing_locations:
logging.getLogger('').error(i)
return False
return True

View File

@@ -10,6 +10,7 @@ import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop from BaseClasses import World, CollectionState, Item, Region, Location, Shop
from Items import ItemFactory from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
from InvertedRegions import create_inverted_regions, mark_dark_world_regions from InvertedRegions import create_inverted_regions, mark_dark_world_regions
from EntranceShuffle import link_entrances, link_inverted_entrances from EntranceShuffle import link_entrances, link_inverted_entrances
@@ -134,6 +135,11 @@ def main(args, seed=None):
else: else:
fill_dungeons(world) fill_dungeons(world)
for player in range(1, world.players+1):
for key_layout in world.key_layout[player].values():
if not validate_key_placement(key_layout, world, player):
raise RuntimeError("Keylock detected: %s (Player %d)" % (key_layout.sector.name, player))
logger.info('Fill the world.') logger.info('Fill the world.')
if args.algorithm == 'flood': if args.algorithm == 'flood':