Merge pull request #1 from aerinon/DoorDev

Update to DoorDev
This commit is contained in:
Mike A. Trethewey
2020-02-22 00:59:19 -08:00
committed by GitHub
8 changed files with 290 additions and 14 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

@@ -158,6 +158,7 @@ def vanilla_key_logic(world, player):
world.key_logic[player] = {} world.key_logic[player] = {}
analyze_dungeon(key_layout, world, player) analyze_dungeon(key_layout, world, player)
world.key_logic[player][builder.name] = key_layout.key_logic world.key_logic[player][builder.name] = key_layout.key_logic
log_key_logic(builder.name, key_layout.key_logic)
last_key = None last_key = None
if world.shuffle[player] == 'vanilla': if world.shuffle[player] == 'vanilla':
validate_vanilla_key_logic(world, player) validate_vanilla_key_logic(world, player)
@@ -861,8 +862,11 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
if recombine.master_sector is None: if recombine.master_sector is None:
recombine.master_sector = builder.master_sector recombine.master_sector = builder.master_sector
recombine.master_sector.name = recombine.name recombine.master_sector.name = recombine.name
recombine.pre_open_stonewall = builder.pre_open_stonewall
else: else:
recombine.master_sector.regions.extend(builder.master_sector.regions) recombine.master_sector.regions.extend(builder.master_sector.regions)
if builder.pre_open_stonewall:
recombine.pre_open_stonewall = builder.pre_open_stonewall
recombine.layout_starts = list(entrances_map[recombine.name]) recombine.layout_starts = list(entrances_map[recombine.name])
dungeon_builders[recombine.name] = recombine dungeon_builders[recombine.name] = recombine
@@ -988,6 +992,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
@@ -1009,6 +1014,13 @@ def log_key_logic(d_name, key_logic):
if rule.alternate_small_key is not None: if rule.alternate_small_key is not None:
for loc in rule.alternate_big_key_loc: for loc in rule.alternate_big_key_loc:
logger.debug('---BK Loc %s', loc.name) logger.debug('---BK Loc %s', loc.name)
logger.debug('Placement rules for %s', d_name)
for rule in key_logic.placement_rules:
logger.debug('*Rule for %s:', rule.door_reference)
if rule.bk_conditional_set:
logger.debug('**BK Checks %s', ','.join([x.name for x in rule.bk_conditional_set]))
logger.debug('**BK Blocked By Door (%s) : %s', rule.needed_keys_wo_bk, ','.join([x.name for x in rule.check_locations_wo_bk]))
logger.debug('**BK Elsewhere (%s) : %s', rule.needed_keys_w_bk, ','.join([x.name for x in rule.check_locations_w_bk]))
def build_pair_list(flat_list): def build_pair_list(flat_list):
@@ -1068,7 +1080,7 @@ def find_key_door_candidates(region, checked, world, player):
candidates.append(d2) candidates.append(d2)
else: else:
valid = True valid = True
if valid: if valid and d not in candidates:
candidates.append(d) candidates.append(d)
if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon: if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon:
queue.append((ext.connected_region, d, current)) queue.append((ext.connected_region, d, current))
@@ -1101,6 +1113,7 @@ def ncr(n, r):
def reassign_key_doors(builder, world, player): def reassign_key_doors(builder, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
logger.debug('Key doors for %s', builder.name)
proposal = builder.key_door_proposal proposal = builder.key_door_proposal
flat_proposal = flatten_pair_list(proposal) flat_proposal = flatten_pair_list(proposal)
queue = deque(find_current_key_doors(builder)) queue = deque(find_current_key_doors(builder))

34
Fill.py
View File

@@ -201,7 +201,8 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
else: else:
test_state = maximum_exploration_state test_state = maximum_exploration_state
if (not single_player_placement or location.player == item_to_place.player)\ if (not single_player_placement or location.player == item_to_place.player)\
and location.can_fill(test_state, item_to_place, perform_access_check): and location.can_fill(test_state, item_to_place, perform_access_check)\
and valid_key_placement(item_to_place, location, itempool, world):
spot_to_fill = location spot_to_fill = location
break break
elif item_to_place.smallkey or item_to_place.bigkey: elif item_to_place.smallkey or item_to_place.bigkey:
@@ -217,11 +218,42 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
raise FillError('No more spots to place %s' % item_to_place) raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, False) world.push_item(spot_to_fill, item_to_place, False)
track_outside_keys(item_to_place, spot_to_fill, world)
locations.remove(spot_to_fill) locations.remove(spot_to_fill)
spot_to_fill.event = True spot_to_fill.event = True
itempool.extend(unplaced_items) itempool.extend(unplaced_items)
def valid_key_placement(item, location, itempool, world):
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player]:
return True
dungeon = location.parent_region.dungeon
if dungeon:
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
return True
key_logic = world.key_logic[item.player][dungeon.name]
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name])
return key_logic.check_placement(unplaced_keys)
else:
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
or (item.bigkey and not world.bigkeyshuffle[item.player]))
return not inside_dungeon_item
def track_outside_keys(item, location, world):
if not item.smallkey:
return
item_dungeon = item.name.split('(')[1][:-1]
if item_dungeon == 'Escape':
item_dungeon = 'Hyrule Castle'
if location.player == item.player:
loc_dungeon = location.parent_region.dungeon
if loc_dungeon and loc_dungeon.name == item_dungeon:
return # this is an inside key
world.key_logic[item.player][item_dungeon].outside_keys += 1
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None): def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
# If not passed in, then get a shuffled list of locations to fill in # If not passed in, then get a shuffled list of locations to fill in
if not fill_locations: if not fill_locations:

