Couple minor door fixes

New key logic algorithm - some advanced rules in place to allow more locations
Changed generation to handle big key placement better
This commit is contained in:
aerinon
2019-11-26 16:43:43 -07:00
parent 1467b76d84
commit c64b2269c0
6 changed files with 468 additions and 287 deletions

View File

@@ -127,12 +127,9 @@ def vanilla_key_logic(world, player):
raise Exception('Vanilla key layout not valid %s' % sector.name) raise Exception('Vanilla key layout not valid %s' % sector.name)
if player not in world.key_logic.keys(): if player not in world.key_logic.keys():
world.key_logic[player] = {} world.key_logic[player] = {}
if sector.name in ['Agahnims Tower', 'Tower of Hera', 'Desert Palace', 'Eastern Palace']: key_layout_2 = KeyLayout(sector, start_regions, doors)
key_layout_2 = KeyLayout(sector, start_regions, doors) key_layout_2 = analyze_dungeon(key_layout_2, world, player)
key_layout_2 = analyze_dungeon(key_layout_2, world, player) world.key_logic[player][sector.name] = key_layout_2.key_logic
world.key_logic[player][sector.name] = key_layout_2.key_logic
else:
world.key_logic[player][sector.name] = key_layout.key_logic
validate_vanilla_key_logic(world, player) validate_vanilla_key_logic(world, player)
@@ -297,16 +294,17 @@ def within_dungeon(world, player):
last_key = None last_key = None
while len(sector_queue) > 0: while len(sector_queue) > 0:
key, sector_list, entrance_list = sector_queue.popleft() key, sector_list, entrance_list = sector_queue.popleft()
split_dungeon = key in split_region_starts.keys()
origin_list = list(entrance_list) origin_list = list(entrance_list)
find_enabled_origins(sector_list, enabled_entrances, origin_list, entrances_map, key) find_enabled_origins(sector_list, enabled_entrances, origin_list, entrances_map, key)
origin_list_sans_drops = remove_drop_origins(origin_list) origin_list_sans_drops = remove_drop_origins(origin_list)
if len(origin_list) <= 0: if len(origin_list_sans_drops) <= 0:
if last_key == key: if last_key == key:
raise Exception('Infinte loop detected %s' % key) raise Exception('Infinte loop detected %s' % key)
sector_queue.append((key, sector_list, entrance_list)) sector_queue.append((key, sector_list, entrance_list))
last_key = key last_key = key
else: else:
ds = generate_dungeon(sector_list, origin_list_sans_drops, world, player) ds = generate_dungeon(sector_list, origin_list_sans_drops, split_dungeon, world, player)
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player) find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
ds.name = key ds.name = key
layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list
@@ -340,7 +338,7 @@ def determine_entrance_list(world, player):
for ent in region.entrances: for ent in region.entrances:
parent = ent.parent_region parent = ent.parent_region
if parent.type != RegionType.Dungeon or parent.name == 'Sewer Drop': if parent.type != RegionType.Dungeon or parent.name == 'Sewer Drop':
if parent.name not in world.inaccessible_regions[player] or drop_exception(region_name): if parent.name not in world.inaccessible_regions[player]:
entrance_map[key].append(region_name) entrance_map[key].append(region_name)
else: else:
if ent.parent_region not in potential_entrances.keys(): if ent.parent_region not in potential_entrances.keys():
@@ -366,8 +364,11 @@ def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key):
for region in sector.regions: for region in sector.regions:
if region.name in enabled.keys() and region.name not in entrance_list: if region.name in enabled.keys() and region.name not in entrance_list:
entrance_list.append(region.name) entrance_list.append(region.name)
if enabled[region.name] != region.name: origin_reg, origin_dungeon = enabled[region.name]
if origin_reg != region.name and origin_dungeon != region.dungeon:
entrance_map[key].append(region.name) entrance_map[key].append(region.name)
if drop_exception(region.name): # only because they have unique regions
entrance_list.append(region.name)
def remove_drop_origins(entrance_list): def remove_drop_origins(entrance_list):
@@ -379,7 +380,7 @@ def find_new_entrances(sector, connections, potentials, enabled, world, player):
if region.name in connections.keys() and connections[region.name] in potentials.keys(): if region.name in connections.keys() and connections[region.name] in potentials.keys():
new_region = connections[region.name] new_region = connections[region.name]
for potential in potentials.pop(new_region): for potential in potentials.pop(new_region):
enabled[potential] = region.name enabled[potential] = (region.name, region.dungeon)
# see if this unexplored region connects elsewhere # see if this unexplored region connects elsewhere
queue = collections.deque(new_region.exits) queue = collections.deque(new_region.exits)
visited = set() visited = set()
@@ -389,7 +390,7 @@ def find_new_entrances(sector, connections, potentials, enabled, world, player):
region_name = ext.connected_region.name region_name = ext.connected_region.name
if region_name in connections.keys() and connections[region_name] in potentials.keys(): if region_name in connections.keys() and connections[region_name] in potentials.keys():
for potential in potentials.pop(connections[region_name]): for potential in potentials.pop(connections[region_name]):
enabled[potential] = region.name enabled[potential] = (region.name, region.dungeon)
if ext.connected_region.name in world.inaccessible_regions[player]: if ext.connected_region.name in world.inaccessible_regions[player]:
for new_exit in ext.connected_region.exits: for new_exit in ext.connected_region.exits:
if new_exit not in visited: if new_exit not in visited:
@@ -528,6 +529,7 @@ def find_compatible_door_in_regions(world, door, regions, player):
return region, proposed_door return region, proposed_door
return None, None return None, None
def find_compatible_door_in_list(ugly_regions, world, door, doors, player): def find_compatible_door_in_list(ugly_regions, world, door, doors, player):
if door.type in [DoorType.Hole, DoorType.Warp, DoorType.Logical]: if door.type in [DoorType.Hole, DoorType.Warp, DoorType.Logical]:
return door return door
@@ -877,8 +879,30 @@ def shuffle_key_doors(dungeon_sector, entrances, world, player):
# make changes # make changes
if player not in world.key_logic.keys(): if player not in world.key_logic.keys():
world.key_logic[player] = {} world.key_logic[player] = {}
world.key_logic[player][dungeon_sector.name] = key_layout.key_logic key_layout_new = analyze_dungeon(key_layout, world, player)
reassign_key_doors(current_doors, proposal, world, player) reassign_key_doors(current_doors, proposal, world, player)
log_key_logic(dungeon_sector.name, key_layout_new.key_logic)
world.key_logic[player][dungeon_sector.name] = key_layout_new.key_logic
def log_key_logic(d_name, key_logic):
logger = logging.getLogger('')
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Key Logic for %s', d_name)
if len(key_logic.bk_restricted) > 0:
logger.debug('-BK Restrictions')
for restriction in key_logic.bk_restricted:
logger.debug(restriction)
if len(key_logic.sm_restricted) > 0:
logger.debug('-Small Restrictions')
for restriction in key_logic.sm_restricted:
logger.debug(restriction)
for key in key_logic.door_rules.keys():
rule = key_logic.door_rules[key]
logger.debug('--Rule for %s: Nrm:%s Allow:%s Loc:%s Alt:%s', key, rule.small_key_num, rule.allow_small, rule.small_location, rule.alternate_small_key)
if rule.alternate_small_key is not None:
for loc in rule.alternate_big_key_loc:
logger.debug('---BK Loc %s', loc.name)
class KeyLayout(object): class KeyLayout(object):
@@ -1792,7 +1816,7 @@ interior_doors = [
('Skull East Bridge ES', 'Skull West Bridge Nook WS'), ('Skull East Bridge ES', 'Skull West Bridge Nook WS'),
('Skull Star Pits WS', 'Skull Torch Room ES'), ('Skull Star Pits WS', 'Skull Torch Room ES'),
('Skull Torch Room EN', 'Skull Vines WN'), ('Skull Torch Room EN', 'Skull Vines WN'),
('Skull Spike Corner WS', 'Skull Final Drop ES'), ('Skull Spike Corner ES', 'Skull Final Drop WS'),
('Thieves Hallway WS', 'Thieves Pot Alcove Mid ES'), ('Thieves Hallway WS', 'Thieves Pot Alcove Mid ES'),
('Thieves Conveyor Maze SW', 'Thieves Pot Alcove Top NW'), ('Thieves Conveyor Maze SW', 'Thieves Pot Alcove Top NW'),
('Thieves Conveyor Maze EN', 'Thieves Hallway WN'), ('Thieves Conveyor Maze EN', 'Thieves Hallway WN'),
@@ -1943,7 +1967,7 @@ default_small_key_doors = {
('Skull Map Room SE', 'Skull Pinball NE'), ('Skull Map Room SE', 'Skull Pinball NE'),
('Skull 2 West Lobby NW', 'Skull X Room SW'), ('Skull 2 West Lobby NW', 'Skull X Room SW'),
('Skull 3 Lobby NW', 'Skull Star Pits SW'), ('Skull 3 Lobby NW', 'Skull Star Pits SW'),
('Skull Spike Corner WS', 'Skull Final Drop ES') ('Skull Spike Corner ES', 'Skull Final Drop WS')
], ],
'Thieves Town': [ 'Thieves Town': [
('Thieves Hallway WS', 'Thieves Pot Alcove Mid ES'), ('Thieves Hallway WS', 'Thieves Pot Alcove Mid ES'),

View File

@@ -524,8 +524,8 @@ def create_doors(world, player):
create_door(player, 'Skull Vines WN', Intr).dir(We, 0x49, Top, High).pos(1), create_door(player, 'Skull Vines WN', Intr).dir(We, 0x49, Top, High).pos(1),
create_door(player, 'Skull Vines NW', Nrml).dir(No, 0x49, Left, High).pos(0), create_door(player, 'Skull Vines NW', Nrml).dir(No, 0x49, Left, High).pos(0),
create_door(player, 'Skull Spike Corner SW', Nrml).dir(So, 0x39, Left, High).no_exit().trap(0x4).pos(0), create_door(player, 'Skull Spike Corner SW', Nrml).dir(So, 0x39, Left, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Skull Spike Corner WS', Intr).dir(We, 0x39, Bot, High).small_key().pos(1), create_door(player, 'Skull Spike Corner ES', Intr).dir(Ea, 0x39, Bot, High).small_key().pos(1),
create_door(player, 'Skull Final Drop ES', Intr).dir(Ea, 0x39, Bot, High).small_key().pos(1), create_door(player, 'Skull Final Drop WS', Intr).dir(We, 0x39, Bot, High).small_key().pos(1),
create_door(player, 'Skull Final Drop Hole', Hole), create_door(player, 'Skull Final Drop Hole', Hole),
create_door(player, 'Thieves Lobby N Edge', Open).dir(No, 0xdb, None, Low), create_door(player, 'Thieves Lobby N Edge', Open).dir(No, 0xdb, None, Low),
@@ -984,7 +984,7 @@ def create_doors(world, player):
create_door(player, 'GT Four Torches NW', Intr).dir(No, 0x1c, Left, High).pos(2), create_door(player, 'GT Four Torches NW', Intr).dir(No, 0x1c, Left, High).pos(2),
create_door(player, 'GT Fairy Abyss SW', Intr).dir(So, 0x1c, Left, High).pos(2), create_door(player, 'GT Fairy Abyss SW', Intr).dir(So, 0x1c, Left, High).pos(2),
create_door(player, 'GT Four Torches Up Stairs', Sprl).dir(Up, 0x1c, 0, HTH).ss(Z, 0x1b, 0x2c, True, True), create_door(player, 'GT Four Torches Up Stairs', Sprl).dir(Up, 0x1c, 0, HTH).ss(Z, 0x1b, 0x2c, True, True),
create_door(player, 'GT Crystal Paths Down Stairs', Sprl).dir(Dn, 0x6b, 0, HTH).ss(A, 0x12, 0x00, True), create_door(player, 'GT Crystal Paths Down Stairs', Sprl).dir(Dn, 0x6b, 0, HTH).ss(A, 0x12, 0x00, False, True),
create_door(player, 'GT Crystal Paths SW', Intr).dir(So, 0x6b, Left, High).pos(3), create_door(player, 'GT Crystal Paths SW', Intr).dir(So, 0x6b, Left, High).pos(3),
create_door(player, 'GT Mimics 1 NW', Intr).dir(No, 0x6b, Left, High).pos(3), create_door(player, 'GT Mimics 1 NW', Intr).dir(No, 0x6b, Left, High).pos(3),
create_door(player, 'GT Mimics 1 ES', Intr).dir(Ea, 0x6b, Bot, High).pos(2), create_door(player, 'GT Mimics 1 ES', Intr).dir(Ea, 0x6b, Bot, High).pos(2),
@@ -1044,7 +1044,7 @@ def create_doors(world, player):
create_door(player, 'GT Right Moldorm Ledge Down Stairs', Sprl).dir(Dn, 0x4d, 0, HTH).ss(S, 0x12, 0x80), create_door(player, 'GT Right Moldorm Ledge Down Stairs', Sprl).dir(Dn, 0x4d, 0, HTH).ss(S, 0x12, 0x80),
create_door(player, 'GT Moldorm Pit Up Stairs', Sprl).dir(Up, 0xa6, 0, HTH).ss(S, 0x1b, 0x6c), create_door(player, 'GT Moldorm Pit Up Stairs', Sprl).dir(Up, 0xa6, 0, HTH).ss(S, 0x1b, 0x6c),
create_door(player, 'GT Frozen Over ES', Nrml).dir(Ea, 0x4c, Bot, High).trap(0x4).pos(0), create_door(player, 'GT Frozen Over ES', Nrml).dir(Ea, 0x4c, Bot, High).trap(0x4).pos(0),
create_door(player, 'GT Frozen Over Up Stairs', Sprl).dir(Up, 0x4c, 0, HTH).ss(S, 0x1a, 0x6c, False, True), create_door(player, 'GT Frozen Over Up Stairs', Sprl).dir(Up, 0x4c, 0, HTH).ss(S, 0x1a, 0x6c, True),
create_door(player, 'GT Brightly Lit Hall Down Stairs', Sprl).dir(Dn, 0x1d, 0, HTH).ss(S, 0x11, 0x80, False, True), create_door(player, 'GT Brightly Lit Hall Down Stairs', Sprl).dir(Dn, 0x1d, 0, HTH).ss(S, 0x11, 0x80, False, True),
create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0), create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0),
create_door(player, 'GT Agahnim 2 SW', Nrml).dir(So, 0x0d, Left, High).no_exit().trap(0x4).pos(0) create_door(player, 'GT Agahnim 2 SW', Nrml).dir(So, 0x0d, Left, High).no_exit().trap(0x4).pos(0)

View File

@@ -24,17 +24,20 @@ class GraphPiece:
self.hanger_crystal = None self.hanger_crystal = None
self.hooks = {} self.hooks = {}
self.visited_regions = set() self.visited_regions = set()
self.possible_bk_locations = set()
def generate_dungeon(available_sectors, entrance_region_names, world, player): def generate_dungeon(available_sectors, entrance_region_names, split_dungeon, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
entrance_regions = convert_regions(entrance_region_names, world, player) entrance_regions = convert_regions(entrance_region_names, world, player)
doors_to_connect = set() doors_to_connect = set()
all_regions = set() all_regions = set()
bk_needed = False
for sector in available_sectors: for sector in available_sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
doors_to_connect.add(door) doors_to_connect.add(door)
all_regions.update(sector.regions) all_regions.update(sector.regions)
bk_needed = bk_needed or determine_if_bk_needed(sector, split_dungeon, world, player)
proposed_map = {} proposed_map = {}
choices_master = [[]] choices_master = [[]]
depth = 0 depth = 0
@@ -51,7 +54,7 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
if depth not in dungeon_cache.keys(): if depth not in dungeon_cache.keys():
dungeon, hangers, hooks = gen_dungeon_info(available_sectors, entrance_regions, proposed_map, doors_to_connect, world, player) dungeon, hangers, hooks = gen_dungeon_info(available_sectors, entrance_regions, proposed_map, doors_to_connect, world, player)
dungeon_cache[depth] = dungeon, hangers, hooks dungeon_cache[depth] = dungeon, hangers, hooks
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions) valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed)
else: else:
dungeon, hangers, hooks = dungeon_cache[depth] dungeon, hangers, hooks = dungeon_cache[depth]
valid = True valid = True
@@ -96,10 +99,20 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
return master_sector return master_sector
def determine_if_bk_needed(sector, split_dungeon, world, player):
if not split_dungeon:
for region in sector.regions:
for ext in region.exits:
door = world.check_for_door(ext.name, player)
if door is not None and door.bigKey:
return True
return False
def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, world, player): def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, world, player):
# step 1 create dungeon: Dict<DoorName|Origin, GraphPiece> # step 1 create dungeon: Dict<DoorName|Origin, GraphPiece>
dungeon = {} dungeon = {}
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, True, world, player) original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, world, player)
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map) dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
doors_to_connect = set() doors_to_connect = set()
hanger_set = set() hanger_set = set()
@@ -110,7 +123,7 @@ def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_do
if not door.stonewall and door not in proposed_map.keys(): if not door.stonewall and door not in proposed_map.keys():
hanger_set.add(door) hanger_set.add(door)
parent = parent_region(door, world, player).parent_region parent = parent_region(door, world, player).parent_region
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, False, world, player) o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, world, player)
o_state_cache[door.name] = o_state o_state_cache[door.name] = o_state
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map) piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
dungeon[door.name] = piece dungeon[door.name] = piece
@@ -170,7 +183,7 @@ def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_do
def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player): def explore_blue_state(door, dungeon, o_state, proposed_map, valid_doors, world, player):
parent = parent_region(door, world, player).parent_region parent = parent_region(door, world, player).parent_region
blue_start = ExplorationState(CrystalBarrier.Blue) blue_start = ExplorationState(CrystalBarrier.Blue)
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, False, world, player) b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, world, player)
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map) dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
@@ -231,7 +244,7 @@ def filter_choices(next_hanger, door, orig_hang, prev_choices, hook_candidates):
return next_hanger != door and orig_hang != next_hanger and door not in hook_candidates return next_hanger != door and orig_hang != next_hanger and door not in hook_candidates
def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions): def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed):
# evaluate if everything is still plausible # evaluate if everything is still plausible
# only origin is left in the dungeon and not everything is connected # only origin is left in the dungeon and not everything is connected
@@ -265,10 +278,15 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
if len(outstanding_doors[key]) > 0 and len(hangers[key]) == 0 and len(hooks[opp_key]) == 0: if len(outstanding_doors[key]) > 0 and len(hangers[key]) == 0 and len(hooks[opp_key]) == 0:
return False return False
all_visited = set() all_visited = set()
bk_possible = not bk_needed
for piece in dungeon.values(): for piece in dungeon.values():
all_visited.update(piece.visited_regions) all_visited.update(piece.visited_regions)
if not bk_possible and len(piece.possible_bk_locations) > 0:
bk_possible = True
if len(all_regions.difference(all_visited)) > 0: if len(all_regions.difference(all_visited)) > 0:
return False return False
if not bk_possible:
return False
new_hangers_found = True new_hangers_found = True
accessible_hook_types = [] accessible_hook_types = []
hanger_matching = set() hanger_matching = set()
@@ -346,9 +364,15 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map):
graph_piece.visited_regions.update(o_state.visited_orange) graph_piece.visited_regions.update(o_state.visited_orange)
graph_piece.visited_regions.update(b_state.visited_blue) graph_piece.visited_regions.update(b_state.visited_blue)
graph_piece.visited_regions.update(b_state.visited_orange) graph_piece.visited_regions.update(b_state.visited_orange)
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found))
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found))
return graph_piece return graph_piece
def filter_for_potential_bk_locations(locations):
return [x for x in locations if '- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events and x.name not in key_only_locations.keys()]
def parent_region(door, world, player): def parent_region(door, world, player):
return world.get_entrance(door.name, player) return world.get_entrance(door.name, player)
@@ -496,6 +520,7 @@ class ExplorationState(object):
self.used_locations = 0 self.used_locations = 0
self.key_locations = 0 self.key_locations = 0
self.used_smalls = 0 self.used_smalls = 0
self.bk_found = set()
self.non_door_entrances = [] self.non_door_entrances = []
@@ -520,6 +545,7 @@ class ExplorationState(object):
ret.used_locations = self.used_locations ret.used_locations = self.used_locations
ret.used_smalls = self.used_smalls ret.used_smalls = self.used_smalls
ret.found_locations = list(self.found_locations) ret.found_locations = list(self.found_locations)
ret.bk_found = set(self.bk_found)
ret.non_door_entrances = list(self.non_door_entrances) ret.non_door_entrances = list(self.non_door_entrances)
return ret return ret
@@ -529,7 +555,7 @@ class ExplorationState(object):
self.crystal = exp_door.crystal self.crystal = exp_door.crystal
return exp_door return exp_door
def visit_region(self, region, key_region=None, key_checks=False): def visit_region(self, region, key_region=None, key_checks=False, bk_Flag=False):
if self.crystal == CrystalBarrier.Either: if self.crystal == CrystalBarrier.Either:
if region not in self.visited_blue: if region not in self.visited_blue:
self.visited_blue.append(region) self.visited_blue.append(region)
@@ -547,6 +573,8 @@ class ExplorationState(object):
self.ttl_locations += 1 self.ttl_locations += 1
if location not in self.found_locations: if location not in self.found_locations:
self.found_locations.append(location) self.found_locations.append(location)
if not bk_Flag:
self.bk_found.add(location)
if location.name in dungeon_events and location.name not in self.events: if location.name in dungeon_events and location.name not in self.events:
if self.flooded_key_check(location): if self.flooded_key_check(location):
self.perform_event(location.name, key_region) self.perform_event(location.name, key_region)
@@ -607,22 +635,22 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors): elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors) self.append_door_to_list(door, self.avail_doors)
def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, isOrigin, world, player): def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, flag, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if self.can_traverse_bk_check(door, isOrigin): if self.can_traverse(door):
if door.controller is not None: if door.controller is not None:
door = door.controller door = door.controller
if door.dest is None and door not in proposed_map.keys() and door in valid_doors: if door.dest is None and door not in proposed_map.keys() and door in valid_doors:
if not self.in_door_list_ic(door, self.unattached_doors): if not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors) self.append_door_to_list(door, self.unattached_doors, flag)
else: else:
other = self.find_door_in_list(door, self.unattached_doors) other = self.find_door_in_list(door, self.unattached_doors)
if self.crystal != other.crystal: if self.crystal != other.crystal:
other.crystal = CrystalBarrier.Either other.crystal = CrystalBarrier.Either
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors): elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors):
self.append_door_to_list(door, self.event_doors) self.append_door_to_list(door, self.event_doors, flag)
elif not self.in_door_list(door, self.avail_doors): elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors) self.append_door_to_list(door, self.avail_doors, flag)
def add_all_doors_check_key_region(self, region, key_region, world, player): def add_all_doors_check_key_region(self, region, key_region, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
@@ -676,9 +704,16 @@ class ExplorationState(object):
return False return False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]: if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return not isOrigin or not door.bigKey or len(self.found_locations) > 0 return not isOrigin or not door.bigKey or self.count_locations_exclude_specials() > 0
# return not door.bigKey or len([x for x in self.found_locations if '- Prize' not in x.name]) > 0 # return not door.bigKey or len([x for x in self.found_locations if '- Prize' not in x.name]) > 0
def count_locations_exclude_specials(self):
cnt = 0
for loc in self.found_locations:
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations.keys():
cnt += 1
return cnt
def validate(self, door, region, world, player): def validate(self, door, region, world, player):
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, world, player) return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, world, player)
@@ -702,11 +737,11 @@ class ExplorationState(object):
return d return d
return None return None
def append_door_to_list(self, door, door_list): def append_door_to_list(self, door, door_list, flag=False):
if door.crystal == CrystalBarrier.Null: if door.crystal == CrystalBarrier.Null:
door_list.append(ExplorableDoor(door, self.crystal)) door_list.append(ExplorableDoor(door, self.crystal, flag))
else: else:
door_list.append(ExplorableDoor(door, door.crystal)) door_list.append(ExplorableDoor(door, door.crystal, flag))
def key_door_sort(self, d): def key_door_sort(self, d):
if d.door.smallKey: if d.door.smallKey:
@@ -719,9 +754,10 @@ class ExplorationState(object):
class ExplorableDoor(object): class ExplorableDoor(object):
def __init__(self, door, crystal): def __init__(self, door, crystal, flag):
self.door = door self.door = door
self.crystal = crystal self.crystal = crystal
self.flag = flag
def __str__(self): def __str__(self):
return str(self.__unicode__()) return str(self.__unicode__())
@@ -745,11 +781,11 @@ def extend_reachable_state(search_regions, state, world, player):
return local_state return local_state
def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, isOrigin, world, player): def extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, world, player):
local_state = state.copy() local_state = state.copy()
for region in search_regions: for region in search_regions:
local_state.visit_region(region) local_state.visit_region(region)
local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, isOrigin, world, player) local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, False, world, player)
while len(local_state.avail_doors) > 0: while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door() explorable_door = local_state.next_avail_door()
if explorable_door.door in proposed_map: if explorable_door.door in proposed_map:
@@ -758,8 +794,9 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, valid_d
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None: if connect_region is not None:
if valid_region_to_explore(connect_region, world, player) and not local_state.visited(connect_region): if valid_region_to_explore(connect_region, world, player) and not local_state.visited(connect_region):
local_state.visit_region(connect_region) flag = explorable_door.flag or explorable_door.door.bigKey
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, isOrigin, world, player) local_state.visit_region(connect_region, bk_Flag=flag)
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, flag, world, player)
return local_state return local_state

