TR Crystal Maze adjustments

Fixed key door candidate finder to stay within own dungeon
Standard mode support added
Added missed Pre-moldorm chest
Started work on new key logic analyzer
This commit is contained in:
aerinon
2019-11-19 10:50:44 -07:00
parent 644785b140
commit 48494a09ba
8 changed files with 237 additions and 12 deletions

View File

@@ -12,6 +12,7 @@ from Dungeons import dungeon_regions, region_starts, split_region_starts, dungeo
from Dungeons import drop_entrances
from RoomData import DoorKind, PairedDoor
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon
from KeyDoorShuffle import analyze_dungeon
def link_doors(world, player):
@@ -124,6 +125,7 @@ def vanilla_key_logic(world, player):
valid = validate_key_layout(key_layout, world, player)
if not valid:
raise Exception('Vanilla key layout not valid %s' % sector.name)
analyze_dungeon(key_layout, world, player)
if player not in world.key_logic.keys():
world.key_logic[player] = {}
world.key_logic[player][sector.name] = key_layout.key_logic
@@ -288,11 +290,12 @@ def within_dungeon(world, player):
for key, sector_list, entrance_list in dungeon_sectors:
origin_list = list(entrance_list)
find_enabled_origins(sector_list, enabled_entrances, origin_list)
remove_drop_origins(origin_list)
origin_list = remove_drop_origins(origin_list)
ds = generate_dungeon(sector_list, origin_list, world, player)
find_new_entrances(ds, connections, potentials, enabled_entrances)
ds.name = key
dungeon_layouts.append((ds, entrance_list))
layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list
dungeon_layouts.append((ds, layout_starts))
combine_layouts(dungeon_layouts, entrances_map)
world.dungeon_layouts[player] = {}
@@ -891,6 +894,7 @@ def flatten_pair_list(paired_list):
def find_key_door_candidates(region, checked, world, player):
dungeon = region.dungeon
candidates = []
checked_doors = list(checked)
queue = collections.deque([(region, None, None)])
@@ -920,7 +924,8 @@ def find_key_door_candidates(region, checked, world, player):
valid = True
if valid:
candidates.append(d)
queue.append((ext.connected_region, d, current)) # - todo: fix isolated ledge from re-entering
if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon:
queue.append((ext.connected_region, d, current))
if d is not None:
checked_doors.append(d)
return candidates, checked_doors
@@ -1206,7 +1211,8 @@ def determine_required_paths(world):
paths['Turtle Rock'].insert(0, 'TR Lazy Eyes')
if world.mode == 'standard':
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
paths['Hyrule Castle'].append('Sanctuary')
# noinspection PyTypeChecker
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if world.doorShuffle in ['basic', 'experimental']:
paths['Thieves Town'].append('Thieves Attic Window')
return paths
@@ -1235,6 +1241,9 @@ def find_inaccessible_regions(world, player):
if connect is not None and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions:
queue.append(connect)
world.inaccessible_regions.extend([r.name for r in all_regions.difference(visited_regions) if r.type is not RegionType.Cave])
if world.mode == 'standard':
world.inaccessible_regions.append('Hyrule Castle Ledge')
world.inaccessible_regions.append('Sewer Drop')
logger = logging.getLogger('')
logger.info('Inaccessible Regions:')
for r in world.inaccessible_regions:
@@ -1462,6 +1471,9 @@ logical_connections = [
('TR Pipe Ledge Drop Down', 'TR Pipe Pit'),
('TR Big Chest Gap', 'TR Big Chest Entrance'),
('TR Big Chest Entrance Gap', 'TR Big Chest'),
('TR Crystal Maze Forwards Path', 'TR Crystal Maze End'),
('TR Crystal Maze Blue Path', 'TR Crystal Maze'),
('TR Crystal Maze Cane Path', 'TR Crystal Maze'),
('GT Blocked Stairs Block Path', 'GT Big Chest'),
('GT Hookshot East-North Path', 'GT Hookshot North Platform'),
('GT Hookshot East-South Path', 'GT Hookshot South Platform'),

View File

@@ -878,6 +878,9 @@ def create_doors(world, player):
create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0),
create_door(player, 'TR Eye Bridge NW', Nrml).dir(No, 0xd5, Left, High).pos(1),
create_door(player, 'TR Crystal Maze ES', Nrml).dir(Ea, 0xc4, Bot, High).small_key().pos(0),
create_door(player, 'TR Crystal Maze Forwards Path', Lgcl),
create_door(player, 'TR Crystal Maze Blue Path', Lgcl),
create_door(player, 'TR Crystal Maze Cane Path', Lgcl),
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(No, 0xb4, Right, High),
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
@@ -1154,7 +1157,9 @@ def create_doors(world, player):
world.get_door('TR Crystaroller SW', player).c_switch()
world.get_door('TR Crystaroller Down Stairs', player).c_switch()
world.get_door('TR Crystal Maze ES', player).c_switch()
world.get_door('TR Crystal Maze North Stairs', player).c_switch()
world.get_door('TR Crystal Maze Forwards Path', player).c_switch()
world.get_door('TR Crystal Maze Cane Path', player).c_switch()
world.get_door('TR Crystal Maze Blue Path', player).barrier(CrystalBarrier.Blue)
world.get_door('GT Crystal Conveyor NE', player).c_switch()
world.get_door('GT Crystal Conveyor WN', player).c_switch()

