Check for key locks after placing dungeon items.
This commit is contained in:
@@ -2,7 +2,7 @@ import copy
|
||||
from enum import Enum, unique, Flag
|
||||
import logging
|
||||
import json
|
||||
from collections import OrderedDict, deque
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
|
||||
from EntranceShuffle import door_addresses
|
||||
from _vendor.collections_extended import bag
|
||||
@@ -70,6 +70,7 @@ class World(object):
|
||||
self.inaccessible_regions = {}
|
||||
self.key_logic = {}
|
||||
self.pool_adjustment = {}
|
||||
self.key_layout = defaultdict(dict)
|
||||
|
||||
for player in range(1, players + 1):
|
||||
def set_player_attr(attr, val):
|
||||
|
||||
@@ -988,6 +988,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
|
||||
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 True
|
||||
|
||||
|
||||
|
||||
@@ -147,8 +147,9 @@ def analyze_dungeon(key_layout, world, player):
|
||||
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)
|
||||
rule = create_rule(best_counter, key_counter, key_layout, world, player)
|
||||
if not rule.is_valid:
|
||||
logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name)
|
||||
# todo: seems to be caused by best_counter not opening the big key door when that's logically required. Re-evaluate usage of this
|
||||
# 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:
|
||||
smallest_rule = rule.small_key_num
|
||||
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)
|
||||
for loc in rule.alternate_big_key_loc:
|
||||
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
|
||||
|
||||
|
||||
6
Main.py
6
Main.py
@@ -10,6 +10,7 @@ import zlib
|
||||
|
||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
||||
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 InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||
@@ -134,6 +135,11 @@ def main(args, seed=None):
|
||||
else:
|
||||
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.')
|
||||
|
||||
if args.algorithm == 'flood':
|
||||
|
||||
Reference in New Issue
Block a user