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
|
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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
6
Main.py
6
Main.py
@@ -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':
|
||||||
|
|||||||
Reference in New Issue
Block a user