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:
@@ -127,12 +127,9 @@ def vanilla_key_logic(world, player):
|
||||
raise Exception('Vanilla key layout not valid %s' % sector.name)
|
||||
if player not in world.key_logic.keys():
|
||||
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 = analyze_dungeon(key_layout_2, world, player)
|
||||
world.key_logic[player][sector.name] = key_layout_2.key_logic
|
||||
else:
|
||||
world.key_logic[player][sector.name] = key_layout.key_logic
|
||||
key_layout_2 = KeyLayout(sector, start_regions, doors)
|
||||
key_layout_2 = analyze_dungeon(key_layout_2, world, player)
|
||||
world.key_logic[player][sector.name] = key_layout_2.key_logic
|
||||
validate_vanilla_key_logic(world, player)
|
||||
|
||||
|
||||
@@ -297,16 +294,17 @@ def within_dungeon(world, player):
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
key, sector_list, entrance_list = sector_queue.popleft()
|
||||
split_dungeon = key in split_region_starts.keys()
|
||||
origin_list = list(entrance_list)
|
||||
find_enabled_origins(sector_list, enabled_entrances, origin_list, entrances_map, key)
|
||||
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:
|
||||
raise Exception('Infinte loop detected %s' % key)
|
||||
sector_queue.append((key, sector_list, entrance_list))
|
||||
last_key = key
|
||||
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)
|
||||
ds.name = key
|
||||
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:
|
||||
parent = ent.parent_region
|
||||
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)
|
||||
else:
|
||||
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:
|
||||
if region.name in enabled.keys() and region.name not in entrance_list:
|
||||
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)
|
||||
if drop_exception(region.name): # only because they have unique regions
|
||||
entrance_list.append(region.name)
|
||||
|
||||
|
||||
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():
|
||||
new_region = connections[region.name]
|
||||
for potential in potentials.pop(new_region):
|
||||
enabled[potential] = region.name
|
||||
enabled[potential] = (region.name, region.dungeon)
|
||||
# see if this unexplored region connects elsewhere
|
||||
queue = collections.deque(new_region.exits)
|
||||
visited = set()
|
||||
@@ -389,7 +390,7 @@ def find_new_entrances(sector, connections, potentials, enabled, world, player):
|
||||
region_name = ext.connected_region.name
|
||||
if region_name in connections.keys() and connections[region_name] in potentials.keys():
|
||||
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]:
|
||||
for new_exit in ext.connected_region.exits:
|
||||
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 None, None
|
||||
|
||||
|
||||
def find_compatible_door_in_list(ugly_regions, world, door, doors, player):
|
||||
if door.type in [DoorType.Hole, DoorType.Warp, DoorType.Logical]:
|
||||
return door
|
||||
@@ -877,8 +879,30 @@ def shuffle_key_doors(dungeon_sector, entrances, world, player):
|
||||
# make changes
|
||||
if player not in world.key_logic.keys():
|
||||
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)
|
||||
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):
|
||||
@@ -1792,7 +1816,7 @@ interior_doors = [
|
||||
('Skull East Bridge ES', 'Skull West Bridge Nook WS'),
|
||||
('Skull Star Pits WS', 'Skull Torch Room ES'),
|
||||
('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 Conveyor Maze SW', 'Thieves Pot Alcove Top NW'),
|
||||
('Thieves Conveyor Maze EN', 'Thieves Hallway WN'),
|
||||
@@ -1943,7 +1967,7 @@ default_small_key_doors = {
|
||||
('Skull Map Room SE', 'Skull Pinball NE'),
|
||||
('Skull 2 West Lobby NW', 'Skull X Room 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 Hallway WS', 'Thieves Pot Alcove Mid ES'),
|
||||
|
||||
8
Doors.py
8
Doors.py
@@ -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 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 WS', Intr).dir(We, 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 Spike Corner 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, '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 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 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 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),
|
||||
@@ -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 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 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 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)
|
||||
|
||||
@@ -24,17 +24,20 @@ class GraphPiece:
|
||||
self.hanger_crystal = None
|
||||
self.hooks = {}
|
||||
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('')
|
||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||
doors_to_connect = set()
|
||||
all_regions = set()
|
||||
bk_needed = False
|
||||
for sector in available_sectors:
|
||||
for door in sector.outstanding_doors:
|
||||
doors_to_connect.add(door)
|
||||
all_regions.update(sector.regions)
|
||||
bk_needed = bk_needed or determine_if_bk_needed(sector, split_dungeon, world, player)
|
||||
proposed_map = {}
|
||||
choices_master = [[]]
|
||||
depth = 0
|
||||
@@ -51,7 +54,7 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
|
||||
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_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:
|
||||
dungeon, hangers, hooks = dungeon_cache[depth]
|
||||
valid = True
|
||||
@@ -96,10 +99,20 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
|
||||
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):
|
||||
# step 1 create dungeon: Dict<DoorName|Origin, GraphPiece>
|
||||
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)
|
||||
doors_to_connect = 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():
|
||||
hanger_set.add(door)
|
||||
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
|
||||
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
|
||||
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):
|
||||
parent = parent_region(door, world, player).parent_region
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
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
|
||||
|
||||
# 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:
|
||||
return False
|
||||
all_visited = set()
|
||||
bk_possible = not bk_needed
|
||||
for piece in dungeon.values():
|
||||
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:
|
||||
return False
|
||||
if not bk_possible:
|
||||
return False
|
||||
new_hangers_found = True
|
||||
accessible_hook_types = []
|
||||
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(b_state.visited_blue)
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
return world.get_entrance(door.name, player)
|
||||
|
||||
@@ -496,6 +520,7 @@ class ExplorationState(object):
|
||||
self.used_locations = 0
|
||||
self.key_locations = 0
|
||||
self.used_smalls = 0
|
||||
self.bk_found = set()
|
||||
|
||||
self.non_door_entrances = []
|
||||
|
||||
@@ -520,6 +545,7 @@ class ExplorationState(object):
|
||||
ret.used_locations = self.used_locations
|
||||
ret.used_smalls = self.used_smalls
|
||||
ret.found_locations = list(self.found_locations)
|
||||
ret.bk_found = set(self.bk_found)
|
||||
|
||||
ret.non_door_entrances = list(self.non_door_entrances)
|
||||
return ret
|
||||
@@ -529,7 +555,7 @@ class ExplorationState(object):
|
||||
self.crystal = exp_door.crystal
|
||||
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 region not in self.visited_blue:
|
||||
self.visited_blue.append(region)
|
||||
@@ -547,6 +573,8 @@ class ExplorationState(object):
|
||||
self.ttl_locations += 1
|
||||
if location not in self.found_locations:
|
||||
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 self.flooded_key_check(location):
|
||||
self.perform_event(location.name, key_region)
|
||||
@@ -607,22 +635,22 @@ class ExplorationState(object):
|
||||
elif not self.in_door_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):
|
||||
if self.can_traverse_bk_check(door, isOrigin):
|
||||
if self.can_traverse(door):
|
||||
if door.controller is not None:
|
||||
door = door.controller
|
||||
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):
|
||||
self.append_door_to_list(door, self.unattached_doors)
|
||||
self.append_door_to_list(door, self.unattached_doors, flag)
|
||||
else:
|
||||
other = self.find_door_in_list(door, self.unattached_doors)
|
||||
if self.crystal != other.crystal:
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
for door in get_doors(world, region, player):
|
||||
@@ -676,9 +704,16 @@ class ExplorationState(object):
|
||||
return False
|
||||
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
|
||||
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
|
||||
|
||||
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):
|
||||
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 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:
|
||||
door_list.append(ExplorableDoor(door, self.crystal))
|
||||
door_list.append(ExplorableDoor(door, self.crystal, flag))
|
||||
else:
|
||||
door_list.append(ExplorableDoor(door, door.crystal))
|
||||
door_list.append(ExplorableDoor(door, door.crystal, flag))
|
||||
|
||||
def key_door_sort(self, d):
|
||||
if d.door.smallKey:
|
||||
@@ -719,9 +754,10 @@ class ExplorationState(object):
|
||||
|
||||
class ExplorableDoor(object):
|
||||
|
||||
def __init__(self, door, crystal):
|
||||
def __init__(self, door, crystal, flag):
|
||||
self.door = door
|
||||
self.crystal = crystal
|
||||
self.flag = flag
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
@@ -745,11 +781,11 @@ def extend_reachable_state(search_regions, state, world, player):
|
||||
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()
|
||||
for region in search_regions:
|
||||
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:
|
||||
explorable_door = local_state.next_avail_door()
|
||||
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
|
||||
if connect_region is not None:
|
||||
if valid_region_to_explore(connect_region, world, player) and not local_state.visited(connect_region):
|
||||
local_state.visit_region(connect_region)
|
||||
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, isOrigin, world, player)
|
||||
flag = explorable_door.flag or explorable_door.door.bigKey
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -8,73 +8,14 @@ from DungeonGenerator import ExplorationState
|
||||
class KeySphere(object):
|
||||
|
||||
def __init__(self):
|
||||
self.access_doors = set()
|
||||
self.free_locations = []
|
||||
self.access_door = None
|
||||
self.free_locations = set()
|
||||
self.prize_region = False
|
||||
self.key_only_locations = []
|
||||
self.key_only_locations = set()
|
||||
self.child_doors = set()
|
||||
self.bk_locked = False
|
||||
self.parent_sphere = None
|
||||
|
||||
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
|
||||
self.other_locations = set()
|
||||
|
||||
|
||||
class KeyLayout(object):
|
||||
@@ -121,6 +62,7 @@ class DoorRules(object):
|
||||
self.alternate_big_key_loc = set()
|
||||
# for a place with only 1 free location/key_only_location behind it ... no goals and locations
|
||||
self.allow_small = False
|
||||
self.small_location = None
|
||||
|
||||
|
||||
class KeyCounter(object):
|
||||
@@ -133,11 +75,21 @@ class KeyCounter(object):
|
||||
self.open_doors = set()
|
||||
self.used_keys = 0
|
||||
self.big_key_opened = False
|
||||
self.important_location = False
|
||||
|
||||
def update(self, key_sphere):
|
||||
self.free_locations.update(key_sphere.free_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):
|
||||
if door in flat_proposal:
|
||||
@@ -146,13 +98,15 @@ class KeyCounter(object):
|
||||
self.open_doors.add(door)
|
||||
if door.dest in flat_proposal:
|
||||
self.open_doors.add(door.dest)
|
||||
if door.dest in self.child_doors:
|
||||
self.child_doors.remove(door.dest)
|
||||
elif door.bigKey:
|
||||
self.big_key_opened = True
|
||||
self.child_doors.remove(door)
|
||||
self.open_doors.add(door)
|
||||
|
||||
def used_smalls_loc(self):
|
||||
return max(self.used_keys - len(self.key_only_locations), 0)
|
||||
def used_smalls_loc(self, reserve=0):
|
||||
return max(self.used_keys + reserve - len(self.key_only_locations), 0)
|
||||
|
||||
def copy(self):
|
||||
ret = KeyCounter(self.max_chests)
|
||||
@@ -161,6 +115,8 @@ class KeyCounter(object):
|
||||
ret.child_doors.update(self.child_doors)
|
||||
ret.used_keys = self.used_keys
|
||||
ret.open_doors.update(self.open_doors)
|
||||
ret.big_key_opened = self.big_key_opened
|
||||
ret.important_location = self.important_location
|
||||
return ret
|
||||
|
||||
|
||||
@@ -171,55 +127,56 @@ def analyze_dungeon(key_layout, world, player):
|
||||
key_logic = key_layout.key_logic
|
||||
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.update(key_layout.key_spheres['Origin'])
|
||||
queue = collections.deque([(key_layout.key_spheres['Origin'], key_counter)])
|
||||
doors_completed = set()
|
||||
|
||||
while len(queue) > 0:
|
||||
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)
|
||||
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)
|
||||
# todo: big chest counts?
|
||||
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls:
|
||||
key_logic.bk_restricted.update(key_counter.free_locations)
|
||||
# logic min
|
||||
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))
|
||||
# if available <= possible_smalls:
|
||||
# in this case, at least 1 child must have the available rule - unless relaxing is possible
|
||||
if not key_counter.big_key_opened:
|
||||
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls:
|
||||
key_logic.bk_restricted.update(key_counter.free_locations)
|
||||
# logic min?
|
||||
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))
|
||||
minimal_keys = None
|
||||
# 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?
|
||||
for child in key_sphere.child_doors:
|
||||
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:
|
||||
# todo: calculate based on big key doors vs smalls - eastern dark square
|
||||
rule = DoorRules(min(available, possible_smalls) + key_counter.used_keys)
|
||||
expanded_counter = expand_counter_to_last_door(child, key_counter, key_layout, set())
|
||||
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
|
||||
doors_completed.add(next_sphere.access_door)
|
||||
next_counter = increment_key_counter(child, next_sphere, key_counter, key_layout.flat_prop)
|
||||
queue.append((next_sphere, next_counter))
|
||||
return key_layout
|
||||
|
||||
|
||||
# for child in key_sphere.child_doors:
|
||||
# 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):
|
||||
def find_bk_locked_sections(key_layout, world):
|
||||
key_spheres = key_layout.key_spheres
|
||||
key_logic = key_layout.key_logic
|
||||
|
||||
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():
|
||||
sphere = key_spheres[key]
|
||||
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
|
||||
if not sphere.bk_locked:
|
||||
bk_key_not_required.update(sphere.free_locations)
|
||||
@@ -234,6 +191,21 @@ def empty_sphere(sphere):
|
||||
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):
|
||||
new_counter = key_counter.copy()
|
||||
new_counter.open_door(door, flat_proposal)
|
||||
@@ -241,75 +213,75 @@ def increment_key_counter(door, sphere, key_counter, flat_proposal):
|
||||
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()
|
||||
for other in key_counter.child_doors:
|
||||
if other != door and other.bigKey:
|
||||
big_doors.add(other)
|
||||
big_key_available = len(key_counter.free_location) - key_counter.used_smalls_loc > 0
|
||||
if len(big_doors) == 0 or not big_key_available:
|
||||
if other != door and other not in ignored_doors:
|
||||
if other.bigKey:
|
||||
big_doors.add(other)
|
||||
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
|
||||
new_counter = key_counter
|
||||
for big_door in big_doors:
|
||||
big_sphere = key_layout.key_spheres[big_door.name]
|
||||
new_counter = increment_key_counter(big_door, big_sphere, new_counter, key_layout.flat_prop)
|
||||
# nested big key doors?
|
||||
last_counter = key_counter
|
||||
new_ignored = set(ignored_doors)
|
||||
for new_door in small_doors.union(big_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 = check_for_big_doors(door, old_counter, key_layout)
|
||||
# I think I've opened them all!
|
||||
new_counter = expand_counter_to_last_door(door, old_counter, key_layout, new_ignored)
|
||||
return new_counter
|
||||
|
||||
|
||||
# def calc_basic_small_key_rule(key_sphere, key_spheres, key_layout, flat_proposal, world, player):
|
||||
# free_locations = set()
|
||||
# key_only_locations = set()
|
||||
# offshoot_doors = set()
|
||||
# queue = collections.deque()
|
||||
# parent = key_sphere.parent_sphere
|
||||
# while parent is not None:
|
||||
# queue.append(parent)
|
||||
# parent = parent.parent_sphere
|
||||
# while len(queue) > 0:
|
||||
# previous = queue.popleft()
|
||||
# free_locations.update(previous.free_locations)
|
||||
# key_only_locations.update(previous.key_only_locations)
|
||||
# for other_door in parent.child_doors:
|
||||
# if other_door not in key_sphere.access_doors:
|
||||
# offshoot_doors.add(other_door)
|
||||
# # todo: bk versions
|
||||
# chest_keys = available_chest_small_keys(key_layout, free_locations, key_sphere.bk_locked, world, player)
|
||||
# parent_avail = chest_keys + len(key_only_locations)
|
||||
#
|
||||
# usuable_elsewhere = 0
|
||||
# open_set = set()
|
||||
# queue = collections.deque(offshoot_doors)
|
||||
# while len(queue) > 0:
|
||||
# offshoot = queue.popleft()
|
||||
# open_set.add(offshoot)
|
||||
# if offshoot in flat_proposal:
|
||||
# usuable_elsewhere += 1
|
||||
# # else bk door
|
||||
# if offshoot.dest in flat_proposal:
|
||||
# open_set.add(offshoot.dest)
|
||||
# off_sphere = key_spheres[offshoot.name]
|
||||
# free_locations.update(off_sphere.free_locations)
|
||||
# key_only_locations.update(off_sphere.key_only_locations)
|
||||
# for other_door in off_sphere.child_doors:
|
||||
# if other_door not in key_sphere.access_doors and other_door not in open_set:
|
||||
# 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 create_rule(key_counter, key_layout, minimal_keys, world):
|
||||
chest_keys = available_chest_small_keys(key_counter, key_counter.big_key_opened, world)
|
||||
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)
|
||||
required_keys = min(available, possible_smalls) + key_counter.used_keys
|
||||
if minimal_keys is None or required_keys <= minimal_keys:
|
||||
return DoorRules(required_keys)
|
||||
else:
|
||||
return DoorRules(minimal_keys)
|
||||
|
||||
|
||||
def check_for_self_lock_key(rule, sphere, key_layout, world):
|
||||
if world.accessibility != 'locations':
|
||||
counter = KeyCounter(key_layout.max_chests)
|
||||
counter.update(sphere)
|
||||
if not self_lock_possible(counter):
|
||||
return
|
||||
queue = collections.deque(counter.child_doors)
|
||||
already_queued = set(counter.child_doors)
|
||||
while len(queue) > 0:
|
||||
child = queue.popleft()
|
||||
if child not in counter.open_doors:
|
||||
counter = increment_key_counter(child, key_layout.key_spheres[child.name], counter, key_layout.flat_prop)
|
||||
if not self_lock_possible(counter):
|
||||
return
|
||||
for new_door in counter.child_doors:
|
||||
if new_door not in already_queued:
|
||||
queue.append(new_door)
|
||||
already_queued.add(new_door)
|
||||
if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location:
|
||||
rule.allow_small = True
|
||||
rule.small_location = next(iter(counter.free_locations))
|
||||
|
||||
|
||||
def self_lock_possible(counter):
|
||||
return len(counter.free_locations) <= 1 and len(counter.key_only_locations) == 0 and not counter.important_location
|
||||
|
||||
|
||||
def available_chest_small_keys(key_counter, bk, world):
|
||||
@@ -322,14 +294,59 @@ def available_chest_small_keys(key_counter, bk, world):
|
||||
else:
|
||||
return key_counter.max_chests
|
||||
|
||||
# derive key rules from key regions
|
||||
# how many small key available at a given point (locations found / keysanity / retro)
|
||||
# how many doors can be opened before you vs. smalls available
|
||||
# soft lock detection - should it be run here?
|
||||
# run with both bk off (locked behind current door) and bk found (elsewhere in the dungeon)
|
||||
# rules generally smaller if bk locked behind current door
|
||||
# big key restriction based on bk_locked
|
||||
# prize regions - TT is weird as there are intermediate goals - assume child doors as well?
|
||||
|
||||
def bk_restricted_rules(rule, sphere, key_counter, key_layout, minimal_keys, world):
|
||||
if sphere.bk_locked:
|
||||
return
|
||||
expanded_counter = expand_counter_no_big_doors(sphere.access_door, key_counter, key_layout, set())
|
||||
bk_number = create_rule(expanded_counter, key_layout, minimal_keys, world).small_key_num
|
||||
if bk_number == rule.small_key_num:
|
||||
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):
|
||||
@@ -352,28 +369,15 @@ def create_key_spheres(key_layout, world, player):
|
||||
open_a_door(door, child_state, flat_proposal)
|
||||
expand_key_state(child_state, flat_proposal, world, player)
|
||||
child_kr = create_key_sphere(child_state, next_key_sphere, door)
|
||||
check_for_duplicates_sub_super_set(key_spheres, child_kr, door.name)
|
||||
queue.append((child_kr, child_state))
|
||||
if door.name not in key_spheres.keys():
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
key_sphere = KeySphere()
|
||||
key_sphere.parent_sphere = parent_sphere
|
||||
@@ -382,7 +386,9 @@ def create_key_sphere(state, parent_sphere, door):
|
||||
parent_locations = set()
|
||||
while p_region is not None:
|
||||
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
|
||||
u_doors = unique_doors(state.small_doors+state.big_doors).difference(parent_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:
|
||||
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
|
||||
key_sphere.prize_region = True
|
||||
key_sphere.other_locations.add(loc)
|
||||
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:
|
||||
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
|
||||
key_sphere.bk_locked = state.big_key_opened if not state.big_key_special else False
|
||||
if door is not None:
|
||||
key_sphere.access_doors.add(door)
|
||||
key_sphere.access_door = door
|
||||
return key_sphere
|
||||
|
||||
|
||||
@@ -483,66 +492,175 @@ def flatten_pair_list(paired_list):
|
||||
return flat_list
|
||||
|
||||
|
||||
## vanilla validation code
|
||||
# Soft lock stuff
|
||||
class SoftLockException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# vanilla validation code
|
||||
def validate_vanilla_key_logic(world, player):
|
||||
validators = {
|
||||
'Hyrule Castle': val_unimplemented,
|
||||
'Hyrule Castle': val_hyrule,
|
||||
'Eastern Palace': val_eastern,
|
||||
'Desert Palace': val_desert,
|
||||
'Tower of Hera': val_hera,
|
||||
'Agahnims Tower': val_tower,
|
||||
'Palace of Darkness': val_unimplemented,
|
||||
'Swamp Palace': val_unimplemented,
|
||||
'Skull Woods': val_unimplemented,
|
||||
'Thieves Town': val_unimplemented,
|
||||
'Ice Palace': val_unimplemented,
|
||||
'Misery Mire': val_unimplemented,
|
||||
'Turtle Rock': val_unimplemented,
|
||||
'Ganons Tower': val_unimplemented
|
||||
'Palace of Darkness': val_pod,
|
||||
'Swamp Palace': val_swamp,
|
||||
'Skull Woods': val_skull,
|
||||
'Thieves Town': val_thieves,
|
||||
'Ice Palace': val_ice,
|
||||
'Misery Mire': val_mire,
|
||||
'Turtle Rock': val_turtle,
|
||||
'Ganons Tower': val_ganons
|
||||
}
|
||||
key_logic_dict = world.key_logic[player]
|
||||
for key, key_logic in key_logic_dict.items():
|
||||
validators[key](key_logic)
|
||||
validators[key](key_logic, world, player)
|
||||
|
||||
|
||||
def val_unimplemented(key_logic):
|
||||
assert True
|
||||
def val_hyrule(key_logic, world, player):
|
||||
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):
|
||||
dark_square_rule = key_logic.door_rules['Eastern Dark Square Key Door WN']
|
||||
assert dark_square_rule.small_key_num == 2
|
||||
# todo: allow big_key behind the door
|
||||
# assert dark_square_rule.alternate_small_key == 1
|
||||
# assert 'Eastern Palace - Big Key Chest' in dark_square_rule.alternat_big_key_loc
|
||||
# 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
|
||||
def val_eastern(key_logic, world, player):
|
||||
# val_rule(key_logic.door_rules['Eastern Dark Square Key Door WN'], 2, False, None, 1, {'Eastern Palace - Big Key Chest'})
|
||||
val_rule(key_logic.door_rules['Eastern Dark Square Key Door WN'], 1)
|
||||
val_rule(key_logic.door_rules['Eastern Darkness Up Stairs'], 2)
|
||||
assert world.get_location('Eastern Palace - Big Chest', player) in key_logic.bk_restricted
|
||||
assert world.get_location('Eastern Palace - Boss', player) in key_logic.bk_restricted
|
||||
assert len(key_logic.bk_restricted) == 2
|
||||
|
||||
|
||||
def val_desert(key_logic):
|
||||
assert key_logic.door_rules['Desert East Wing Key Door EN'].small_key_num == 2
|
||||
assert key_logic.door_rules['Desert Tiles 1 Up Stairs'].small_key_num == 2
|
||||
assert key_logic.door_rules['Desert Beamos Hall NE'].small_key_num == 3
|
||||
assert key_logic.door_rules['Desert Tiles 2 NE'].small_key_num == 4
|
||||
assert 'Desert Palace - Big Chest' in key_logic.bk_restricted
|
||||
assert 'Desert Palace - Boss' in key_logic.bk_restricted
|
||||
def val_desert(key_logic, world, player):
|
||||
val_rule(key_logic.door_rules['Desert East Wing Key Door EN'], 2)
|
||||
val_rule(key_logic.door_rules['Desert Tiles 1 Up Stairs'], 2)
|
||||
val_rule(key_logic.door_rules['Desert Beamos Hall NE'], 3)
|
||||
val_rule(key_logic.door_rules['Desert Tiles 2 NE'], 4)
|
||||
assert world.get_location('Desert Palace - Big Chest', player) 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
|
||||
|
||||
|
||||
def val_hera(key_logic):
|
||||
assert key_logic.door_rules['Hera Lobby Key Stairs'].small_key_num == 1
|
||||
assert 'Tower of Hera - Big Chest' in key_logic.bk_restricted
|
||||
assert 'Tower of Hera - Compass Chest' in key_logic.bk_restricted
|
||||
assert 'Tower of Hera - Boss' in key_logic.bk_restricted
|
||||
def val_hera(key_logic, world, player):
|
||||
val_rule(key_logic.door_rules['Hera Lobby Key Stairs'], 1, True, 'Tower of Hera - Big Key Chest')
|
||||
assert world.get_location('Tower of Hera - Big Chest', player) in key_logic.bk_restricted
|
||||
assert world.get_location('Tower of Hera - Compass Chest', player) 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
|
||||
|
||||
|
||||
def val_tower(key_logic):
|
||||
assert key_logic.door_rules['Tower Room 03 Up Stairs'].small_key_num == 1
|
||||
assert key_logic.door_rules['Tower Dark Maze ES'].small_key_num == 2
|
||||
assert key_logic.door_rules['Tower Dark Chargers Up Stairs'].small_key_num == 3
|
||||
assert key_logic.door_rules['Tower Circle of Pots WS'].small_key_num == 4
|
||||
def val_tower(key_logic, world, player):
|
||||
val_rule(key_logic.door_rules['Tower Room 03 Up Stairs'], 1)
|
||||
val_rule(key_logic.door_rules['Tower Dark Maze ES'], 2)
|
||||
val_rule(key_logic.door_rules['Tower Dark Archers Up Stairs'], 3)
|
||||
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
|
||||
|
||||
@@ -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 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 Spike Corner', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop'], ['Skull Spike Corner SW', 'Skull Spike Corner WS']),
|
||||
create_dungeon_region(player, 'Skull Final Drop', 'Skull Woods', None, ['Skull Final Drop ES', 'Skull Final Drop Hole']),
|
||||
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 WS', 'Skull Final Drop Hole']),
|
||||
create_dungeon_region(player, 'Skull Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
|
||||
# tt
|
||||
|
||||
56
Rules.py
56
Rules.py
@@ -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))
|
||||
# Big key rules
|
||||
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 Courtyard N', player), lambda state: state.has('Big Key (Eastern Palace)', player))
|
||||
|
||||
@@ -276,8 +274,6 @@ def global_rules(world, player):
|
||||
|
||||
# Desert
|
||||
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_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))
|
||||
@@ -285,8 +281,6 @@ def global_rules(world, player):
|
||||
|
||||
# Tower of Hera
|
||||
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_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))
|
||||
@@ -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 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))
|
||||
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 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))
|
||||
@@ -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_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))
|
||||
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 - Prize', 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))
|
||||
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 Vines NW', player), lambda state: state.has_sword(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))
|
||||
# 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)))
|
||||
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']:
|
||||
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']:
|
||||
@@ -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_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_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))
|
||||
@@ -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 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)))
|
||||
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_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))
|
||||
@@ -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 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))
|
||||
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 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))
|
||||
@@ -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))
|
||||
# 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))
|
||||
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 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):
|
||||
logger = logging.getLogger('')
|
||||
key_logic = world.key_logic[player]
|
||||
for d_name, d_logic in key_logic.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_key_rule(d_logic.small_key_name, player, keys))
|
||||
add_rule(world.get_entrance(door_name, player), create_advanced_key_rule(d_logic, player, keys))
|
||||
for location in d_logic.bk_restricted:
|
||||
if location.name not in key_only_locations.keys():
|
||||
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)
|
||||
|
||||
|
||||
def create_forced_small_rule(small_key_name, player):
|
||||
return lambda item: item.name == small_key_name and item.player == player
|
||||
def create_key_rule_allow_small(small_key_name, player, keys, location):
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user