Merged in DR v.1.2.0.21

This commit is contained in:
codemann8
2023-09-29 15:11:06 -05:00
6 changed files with 96 additions and 30 deletions

View File

@@ -178,6 +178,7 @@ def create_door_spoiler(world, player):
queue = deque(world.dungeon_layouts[player].values()) queue = deque(world.dungeon_layouts[player].values())
while len(queue) > 0: while len(queue) > 0:
builder = queue.popleft() builder = queue.popleft()
std_flag = world.mode[player] == 'standard' and builder.name == 'Hyrule Castle' and world.shuffle[player] == 'vanilla'
done = set() done = set()
start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic
reg_queue = deque(start_regions) reg_queue = deque(start_regions)
@@ -206,11 +207,15 @@ def create_door_spoiler(world, player):
logger.warning('This is a bug during door spoiler') logger.warning('This is a bug during door spoiler')
elif not isinstance(door_b, Region): elif not isinstance(door_b, Region):
logger.warning('Door not connected: %s', door_a.name) logger.warning('Door not connected: %s', door_a.name)
if connect and connect.type == RegionType.Dungeon and connect not in visited: if valid_connection(connect, std_flag, world, player) and connect not in visited:
visited.add(connect) visited.add(connect)
reg_queue.append(connect) reg_queue.append(connect)
def valid_connection(region, std_flag, world, player):
return region and (region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player] or
(std_flag and region.name == 'Hyrule Castle Ledge'))
def vanilla_key_logic(world, player): def vanilla_key_logic(world, player):
builders = [] builders = []
world.dungeon_layouts[player] = {} world.dungeon_layouts[player] = {}
@@ -3331,6 +3336,9 @@ def find_inaccessible_regions(world, player):
ledge = world.get_region('Hyrule Castle Ledge', player) ledge = world.get_region('Hyrule Castle Ledge', player)
if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'): if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'):
world.inaccessible_regions[player].append('Hyrule Castle Ledge') world.inaccessible_regions[player].append('Hyrule Castle Ledge')
# this should be considered as part of the inaccessible regions, dungeonssimple?
if world.mode[player] == 'standard' and world.shuffle[player] == 'vanilla':
world.inaccessible_regions[player].append('Hyrule Castle Ledge')
logger = logging.getLogger('') logger = logging.getLogger('')
#logger.debug('Inaccessible Regions:') #logger.debug('Inaccessible Regions:')
#for r in world.inaccessible_regions[player]: #for r in world.inaccessible_regions[player]:

View File

@@ -36,7 +36,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data from source.tools.BPS import create_bps_from_data
from source.classes.CustomSettings import CustomSettings from source.classes.CustomSettings import CustomSettings
version_number = '1.2.0.20' version_number = '1.2.0.21'
version_branch = '-u' version_branch = '-u'
__version__ = f'{version_number}{version_branch}' __version__ = f'{version_number}{version_branch}'

View File

@@ -109,6 +109,10 @@ These are now independent of retro mode and have three options: None, Random, an
# Bug Fixes and Notes # Bug Fixes and Notes
* 1.2.0.21u
* Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory
* Small fix for Tavern Shuffle (thanks Catobat)
* Several small generation fixes
* 1.2.0.20u * 1.2.0.20u
* New generation feature that allows Spiral Stair to link to themselves (thank Catobat) * New generation feature that allows Spiral Stair to link to themselves (thank Catobat)
* Added logic for trap doors that could be opened using existing room triggers * Added logic for trap doors that could be opened using existing room triggers

View File