View File

@@ -21,6 +21,7 @@ class KeyLayout(object):
self.max_drops = None self.max_drops = None
self.all_chest_locations = {} self.all_chest_locations = {}
self.big_key_special = False self.big_key_special = False
self.all_locations = set()
# bk special? # bk special?
# bk required? True if big chests or big doors exists # bk required? True if big chests or big doors exists
@@ -44,6 +45,14 @@ class KeyLogic(object):
self.bk_chests = set() self.bk_chests = set()
self.logic_min = {} self.logic_min = {}
self.logic_max = {} self.logic_max = {}
self.placement_rules = []
self.outside_keys = 0
def check_placement(self, unplaced_keys):
for rule in self.placement_rules:
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
return False
return True
class DoorRules(object): class DoorRules(object):
@@ -59,6 +68,38 @@ class DoorRules(object):
self.small_location = None self.small_location = None
class PlacementRule(object):
def __init__(self):
self.door_reference = None
self.small_key = None
self.bk_conditional_set = None # the location that means
self.needed_keys_w_bk = None
self.needed_keys_wo_bk = None
self.check_locations_w_bk = None
self.check_locations_wo_bk = None
def is_satisfiable(self, outside_keys, unplaced_keys):
bk_blocked = False
if self.bk_conditional_set:
for loc in self.bk_conditional_set:
if loc.item and loc.item.bigkey:
bk_blocked = True
break
available_keys = outside_keys
empty_chests = 0
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
for loc in check_locations:
if not loc.item:
empty_chests += 1
elif loc.item and loc.item.name == self.small_key:
available_keys += 1
place_able_keys = min(empty_chests, unplaced_keys)
available_keys += place_able_keys
return available_keys >= threshold
class KeyCounter(object): class KeyCounter(object):
def __init__(self, max_chests): def __init__(self, max_chests):
@@ -108,6 +149,8 @@ def analyze_dungeon(key_layout, world, player):
find_bk_locked_sections(key_layout, world) find_bk_locked_sections(key_layout, world)
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
if world.retro[player] and world.mode[player] != 'standard':
return
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)])
@@ -147,13 +190,15 @@ 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)
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player) bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player)
key_logic.door_rules[child.name] = rule key_logic.door_rules[child.name] = rule
create_placement_rule(key_layout, child, odd_counter, key_counter, world, player)
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)
ctr_id = cid(next_counter, key_layout) ctr_id = cid(next_counter, key_layout)
@@ -176,6 +221,65 @@ def analyze_dungeon(key_layout, world, player):
rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num
def create_placement_rule(key_layout, door, odd_ctr, current_ctr, world, player):
key_logic = key_layout.key_logic
worst_ctr = find_worst_counter(door, odd_ctr, current_ctr, key_layout, False)
sm_num = worst_ctr.used_keys + 1
accessible_loc = set()
accessible_loc.update(worst_ctr.free_locations)
accessible_loc.update(worst_ctr.key_only_locations)
worst_ctr_wo_bk, post_ctr, alt_num = find_worst_counter_wo_bk(sm_num, accessible_loc, door, odd_ctr, current_ctr, key_layout)
blocked_loc = key_layout.all_locations.difference(accessible_loc)
if len(blocked_loc) > 0:
rule = PlacementRule()
rule.door_reference = door
rule.small_key = key_logic.small_key_name
rule.needed_keys_w_bk = sm_num
placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player)
rule.check_locations_w_bk = accessible_loc
if worst_ctr_wo_bk:
accessible_wo_bk, post_set = set(), set()
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
post_set.update(post_ctr.free_locations)
post_set.update(post_ctr.key_only_locations)
blocked_wo_bk = post_set.difference(accessible_wo_bk)
if len(blocked_wo_bk) > 0:
rule.bk_conditional_set = blocked_wo_bk
rule.needed_keys_wo_bk = alt_num
# can this self lock a key if bk not avail? I'm thinking no.
# placement_self_lock_adjustment(rule, key_layout, ???, worst_ctr_wo_bk, world, player)
rule.check_locations_wo_bk = accessible_wo_bk
key_logic.placement_rules.append(rule)
if worst_ctr_wo_bk:
check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num)
def check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num):
avail_keys = len(worst_ctr_wo_bk.key_only_locations)
place_able_keys = min(key_layout.max_chests, len(worst_ctr_wo_bk.free_locations))
if avail_keys + place_able_keys < alt_num:
accessible_wo_bk, post_set = set(), set()
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
post_set.update(post_ctr.free_locations)
post_set.update(post_ctr.key_only_locations)
key_layout.key_logic.bk_restricted.update(post_set.difference(accessible_wo_bk))
def placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player):
if len(blocked_loc) == 1 and world.accessibility[player] != 'locations':
max_ctr = find_max_counter(key_layout)
blocked_others = set(max_ctr.other_locations).difference(set(worst_ctr.other_locations))
important_found = False
for loc in blocked_others:
if important_location(loc, world, player):
important_found = True
break
if not important_found:
rule.needed_keys_w_bk -= 1
def count_key_drops(sector): def count_key_drops(sector):
cnt = 0 cnt = 0
@@ -210,6 +314,8 @@ def find_bk_locked_sections(key_layout, world):
big_chest_allowed_big_key = world.accessibility != 'locations' big_chest_allowed_big_key = world.accessibility != 'locations'
for counter in key_counters.values(): for counter in key_counters.values():
key_layout.all_chest_locations.update(counter.free_locations) key_layout.all_chest_locations.update(counter.free_locations)
key_layout.all_locations.update(counter.free_locations)
key_layout.all_locations.update(counter.key_only_locations)
if counter.big_key_opened and counter.important_location: if counter.big_key_opened and counter.important_location:
big_chest_allowed_big_key = False big_chest_allowed_big_key = False
if not counter.big_key_opened: if not counter.big_key_opened:
@@ -240,6 +346,28 @@ def relative_empty_counter(odd_counter, key_counter):
return True return True
def relative_empty_counter_2(odd_counter, key_counter):
if len(set(odd_counter.key_only_locations).difference(key_counter.key_only_locations)) > 0:
return False
if len(set(odd_counter.free_locations).difference(key_counter.free_locations)) > 0:
return False
for child in odd_counter.child_doors:
if unique_child_door_2(child, key_counter):
return False
return True
def progressive_ctr(new_counter, last_counter):
if len(set(new_counter.key_only_locations).difference(last_counter.key_only_locations)) > 0:
return True
if len(set(new_counter.free_locations).difference(last_counter.free_locations)) > 0:
return True
for child in new_counter.child_doors:
if unique_child_door_2(child, last_counter):
return True
return False
def unique_child_door(child, key_counter): def unique_child_door(child, key_counter):
if child in key_counter.child_doors or child.dest in key_counter.child_doors: if child in key_counter.child_doors or child.dest in key_counter.child_doors:
return False return False
@@ -250,6 +378,14 @@ def unique_child_door(child, key_counter):
return True return True
def unique_child_door_2(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
return True
def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, skip_bk, empty_flag): # try to waste as many keys as possible? def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, 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
@@ -279,7 +415,34 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, player,
return last_counter return last_counter
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk): def find_worst_counter(door, odd_counter, key_counter, key_layout, skip_bk): # try to waste as many keys as possible?
ignored_doors = {door, door.dest} if door is not None else {}
finished = False
opened_doors = dict(key_counter.open_doors)
bk_opened = key_counter.big_key_opened
# new_counter = key_counter
last_counter = key_counter
while not finished:
door_set = find_potential_open_doors(last_counter, ignored_doors, key_layout, skip_bk, 0)
if door_set is None or len(door_set) == 0:
finished = True
continue
for new_door in door_set:
proposed_doors = {**opened_doors, **dict.fromkeys([new_door, new_door.dest])}
bk_open = bk_opened or new_door.bigKey
new_counter = find_counter(proposed_doors, bk_open, key_layout)
bk_open = new_counter.big_key_opened
if not new_door.bigKey and progressive_ctr(new_counter, last_counter) and relative_empty_counter_2(odd_counter, new_counter):
ignored_doors.add(new_door)
else:
last_counter = new_counter
opened_doors = proposed_doors
bk_opened = bk_open
# this means the new_door invalidates the door / leads to the same stuff
return last_counter
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk, reserve=1):
small_doors = [] small_doors = []
big_doors = [] big_doors = []
for other in key_counter.child_doors: for other in key_counter.child_doors:
@@ -292,7 +455,7 @@ def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
if key_layout.big_key_special: if key_layout.big_key_special:
big_key_available = key_counter.big_key_opened big_key_available = key_counter.big_key_opened
else: else:
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0 big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)): if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
return None return None
return small_doors + big_doors return small_doors + big_doors
@@ -366,7 +529,7 @@ def create_rule(key_counter, prev_counter, key_layout, world, player):
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player): def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
counter = find_inverted_counter(door, parent_counter, key_layout, world, player) counter = find_inverted_counter(door, parent_counter, key_layout, world, player)
if not self_lock_possible(counter): if not self_lock_possible(counter):
return return
@@ -488,6 +651,27 @@ def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_la
# key_layout.key_logic.bk_restricted.update(unique_loc) # key_layout.key_logic.bk_restricted.update(unique_loc)
def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_counter, key_layout):
if key_counter.big_key_opened:
return None, None, None
worst_counter = find_worst_counter(door, odd_ctr, key_counter, key_layout, True)
bk_rule_num = worst_counter.used_keys + 1
bk_access_set = set()
bk_access_set.update(worst_counter.free_locations)
bk_access_set.update(worst_counter.key_only_locations)
if bk_rule_num == small_key_num and len(bk_access_set ^ accessible_set) == 0:
return None, None, None
door_open = find_next_counter(door, worst_counter, key_layout)
ignored_doors = dict_intersection(worst_counter.child_doors, door_open.child_doors)
dest_ignored = []
for door in ignored_doors.keys():
if door.dest not in ignored_doors:
dest_ignored.append(door.dest)
ignored_doors = {**ignored_doors, **dict.fromkeys(dest_ignored)}
post_counter = open_some_counter(door_open, key_layout, ignored_doors.keys())
return worst_counter, post_counter, bk_rule_num
def open_a_door(door, child_state, flat_proposal): def open_a_door(door, child_state, flat_proposal):
if door.bigKey: if door.bigKey:
child_state.big_key_opened = True child_state.big_key_opened = True
@@ -832,6 +1016,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player) available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
if invalid_self_locking_key(state, prev_state, prev_avail, world, player): if invalid_self_locking_key(state, prev_state, prev_avail, world, player):
return False return False
# todo: allow more key shuffles - refine placement rules
# if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0): if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
return False return False
else: else:
@@ -1233,3 +1419,43 @@ 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
big_found = any(i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
if counter.big_key_opened and not big_found:
continue # Can't get to this state
found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name)
found_keys = sum(1 for i in found_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
can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) 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(found_locations)
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name]
#missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
if len(missing_items) > 0: #world.accessibility[player]=='locations' and (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
@@ -23,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.0.15pre' __version__ = '0.0.17pre'
def main(args, seed=None): def main(args, seed=None):
@@ -135,6 +136,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':

2
Rom.py
View File

@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '746b52b20a116648b624ebe58af9e54b' RANDOMIZERBASEHASH = '5e01caffabb4509a0987ef2f2f0bcd56'
class JsonRom(object): class JsonRom(object):

View File

@@ -50,6 +50,4 @@ MirrorCheckOverride:
+ lda DRScroll : rtl + lda DRScroll : rtl
MirrorCheckOverride2: MirrorCheckOverride2:
lda $7ef353 : cmp #$02 : beq + lda $7ef353 : and #$02 : rtl
sep #$02 ; clear zero flag, I think
+ rtl

File diff suppressed because one or more lines are too long