View File

@@ -41,8 +41,9 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
dungeon_cache = {}
backtrack = False
itr = 0
finished = False
# last_choice = None
while len(proposed_map) < len(doors_to_connect):
while not finished:
# what are my choices?
itr += 1
if itr > 5000:
@@ -55,6 +56,9 @@ def generate_dungeon(available_sectors, entrance_region_names, world, player):
dungeon, hangers, hooks = dungeon_cache[depth]
valid = True
if valid:
if len(proposed_map) == len(doors_to_connect):
finished = True
continue
prev_choices = choices_master[depth]
# make a choice
hanger, hook = make_a_choice(dungeon, hangers, hooks, prev_choices)

View File

@@ -274,7 +274,8 @@ tr_regions = [
'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps', 'TR Pipe Pit', 'TR Pipe Ledge', 'TR Lava Dual Pipes',
'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2', 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers', 'TR Big View',
'TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull', 'TR Rupees',
'TR Crystaroller', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge', 'TR Crystal Maze', 'TR Final Abyss', 'TR Boss'
'TR Crystaroller', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge', 'TR Crystal Maze', 'TR Crystal Maze End',
'TR Final Abyss', 'TR Boss'
]
gt_regions = [

View File

@@ -3138,7 +3138,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
('Lumberjack House', 'Lumberjack House'),
("Hyrule Castle Secret Entrance Drop", "Hyrule Castle Secret Entrance"),
("Hyrule Castle Secret Entrance Stairs", "Hyrule Castle Secret Entrance"),
("Hyrule Castle Secret Entrance Exit", "Light World"),
("Hyrule Castle Secret Entrance Exit", "Hyrule Castle Courtyard"),
('Bonk Fairy (Light)', 'Bonk Fairy (Light)'),
('Lake Hylia Fairy', 'Lake Hylia Healer Fairy'),
('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'),
@@ -3449,7 +3449,7 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Main L
('Hyrule Castle Entrance (South)', 'Hyrule Castle Lobby'),
('Hyrule Castle Entrance (West)', 'Hyrule Castle West Lobby'),
('Hyrule Castle Entrance (East)', 'Hyrule Castle East Lobby'),
('Hyrule Castle Exit (South)', 'Light World'),
('Hyrule Castle Exit (South)', 'Hyrule Castle Courtyard'),
('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'),
('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'),
('Agahnims Tower', 'Tower Lobby'),

198
KeyDoorShuffle.py Normal file
View File

@@ -0,0 +1,198 @@
import collections
from Regions import dungeon_events
from DungeonGenerator import ExplorationState
class KeyRegion(object):
def __init__(self):
self.access_doors = set()
self.free_locations = []
self.prize_region = False
self.key_only_locations = []
self.child_doors = set()
self.bk_locked = False
self.parent_region = 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
def analyze_dungeon(key_layout, world, player):
flat_proposal = flatten_pair_list(key_layout.proposal)
key_regions = create_key_regions(key_layout, flat_proposal, world, player)
start = key_regions['Origin']
def create_key_regions(key_layout, flat_proposal, world, player):
key_regions = {}
state = ExplorationState()
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
for region in key_layout.start_regions:
state.visit_region(region, key_checks=True)
state.add_all_doors_check_keys(region, flat_proposal, world, player)
expand_key_state(state, flat_proposal, world, player)
key_regions['Origin'] = create_key_region(state, None, None)
queue = collections.deque([(key_regions['Origin'], state)])
while len(queue) > 0:
next_key_region, parent_state = queue.popleft()
for door in next_key_region.child_doors:
child_state = parent_state.copy()
# open the door
open_a_door(door, child_state, flat_proposal)
expand_key_state(child_state, flat_proposal, world, player)
child_kr = create_key_region(child_state, next_key_region, door)
check_for_duplicates_sub_super_set(key_regions, child_kr, door.name)
queue.append((child_kr, child_state))
return key_regions
def check_for_duplicates_sub_super_set(key_regions, new_kr, door_name):
is_new = True
for kr in key_regions.values():
if new_kr == kr:
kr.access_doors.update(new_kr.access_doors)
kr.child_doors.update(new_kr.child_doors)
key_regions[door_name] = kr
is_new = False
break
# if new_kr.issubset(kr):
# break
# if new_kr.issuperset(kr):
# break
if is_new:
key_regions[door_name] = new_kr
def create_key_region(state, parent_region, door):
key_region = KeyRegion()
key_region.parent_region = parent_region
p_region = parent_region
parent_doors = set()
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)
p_region = p_region.parent_region
u_doors = unique_doors(state.small_doors+state.big_doors).difference(parent_doors)
key_region.child_doors.update(u_doors)
region_locations = set(state.found_locations).difference(parent_locations)
for loc in region_locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
key_region.prize_region = True
elif loc.event and 'Small Key' in loc.item.name:
key_region.key_only_locations.append(loc)
elif loc.name not in dungeon_events:
key_region.free_locations.append(loc)
key_region.bk_locked = state.big_key_opened
if door is not None:
key_region.access_doors.add(door)
return key_region
def open_a_door(door, child_state, flat_proposal):
if door.bigKey:
child_state.big_key_opened = True
child_state.avail_doors.extend(child_state.big_doors)
child_state.opened_doors.extend(set([d.door for d in child_state.big_doors]))
child_state.big_doors.clear()
else:
child_state.opened_doors.append(door)
doors_to_open = [x for x in child_state.small_doors if x.door == door]
child_state.small_doors[:] = [x for x in child_state.small_doors if x.door != door]
child_state.avail_doors.extend(doors_to_open)
dest_door = door.dest
if dest_door in flat_proposal:
child_state.opened_doors.append(dest_door)
if child_state.in_door_list_ic(dest_door, child_state.small_doors):
now_available = [x for x in child_state.small_doors if x.door == dest_door]
child_state.small_doors[:] = [x for x in child_state.small_doors if x.door != dest_door]
child_state.avail_doors.extend(now_available)
def unique_doors(doors):
unique_d_set = set()
for d in doors:
if d.door not in unique_d_set:
unique_d_set.add(d.door)
return unique_d_set
def expand_key_state(state, flat_proposal, world, player):
while len(state.avail_doors) > 0:
exp_door = state.next_avail_door()
door = exp_door.door
connect_region = world.get_entrance(door.name, player).connected_region
if state.validate(door, connect_region, world):
state.visit_region(connect_region, key_checks=True)
state.add_all_doors_check_keys(connect_region, flat_proposal, world, player)
def flatten_pair_list(paired_list):
flat_list = []
for d in paired_list:
if type(d) is tuple:
flat_list.append(d[0])
flat_list.append(d[1])
else:
flat_list.append(d)
return flat_list