View File

@@ -8,73 +8,14 @@ from DungeonGenerator import ExplorationState
class KeySphere(object): class KeySphere(object):
def __init__(self): def __init__(self):
self.access_doors = set() self.access_door = None
self.free_locations = [] self.free_locations = set()
self.prize_region = False self.prize_region = False
self.key_only_locations = [] self.key_only_locations = set()
self.child_doors = set() self.child_doors = set()
self.bk_locked = False self.bk_locked = False
self.parent_sphere = None self.parent_sphere = None
self.other_locations = set()
def __eq__(self, other):
if self.prize_region != other.prize_region:
return False
if self.bk_locked != other.bk_locked:
return False
if len(self.free_locations) != len(other.free_locations):
return False
if len(self.key_only_locations) != len(other.key_only_locations):
return False
if len(set(self.free_locations).difference(set(other.free_locations))) > 0:
return False
if len(set(self.key_only_locations).difference(set(other.key_only_locations))) > 0:
return False
if not self.check_child_dest(self.child_doors, other.child_doors, other.access_doors):
return False
if not self.check_child_dest(other.child_doors, self.child_doors, self.access_doors):
return False
return True
@staticmethod
def check_child_dest(child_doors, other_child, other_access):
for child in child_doors:
if child in other_child:
continue
else:
found = False
for access in other_access:
if access.dest == child:
found = True
break
if not found:
return False
return True
# def issubset(self, other):
# if self.prize_region != other.prize_region:
# return False
# if self.bk_locked != other.bk_locked:
# return False
# if not set(self.free_locations).issubset(set(other.free_locations)):
# return False
# if not set(self.key_only_locations).issubset(set(other.key_only_locations)):
# return False
# if not set(self.child_doors).issubset(set(other.child_doors)):
# return False
# return True
#
# def issuperset(self, other):
# if self.prize_region != other.prize_region:
# return False
# if self.bk_locked != other.bk_locked:
# return False
# if not set(self.free_locations).issuperset(set(other.free_locations)):
# return False
# if not set(self.key_only_locations).issuperset(set(other.key_only_locations)):
# return False
# if not set(self.child_doors).issuperset(set(other.child_doors)):
# return False
# return True
class KeyLayout(object): class KeyLayout(object):
@@ -121,6 +62,7 @@ class DoorRules(object):
self.alternate_big_key_loc = set() self.alternate_big_key_loc = set()
# 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
class KeyCounter(object): class KeyCounter(object):
@@ -133,11 +75,21 @@ class KeyCounter(object):
self.open_doors = set() self.open_doors = set()
self.used_keys = 0 self.used_keys = 0
self.big_key_opened = False self.big_key_opened = False
self.important_location = False
def update(self, key_sphere): def update(self, key_sphere):
self.free_locations.update(key_sphere.free_locations) self.free_locations.update(key_sphere.free_locations)
self.key_only_locations.update(key_sphere.key_only_locations) self.key_only_locations.update(key_sphere.key_only_locations)
self.child_doors.update(key_sphere.child_doors) self.child_doors.update([x for x in key_sphere.child_doors if x not in self.open_doors and x.dest not in self.open_doors])
self.important_location = self.important_location or key_sphere.prize_region or self.special_region(key_sphere)
@staticmethod
def special_region(key_sphere):
for other in key_sphere.other_locations:
# todo: zelda's cell is special in standard, and probably crossed too
if other.name in ['Attic Cracked Floor', 'Suspicious Maiden']:
return True
return False
def open_door(self, door, flat_proposal): def open_door(self, door, flat_proposal):
if door in flat_proposal: if door in flat_proposal:
@@ -146,13 +98,15 @@ class KeyCounter(object):
self.open_doors.add(door) self.open_doors.add(door)
if door.dest in flat_proposal: if door.dest in flat_proposal:
self.open_doors.add(door.dest) self.open_doors.add(door.dest)
if door.dest in self.child_doors:
self.child_doors.remove(door.dest)
elif door.bigKey: elif door.bigKey:
self.big_key_opened = True self.big_key_opened = True
self.child_doors.remove(door) self.child_doors.remove(door)
self.open_doors.add(door) self.open_doors.add(door)
def used_smalls_loc(self): def used_smalls_loc(self, reserve=0):
return max(self.used_keys - len(self.key_only_locations), 0) return max(self.used_keys + reserve - len(self.key_only_locations), 0)
def copy(self): def copy(self):
ret = KeyCounter(self.max_chests) ret = KeyCounter(self.max_chests)
@@ -161,6 +115,8 @@ class KeyCounter(object):
ret.child_doors.update(self.child_doors) ret.child_doors.update(self.child_doors)
ret.used_keys = self.used_keys ret.used_keys = self.used_keys
ret.open_doors.update(self.open_doors) ret.open_doors.update(self.open_doors)
ret.big_key_opened = self.big_key_opened
ret.important_location = self.important_location
return ret return ret
@@ -171,55 +127,56 @@ def analyze_dungeon(key_layout, world, player):
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
key_layout.max_chests = len(world.get_dungeon(key_layout.sector.name, player).small_keys) key_layout.max_chests = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
find_bk_locked_sections(key_layout) find_bk_locked_sections(key_layout, world)
key_counter = KeyCounter(key_layout.max_chests) key_counter = KeyCounter(key_layout.max_chests)
key_counter.update(key_layout.key_spheres['Origin']) key_counter.update(key_layout.key_spheres['Origin'])
queue = collections.deque([(key_layout.key_spheres['Origin'], key_counter)]) queue = collections.deque([(key_layout.key_spheres['Origin'], key_counter)])
doors_completed = set()
while len(queue) > 0: while len(queue) > 0:
key_sphere, key_counter = queue.popleft() key_sphere, key_counter = queue.popleft()
chest_keys = available_chest_small_keys(key_counter, False, world) # todo: when to count the bk chests chest_keys = available_chest_small_keys(key_counter, False, world)
# chest_keys_bk = available_chest_small_keys(key_counter, True, world) # chest_keys_bk = available_chest_small_keys(key_counter, True, world)
available = chest_keys + len(key_counter.key_only_locations) - key_counter.used_keys available = chest_keys + len(key_counter.key_only_locations) - 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)
# todo: big chest counts? if not key_counter.big_key_opened:
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls: if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls:
key_logic.bk_restricted.update(key_counter.free_locations) key_logic.bk_restricted.update(key_counter.free_locations)
# logic min # logic min?
if not key_sphere.bk_locked and big_chest_in_locations(key_counter.free_locations): if not key_sphere.bk_locked and big_chest_in_locations(key_counter.free_locations):
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations)) key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
# if available <= possible_smalls: minimal_keys = None
# in this case, at least 1 child must have the available rule - unless relaxing is possible # todo: this feels like big key doors aren't accounted for - you may or may not find the big_key door at this point
if available + key_counter.used_keys <= possible_smalls:
minimal_keys = available + key_counter.used_keys
# todo: detect forced subsequent keys - see keypuzzles
# try to relax the rules here? # try to relax the rules here?
for child in key_sphere.child_doors: for child in key_sphere.child_doors:
next_sphere = key_layout.key_spheres[child.name] next_sphere = key_layout.key_spheres[child.name]
if not empty_sphere(next_sphere): if not empty_sphere(next_sphere) and child not in doors_completed:
if not child.bigKey: if not child.bigKey:
# todo: calculate based on big key doors vs smalls - eastern dark square expanded_counter = expand_counter_to_last_door(child, key_counter, key_layout, set())
rule = DoorRules(min(available, possible_smalls) + key_counter.used_keys) rule = create_rule(expanded_counter, key_layout, minimal_keys, world)
check_for_self_lock_key(rule, next_sphere, key_layout, world)
bk_restricted_rules(rule, next_sphere, key_counter, key_layout, minimal_keys, world)
key_logic.door_rules[child.name] = rule key_logic.door_rules[child.name] = rule
doors_completed.add(next_sphere.access_door)
next_counter = increment_key_counter(child, next_sphere, key_counter, key_layout.flat_prop) next_counter = increment_key_counter(child, next_sphere, key_counter, key_layout.flat_prop)
queue.append((next_sphere, next_counter)) queue.append((next_sphere, next_counter))
return key_layout return key_layout
# for child in key_sphere.child_doors: def find_bk_locked_sections(key_layout, world):
# next_sphere = key_spheres[child.name]
# if not empty_sphere(next_sphere):
# sm_rule = calc_basic_small_key_rule(key_sphere, key_spheres, key_layout, flat_proposal, world, player)
def find_bk_locked_sections(key_layout):
key_spheres = key_layout.key_spheres key_spheres = key_layout.key_spheres
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
bk_key_not_required = set() bk_key_not_required = set()
big_chest_allowed_big_key = True big_chest_allowed_big_key = world.accessibility != 'locations'
for key in key_spheres.keys(): for key in key_spheres.keys():
sphere = key_spheres[key] sphere = key_spheres[key]
key_layout.all_chest_locations.update(sphere.free_locations) key_layout.all_chest_locations.update(sphere.free_locations)
if sphere.bk_locked and sphere.prize_region: if sphere.bk_locked and (sphere.prize_region or KeyCounter.special_region(sphere)):
big_chest_allowed_big_key = False big_chest_allowed_big_key = False
if not sphere.bk_locked: if not sphere.bk_locked:
bk_key_not_required.update(sphere.free_locations) bk_key_not_required.update(sphere.free_locations)
@@ -234,6 +191,21 @@ def empty_sphere(sphere):
return not sphere.prize_region return not sphere.prize_region
def relative_empty_sphere(sphere, key_counter):
if len(sphere.key_only_locations.difference(key_counter.key_only_locations)) > 0:
return False
if len(sphere.free_locations.difference(key_counter.free_locations)) > 0:
return False
new_child_door = False
for child in sphere.child_doors:
if child not in key_counter.child_doors and child not in key_counter.open_doors and (not child.bigKey or not key_counter.big_key_opened):
new_child_door = True
break
if new_child_door:
return False
return True
def increment_key_counter(door, sphere, key_counter, flat_proposal): def increment_key_counter(door, sphere, key_counter, flat_proposal):
new_counter = key_counter.copy() new_counter = key_counter.copy()
new_counter.open_door(door, flat_proposal) new_counter.open_door(door, flat_proposal)
@@ -241,75 +213,75 @@ def increment_key_counter(door, sphere, key_counter, flat_proposal):
return new_counter return new_counter
def check_for_big_doors(door, key_counter, key_layout): def expand_counter_to_last_door(door, key_counter, key_layout, ignored_doors):
door_sphere = key_layout.key_spheres[door.name]
small_doors = set()
big_doors = set() big_doors = set()
for other in key_counter.child_doors: for other in key_counter.child_doors:
if other != door and other.bigKey: if other != door and other not in ignored_doors:
big_doors.add(other) if other.bigKey:
big_key_available = len(key_counter.free_location) - key_counter.used_smalls_loc > 0 big_doors.add(other)
if len(big_doors) == 0 or not big_key_available: elif other.dest not in small_doors:
small_doors.add(other)
# I feel bk might be available if the current small door could use a key_only_loc - the param might cover this case
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
if len(small_doors) == 0 and (len(big_doors) == 0 or not big_key_available):
return key_counter return key_counter
new_counter = key_counter new_counter = key_counter
for big_door in big_doors: last_counter = key_counter
big_sphere = key_layout.key_spheres[big_door.name] new_ignored = set(ignored_doors)
new_counter = increment_key_counter(big_door, big_sphere, new_counter, key_layout.flat_prop) for new_door in small_doors.union(big_doors):
# nested big key doors? new_sphere = key_layout.key_spheres[new_door.name]
new_counter = increment_key_counter(new_door, new_sphere, new_counter, key_layout.flat_prop)
# this means the new_door invalidates the door / leads to the same stuff
if relative_empty_sphere(door_sphere, new_counter):
new_counter = last_counter
new_ignored.add(new_door)
else:
last_counter = new_counter
old_counter = None old_counter = None
while old_counter != new_counter: while old_counter != new_counter:
old_counter = new_counter old_counter = new_counter
new_counter = check_for_big_doors(door, old_counter, key_layout) new_counter = expand_counter_to_last_door(door, old_counter, key_layout, new_ignored)
# I think I've opened them all!
return new_counter return new_counter
# def calc_basic_small_key_rule(key_sphere, key_spheres, key_layout, flat_proposal, world, player): def create_rule(key_counter, key_layout, minimal_keys, world):
# free_locations = set() chest_keys = available_chest_small_keys(key_counter, key_counter.big_key_opened, world)
# key_only_locations = set() available = chest_keys + len(key_counter.key_only_locations) - key_counter.used_keys
# offshoot_doors = set() possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
# queue = collections.deque() required_keys = min(available, possible_smalls) + key_counter.used_keys
# parent = key_sphere.parent_sphere if minimal_keys is None or required_keys <= minimal_keys:
# while parent is not None: return DoorRules(required_keys)
# queue.append(parent) else:
# parent = parent.parent_sphere return DoorRules(minimal_keys)
# while len(queue) > 0:
# previous = queue.popleft()
# free_locations.update(previous.free_locations) def check_for_self_lock_key(rule, sphere, key_layout, world):
# key_only_locations.update(previous.key_only_locations) if world.accessibility != 'locations':
# for other_door in parent.child_doors: counter = KeyCounter(key_layout.max_chests)
# if other_door not in key_sphere.access_doors: counter.update(sphere)
# offshoot_doors.add(other_door) if not self_lock_possible(counter):
# # todo: bk versions return
# chest_keys = available_chest_small_keys(key_layout, free_locations, key_sphere.bk_locked, world, player) queue = collections.deque(counter.child_doors)
# parent_avail = chest_keys + len(key_only_locations) already_queued = set(counter.child_doors)
# while len(queue) > 0:
# usuable_elsewhere = 0 child = queue.popleft()
# open_set = set() if child not in counter.open_doors:
# queue = collections.deque(offshoot_doors) counter = increment_key_counter(child, key_layout.key_spheres[child.name], counter, key_layout.flat_prop)
# while len(queue) > 0: if not self_lock_possible(counter):
# offshoot = queue.popleft() return
# open_set.add(offshoot) for new_door in counter.child_doors:
# if offshoot in flat_proposal: if new_door not in already_queued:
# usuable_elsewhere += 1 queue.append(new_door)
# # else bk door already_queued.add(new_door)
# if offshoot.dest in flat_proposal: if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location:
# open_set.add(offshoot.dest) rule.allow_small = True
# off_sphere = key_spheres[offshoot.name] rule.small_location = next(iter(counter.free_locations))
# free_locations.update(off_sphere.free_locations)
# key_only_locations.update(off_sphere.key_only_locations)
# for other_door in off_sphere.child_doors: def self_lock_possible(counter):
# if other_door not in key_sphere.access_doors and other_door not in open_set: return len(counter.free_locations) <= 1 and len(counter.key_only_locations) == 0 and not counter.important_location
# queue.append(other_door)
# # todo: bk versions
# offshoot_chest = available_chest_small_keys(key_layout, free_locations, key_sphere.bk_locked, world, player)
# offshoot_avail = offshoot_chest + len(key_only_locations)
#
# if usuable_elsewhere == parent_avail and offshoot_avail > parent_avail:
# return usuable_elsewhere + 1
# if usuable_elsewhere == parent_avail and offshoot_avail == parent_avail:
# return usuable_elsewhere
# if usuable_elsewhere < parent_avail:
# return usuable_elsewhere + 1
# return 10
def available_chest_small_keys(key_counter, bk, world): def available_chest_small_keys(key_counter, bk, world):
@@ -322,14 +294,59 @@ def available_chest_small_keys(key_counter, bk, world):
else: else:
return key_counter.max_chests return key_counter.max_chests
# derive key rules from key regions
# how many small key available at a given point (locations found / keysanity / retro) def bk_restricted_rules(rule, sphere, key_counter, key_layout, minimal_keys, world):
# how many doors can be opened before you vs. smalls available if sphere.bk_locked:
# soft lock detection - should it be run here? return
# run with both bk off (locked behind current door) and bk found (elsewhere in the dungeon) expanded_counter = expand_counter_no_big_doors(sphere.access_door, key_counter, key_layout, set())
# rules generally smaller if bk locked behind current door bk_number = create_rule(expanded_counter, key_layout, minimal_keys, world).small_key_num
# big key restriction based on bk_locked if bk_number == rule.small_key_num:
# prize regions - TT is weird as there are intermediate goals - assume child doors as well? return
post_counter = KeyCounter(key_layout.max_chests)
post_counter.update(sphere)
other_doors_beyond_me = [x for x in post_counter.child_doors if not x.bigKey]
queue = collections.deque(other_doors_beyond_me)
already_queued = set(other_doors_beyond_me)
while len(queue) > 0:
child = queue.popleft()
if child not in post_counter.open_doors:
post_counter = increment_key_counter(child, key_layout.key_spheres[child.name], post_counter, key_layout.flat_prop)
for new_door in post_counter.child_doors:
if not new_door.bigKey and new_door not in already_queued and new_door.dest not in already_queued:
queue.append(new_door)
already_queued.add(new_door)
unique_loc = post_counter.free_locations.difference(expanded_counter.free_locations)
if len(unique_loc) > 0:
rule.alternate_small_key = bk_number
rule.alternate_big_key_loc.update(unique_loc)
def expand_counter_no_big_doors(door, key_counter, key_layout, ignored_doors):
door_sphere = key_layout.key_spheres[door.name]
small_doors = set()
for other in key_counter.child_doors:
if other != door and other not in ignored_doors:
if other.dest not in small_doors and not other.bigKey:
small_doors.add(other)
if len(small_doors) == 0:
return key_counter
new_counter = key_counter
last_counter = key_counter
new_ignored = set(ignored_doors)
for new_door in small_doors:
new_sphere = key_layout.key_spheres[new_door.name]
new_counter = increment_key_counter(new_door, new_sphere, new_counter, key_layout.flat_prop)
# this means the new_door invalidates the door / leads to the same stuff
if relative_empty_sphere(door_sphere, new_counter):
new_counter = last_counter
new_ignored.add(new_door)
else:
last_counter = new_counter
old_counter = None
while old_counter != new_counter:
old_counter = new_counter
new_counter = expand_counter_no_big_doors(door, old_counter, key_layout, new_ignored)
return new_counter
def create_key_spheres(key_layout, world, player): def create_key_spheres(key_layout, world, player):
@@ -352,28 +369,15 @@ def create_key_spheres(key_layout, world, player):
open_a_door(door, child_state, flat_proposal) open_a_door(door, child_state, flat_proposal)
expand_key_state(child_state, flat_proposal, world, player) expand_key_state(child_state, flat_proposal, world, player)
child_kr = create_key_sphere(child_state, next_key_sphere, door) child_kr = create_key_sphere(child_state, next_key_sphere, door)
check_for_duplicates_sub_super_set(key_spheres, child_kr, door.name) if door.name not in key_spheres.keys():
queue.append((child_kr, child_state)) key_spheres[door.name] = child_kr
queue.append((child_kr, child_state))
else:
old_sphere = key_spheres[door.name]
old_sphere.bk_locked = old_sphere.bk_locked and child_kr.bk_locked
return key_spheres return key_spheres
def check_for_duplicates_sub_super_set(key_spheres, new_kr, door_name):
is_new = True
for kr in key_spheres.values():
if new_kr == kr: # todo: what about parent regions...
kr.access_doors.update(new_kr.access_doors)
kr.child_doors.update(new_kr.child_doors)
key_spheres[door_name] = kr
is_new = False
break
# if new_kr.issubset(kr):
# break
# if new_kr.issuperset(kr):
# break
if is_new:
key_spheres[door_name] = new_kr
def create_key_sphere(state, parent_sphere, door): def create_key_sphere(state, parent_sphere, door):
key_sphere = KeySphere() key_sphere = KeySphere()
key_sphere.parent_sphere = parent_sphere key_sphere.parent_sphere = parent_sphere
@@ -382,7 +386,9 @@ def create_key_sphere(state, parent_sphere, door):
parent_locations = set() parent_locations = set()
while p_region is not None: while p_region is not None:
parent_doors.update(p_region.child_doors) parent_doors.update(p_region.child_doors)
parent_locations.update(p_region.free_locations+p_region.key_only_locations) parent_locations.update(p_region.free_locations)
parent_locations.update(p_region.key_only_locations)
parent_locations.update(p_region.other_locations)
p_region = p_region.parent_sphere p_region = p_region.parent_sphere
u_doors = unique_doors(state.small_doors+state.big_doors).difference(parent_doors) u_doors = unique_doors(state.small_doors+state.big_doors).difference(parent_doors)
key_sphere.child_doors.update(u_doors) key_sphere.child_doors.update(u_doors)
@@ -390,14 +396,17 @@ def create_key_sphere(state, parent_sphere, door):
for loc in region_locations: for loc in region_locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']: if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
key_sphere.prize_region = True key_sphere.prize_region = True
key_sphere.other_locations.add(loc)
elif loc.event and 'Small Key' in loc.item.name: elif loc.event and 'Small Key' in loc.item.name:
key_sphere.key_only_locations.append(loc) key_sphere.key_only_locations.add(loc)
elif loc.name not in dungeon_events: elif loc.name not in dungeon_events:
key_sphere.free_locations.append(loc) key_sphere.free_locations.add(loc)
else:
key_sphere.other_locations.add(loc)
# todo: Cellblock in a dungeon with a big_key door or chest - Crossed Mode # todo: Cellblock in a dungeon with a big_key door or chest - Crossed Mode
key_sphere.bk_locked = state.big_key_opened if not state.big_key_special else False key_sphere.bk_locked = state.big_key_opened if not state.big_key_special else False
if door is not None: if door is not None:
key_sphere.access_doors.add(door) key_sphere.access_door = door
return key_sphere return key_sphere
@@ -483,66 +492,175 @@ def flatten_pair_list(paired_list):
return flat_list return flat_list
## vanilla validation code # Soft lock stuff
class SoftLockException(Exception):
pass
# vanilla validation code
def validate_vanilla_key_logic(world, player): def validate_vanilla_key_logic(world, player):
validators = { validators = {
'Hyrule Castle': val_unimplemented, 'Hyrule Castle': val_hyrule,
'Eastern Palace': val_eastern, 'Eastern Palace': val_eastern,
'Desert Palace': val_desert, 'Desert Palace': val_desert,
'Tower of Hera': val_hera, 'Tower of Hera': val_hera,
'Agahnims Tower': val_tower, 'Agahnims Tower': val_tower,
'Palace of Darkness': val_unimplemented, 'Palace of Darkness': val_pod,
'Swamp Palace': val_unimplemented, 'Swamp Palace': val_swamp,
'Skull Woods': val_unimplemented, 'Skull Woods': val_skull,
'Thieves Town': val_unimplemented, 'Thieves Town': val_thieves,
'Ice Palace': val_unimplemented, 'Ice Palace': val_ice,
'Misery Mire': val_unimplemented, 'Misery Mire': val_mire,
'Turtle Rock': val_unimplemented, 'Turtle Rock': val_turtle,
'Ganons Tower': val_unimplemented 'Ganons Tower': val_ganons
} }
key_logic_dict = world.key_logic[player] key_logic_dict = world.key_logic[player]
for key, key_logic in key_logic_dict.items(): for key, key_logic in key_logic_dict.items():
validators[key](key_logic) validators[key](key_logic, world, player)
def val_unimplemented(key_logic): def val_hyrule(key_logic, world, player):
assert True val_rule(key_logic.door_rules['Sewers Secret Room Key Door S'], 2)
val_rule(key_logic.door_rules['Sewers Dark Cross Key Door N'], 2)
val_rule(key_logic.door_rules['Hyrule Dungeon Map Room Key Door S'], 2)
val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 3, True, 'Hyrule Castle - Zelda\'s Chest')
# val_rule(key_logic.door_rules['Hyrule Dungeon Armory Interior Key Door N'], 4)
def val_eastern(key_logic): def val_eastern(key_logic, world, player):
dark_square_rule = key_logic.door_rules['Eastern Dark Square Key Door WN'] # val_rule(key_logic.door_rules['Eastern Dark Square Key Door WN'], 2, False, None, 1, {'Eastern Palace - Big Key Chest'})
assert dark_square_rule.small_key_num == 2 val_rule(key_logic.door_rules['Eastern Dark Square Key Door WN'], 1)
# todo: allow big_key behind the door val_rule(key_logic.door_rules['Eastern Darkness Up Stairs'], 2)
# assert dark_square_rule.alternate_small_key == 1 assert world.get_location('Eastern Palace - Big Chest', player) in key_logic.bk_restricted
# assert 'Eastern Palace - Big Key Chest' in dark_square_rule.alternat_big_key_loc assert world.get_location('Eastern Palace - Boss', player) in key_logic.bk_restricted
# assert len(dark_square_rule.alternat_big_key_loc) == 1
assert key_logic.door_rules['Eastern Darkness Up Stairs'].small_key_num == 2
assert 'Eastern Palace - Big Chest' in key_logic.bk_restricted
assert 'Eastern Palace - Boss' in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 2 assert len(key_logic.bk_restricted) == 2
def val_desert(key_logic): def val_desert(key_logic, world, player):
assert key_logic.door_rules['Desert East Wing Key Door EN'].small_key_num == 2 val_rule(key_logic.door_rules['Desert East Wing Key Door EN'], 2)
assert key_logic.door_rules['Desert Tiles 1 Up Stairs'].small_key_num == 2 val_rule(key_logic.door_rules['Desert Tiles 1 Up Stairs'], 2)
assert key_logic.door_rules['Desert Beamos Hall NE'].small_key_num == 3 val_rule(key_logic.door_rules['Desert Beamos Hall NE'], 3)
assert key_logic.door_rules['Desert Tiles 2 NE'].small_key_num == 4 val_rule(key_logic.door_rules['Desert Tiles 2 NE'], 4)
assert 'Desert Palace - Big Chest' in key_logic.bk_restricted assert world.get_location('Desert Palace - Big Chest', player) in key_logic.bk_restricted
assert 'Desert Palace - Boss' in key_logic.bk_restricted assert world.get_location('Desert Palace - Boss', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 2 assert len(key_logic.bk_restricted) == 2
def val_hera(key_logic): def val_hera(key_logic, world, player):
assert key_logic.door_rules['Hera Lobby Key Stairs'].small_key_num == 1 val_rule(key_logic.door_rules['Hera Lobby Key Stairs'], 1, True, 'Tower of Hera - Big Key Chest')
assert 'Tower of Hera - Big Chest' in key_logic.bk_restricted assert world.get_location('Tower of Hera - Big Chest', player) in key_logic.bk_restricted
assert 'Tower of Hera - Compass Chest' in key_logic.bk_restricted assert world.get_location('Tower of Hera - Compass Chest', player) in key_logic.bk_restricted
assert 'Tower of Hera - Boss' in key_logic.bk_restricted assert world.get_location('Tower of Hera - Boss', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 3 assert len(key_logic.bk_restricted) == 3
def val_tower(key_logic): def val_tower(key_logic, world, player):
assert key_logic.door_rules['Tower Room 03 Up Stairs'].small_key_num == 1 val_rule(key_logic.door_rules['Tower Room 03 Up Stairs'], 1)
assert key_logic.door_rules['Tower Dark Maze ES'].small_key_num == 2 val_rule(key_logic.door_rules['Tower Dark Maze ES'], 2)
assert key_logic.door_rules['Tower Dark Chargers Up Stairs'].small_key_num == 3 val_rule(key_logic.door_rules['Tower Dark Archers Up Stairs'], 3)
assert key_logic.door_rules['Tower Circle of Pots WS'].small_key_num == 4 val_rule(key_logic.door_rules['Tower Circle of Pots WS'], 4)
def val_pod(key_logic, world, player):
val_rule(key_logic.door_rules['PoD Arena Main NW'], 4)
val_rule(key_logic.door_rules['PoD Basement Ledge Up Stairs'], 6, True, 'Palace of Darkness - Big Key Chest')
val_rule(key_logic.door_rules['PoD Compass Room SE'], 6, True, 'Palace of Darkness - Harmless Hellway')
val_rule(key_logic.door_rules['PoD Falling Bridge WN'], 6)
val_rule(key_logic.door_rules['PoD Dark Pegs WN'], 6)
assert world.get_location('Palace of Darkness - Big Chest', player) in key_logic.bk_restricted
assert world.get_location('Palace of Darkness - Boss', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 2
def val_swamp(key_logic, world, player):
val_rule(key_logic.door_rules['Swamp Entrance Down Stairs'], 1)
val_rule(key_logic.door_rules['Swamp Pot Row WS'], 2)
val_rule(key_logic.door_rules['Swamp Trench 1 Key Ledge NW'], 3)
val_rule(key_logic.door_rules['Swamp Hub North Ledge N'], 5)
val_rule(key_logic.door_rules['Swamp Hub WN'], 6)
val_rule(key_logic.door_rules['Swamp Waterway NW'], 6)
assert world.get_location('Swamp Palace - Entrance', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 1
def val_skull(key_logic, world, player):
val_rule(key_logic.door_rules['Skull 3 Lobby NW'], 4)
val_rule(key_logic.door_rules['Skull Spike Corner ES'], 5)
def val_thieves(key_logic, world, player):
val_rule(key_logic.door_rules['Thieves Hallway WS'], 1)
val_rule(key_logic.door_rules['Thieves Spike Switch Up Stairs'], 3)
val_rule(key_logic.door_rules['Thieves Conveyor Bridge WS'], 3, True, 'Thieves\' Town - Big Chest')
assert world.get_location('Thieves\' Town - Attic', player) in key_logic.bk_restricted
assert world.get_location('Thieves\' Town - Boss', player) in key_logic.bk_restricted
assert world.get_location('Thieves\' Town - Blind\'s Cell', player) in key_logic.bk_restricted
assert world.get_location('Thieves\' Town - Big Chest', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 4
def val_ice(key_logic, world, player):
val_rule(key_logic.door_rules['Ice Jelly Key Down Stairs'], 1)
val_rule(key_logic.door_rules['Ice Conveyor SW'], 2)
val_rule(key_logic.door_rules['Ice Backwards Room Down Stairs'], 5)
assert world.get_location('Ice Palace - Boss', player) in key_logic.bk_restricted
assert world.get_location('Ice Palace - Big Chest', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 2
def val_mire(key_logic, world, player):
mire_west_wing = {'Misery Mire - Big Key Chest', 'Misery Mire - Compass Chest'}
val_rule(key_logic.door_rules['Mire Spikes NW'], 5)
val_rule(key_logic.door_rules['Mire Hub WS'], 5, False, None, 4, mire_west_wing)
val_rule(key_logic.door_rules['Mire Conveyor Crystal WS'], 6, False, None, 5, mire_west_wing)
assert world.get_location('Misery Mire - Boss', player) in key_logic.bk_restricted
assert world.get_location('Misery Mire - Big Chest', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 2
def val_turtle(key_logic, world, player):
val_rule(key_logic.door_rules['TR Hub NW'], 1)
val_rule(key_logic.door_rules['TR Pokey 1 NW'], 2)
val_rule(key_logic.door_rules['TR Chain Chomps Down Stairs'], 3)
val_rule(key_logic.door_rules['TR Pokey 2 ES'], 6, True, 'Turtle Rock - Big Key Chest', 4, {'Turtle Rock - Big Key Chest'})
val_rule(key_logic.door_rules['TR Crystaroller Down Stairs'], 5)
val_rule(key_logic.door_rules['TR Dash Bridge WS'], 6)
assert world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player) in key_logic.bk_restricted
assert world.get_location('Turtle Rock - Eye Bridge - Top Left', player) in key_logic.bk_restricted
assert world.get_location('Turtle Rock - Eye Bridge - Top Right', player) in key_logic.bk_restricted
assert world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player) in key_logic.bk_restricted
assert world.get_location('Turtle Rock - Boss', player) in key_logic.bk_restricted
assert world.get_location('Turtle Rock - Crystaroller Room', player) in key_logic.bk_restricted
assert world.get_location('Turtle Rock - Big Chest', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 7
def val_ganons(key_logic, world, player):
rando_room = {'Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'}
compass_room = {'Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'}
gt_middle = {'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Chest', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest'}
val_rule(key_logic.door_rules['GT Double Switch EN'], 7, False, None, 5, rando_room.union({'Ganons Tower - Firesnake Room'}))
val_rule(key_logic.door_rules['GT Hookshot ES'], 8, True, 'Ganons Tower - Map Chest', 6, {'Ganons Tower - Map Chest'})
val_rule(key_logic.door_rules['GT Tile Room EN'], 7, False, None, 5, compass_room)
val_rule(key_logic.door_rules['GT Firesnake Room SW'], 8, False, None, 6, rando_room)
val_rule(key_logic.door_rules['GT Conveyor Star Pits EN'], 8, False, None, 6, gt_middle)
val_rule(key_logic.door_rules['GT Mini Helmasaur Room WN'], 7)
val_rule(key_logic.door_rules['GT Crystal Circles SW'], 8)
assert world.get_location('Ganons Tower - Mini Helmasaur Room - Left', player) in key_logic.bk_restricted
assert world.get_location('Ganons Tower - Mini Helmasaur Room - Right', player) in key_logic.bk_restricted
assert world.get_location('Ganons Tower - Big Chest', player) in key_logic.bk_restricted
assert world.get_location('Ganons Tower - Pre-Moldorm Chest', player) in key_logic.bk_restricted
assert world.get_location('Ganons Tower - Validation Chest', player) in key_logic.bk_restricted
assert len(key_logic.bk_restricted) == 5
def val_rule(rule, skn, allow=False, loc=None, askn=None, setCheck=None):
if setCheck is None:
setCheck = set()
assert rule.small_key_num == skn
assert rule.allow_small == allow
assert rule.small_location == loc or rule.small_location.name == loc
assert rule.alternate_small_key == askn
assert len(setCheck) == len(rule.alternate_big_key_loc)
for loc in rule.alternate_big_key_loc:
assert loc.name in setCheck

View File

@@ -438,8 +438,8 @@ def create_regions(world, player):
create_dungeon_region(player, 'Skull Star Pits', 'Skull Woods', None, ['Skull Star Pits SW', 'Skull Star Pits WS']), create_dungeon_region(player, 'Skull Star Pits', 'Skull Woods', None, ['Skull Star Pits SW', 'Skull Star Pits WS']),
create_dungeon_region(player, 'Skull Torch Room', 'Skull Woods', None, ['Skull Torch Room ES', 'Skull Torch Room EN']), create_dungeon_region(player, 'Skull Torch Room', 'Skull Woods', None, ['Skull Torch Room ES', 'Skull Torch Room EN']),
create_dungeon_region(player, 'Skull Vines', 'Skull Woods', None, ['Skull Vines WN', 'Skull Vines NW']), create_dungeon_region(player, 'Skull Vines', 'Skull Woods', None, ['Skull Vines WN', 'Skull Vines NW']),
create_dungeon_region(player, 'Skull Spike Corner', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop'], ['Skull Spike Corner SW', 'Skull Spike Corner WS']), create_dungeon_region(player, 'Skull Spike Corner', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop'], ['Skull Spike Corner SW', 'Skull Spike Corner ES']),
create_dungeon_region(player, 'Skull Final Drop', 'Skull Woods', None, ['Skull Final Drop ES', 'Skull Final Drop Hole']), create_dungeon_region(player, 'Skull Final Drop', 'Skull Woods', None, ['Skull Final Drop WS', 'Skull Final Drop Hole']),
create_dungeon_region(player, 'Skull Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), create_dungeon_region(player, 'Skull Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']),
# tt # tt

View File

@@ -265,8 +265,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player))
# Big key rules # Big key rules
set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Eastern Palace - Big Chest', player), 'Big Key (Eastern Palace)', player)
set_rule(world.get_entrance('Eastern Big Key NE', player), lambda state: state.has('Big Key (Eastern Palace)', player)) set_rule(world.get_entrance('Eastern Big Key NE', player), lambda state: state.has('Big Key (Eastern Palace)', player))
set_rule(world.get_entrance('Eastern Courtyard N', player), lambda state: state.has('Big Key (Eastern Palace)', player)) set_rule(world.get_entrance('Eastern Courtyard N', player), lambda state: state.has('Big Key (Eastern Palace)', player))
@@ -276,8 +274,6 @@ def global_rules(world, player):
# Desert # Desert
set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Desert Palace - Big Chest', player), 'Big Key (Desert Palace)', player)
set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player)) set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player))
set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Prize', player))
@@ -285,8 +281,6 @@ def global_rules(world, player):
# Tower of Hera # Tower of Hera
set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Tower of Hera - Big Chest', player), 'Big Key (Tower of Hera)', player)
set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player)) set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player))
set_rule(world.get_entrance('Hera Startile Corner NW', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(world.get_entrance('Hera Startile Corner NW', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player))
@@ -299,8 +293,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('PoD Dark Alley NE', player), lambda state: state.has('Big Key (Palace of Darkness)', player)) set_rule(world.get_entrance('PoD Dark Alley NE', player), lambda state: state.has('Big Key (Palace of Darkness)', player))
set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player)) set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Palace of Darkness - Big Chest', player), 'Big Key (Palace of Darkness)', player)
set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('PoD Dark Pegs WN', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs WN', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('PoD Dark Pegs Up Ladder', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs Up Ladder', player), lambda state: state.has('Hammer', player))
@@ -336,15 +328,11 @@ def global_rules(world, player):
set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player))
set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: state.has('Flippers', player)) set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: state.has('Flippers', player))
set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)', player)
set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', player))
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player)) set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)', player)
set_rule(world.get_entrance('Skull Torch Room EN', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Skull Torch Room EN', player), lambda state: state.has('Fire Rod', player))
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player)) set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player))
@@ -353,8 +341,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('Thieves BK Corner NE', player), lambda state: state.has('Big Key (Thieves Town)', player)) set_rule(world.get_entrance('Thieves BK Corner NE', player), lambda state: state.has('Big Key (Thieves Town)', player))
# blind can't have the small key? - not necessarily true anymore - but likely still # blind can't have the small key? - not necessarily true anymore - but likely still
set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has('Big Key (Thieves Town)', player) and state.has('Hammer', player))) set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has('Big Key (Thieves Town)', player) and state.has('Hammer', player)))
if world.accessibility == 'locations':
forbid_item(world.get_location('Thieves\' Town - Big Chest', player), 'Big Key (Thieves Town)', player)
for entrance in ['Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Block Path', 'Thieves Conveyor Bridge Block Path']: for entrance in ['Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Block Path', 'Thieves Conveyor Bridge Block Path']:
set_rule(world.get_entrance(entrance, player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance(entrance, player), lambda state: state.can_lift_rocks(player))
for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']:
@@ -368,8 +354,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player)) set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player))
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Ice Palace - Big Chest', player), 'Big Key (Ice Palace)', player)
set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player)) set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
@@ -390,8 +374,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('Mire Falling Bridge WN', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) # this is due to the fact the the door opposite is blocked set_rule(world.get_entrance('Mire Falling Bridge WN', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) # this is due to the fact the the door opposite is blocked
set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ... set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ...
set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player) and (state.has_Boots(player) or state.has('Hookshot', player))) set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player) and (state.has_Boots(player) or state.has('Hookshot', player)))
if world.accessibility == 'locations':
forbid_item(world.get_location('Misery Mire - Big Chest', player), 'Big Key (Misery Mire)', player)
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
set_rule(world.get_entrance('Mire BK Door Room N', player), lambda state: state.has('Big Key (Misery Mire)', player)) set_rule(world.get_entrance('Mire BK Door Room N', player), lambda state: state.has('Big Key (Misery Mire)', player))
set_rule(world.get_entrance('Mire Square Rail NW', player), lambda state: state.has('Big Key (Misery Mire)', player)) set_rule(world.get_entrance('Mire Square Rail NW', player), lambda state: state.has('Big Key (Misery Mire)', player))
@@ -413,8 +395,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player)) set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Turtle Rock - Big Chest', player), 'Big Key (Turtle Rock)', player)
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('TR Dodgers NE', player), lambda state: state.has('Big Key (Turtle Rock)', player)) set_rule(world.get_entrance('TR Dodgers NE', player), lambda state: state.has('Big Key (Turtle Rock)', player))
@@ -443,8 +423,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player))
# I am tempted to stick an invincibility rule for getting across falling bridge # I am tempted to stick an invincibility rule for getting across falling bridge
set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player)) set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
if world.accessibility == 'locations':
forbid_item(world.get_location('Ganons Tower - Big Chest', player), 'Big Key (Ganons Tower)', player)
set_rule(world.get_entrance('GT Ice Armos NE', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state)) set_rule(world.get_entrance('GT Ice Armos NE', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))
set_rule(world.get_entrance('GT Ice Armos WS', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state)) set_rule(world.get_entrance('GT Ice Armos WS', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))
@@ -1700,12 +1678,10 @@ def set_inverted_bunny_rules(world, player):
def add_key_logic_rules(world, player): def add_key_logic_rules(world, player):
logger = logging.getLogger('')
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():
logger.debug(' %s needs %s keys', door_name, keys) add_rule(world.get_entrance(door_name, player), create_advanced_key_rule(d_logic, player, keys))
add_rule(world.get_entrance(door_name, player), create_key_rule(d_logic.small_key_name, player, keys))
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)
@@ -1717,5 +1693,31 @@ def create_key_rule(small_key_name, player, keys):
return lambda state: state.has_key(small_key_name, player, keys) return lambda state: state.has_key(small_key_name, player, keys)
def create_forced_small_rule(small_key_name, player): def create_key_rule_allow_small(small_key_name, player, keys, location):
return lambda item: item.name == small_key_name and item.player == player loc = location.name
return lambda state: state.has_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1))
def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs):
chest_names = [x.name for x in bk_locs]
return lambda state: state.has_key(small_key_name, player, keys) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs):
loc = location.name
chest_names = [x.name for x in bk_locs]
return lambda state: state.has_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
def create_advanced_key_rule(key_logic, player, rule):
if not rule.allow_small and rule.alternate_small_key is None:
return create_key_rule(key_logic.small_key_name, player, rule.small_key_num)
if rule.allow_small and rule.alternate_small_key is None:
return create_key_rule_allow_small(key_logic.small_key_name, player, rule.small_key_num, rule.small_location)
if not rule.allow_small and rule.alternate_small_key is not None:
return create_key_rule_bk_exception(key_logic.small_key_name, key_logic.bk_name, player, rule.small_key_num,
rule.alternate_small_key, rule.alternate_big_key_loc)
if rule.allow_small and rule.alternate_small_key is not None:
return create_key_rule_bk_exception_or_allow(key_logic.small_key_name, key_logic.bk_name, player,
rule.small_key_num, rule.small_location, rule.alternate_small_key,
rule.alternate_big_key_loc)