Key logic rework for placement rules
--exhaustive per key_counter --contradictions between rules Mire Lobby chest fix
This commit is contained in:
@@ -1050,8 +1050,9 @@ def log_key_logic(d_name, key_logic):
|
|||||||
logger.debug('*Rule for %s:', rule.door_reference)
|
logger.debug('*Rule for %s:', rule.door_reference)
|
||||||
if rule.bk_conditional_set:
|
if rule.bk_conditional_set:
|
||||||
logger.debug('**BK Checks %s', ','.join([x.name for x in 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 Blocked (%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]))
|
if rule.needed_keys_w_bk:
|
||||||
|
logger.debug('**BK Available (%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):
|
||||||
@@ -1579,10 +1580,12 @@ logical_connections = [
|
|||||||
('Ice Big Chest Landing Push Blocks', 'Ice Big Chest View'),
|
('Ice Big Chest Landing Push Blocks', 'Ice Big Chest View'),
|
||||||
('Mire Lobby Gap', 'Mire Post-Gap'),
|
('Mire Lobby Gap', 'Mire Post-Gap'),
|
||||||
('Mire Post-Gap Gap', 'Mire Lobby'),
|
('Mire Post-Gap Gap', 'Mire Lobby'),
|
||||||
('Mire Hub Upper Blue Barrier', 'Mire Hub Top'),
|
('Mire Hub Upper Blue Barrier', 'Mire Hub Switch'),
|
||||||
('Mire Hub Lower Blue Barrier', 'Mire Hub Right'),
|
('Mire Hub Lower Blue Barrier', 'Mire Hub Right'),
|
||||||
('Mire Hub Right Blue Barrier', 'Mire Hub'),
|
('Mire Hub Right Blue Barrier', 'Mire Hub'),
|
||||||
('Mire Hub Top Blue Barrier', 'Mire Hub'),
|
('Mire Hub Top Blue Barrier', 'Mire Hub Switch'),
|
||||||
|
('Mire Hub Switch Blue Barrier N', 'Mire Hub Top'),
|
||||||
|
('Mire Hub Switch Blue Barrier S', 'Mire Hub'),
|
||||||
('Mire Map Spike Side Drop Down', 'Mire Lone Shooter'),
|
('Mire Map Spike Side Drop Down', 'Mire Lone Shooter'),
|
||||||
('Mire Map Spike Side Blue Barrier', 'Mire Crystal Dead End'),
|
('Mire Map Spike Side Blue Barrier', 'Mire Crystal Dead End'),
|
||||||
('Mire Map Spot Blue Barrier', 'Mire Crystal Dead End'),
|
('Mire Map Spot Blue Barrier', 'Mire Crystal Dead End'),
|
||||||
|
|||||||
4
Doors.py
4
Doors.py
@@ -722,6 +722,8 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'Mire Hub Lower Blue Barrier', Lgcl),
|
create_door(player, 'Mire Hub Lower Blue Barrier', Lgcl),
|
||||||
create_door(player, 'Mire Hub Right Blue Barrier', Lgcl),
|
create_door(player, 'Mire Hub Right Blue Barrier', Lgcl),
|
||||||
create_door(player, 'Mire Hub Top Blue Barrier', Lgcl),
|
create_door(player, 'Mire Hub Top Blue Barrier', Lgcl),
|
||||||
|
create_door(player, 'Mire Hub Switch Blue Barrier N', Lgcl),
|
||||||
|
create_door(player, 'Mire Hub Switch Blue Barrier S', Lgcl),
|
||||||
create_door(player, 'Mire Hub Right EN', Nrml).dir(Ea, 0xc2, Top, High).small_key().pos(0),
|
create_door(player, 'Mire Hub Right EN', Nrml).dir(Ea, 0xc2, Top, High).small_key().pos(0),
|
||||||
create_door(player, 'Mire Hub Top NW', Nrml).dir(No, 0xc2, Left, High).pos(2),
|
create_door(player, 'Mire Hub Top NW', Nrml).dir(No, 0xc2, Left, High).pos(2),
|
||||||
create_door(player, 'Mire Lone Shooter WS', Nrml).dir(We, 0xc3, Bot, High).pos(6),
|
create_door(player, 'Mire Lone Shooter WS', Nrml).dir(We, 0xc3, Bot, High).pos(6),
|
||||||
@@ -1161,6 +1163,8 @@ def create_doors(world, player):
|
|||||||
world.get_door('Mire Hub Lower Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Mire Hub Lower Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('Mire Hub Right Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Mire Hub Right Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('Mire Hub Top Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Mire Hub Top Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
|
world.get_door('Mire Hub Switch Blue Barrier N', player).barrier(CrystalBarrier.Blue)
|
||||||
|
world.get_door('Mire Hub Switch Blue Barrier S', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('Mire Map Spike Side Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Mire Map Spike Side Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('Mire Map Spot Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Mire Map Spot Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('Mire Crystal Dead End Left Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Mire Crystal Dead End Left Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
|
|||||||
10
Dungeons.py
10
Dungeons.py
@@ -255,11 +255,11 @@ ice_regions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
mire_regions = [
|
mire_regions = [
|
||||||
'Mire Lobby', 'Mire Post-Gap', 'Mire 2', 'Mire Hub', 'Mire Hub Right', 'Mire Hub Top', 'Mire Lone Shooter',
|
'Mire Lobby', 'Mire Post-Gap', 'Mire 2', 'Mire Hub', 'Mire Hub Right', 'Mire Hub Top', 'Mire Hub Switch',
|
||||||
'Mire Failure Bridge', 'Mire Falling Bridge', 'Mire Map Spike Side', 'Mire Map Spot', 'Mire Crystal Dead End',
|
'Mire Lone Shooter', 'Mire Failure Bridge', 'Mire Falling Bridge', 'Mire Map Spike Side', 'Mire Map Spot',
|
||||||
'Mire Hidden Shooters', 'Mire Hidden Shooters Blocked', 'Mire Cross', 'Mire Minibridge', 'Mire BK Door Room',
|
'Mire Crystal Dead End', 'Mire Hidden Shooters', 'Mire Hidden Shooters Blocked', 'Mire Cross', 'Mire Minibridge',
|
||||||
'Mire Spikes', 'Mire Ledgehop', 'Mire Bent Bridge', 'Mire Over Bridge', 'Mire Right Bridge', 'Mire Left Bridge',
|
'Mire BK Door Room', 'Mire Spikes', 'Mire Ledgehop', 'Mire Bent Bridge', 'Mire Over Bridge', 'Mire Right Bridge',
|
||||||
'Mire Fishbone', 'Mire South Fish', 'Mire Spike Barrier', 'Mire Square Rail', 'Mire Lone Warp',
|
'Mire Left Bridge', 'Mire Fishbone', 'Mire South Fish', 'Mire Spike Barrier', 'Mire Square Rail', 'Mire Lone Warp',
|
||||||
'Mire Wizzrobe Bypass', 'Mire Conveyor Crystal', 'Mire Tile Room', 'Mire Compass Room', 'Mire Compass Chest',
|
'Mire Wizzrobe Bypass', 'Mire Conveyor Crystal', 'Mire Tile Room', 'Mire Compass Room', 'Mire Compass Chest',
|
||||||
'Mire Neglected Room', 'Mire Chest View', 'Mire Conveyor Barrier', 'Mire BK Chest Ledge', 'Mire Warping Pool',
|
'Mire Neglected Room', 'Mire Chest View', 'Mire Conveyor Barrier', 'Mire BK Chest Ledge', 'Mire Warping Pool',
|
||||||
'Mire Torches Top', 'Mire Torches Bottom', 'Mire Attic Hint', 'Mire Dark Shooters', 'Mire Key Rupees',
|
'Mire Torches Top', 'Mire Torches Bottom', 'Mire Attic Hint', 'Mire Dark Shooters', 'Mire Key Rupees',
|
||||||
|
|||||||
2
Fill.py
2
Fill.py
@@ -234,7 +234,7 @@ def valid_key_placement(item, location, itempool, world):
|
|||||||
return True
|
return True
|
||||||
key_logic = world.key_logic[item.player][dungeon.name]
|
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 and x.player == item.player])
|
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
|
||||||
return key_logic.check_placement(unplaced_keys)
|
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None)
|
||||||
else:
|
else:
|
||||||
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
||||||
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ class KeyLayout(object):
|
|||||||
self.all_chest_locations = {}
|
self.all_chest_locations = {}
|
||||||
self.big_key_special = False
|
self.big_key_special = False
|
||||||
self.all_locations = set()
|
self.all_locations = set()
|
||||||
|
self.item_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
|
||||||
@@ -31,6 +33,8 @@ class KeyLayout(object):
|
|||||||
self.flat_prop = flatten_pair_list(self.proposal)
|
self.flat_prop = flatten_pair_list(self.proposal)
|
||||||
self.key_logic = KeyLogic(self.sector.name)
|
self.key_logic = KeyLogic(self.sector.name)
|
||||||
self.max_chests = calc_max_chests(builder, self, world, player)
|
self.max_chests = calc_max_chests(builder, self, world, player)
|
||||||
|
self.all_locations = set()
|
||||||
|
self.item_locations = set()
|
||||||
|
|
||||||
|
|
||||||
class KeyLogic(object):
|
class KeyLogic(object):
|
||||||
@@ -48,10 +52,14 @@ class KeyLogic(object):
|
|||||||
self.placement_rules = []
|
self.placement_rules = []
|
||||||
self.outside_keys = 0
|
self.outside_keys = 0
|
||||||
|
|
||||||
def check_placement(self, unplaced_keys):
|
def check_placement(self, unplaced_keys, big_key_loc=None):
|
||||||
for rule in self.placement_rules:
|
for rule in self.placement_rules:
|
||||||
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
|
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
|
||||||
return False
|
return False
|
||||||
|
if big_key_loc:
|
||||||
|
for rule_a, rule_b in itertools.combinations(self.placement_rules, 2):
|
||||||
|
if rule_a.contradicts(rule_b, unplaced_keys, big_key_loc):
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +74,7 @@ class DoorRules(object):
|
|||||||
# for a place with only 1 free location/key_only_location behind it ... no goals and locations
|
# for a place with only 1 free location/key_only_location behind it ... no goals and locations
|
||||||
self.allow_small = False
|
self.allow_small = False
|
||||||
self.small_location = None
|
self.small_location = None
|
||||||
|
self.opposite = None
|
||||||
|
|
||||||
|
|
||||||
class PlacementRule(object):
|
class PlacementRule(object):
|
||||||
@@ -79,6 +88,35 @@ class PlacementRule(object):
|
|||||||
self.check_locations_w_bk = None
|
self.check_locations_w_bk = None
|
||||||
self.check_locations_wo_bk = None
|
self.check_locations_wo_bk = None
|
||||||
|
|
||||||
|
def contradicts(self, rule, unplaced_keys, big_key_loc):
|
||||||
|
bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False
|
||||||
|
rule_blocked = big_key_loc in rule.bk_conditional_set if rule.bk_conditional_set else False
|
||||||
|
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||||
|
rule_locations = rule.check_locations_wo_bk if rule_blocked else rule.check_locations_w_bk
|
||||||
|
if check_locations is None or rule_locations is None:
|
||||||
|
return False
|
||||||
|
check_locations = check_locations - {big_key_loc}
|
||||||
|
rule_locations = rule_locations - {big_key_loc}
|
||||||
|
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||||
|
rule_threshold = rule.needed_keys_wo_bk if rule_blocked else rule.needed_keys_w_bk
|
||||||
|
common_locations = rule_locations & check_locations
|
||||||
|
shared = len(common_locations)
|
||||||
|
if min(rule_threshold, threshold) - shared > 0:
|
||||||
|
left = unplaced_keys - shared
|
||||||
|
check_locations = check_locations - common_locations
|
||||||
|
check_needed = threshold - shared
|
||||||
|
if len(check_locations) < check_needed or left < check_needed:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
left -= check_needed
|
||||||
|
rule_locations = rule_locations - common_locations
|
||||||
|
rule_needed = rule_threshold - shared
|
||||||
|
if len(rule_locations) < rule_needed or left < rule_needed:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
left -= rule_needed
|
||||||
|
return False
|
||||||
|
|
||||||
def is_satisfiable(self, outside_keys, unplaced_keys):
|
def is_satisfiable(self, outside_keys, unplaced_keys):
|
||||||
bk_blocked = False
|
bk_blocked = False
|
||||||
if self.bk_conditional_set:
|
if self.bk_conditional_set:
|
||||||
@@ -86,9 +124,11 @@ class PlacementRule(object):
|
|||||||
if loc.item and loc.item.bigkey:
|
if loc.item and loc.item.bigkey:
|
||||||
bk_blocked = True
|
bk_blocked = True
|
||||||
break
|
break
|
||||||
|
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||||
|
if not bk_blocked and check_locations is None:
|
||||||
|
return True
|
||||||
available_keys = outside_keys
|
available_keys = outside_keys
|
||||||
empty_chests = 0
|
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
|
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||||
for loc in check_locations:
|
for loc in check_locations:
|
||||||
if not loc.item:
|
if not loc.item:
|
||||||
@@ -134,9 +174,27 @@ def build_key_layout(builder, start_regions, proposal, world, player):
|
|||||||
key_layout.max_drops = count_key_drops(key_layout.sector)
|
key_layout.max_drops = count_key_drops(key_layout.sector)
|
||||||
key_layout.max_chests = calc_max_chests(builder, key_layout, world, player)
|
key_layout.max_chests = calc_max_chests(builder, key_layout, world, player)
|
||||||
key_layout.big_key_special = 'Hyrule Dungeon Cellblock' in key_layout.sector.region_set()
|
key_layout.big_key_special = 'Hyrule Dungeon Cellblock' in key_layout.sector.region_set()
|
||||||
|
key_layout.all_locations = find_all_locations(key_layout.sector)
|
||||||
return key_layout
|
return key_layout
|
||||||
|
|
||||||
|
|
||||||
|
def count_key_drops(sector):
|
||||||
|
cnt = 0
|
||||||
|
for region in sector.regions:
|
||||||
|
for loc in region.locations:
|
||||||
|
if loc.event and 'Small Key' in loc.item.name:
|
||||||
|
cnt += 1
|
||||||
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
|
def find_all_locations(sector):
|
||||||
|
all_locations = set()
|
||||||
|
for region in sector.regions:
|
||||||
|
for loc in region.locations:
|
||||||
|
all_locations.add(loc)
|
||||||
|
return all_locations
|
||||||
|
|
||||||
|
|
||||||
def calc_max_chests(builder, key_layout, world, player):
|
def calc_max_chests(builder, key_layout, world, player):
|
||||||
if world.doorShuffle[player] != 'crossed':
|
if world.doorShuffle[player] != 'crossed':
|
||||||
return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||||
@@ -165,17 +223,13 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||||
available = raw_avail - key_counter.used_keys
|
available = raw_avail - key_counter.used_keys
|
||||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||||
avail_bigs = exist_relevant_big_doors(key_counter, key_layout)
|
avail_bigs = exist_relevant_big_doors(key_counter, key_layout) or exist_big_chest(key_counter)
|
||||||
non_big_locs = count_locations_big_optional(key_counter.free_locations)
|
non_big_locs = count_locations_big_optional(key_counter.free_locations)
|
||||||
if not key_counter.big_key_opened:
|
if not key_counter.big_key_opened:
|
||||||
if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs:
|
if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs:
|
||||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
|
||||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
|
||||||
# todo: detect forced subsequent keys - see keypuzzles
|
|
||||||
# try to relax the rules here? - smallest requirement that doesn't force a softlock
|
# try to relax the rules here? - smallest requirement that doesn't force a softlock
|
||||||
child_queue = deque()
|
child_queue = deque()
|
||||||
smallest_rule = None
|
|
||||||
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, player)
|
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world, player)
|
||||||
@@ -183,34 +237,20 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
child_queue.append((child, odd_counter, empty_flag))
|
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:
|
|
||||||
smallest_rule = rule.small_key_num
|
|
||||||
while len(child_queue) > 0:
|
while len(child_queue) > 0:
|
||||||
child, odd_counter, empty_flag = child_queue.popleft()
|
child, odd_counter, empty_flag = child_queue.popleft()
|
||||||
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)
|
||||||
# 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)
|
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)
|
||||||
if ctr_id not in visited_cid:
|
if ctr_id not in visited_cid:
|
||||||
queue.append((child, next_counter))
|
queue.append((child, next_counter))
|
||||||
visited_cid.add(ctr_id)
|
visited_cid.add(ctr_id)
|
||||||
possible_smalls_collected = len(key_counter.key_only_locations) + non_big_locs
|
|
||||||
if not key_counter.big_key_opened:
|
|
||||||
if smallest_rule is not None and smallest_rule >= possible_smalls_collected and not avail_bigs:
|
|
||||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
|
||||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
|
||||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
|
||||||
check_rules(original_key_counter, key_layout, world, player)
|
check_rules(original_key_counter, key_layout, world, player)
|
||||||
|
|
||||||
# Flip bk rules if more restrictive, to prevent placing a big key in a softlocking location
|
# Flip bk rules if more restrictive, to prevent placing a big key in a softlocking location
|
||||||
@@ -219,59 +259,52 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
max_counter = find_max_counter(key_layout)
|
max_counter = find_max_counter(key_layout)
|
||||||
rule.alternate_big_key_loc = set(max_counter.free_locations.keys()).difference(rule.alternate_big_key_loc)
|
rule.alternate_big_key_loc = set(max_counter.free_locations.keys()).difference(rule.alternate_big_key_loc)
|
||||||
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
|
||||||
|
create_exhaustive_placement_rules(key_layout, world, player)
|
||||||
|
set_paired_rules(key_logic, world, player)
|
||||||
|
|
||||||
|
|
||||||
def create_placement_rule(key_layout, door, odd_ctr, current_ctr, world, player):
|
def create_exhaustive_placement_rules(key_layout, world, player):
|
||||||
key_logic = key_layout.key_logic
|
key_logic = key_layout.key_logic
|
||||||
worst_ctr = find_worst_counter(door, odd_ctr, current_ctr, key_layout, False)
|
max_ctr = find_max_counter(key_layout)
|
||||||
sm_num = worst_ctr.used_keys + 1
|
for code, key_counter in key_layout.key_counters.items():
|
||||||
accessible_loc = set()
|
accessible_loc = set()
|
||||||
accessible_loc.update(worst_ctr.free_locations)
|
accessible_loc.update(key_counter.free_locations)
|
||||||
accessible_loc.update(worst_ctr.key_only_locations)
|
accessible_loc.update(key_counter.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.item_locations.difference(accessible_loc)
|
||||||
blocked_loc = key_layout.all_locations.difference(accessible_loc)
|
valid_rule = True
|
||||||
|
# min_keys = max(count_unique_sm_doors(key_counter.child_doors), key_counter.used_keys + 1)
|
||||||
if len(blocked_loc) > 0:
|
min_keys = key_counter.used_keys + 1
|
||||||
rule = PlacementRule()
|
if len(blocked_loc) > 0 and len(key_counter.key_only_locations) < min_keys:
|
||||||
rule.door_reference = door
|
rule = PlacementRule()
|
||||||
rule.small_key = key_logic.small_key_name
|
rule.door_reference = code
|
||||||
rule.needed_keys_w_bk = sm_num
|
rule.small_key = key_logic.small_key_name
|
||||||
placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player)
|
if key_counter.big_key_opened or not big_key_progress(key_counter):
|
||||||
rule.check_locations_w_bk = accessible_loc
|
# rule.needed_keys_w_bk = key_counter.used_keys + 1
|
||||||
if worst_ctr_wo_bk:
|
rule.needed_keys_w_bk = min_keys
|
||||||
accessible_wo_bk, post_set = set(), set()
|
if key_counter.big_key_opened and rule.needed_keys_w_bk + 1 > len(accessible_loc):
|
||||||
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
valid_rule = False # indicates that the big key cannot be in the accessible locations
|
||||||
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
key_logic.bk_restricted.update(accessible_loc.difference(max_ctr.key_only_locations))
|
||||||
post_set.update(post_ctr.free_locations)
|
else:
|
||||||
post_set.update(post_ctr.key_only_locations)
|
placement_self_lock_adjustment(rule, max_ctr, blocked_loc, key_counter, world, player)
|
||||||
blocked_wo_bk = post_set.difference(accessible_wo_bk)
|
rule.check_locations_w_bk = accessible_loc
|
||||||
if len(blocked_wo_bk) > 0:
|
check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc)
|
||||||
rule.bk_conditional_set = blocked_wo_bk
|
else:
|
||||||
rule.needed_keys_wo_bk = alt_num
|
if big_key_progress(key_counter) and only_sm_doors(key_counter):
|
||||||
# can this self lock a key if bk not avail? I'm thinking no.
|
create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player)
|
||||||
# placement_self_lock_adjustment(rule, key_layout, ???, worst_ctr_wo_bk, world, player)
|
# if key_counter.used_keys + 2 > len(accessible_loc) and big_key_no_progress(key_counter):
|
||||||
rule.check_locations_wo_bk = accessible_wo_bk
|
# key_logic.bk_restricted.update(accessible_loc.difference(max_ctr.key_only_locations))
|
||||||
key_logic.placement_rules.append(rule)
|
rule.bk_conditional_set = blocked_loc
|
||||||
if worst_ctr_wo_bk:
|
# rule.needed_keys_wo_bk = key_counter.used_keys + 1
|
||||||
check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num)
|
rule.needed_keys_wo_bk = min_keys
|
||||||
|
rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc))
|
||||||
|
if valid_rule:
|
||||||
|
key_logic.placement_rules.append(rule)
|
||||||
|
refine_placement_rules(key_layout, max_ctr)
|
||||||
|
|
||||||
|
|
||||||
def check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num):
|
def placement_self_lock_adjustment(rule, max_ctr, blocked_loc, ctr, world, player):
|
||||||
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':
|
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(ctr.other_locations))
|
||||||
blocked_others = set(max_ctr.other_locations).difference(set(worst_ctr.other_locations))
|
|
||||||
important_found = False
|
important_found = False
|
||||||
for loc in blocked_others:
|
for loc in blocked_others:
|
||||||
if important_location(loc, world, player):
|
if important_location(loc, world, player):
|
||||||
@@ -281,13 +314,115 @@ def placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, wor
|
|||||||
rule.needed_keys_w_bk -= 1
|
rule.needed_keys_w_bk -= 1
|
||||||
|
|
||||||
|
|
||||||
def count_key_drops(sector):
|
def check_sm_restriction_needed(key_layout, max_ctr, rule, blocked):
|
||||||
cnt = 0
|
if rule.needed_keys_w_bk == key_layout.max_chests + len(max_ctr.key_only_locations):
|
||||||
for region in sector.regions:
|
key_layout.key_logic.sm_restricted.update(blocked.difference(max_ctr.key_only_locations))
|
||||||
for loc in region.locations:
|
return True
|
||||||
if loc.event and 'Small Key' in loc.item.name:
|
return False
|
||||||
cnt += 1
|
|
||||||
return cnt
|
|
||||||
|
def refine_placement_rules(key_layout, max_ctr):
|
||||||
|
key_logic = key_layout.key_logic
|
||||||
|
changed = True
|
||||||
|
while changed:
|
||||||
|
changed = False
|
||||||
|
rules_to_remove = []
|
||||||
|
for rule in key_logic.placement_rules:
|
||||||
|
if rule.check_locations_w_bk:
|
||||||
|
rule.check_locations_w_bk.difference_update(key_logic.sm_restricted)
|
||||||
|
key_onlys = rule.check_locations_w_bk.intersection(max_ctr.key_only_locations)
|
||||||
|
if len(key_onlys) > 0:
|
||||||
|
rule.check_locations_w_bk.difference_update(key_onlys)
|
||||||
|
rule.needed_keys_w_bk -= len(key_onlys)
|
||||||
|
if rule.needed_keys_w_bk == 0:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
if len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1:
|
||||||
|
new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk
|
||||||
|
if len(new_restricted - key_logic.bk_restricted) > 0:
|
||||||
|
key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
|
||||||
|
changed = True
|
||||||
|
if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk:
|
||||||
|
logging.getLogger('').warning('Invalid rule - what went wrong here??')
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
changed = True
|
||||||
|
if rule.bk_conditional_set is not None:
|
||||||
|
rule.bk_conditional_set.difference_update(key_logic.bk_restricted)
|
||||||
|
rule.bk_conditional_set.difference_update(max_ctr.key_only_locations)
|
||||||
|
if len(rule.bk_conditional_set) == 0:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
if rule.check_locations_wo_bk:
|
||||||
|
rule.check_locations_wo_bk.difference_update(key_logic.sm_restricted)
|
||||||
|
key_onlys = rule.check_locations_wo_bk.intersection(max_ctr.key_only_locations)
|
||||||
|
if len(key_onlys) > 0:
|
||||||
|
rule.check_locations_wo_bk.difference_update(key_onlys)
|
||||||
|
rule.needed_keys_wo_bk -= len(key_onlys)
|
||||||
|
if rule.needed_keys_wo_bk == 0:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
if len(rule.check_locations_wo_bk) < rule.needed_keys_wo_bk or rule.needed_keys_wo_bk > key_layout.max_chests:
|
||||||
|
if len(rule.bk_conditional_set) > 0:
|
||||||
|
key_logic.bk_restricted.update(rule.bk_conditional_set)
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
changed = True # impossible for bk to be here, I think
|
||||||
|
for rule_a, rule_b in itertools.combinations([x for x in key_logic.placement_rules if x not in rules_to_remove], 2):
|
||||||
|
if rule_b.bk_conditional_set and rule_a.check_locations_w_bk:
|
||||||
|
temp = rule_a
|
||||||
|
rule_a = rule_b
|
||||||
|
rule_b = temp
|
||||||
|
if rule_a.bk_conditional_set and rule_b.check_locations_w_bk:
|
||||||
|
common_needed = min(rule_a.needed_keys_wo_bk, rule_b.needed_keys_w_bk)
|
||||||
|
if len(rule_b.check_locations_w_bk & rule_a.check_locations_wo_bk) < common_needed:
|
||||||
|
key_logic.bk_restricted.update(rule_a.bk_conditional_set)
|
||||||
|
rules_to_remove.append(rule_a)
|
||||||
|
changed = True
|
||||||
|
break
|
||||||
|
equivalent_rules = []
|
||||||
|
for rule in key_logic.placement_rules:
|
||||||
|
for rule2 in key_logic.placement_rules:
|
||||||
|
if rule != rule2:
|
||||||
|
if rule.check_locations_w_bk and rule2.check_locations_w_bk:
|
||||||
|
if rule2.check_locations_w_bk == rule.check_locations_w_bk and rule2.needed_keys_w_bk > rule.needed_keys_w_bk:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
elif rule2.needed_keys_w_bk == rule.needed_keys_w_bk and rule2.check_locations_w_bk < rule.check_locations_w_bk:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
elif rule2.check_locations_w_bk == rule.check_locations_w_bk and rule2.needed_keys_w_bk == rule.needed_keys_w_bk:
|
||||||
|
equivalent_rules.append((rule, rule2))
|
||||||
|
if rule.check_locations_wo_bk and rule2.check_locations_wo_bk and rule.bk_conditional_set == rule2.bk_conditional_set:
|
||||||
|
if rule2.check_locations_wo_bk == rule.check_locations_wo_bk and rule2.needed_keys_wo_bk > rule.needed_keys_wo_bk:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
elif rule2.needed_keys_wo_bk == rule.needed_keys_wo_bk and rule2.check_locations_wo_bk < rule.check_locations_wo_bk:
|
||||||
|
rules_to_remove.append(rule)
|
||||||
|
elif rule2.check_locations_wo_bk == rule.check_locations_wo_bk and rule2.needed_keys_wo_bk == rule.needed_keys_wo_bk:
|
||||||
|
equivalent_rules.append((rule, rule2))
|
||||||
|
if len(rules_to_remove) > 0:
|
||||||
|
key_logic.placement_rules = [x for x in key_logic.placement_rules if x not in rules_to_remove]
|
||||||
|
equivalent_rules = [x for x in equivalent_rules if x[0] not in rules_to_remove and x[1] not in rules_to_remove]
|
||||||
|
if len(equivalent_rules) > 0:
|
||||||
|
removed_rules = {}
|
||||||
|
for r1, r2 in equivalent_rules:
|
||||||
|
if r1 in removed_rules.keys():
|
||||||
|
r1 = removed_rules[r1]
|
||||||
|
if r2 in removed_rules.keys():
|
||||||
|
r2 = removed_rules[r2]
|
||||||
|
if r1 != r2:
|
||||||
|
r1.door_reference += ','+r2.door_reference
|
||||||
|
key_logic.placement_rules.remove(r2)
|
||||||
|
removed_rules[r2] = r1
|
||||||
|
|
||||||
|
|
||||||
|
def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player):
|
||||||
|
key_logic = key_layout.key_logic
|
||||||
|
rule = PlacementRule()
|
||||||
|
rule.door_reference = code
|
||||||
|
rule.small_key = key_logic.small_key_name
|
||||||
|
rule.needed_keys_w_bk = min_keys
|
||||||
|
if key_counter.big_key_opened and rule.needed_keys_w_bk + 1 > len(accessible_loc):
|
||||||
|
# indicates that the big key cannot be in the accessible locations
|
||||||
|
key_logic.bk_restricted.update(accessible_loc.difference(max_ctr.key_only_locations))
|
||||||
|
else:
|
||||||
|
placement_self_lock_adjustment(rule, max_ctr, blocked_loc, key_counter, world, player)
|
||||||
|
rule.check_locations_w_bk = accessible_loc
|
||||||
|
check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc)
|
||||||
|
key_logic.placement_rules.append(rule)
|
||||||
|
|
||||||
|
|
||||||
def queue_sorter(queue_item):
|
def queue_sorter(queue_item):
|
||||||
@@ -314,8 +449,8 @@ def find_bk_locked_sections(key_layout, world, player):
|
|||||||
big_chest_allowed_big_key = world.accessibility[player] != 'locations'
|
big_chest_allowed_big_key = world.accessibility[player] != '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.item_locations.update(counter.free_locations)
|
||||||
key_layout.all_locations.update(counter.key_only_locations)
|
key_layout.item_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:
|
||||||
@@ -710,6 +845,16 @@ def count_unique_sm_doors(doors):
|
|||||||
return len(unique_d_set)
|
return len(unique_d_set)
|
||||||
|
|
||||||
|
|
||||||
|
def big_key_progress(key_counter):
|
||||||
|
return not only_sm_doors(key_counter) or exist_big_chest(key_counter)
|
||||||
|
|
||||||
|
|
||||||
|
def only_sm_doors(key_counter):
|
||||||
|
for door in key_counter.child_doors:
|
||||||
|
if door.bigKey:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
# doesn't count dest doors
|
# doesn't count dest doors
|
||||||
def count_unique_small_doors(key_counter, proposal):
|
def count_unique_small_doors(key_counter, proposal):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
@@ -738,6 +883,13 @@ def exist_relevant_big_doors(key_counter, key_layout):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def exist_big_chest(key_counter):
|
||||||
|
for loc in key_counter.free_locations:
|
||||||
|
if '- Big Chest' in loc.name:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def count_locations_big_optional(locations, bk=False):
|
def count_locations_big_optional(locations, bk=False):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in locations:
|
for loc in locations:
|
||||||
@@ -989,6 +1141,13 @@ def reduce_rules(small_rules, collected, collected_alt):
|
|||||||
rule.small_key_num = collected
|
rule.small_key_num = collected
|
||||||
|
|
||||||
|
|
||||||
|
def set_paired_rules(key_logic, world, player):
|
||||||
|
for d_name, rule in key_logic.door_rules.items():
|
||||||
|
door = world.get_door(d_name, player)
|
||||||
|
if door.dest.name in key_logic.door_rules.keys():
|
||||||
|
rule.opposite = key_logic.door_rules[door.dest.name]
|
||||||
|
|
||||||
|
|
||||||
# Soft lock stuff
|
# Soft lock stuff
|
||||||
def validate_key_layout(key_layout, world, player):
|
def validate_key_layout(key_layout, world, player):
|
||||||
# retro is all good - except for hyrule castle in standard mode
|
# retro is all good - except for hyrule castle in standard mode
|
||||||
@@ -1015,7 +1174,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
ttl_key_only = count_key_only_locations(state)
|
ttl_key_only = count_key_only_locations(state)
|
||||||
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_key_only, state, world, player)
|
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_key_only, state, world, player)
|
||||||
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(key_layout, state, prev_state, prev_avail, world, player):
|
||||||
return False
|
return False
|
||||||
# todo: allow more key shuffles - refine placement rules
|
# 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 available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||||
@@ -1054,18 +1213,24 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
def invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player):
|
||||||
if prev_state is None or state.used_smalls == prev_state.used_smalls:
|
if prev_state is None or state.used_smalls == prev_state.used_smalls:
|
||||||
return False
|
return False
|
||||||
new_locations = set(state.found_locations).difference(set(prev_state.found_locations))
|
new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors))
|
||||||
|
state_copy = state.copy()
|
||||||
|
while len(new_bk_doors) > 0:
|
||||||
|
for door in new_bk_doors:
|
||||||
|
open_a_door(door.door, state_copy, key_layout.flat_prop)
|
||||||
|
new_bk_doors = set(state_copy.big_doors).difference(set(prev_state.big_doors))
|
||||||
|
expand_key_state(state_copy, key_layout.flat_prop, world, player)
|
||||||
|
new_locations = set(state_copy.found_locations).difference(set(prev_state.found_locations))
|
||||||
important_found = False
|
important_found = False
|
||||||
for loc in new_locations:
|
for loc in new_locations:
|
||||||
important_found |= important_location(loc, world, player)
|
important_found |= important_location(loc, world, player)
|
||||||
if not important_found:
|
if not important_found:
|
||||||
return False
|
return False
|
||||||
new_small_doors = set(state.small_doors).difference(set(prev_state.small_doors))
|
new_small_doors = set(state.small_doors).difference(set(prev_state.small_doors))
|
||||||
new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors))
|
if len(new_small_doors) > 0:
|
||||||
if len(new_small_doors) > 0 or len(new_bk_doors) > 0:
|
|
||||||
return False
|
return False
|
||||||
return prev_avail - 1 == 0
|
return prev_avail - 1 == 0
|
||||||
|
|
||||||
@@ -1151,11 +1316,21 @@ def create_key_counter(state, key_layout, world, player):
|
|||||||
return key_counter
|
return key_counter
|
||||||
|
|
||||||
|
|
||||||
def important_location(loc, world, player):
|
imp_locations = None
|
||||||
important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
|
||||||
|
|
||||||
|
def imp_locations_factory(world, player):
|
||||||
|
global imp_locations
|
||||||
|
if imp_locations:
|
||||||
|
return imp_locations
|
||||||
|
imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
||||||
if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed':
|
if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed':
|
||||||
important_locations.append('Hyrule Dungeon Cellblock')
|
imp_locations.append('Hyrule Dungeon Cellblock')
|
||||||
return '- Prize' in loc.name or loc.name in important_locations
|
return imp_locations
|
||||||
|
|
||||||
|
|
||||||
|
def important_location(loc, world, player):
|
||||||
|
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player)
|
||||||
|
|
||||||
|
|
||||||
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
|
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
|
||||||
@@ -1428,7 +1603,7 @@ def validate_key_placement(key_layout, world, player):
|
|||||||
max_counter = find_max_counter(key_layout)
|
max_counter = find_max_counter(key_layout)
|
||||||
big_key_outside = dungeon.big_key not in (l.item for l in max_counter.free_locations)
|
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():
|
for code, counter in key_layout.key_counters.items():
|
||||||
if len(counter.child_doors) == 0:
|
if len(counter.child_doors) == 0:
|
||||||
continue
|
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
|
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
|
||||||
@@ -1445,6 +1620,7 @@ def validate_key_placement(key_layout, world, player):
|
|||||||
# missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
|
# 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):
|
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: ")
|
logging.getLogger('').error("Keylock - can't open locations: ")
|
||||||
|
logging.getLogger('').error("code: " + code)
|
||||||
for i in missing_locations:
|
for i in missing_locations:
|
||||||
logging.getLogger('').error(i)
|
logging.getLogger('').error(i)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -540,7 +540,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'Mire 2', 'Misery Mire', None, ['Mire 2 Up Stairs', 'Mire 2 NE']),
|
create_dungeon_region(player, 'Mire 2', 'Misery Mire', None, ['Mire 2 Up Stairs', 'Mire 2 NE']),
|
||||||
create_dungeon_region(player, 'Mire Hub', 'Misery Mire', None, ['Mire Hub SE', 'Mire Hub ES', 'Mire Hub E', 'Mire Hub NE', 'Mire Hub WN', 'Mire Hub WS', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier']),
|
create_dungeon_region(player, 'Mire Hub', 'Misery Mire', None, ['Mire Hub SE', 'Mire Hub ES', 'Mire Hub E', 'Mire Hub NE', 'Mire Hub WN', 'Mire Hub WS', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier']),
|
||||||
create_dungeon_region(player, 'Mire Hub Right', 'Misery Mire', None, ['Mire Hub Right EN', 'Mire Hub Right Blue Barrier']),
|
create_dungeon_region(player, 'Mire Hub Right', 'Misery Mire', None, ['Mire Hub Right EN', 'Mire Hub Right Blue Barrier']),
|
||||||
create_dungeon_region(player, 'Mire Hub Top', 'Misery Mire', ['Misery Mire - Main Lobby'], ['Mire Hub Top NW', 'Mire Hub Top Blue Barrier']),
|
create_dungeon_region(player, 'Mire Hub Top', 'Misery Mire', None, ['Mire Hub Top NW', 'Mire Hub Top Blue Barrier']),
|
||||||
|
create_dungeon_region(player, 'Mire Hub Switch', 'Misery Mire', ['Misery Mire - Main Lobby'], ['Mire Hub Switch Blue Barrier N', 'Mire Hub Switch Blue Barrier S']),
|
||||||
create_dungeon_region(player, 'Mire Lone Shooter', 'Misery Mire', None, ['Mire Lone Shooter WS', 'Mire Lone Shooter ES']),
|
create_dungeon_region(player, 'Mire Lone Shooter', 'Misery Mire', None, ['Mire Lone Shooter WS', 'Mire Lone Shooter ES']),
|
||||||
create_dungeon_region(player, 'Mire Failure Bridge', 'Misery Mire', None, ['Mire Failure Bridge W', 'Mire Failure Bridge E']),
|
create_dungeon_region(player, 'Mire Failure Bridge', 'Misery Mire', None, ['Mire Failure Bridge W', 'Mire Failure Bridge E']),
|
||||||
create_dungeon_region(player, 'Mire Falling Bridge', 'Misery Mire', ['Misery Mire - Big Chest'], ['Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Mire Falling Bridge WN']),
|
create_dungeon_region(player, 'Mire Falling Bridge', 'Misery Mire', ['Misery Mire - Big Chest'], ['Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Mire Falling Bridge WN']),
|
||||||
|
|||||||
10
Rules.py
10
Rules.py
@@ -342,6 +342,8 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
|
set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
|
||||||
set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
|
set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
|
||||||
set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
|
set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Hub Switch Blue Barrier N', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Switch', player), player))
|
||||||
|
set_rule(world.get_entrance('Mire Hub Switch Blue Barrier S', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Switch', player), player))
|
||||||
set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
|
set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
|
||||||
set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
|
set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
|
||||||
set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
|
set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
|
||||||
@@ -1516,7 +1518,8 @@ bunny_impassible_doors = {
|
|||||||
'Ice Crystal Right Blue Hole', 'Ice Crystal Left Blue Barrier', 'Ice Big Chest Landing Push Blocks',
|
'Ice Crystal Right Blue Hole', 'Ice Crystal Left Blue Barrier', 'Ice Big Chest Landing Push Blocks',
|
||||||
'Ice Backwards Room Hole', 'Ice Switch Room SE', 'Ice Antechamber NE', 'Ice Antechamber Hole', 'Mire Lobby Gap',
|
'Ice Backwards Room Hole', 'Ice Switch Room SE', 'Ice Antechamber NE', 'Ice Antechamber Hole', 'Mire Lobby Gap',
|
||||||
'Mire Post-Gap Gap', 'Mire 2 NE', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier',
|
'Mire Post-Gap Gap', 'Mire 2 NE', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier',
|
||||||
'Mire Hub Right Blue Barrier', 'Mire Hub Top Blue Barrier', 'Mire Falling Bridge WN',
|
'Mire Hub Right Blue Barrier', 'Mire Hub Top Blue Barrier', 'Mire Hub Switch Blue Barrier N',
|
||||||
|
'Mire Hub Switch Blue Barrier S', 'Mire Falling Bridge WN',
|
||||||
'Mire Map Spike Side Blue Barrier', 'Mire Map Spot Blue Barrier', 'Mire Crystal Dead End Left Barrier',
|
'Mire Map Spike Side Blue Barrier', 'Mire Map Spot Blue Barrier', 'Mire Crystal Dead End Left Barrier',
|
||||||
'Mire Crystal Dead End Right Barrier', 'Mire Cross ES', 'Mire Hidden Shooters Block Path S',
|
'Mire Crystal Dead End Right Barrier', 'Mire Cross ES', 'Mire Hidden Shooters Block Path S',
|
||||||
'Mire Hidden Shooters Block Path N', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
|
'Mire Hidden Shooters Block Path N', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
|
||||||
@@ -1546,7 +1549,10 @@ def add_key_logic_rules(world, player):
|
|||||||
key_logic = world.key_logic[player]
|
key_logic = world.key_logic[player]
|
||||||
for d_name, d_logic in key_logic.items():
|
for d_name, d_logic in key_logic.items():
|
||||||
for door_name, keys in d_logic.door_rules.items():
|
for door_name, keys in d_logic.door_rules.items():
|
||||||
add_rule(world.get_entrance(door_name, player), create_advanced_key_rule(d_logic, player, keys))
|
spot = world.get_entrance(door_name, player)
|
||||||
|
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
|
||||||
|
if keys.opposite:
|
||||||
|
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
|
||||||
for location in d_logic.bk_restricted:
|
for location in d_logic.bk_restricted:
|
||||||
if location.name not in key_only_locations.keys():
|
if location.name not in key_only_locations.keys():
|
||||||
forbid_item(location, d_logic.bk_name, player)
|
forbid_item(location, d_logic.bk_name, player)
|
||||||
|
|||||||
Reference in New Issue
Block a user