View File

@@ -611,7 +611,8 @@ def create_regions(world, player):
create_dungeon_region(player, 'TR Eye Bridge', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
['Turtle Rock Isolated Ledge Exit', 'TR Eye Bridge NW']),
create_dungeon_region(player, 'TR Crystal Maze', 'Turtle Rock', None, ['TR Crystal Maze ES', 'TR Crystal Maze North Stairs']),
create_dungeon_region(player, 'TR Crystal Maze', 'Turtle Rock', None, ['TR Crystal Maze ES', 'TR Crystal Maze Forwards Path']),
create_dungeon_region(player, 'TR Crystal Maze End', 'Turtle Rock', None, ['TR Crystal Maze Blue Path', 'TR Crystal Maze Cane Path', 'TR Crystal Maze North Stairs']),
create_dungeon_region(player, 'TR Final Abyss', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss NW']),
create_dungeon_region(player, 'TR Boss', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize'], ['TR Boss SW']),
@@ -692,7 +693,7 @@ def create_regions(world, player):
create_dungeon_region(player, 'GT Mini Helmasaur Room', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left',
'Ganons Tower - Mini Helmasaur Room - Right', 'Ganons Tower - Mini Helmasuar Key Drop'], ['GT Mini Helmasaur Room SE', 'GT Mini Helmasaur Room WN']),
create_dungeon_region(player, 'GT Bomb Conveyor', 'Ganon\'s Tower', None, ['GT Bomb Conveyor EN', 'GT Bomb Conveyor SW']),
create_dungeon_region(player, 'GT Crystal Circles', 'Ganon\'s Tower', None, ['GT Crystal Circles NW', 'GT Crystal Circles SW']),
create_dungeon_region(player, 'GT Crystal Circles', 'Ganon\'s Tower', ['Ganons Tower - Pre-Moldorm Chest'], ['GT Crystal Circles NW', 'GT Crystal Circles SW']),
create_dungeon_region(player, 'GT Left Moldorm Ledge', 'Ganon\'s Tower', None, ['GT Left Moldorm Ledge Drop Down', 'GT Left Moldorm Ledge NW']),
create_dungeon_region(player, 'GT Right Moldorm Ledge', 'Ganon\'s Tower', None, ['GT Right Moldorm Ledge Down Stairs', 'GT Right Moldorm Ledge Drop Down']),
create_dungeon_region(player, 'GT Moldorm', 'Ganon\'s Tower', None, ['GT Moldorm Hole', 'GT Moldorm Gap']),

View File

@@ -420,6 +420,7 @@ def global_rules(world, 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 Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Crystal Maze Cane Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss South Stairs', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Final Abyss NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Big Key (Turtle Rock)', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
@@ -1046,7 +1047,10 @@ def standard_rules(world, player):
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_location('Hyrule Castle - Key Rat Key Drop', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Hyrule Dungeon Armory S', player), lambda state: state.can_kill_most_things(player))
def set_trock_key_rules(world, player):