@@ -1577,13 +1577,15 @@ def standard_rules(world, player):
add_rule(ent, lambda state: standard_escape_rule(state)) add_rule(ent, lambda state: standard_escape_rule(state))
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player))
set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player)) set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player))
def check_rule_list(state, r_list): def check_rule_list(state, r_list):
return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:])
rule_list, debug_path = find_rules_for_zelda_delivery(world, player) rule_list, debug_path = find_rules_for_zelda_delivery(world, player)
set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list)) set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player),
lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))
set_rule(world.get_location('Zelda Drop Off', player),
lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))
for entrance in ['Links House SC', 'Links House ES', 'Central Bonk Rocks SW', 'Hyrule Castle WN', 'Hyrule Castle ES', for entrance in ['Links House SC', 'Links House ES', 'Central Bonk Rocks SW', 'Hyrule Castle WN', 'Hyrule Castle ES',
'Bonk Fairy (Light)', 'Hyrule Castle Main Gate (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Ledge Drop']: 'Bonk Fairy (Light)', 'Hyrule Castle Main Gate (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Ledge Drop']:

View File

@@ -72,11 +72,13 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()] entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect, idx = {}, 0 doors_to_connect, idx = {}, 0
all_regions = set() all_regions = set()
bk_special = False
for sector in builder.sectors: for sector in builder.sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
doors_to_connect[door.name] = door, idx doors_to_connect[door.name] = door, idx
idx += 1 idx += 1
all_regions.update(sector.regions) all_regions.update(sector.regions)
bk_special |= check_for_special(sector.regions)
finished = False finished = False
# flag if standard and this is hyrule castle # flag if standard and this is hyrule castle
paths = determine_paths_for_dungeon(world, player, all_regions, name) paths = determine_paths_for_dungeon(world, player, all_regions, name)
@@ -95,9 +97,9 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
if hash_code not in hash_code_set: if hash_code not in hash_code_set:
hash_code_set.add(hash_code) hash_code_set.add(hash_code)
explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect, explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect,
world, player) bk_special, world, player)
if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions, if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions,
paths, entrance_regions, world, player): paths, entrance_regions, bk_special, world, player):
finished = True finished = True
else: else:
proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect, proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect,
@@ -229,21 +231,24 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
return proposed_map, hash_code return proposed_map, hash_code
def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player): def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, bk_special, world, player):
start = ExplorationState(dungeon=name) start = ExplorationState(dungeon=name)
bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special
start.big_key_special = bk_special
original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map, original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map,
all_regions, valid_doors, world, player) all_regions, valid_doors, bk_relevant, world, player)
return original_state return original_state
def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions, def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions,
paths, entrance_regions, world, player): paths, entrance_regions, bk_special, world, player):
all_visited = set() all_visited = set()
all_visited.update(exploration_state.visited_blue) all_visited.update(exploration_state.visited_blue)
all_visited.update(exploration_state.visited_orange) all_visited.update(exploration_state.visited_orange)
if len(all_regions.difference(all_visited)) > 0: if len(all_regions.difference(all_visited)) > 0:
return False return False
if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player): if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map,
bk_special, world, player):
return False return False
return True return True
@@ -266,7 +271,7 @@ def check_for_special(regions):
return False return False
def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player): def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, bk_special, world, player):
for path in paths: for path in paths:
if type(path) is tuple: if type(path) is tuple:
target = path[1] target = path[1]
@@ -278,12 +283,13 @@ def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, propose
else: else:
target = path target = path
start_regions = entrance_regions start_regions = entrance_regions
if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player): if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions,
bk_special, world, player):
return False return False
return True return True
def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player): def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, bk_special, world, player):
target_regions = set() target_regions = set()
if type(target) is not list: if type(target) is not list:
for region in all_regions: for region in all_regions:
@@ -296,8 +302,10 @@ def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_re
target_regions.add(region) target_regions.add(region)
start = ExplorationState(dungeon=name) start = ExplorationState(dungeon=name)
bk_relevant = (world.door_type_mode[player] == 'original' and not world.bigkeyshuffle[player]) or bk_special
start.big_key_special = bk_special
original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions, original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions,
valid_doors, world, player) valid_doors, bk_relevant, world, player)
for exp_door in original_state.unattached_doors: for exp_door in original_state.unattached_doors:
if not exp_door.door.blocked or exp_door.door.trapFlag != 0: if not exp_door.door.blocked or exp_door.door.trapFlag != 0:
@@ -531,7 +539,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, bk_flag=False): def visit_region(self, region, key_region=None, key_checks=False, bk_relevant=False):
if region.type != RegionType.Dungeon: if region.type != RegionType.Dungeon:
self.crystal = CrystalBarrier.Orange self.crystal = CrystalBarrier.Orange
if self.crystal == CrystalBarrier.Either: if self.crystal == CrystalBarrier.Either:
@@ -552,8 +560,14 @@ 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: if bk_relevant:
self.bk_found.add(location) if self.big_key_special:
if special_big_key_found(self):
self.bk_found.add(location)
self.re_add_big_key_doors()
else:
self.bk_found.add(location)
self.re_add_big_key_doors()
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)
@@ -574,6 +588,14 @@ class ExplorationState(object):
return True return True
return False return False
def re_add_big_key_doors(self):
self.big_key_opened = True
queue = collections.deque(self.big_doors)
while len(queue) > 0:
exp_door = queue.popleft()
self.avail_doors.append(exp_door)
self.big_doors.remove(exp_door)
def perform_event(self, location_name, key_region): def perform_event(self, location_name, key_region):
self.events.add(location_name) self.events.add(location_name)
queue = collections.deque(self.event_doors) queue = collections.deque(self.event_doors)
@@ -640,7 +662,7 @@ class ExplorationState(object):
self.append_door_to_list(door, self.avail_doors, flag) self.append_door_to_list(door, self.avail_doors, flag)
# same as above but traps are ignored, and flag is not used # same as above but traps are ignored, and flag is not used
def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player): def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, bk_relevant, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors: if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door) self.visited_doors.add(door)
@@ -654,14 +676,18 @@ class ExplorationState(object):
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, elif (door.req_event is not None and door.req_event not in self.events
self.event_doors): 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)
elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player))
and not self.big_key_opened):
if not self.in_door_list(door, self.big_doors):
self.append_door_to_list(door, self.big_doors)
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)
# same as above but traps are checked for # same as above but traps are checked for
def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player): def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, bk_relevant, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors: if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door) self.visited_doors.add(door)
@@ -675,9 +701,13 @@ class ExplorationState(object):
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, elif (door.req_event is not None and door.req_event not in self.events
self.event_doors): 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)
elif (bk_relevant and (door.bigKey or door.name in get_special_big_key_doors(world, player))
and not self.big_key_opened):
if not self.in_door_list(door, self.big_doors):
self.append_door_to_list(door, self.big_doors)
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)
@@ -863,16 +893,22 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
return local_state return local_state
def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player): # bk_relevant means the big key doors need to be checks
def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, bk_relevant,
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, bk_relevant=bk_relevant)
if world.trap_door_mode[player] == 'vanilla': if world.trap_door_mode[player] == 'vanilla':
local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player) local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, bk_relevant, world, player)
else: else:
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player) local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, bk_relevant, 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.bigKey:
if bk_relevant and (not special_big_key_found(local_state) if local_state.big_key_special
else local_state.count_locations_exclude_specials(world, player) == 0):
continue
if explorable_door.door in proposed_map: if explorable_door.door in proposed_map:
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
else: else:
@@ -880,11 +916,13 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
if connect_region is not None: if connect_region is not None:
if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player) if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player)
and not local_state.visited(connect_region)): and not local_state.visited(connect_region)):
local_state.visit_region(connect_region) local_state.visit_region(connect_region, bk_relevant=bk_relevant)
if world.trap_door_mode[player] == 'vanilla': if world.trap_door_mode[player] == 'vanilla':
local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player) local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors,
bk_relevant, world, player)
else: else:
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player) local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors,
bk_relevant, world, player)
return local_state return local_state

View File

@@ -0,0 +1,14 @@
meta:
players: 1
settings:
1:
door_shuffle: crossed
intensity: 3
mode: standard
pottery: keys
dropshuffle: 'on'
doors:
1:
doors:
Hyrule Dungeon Cellblock Up Stairs:
dest: Ice Hammer Block Down Stairs