Standard + Crossed bug with Hyrule Castle

Big Key logic for standard
Some generation issues with ER
Redesigned Map/Compass section of Keysanity menu
This commit is contained in:
aerinon
2020-10-09 16:02:39 -06:00
parent 38be9e6ec0
commit 7aca24b10f
11 changed files with 230 additions and 205 deletions

View File

@@ -1620,6 +1620,15 @@ class Location(object):
return True return True
return False return False
def forced_big_key(self):
if self.forced_item and self.forced_item.bigkey and self.player == self.forced_item.player:
item_dungeon = self.forced_item.name.split('(')[1][:-1]
if item_dungeon == 'Escape':
item_dungeon = 'Hyrule Castle'
if self.parent_region.dungeon.name == item_dungeon:
return True
return False
def __str__(self): def __str__(self):
return str(self.__unicode__()) return str(self.__unicode__())

View File

@@ -42,18 +42,22 @@ def link_doors(world, player):
for entrance, ext in straight_staircases: for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
if world.doorShuffle[player] in ['basic', 'crossed']: find_inaccessible_regions(world, player)
find_inaccessible_regions(world, player)
if world.intensity[player] >= 3: if world.intensity[player] >= 3 and world.doorShuffle[player] in ['basic', 'crossed']:
choose_portals(world, player) choose_portals(world, player)
else: else:
if world.shuffle[player] == 'vanilla':
if world.mode[player] == 'standard':
world.get_portal('Sanctuary', player).destination = True
world.get_portal('Desert East', player).destination = True
world.get_portal('Skull 2 West', player).destination = True
world.get_portal('Turtle Rock Lazy Eyes', player).destination = True
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
else:
analyze_portals(world, player)
for portal in world.dungeon_portals[player]: for portal in world.dungeon_portals[player]:
connect_portal(portal, world, player) connect_portal(portal, world, player)
world.get_portal('Desert East', player).destination = True
world.get_portal('Skull 2 West', player).destination = True
world.get_portal('Turtle Rock Lazy Eyes', player).destination = True
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
if world.doorShuffle[player] == 'vanilla': if world.doorShuffle[player] == 'vanilla':
for entrance, ext in open_edges: for entrance, ext in open_edges:
@@ -150,8 +154,8 @@ def vanilla_key_logic(world, player):
builders.append(builder) builders.append(builder)
world.dungeon_layouts[player][builder.name] = builder world.dungeon_layouts[player][builder.name] = builder
overworld_prep(world, player) add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list(world, player) entrances_map, potentials, connections = determine_entrance_list_vanilla(world, player)
enabled_entrances = {} enabled_entrances = {}
sector_queue = deque(builders) sector_queue = deque(builders)
@@ -412,6 +416,48 @@ def choose_portals(world, player):
world.swamp_patch_required[player] = True world.swamp_patch_required[player] = True
def analyze_portals(world, player):
info_map = {}
for dungeon, portal_list in dungeon_portals.items():
info = DungeonInfo(dungeon)
region_map = defaultdict(list)
reachable_portals = []
inaccessible_portals = []
for portal in portal_list:
placeholder = world.get_region(portal + ' Placeholder', player)
portal_region = placeholder.exits[0].connected_region
name = portal_region.name
if portal_region.type == RegionType.LightWorld:
world.get_portal(portal, player).light_world = True
if name in world.inaccessible_regions[player]:
name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name
region_map[name_key].append(portal)
inaccessible_portals.append(portal)
else:
reachable_portals.append(portal)
info.total = len(portal_list)
info.required_passage = region_map
if len(reachable_portals) == 0:
if len(inaccessible_portals) == 1:
info.sole_entrance = inaccessible_portals[0]
info.required_passage.clear()
else:
raise Exception('please inspect this case')
if len(reachable_portals) == 1:
info.sole_entrance = reachable_portals[0]
info_map[dungeon] = info
for dungeon, info in info_map.items():
if dungeon == 'Hyrule Castle' and world.mode[player] == 'standard':
sanc = world.get_portal('Sanctuary', player)
sanc.destination = True
for target_region, possible_portals in info.required_passage.items():
if len(possible_portals) == 1:
world.get_portal(possible_portals[0], player).destination = True
elif len(possible_portals) > 1:
world.get_portal(random.choice(possible_portals), player).destination = True
def connect_portal(portal, world, player): def connect_portal(portal, world, player):
ent, ext = portal_map[portal.name] ent, ext = portal_map[portal.name]
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']: if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
@@ -516,60 +562,56 @@ def create_dungeon_entrances(world, player):
split_map: DefaultDict[str, DefaultDict[str, List]] = defaultdict(lambda: defaultdict(list)) split_map: DefaultDict[str, DefaultDict[str, List]] = defaultdict(lambda: defaultdict(list))
originating: DefaultDict[str, DefaultDict[str, Dict]] = defaultdict(lambda: defaultdict(dict)) originating: DefaultDict[str, DefaultDict[str, Dict]] = defaultdict(lambda: defaultdict(dict))
for key, portal_list in dungeon_portals.items(): for key, portal_list in dungeon_portals.items():
if world.mode[player] == 'standard' and key in standard_starts.keys(): if key in dungeon_drops.keys():
portal = world.get_portal('Hyrule Castle South', player) entrance_map[key].extend(dungeon_drops[key])
entrance_map[key].append(portal.door.entrance.parent_region.name) if key in split_portals.keys() and world.intensity[player] >= 3:
dead_ends = []
destinations = []
the_rest = []
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
entrance_map[key].append(portal.door.entrance.parent_region.name)
if portal.deadEnd:
dead_ends.append(portal)
elif portal.destination:
destinations.append(portal)
else:
the_rest.append(portal)
choices = list(split_portals[key])
for portal in dead_ends:
choice = random.choice(choices)
choices.remove(choice)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
for portal in the_rest:
if len(choices) == 0:
choices.append('Extra')
choice = random.choice(choices)
p_entrance = portal.door.entrance
r_name = p_entrance.parent_region.name
split_map[key][choice].append(r_name)
originating[key][choice][p_entrance.connected_region.name] = None
dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
for portal in destinations:
restricted = portal.door.entrance.connected_region.name in world.inaccessible_regions[player]
if restricted:
filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
else:
filtered_choices = dest_choices
choice = random.choice(filtered_choices)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
else: else:
if key in dungeon_drops.keys(): for portal_name in portal_list:
entrance_map[key].extend(dungeon_drops[key]) portal = world.get_portal(portal_name, player)
if key in split_portals.keys() and world.intensity[player] >= 3: r_name = portal.door.entrance.parent_region.name
dead_ends = [] entrance_map[key].append(r_name)
destinations = [] if key in split_portals.keys():
the_rest = [] for split_key in split_portals[key]:
for portal_name in portal_list: if split_key not in split_map[key]:
portal = world.get_portal(portal_name, player) split_map[key][split_key] = []
entrance_map[key].append(portal.door.entrance.parent_region.name) if world.intensity[player] < 3:
if portal.deadEnd: split_map[key][split_portal_defaults[key][r_name]].append(r_name)
dead_ends.append(portal)
elif portal.destination:
destinations.append(portal)
else:
the_rest.append(portal)
choices = list(split_portals[key])
for portal in dead_ends:
choice = random.choice(choices)
choices.remove(choice)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
for portal in the_rest:
if len(choices) == 0:
choices.append('Extra')
choice = random.choice(choices)
p_entrance = portal.door.entrance
r_name = p_entrance.parent_region.name
split_map[key][choice].append(r_name)
originating[key][choice][p_entrance.connected_region.name] = None
dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
for portal in destinations:
restricted = portal.door.entrance.connected_region.name in world.inaccessible_regions[player]
if restricted:
filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
else:
filtered_choices = dest_choices
choice = random.choice(filtered_choices)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
else:
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
r_name = portal.door.entrance.parent_region.name
entrance_map[key].append(r_name)
if key in split_portals.keys():
for split_key in split_portals[key]:
if split_key not in split_map[key]:
split_map[key][split_key] = []
if world.intensity[player] < 3:
split_map[key][split_portal_defaults[key][r_name]].append(r_name)
return entrance_map, split_map return entrance_map, split_map
@@ -580,7 +622,7 @@ def create_dungeon_entrances(world, player):
def within_dungeon(world, player): def within_dungeon(world, player):
fix_big_key_doors_with_ugly_smalls(world, player) fix_big_key_doors_with_ugly_smalls(world, player)
add_inaccessible_doors(world, player) add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list_2(world, player) entrances_map, potentials, connections = determine_entrance_list(world, player)
connections_tuple = (entrances_map, potentials, connections) connections_tuple = (entrances_map, potentials, connections)
dungeon_builders = {} dungeon_builders = {}
@@ -672,14 +714,14 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
world.dungeon_layouts[player] = dungeon_builders world.dungeon_layouts[player] = dungeon_builders
def determine_entrance_list(world, player): def determine_entrance_list_vanilla(world, player):
entrance_map = {} entrance_map = {}
potential_entrances = {} potential_entrances = {}
connections = {} connections = {}
for key, r_names in region_starts.items(): for key, r_names in region_starts.items():
entrance_map[key] = [] entrance_map[key] = []
if world.mode[player] == 'standard' and key in standard_starts.keys(): if world.mode[player] == 'standard' and key in standard_starts.keys():
r_names = standard_starts[key] r_names = ['Hyrule Castle Lobby']
for region_name in r_names: for region_name in r_names:
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
for ent in region.entrances: for ent in region.entrances:
@@ -695,28 +737,26 @@ def determine_entrance_list(world, player):
return entrance_map, potential_entrances, connections return entrance_map, potential_entrances, connections
def determine_entrance_list_2(world, player): def determine_entrance_list(world, player):
entrance_map = {} entrance_map = {}
potential_entrances = {} potential_entrances = {}
connections = {} connections = {}
for key, portal_list in dungeon_portals.items(): for key, portal_list in dungeon_portals.items():
entrance_map[key] = [] entrance_map[key] = []
r_names = [] r_names = {}
if world.mode[player] == 'standard' and key in standard_starts.keys(): if key in dungeon_drops.keys():
portal = world.get_portal('Hyrule Castle South', player) for drop in dungeon_drops[key]:
r_names.append(portal.door.entrance.parent_region.name) r_names[drop] = None
else: for portal_name in portal_list:
if key in dungeon_drops.keys(): portal = world.get_portal(portal_name, player)
r_names.extend(dungeon_drops[key]) r_names[portal.door.entrance.parent_region.name] = portal
for portal_name in portal_list: for region_name, portal in r_names.items():
portal = world.get_portal(portal_name, player)
r_names.append(portal.door.entrance.parent_region.name)
for region_name in r_names:
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
for ent in region.entrances: for ent in region.entrances:
parent = ent.parent_region parent = ent.parent_region
if (parent.type != RegionType.Dungeon and parent.name != 'Menu') or parent.name == 'Sewer Drop': if (parent.type != RegionType.Dungeon and parent.name != 'Menu') or parent.name == 'Sewer Drop':
if parent.name not in world.inaccessible_regions[player]: std_inaccessible = is_standard_inaccessible(key, portal, world, player)
if parent.name not in world.inaccessible_regions[player] and not std_inaccessible:
entrance_map[key].append(region_name) entrance_map[key].append(region_name)
else: else:
if parent not in potential_entrances.keys(): if parent not in potential_entrances.keys():
@@ -727,10 +767,14 @@ def determine_entrance_list_2(world, player):
return entrance_map, potential_entrances, connections return entrance_map, potential_entrances, connections
def is_standard_inaccessible(key, portal, world, player):
return world.mode[player] == 'standard' and key in standard_starts and (not portal or portal.name not in standard_starts[key])
def add_shuffled_entrances(sectors, region_list, entrance_list): def add_shuffled_entrances(sectors, region_list, entrance_list):
for sector in sectors: for sector in sectors:
for region in sector.regions: for region in sector.regions:
if region.name in region_list: if region.name in region_list and region.name not in entrance_list:
entrance_list.append(region.name) entrance_list.append(region.name)
@@ -805,8 +849,8 @@ def aga_tower_enabled(enabled):
def cross_dungeon(world, player): def cross_dungeon(world, player):
fix_big_key_doors_with_ugly_smalls(world, player) fix_big_key_doors_with_ugly_smalls(world, player)
overworld_prep(world, player) add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list_2(world, player) entrances_map, potentials, connections = determine_entrance_list(world, player)
connections_tuple = (entrances_map, potentials, connections) connections_tuple = (entrances_map, potentials, connections)
all_sectors, all_regions = [], [] all_sectors, all_regions = [], []
@@ -826,7 +870,7 @@ def cross_dungeon(world, player):
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name] key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
loc.forced_item = loc.item = ItemFactory(key_name, player) loc.forced_item = loc.item = ItemFactory(key_name, player)
recombinant_builders = {} recombinant_builders = {}
builder_info = None, None, world, player builder_info = entrances, splits, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info) handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
@@ -1486,11 +1530,6 @@ def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player) world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
def overworld_prep(world, player):
find_inaccessible_regions(world, player)
add_inaccessible_doors(world, player)
def find_inaccessible_regions(world, player): def find_inaccessible_regions(world, player):
world.inaccessible_regions[player] = [] world.inaccessible_regions[player] = []
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':

View File

@@ -858,10 +858,6 @@ class ExplorationState(object):
if location.name in flooded_keys_reverse.keys() and self.location_found( if location.name in flooded_keys_reverse.keys() and self.location_found(
flooded_keys_reverse[location.name]): flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region) self.perform_event(flooded_keys_reverse[location.name], key_region)
if key_checks and region.name == 'Hyrule Dungeon Cellblock' and not self.big_key_opened:
self.big_key_opened = True
self.avail_doors.extend(self.big_doors)
self.big_doors.clear()
def flooded_key_check(self, location): def flooded_key_check(self, location):
if location.name not in flooded_keys.keys(): if location.name not in flooded_keys.keys():
@@ -980,6 +976,12 @@ class ExplorationState(object):
def visited_at_all(self, region): def visited_at_all(self, region):
return region in self.visited_blue or region in self.visited_orange return region in self.visited_blue or region in self.visited_orange
def found_forced_bk(self):
for location in self.found_locations:
if location.forced_big_key():
return True
return False
def can_traverse(self, door, exception=None): def can_traverse(self, door, exception=None):
if door.blocked: if door.blocked:
return exception(door) if exception else False return exception(door) if exception else False
@@ -2763,7 +2765,7 @@ def split_dungeon_builder(builder, split_list, builder_info):
p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name) p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name)
if not p.deadEnd: if not p.deadEnd:
candidates.append(name) candidates.append(name)
merge_keys = random.sample(candidates, 2) merge_keys = random.sample(candidates, 2) if len(candidates) >= 2 else []
for name, split_entrances in split_list.items(): for name, split_entrances in split_list.items():
key = builder.name + ' ' + name key = builder.name + ' ' + name
if merge_keys and name in merge_keys: if merge_keys and name in merge_keys:

View File

@@ -333,7 +333,7 @@ region_starts = {
} }
standard_starts = { standard_starts = {
'Hyrule Castle': ['Hyrule Castle Lobby'] 'Hyrule Castle': ['Hyrule Castle South']
} }
split_region_starts = { split_region_starts = {

View File

@@ -832,6 +832,13 @@ def available_chest_small_keys_logic(key_counter, world, player, sm_restricted):
return key_counter.max_chests return key_counter.max_chests
def big_key_drop_available(key_counter):
for loc in key_counter.other_locations:
if loc.forced_big_key():
return True
return False
def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_layout, world, player): def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_layout, world, player):
if key_counter.big_key_opened: if key_counter.big_key_opened:
return return
@@ -1138,8 +1145,9 @@ def check_rules_deep(original_counter, key_layout, world, player):
bail = 0 bail = 0
last_counter = counter last_counter = counter
chest_keys = available_chest_small_keys_logic(counter, world, player, key_logic.sm_restricted) chest_keys = available_chest_small_keys_logic(counter, world, player, key_logic.sm_restricted)
big_avail = counter.big_key_opened bk_drop = big_key_drop_available(counter)
big_maybe_not_found = not counter.big_key_opened big_avail = counter.big_key_opened or bk_drop
big_maybe_not_found = not counter.big_key_opened and not bk_drop # better named as big_missing?
if not key_layout.big_key_special and not big_avail: if not key_layout.big_key_special and not big_avail:
if world.bigkeyshuffle[player]: if world.bigkeyshuffle[player]:
big_avail = True big_avail = True
@@ -1150,7 +1158,7 @@ def check_rules_deep(original_counter, key_layout, world, player):
break break
outstanding_big_locs = {x for x in big_locations if x not in counter.free_locations} outstanding_big_locs = {x for x in big_locations if x not in counter.free_locations}
if big_maybe_not_found: if big_maybe_not_found:
if len(outstanding_big_locs) == 0: if len(outstanding_big_locs) == 0 and not key_layout.big_key_special:
big_maybe_not_found = False big_maybe_not_found = False
big_uses_chest = big_avail and not key_layout.big_key_special big_uses_chest = big_avail and not key_layout.big_key_special
collected_alt = len(counter.key_only_locations) + chest_keys collected_alt = len(counter.key_only_locations) + chest_keys
@@ -1158,7 +1166,7 @@ def check_rules_deep(original_counter, key_layout, world, player):
chest_keys -= 1 chest_keys -= 1
collected = len(counter.key_only_locations) + chest_keys collected = len(counter.key_only_locations) + chest_keys
can_progress = len(counter.child_doors) == 0 can_progress = len(counter.child_doors) == 0
smalls_opened = False smalls_opened, big_opened = False, False
small_rules = [] small_rules = []
for door in counter.child_doors.keys(): for door in counter.child_doors.keys():
can_open = False can_open = False
@@ -1232,6 +1240,15 @@ def set_paired_rules(key_logic, world, player):
rule.opposite = key_logic.door_rules[door.dest.name] rule.opposite = key_logic.door_rules[door.dest.name]
def check_bk_special(regions, world, player):
for r_name in regions:
region = world.get_region(r_name, player)
for loc in region.locations:
if loc.forced_big_key():
return True
return False
# Soft lock stuff # Soft lock stuff
def validate_key_layout(key_layout, world, player): def validate_key_layout(key_layout, world, player):
# retro is all good - except for hyrule castle in standard mode # retro is all good - except for hyrule castle in standard mode
@@ -1240,7 +1257,7 @@ def validate_key_layout(key_layout, world, player):
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState(dungeon=key_layout.sector.name) state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = key_layout.max_chests state.key_locations = key_layout.max_chests
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions state.big_key_special = check_bk_special(key_layout.sector.regions, world, player)
for region in key_layout.start_regions: for region in key_layout.start_regions:
state.visit_region(region, key_checks=True) state.visit_region(region, key_checks=True)
state.add_all_doors_check_keys(region, flat_proposal, world, player) state.add_all_doors_check_keys(region, flat_proposal, world, player)
@@ -1262,7 +1279,10 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
return False return False
# todo: allow more key shuffles - refine placement rules # todo: allow more key shuffles - refine placement rules
# if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0): # if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0): found_forced_bk = state.found_forced_bk()
smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations)
bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk)
if smalls_done and bk_done:
return False return False
else: else:
if smalls_avail and available_small_locations > 0: if smalls_avail and available_small_locations > 0:
@@ -1281,10 +1301,11 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
valid = checked_states[code] valid = checked_states[code]
if not valid: if not valid:
return False return False
if not state.big_key_opened and available_big_locations >= num_bigs > 0: if not state.big_key_opened and (available_big_locations >= num_bigs > 0 or (found_forced_bk and num_bigs > 0)):
state_copy = state.copy() state_copy = state.copy()
open_a_door(state.big_doors[0].door, state_copy, flat_proposal) open_a_door(state.big_doors[0].door, state_copy, flat_proposal)
state_copy.used_locations += 1 if not found_forced_bk:
state_copy.used_locations += 1
code = state_id(state_copy, flat_proposal) code = state_id(state_copy, flat_proposal)
if code not in checked_states.keys(): if code not in checked_states.keys():
valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal,
@@ -1350,7 +1371,10 @@ def create_key_counters(key_layout, world, player):
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys) state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
else: else:
state.key_locations = world.dungeon_layouts[player][key_layout.sector.name].key_doors_num state.key_locations = world.dungeon_layouts[player][key_layout.sector.name].key_doors_num
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions state.big_key_special, special_region = False, None
forced_bk = world.get_region('Hyrule Dungeon Cellblock', player)
if forced_bk in key_layout.sector.regions:
state.big_key_special, special_region = True, forced_bk
for region in key_layout.start_regions: for region in key_layout.start_regions:
state.visit_region(region, key_checks=True) state.visit_region(region, key_checks=True)
state.add_all_doors_check_keys(region, flat_proposal, world, player) state.add_all_doors_check_keys(region, flat_proposal, world, player)
@@ -1365,7 +1389,7 @@ def create_key_counters(key_layout, world, player):
if door.bigKey: if door.bigKey:
key_layout.key_logic.bk_doors.add(door) key_layout.key_logic.bk_doors.add(door)
# open the door, if possible # open the door, if possible
if not door.bigKey or not child_state.big_key_special or child_state.big_key_opened: if not door.bigKey or not child_state.big_key_special or child_state.visited(special_region):
open_a_door(door, child_state, flat_proposal) open_a_door(door, child_state, flat_proposal)
expand_key_state(child_state, flat_proposal, world, player) expand_key_state(child_state, flat_proposal, world, player)
code = state_id(child_state, key_layout.flat_prop) code = state_id(child_state, key_layout.flat_prop)
@@ -1393,10 +1417,7 @@ def create_key_counter(state, key_layout, world, player):
key_counter.other_locations[loc] = None key_counter.other_locations[loc] = None
key_counter.open_doors.update(dict.fromkeys(state.opened_doors)) key_counter.open_doors.update(dict.fromkeys(state.opened_doors))
key_counter.used_keys = count_unique_sm_doors(state.opened_doors) key_counter.used_keys = count_unique_sm_doors(state.opened_doors)
if state.big_key_special: key_counter.big_key_opened = state.big_key_opened
key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player))
else:
key_counter.big_key_opened = state.big_key_opened
return key_counter return key_counter

View File

@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.2.0.0-u' __version__ = '0.2.0.1-u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass

View File

@@ -1,38 +1,16 @@
# New Features # New Features
* Crossed Dungeon generation improvements * Lobby shuffle added as Intensity level 3
* Standard mode generation improvements
* Spoiler lists bosses (multiworld compatible)
* Bombs escape not valid for Crossed Dungeon
* Graph algorithm speed improvement for placements and playthrough
* TT Attic Hint tile should have a crystal switch accessible now
* Updated to v.31.0.5
### Experimental features ### Experimental features
* Moved BK information and total chest keys per dungeon to keysanity menu. The info there requires compass for all info. * Redesign of Keysanity Menu for Crossed Dungeon
* Map still required for on-hud key counter.
* Added total counter to keysanity the compass/map screen when you have the compass for the dungeon.
* Open "Edge" transitions can now be linked with normal doors
* "Straight" staircases (the ones similar to normal doors) can be linked with both normal doors and edges
#### Couple of temporary debug features added: #### Temporary debug features:
* Total item count displays where TFH's goal usually does * Removed the red square in the upper right corner of the hud if the castle gate is closed
* A red square appears in the upper right corner of the hud if the castle gate is closed
# Bug Fixes # Bug Fixes
* Fix for Animated Tiles in crossed dungeon * Fixed a situation where logic did not account properly for Big Key doors in standard Hyrule Castle
* Stonewall hardlock no longer reachable from certain drops (Sewer Drop, some Skull Woods drops) that were previously possible * Fixed a problem ER shuffle generation that did not account for lobbies moving around
* No logic uses less key door logic
* Spoiler log encoding
* Enemizer settings made consistent with website
* Swamp flooded ladders in the basement now requires Flippers
* PoD EG Glitch gets killed on transitions (Only when DR is on)
* Problem with standard logic fixed wanting you to pass through the tapestry backwards to rescue Zelda
* Fixed SRAM corruption issues
* Problem with the dungeons requiring you to take Blind through her attic fixed. (Maiden no longer despawns)
* Hyrule Castle will not be your DW access in various Entrance Shuffles: simple, restricted, dungeonssimple, dungeonsfull
(Also prevents getting stuck in TR opening)
* Beatable only (accessibility: none) no longer fails when there are unplaced items

2
Rom.py
View File

@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '7d4881c390855dbe2f9c308bed2e61ef' RANDOMIZERBASEHASH = '094edde26279e8084d525135366fa67c'
class JsonRom(object): class JsonRom(object):

View File

@@ -1,4 +1,5 @@
!add = "clc : adc" !add = "clc : adc"
!addl = "clc : adc.l"
!sub = "sec : sbc" !sub = "sec : sbc"
!bge = "bcs" !bge = "bcs"
!blt = "bcc" !blt = "bcc"

View File

@@ -13,11 +13,6 @@ HudAdditions:
LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+10 ; draw 100's digit LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+10 ; draw 100's digit
LDX.b $06 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+12 ; draw 10's digit LDX.b $06 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+12 ; draw 10's digit
LDX.b $07 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+14 ; draw 1's digit LDX.b $07 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+14 ; draw 1's digit
lda $7ef29b : and #$0020 : beq +
lda #$207f : bra .drawthing
+ lda #$345e
.drawthing STA !GOAL_DRAW_ADDRESS+16 ; castle gate indicator
++ ++
ldx $040c : cpx #$ff : bne + : rts : + ldx $040c : cpx #$ff : bne + : rts : +
@@ -35,35 +30,30 @@ HudAdditions:
.reminder sta $7ec702 .reminder sta $7ec702
+ lda.w DRFlags : and #$0004 : beq .restore + lda.w DRFlags : and #$0004 : beq .restore
lda $7ef368 : and.l $0098c0, x : beq .restore lda $7ef368 : and.l $0098c0, x : beq .restore
; lda #$2811 : sta $7ec740
; lda $7ef366 : and.l $0098c0, x : bne .check
; lda.w BigKeyStatus, x : bne + ; change this, if bk status changes to one byte
; lda #$2574 : bra ++
; + cmp #$0002 : bne +
; lda #$2420 : bra ++
; + lda #$207f : bra ++
; .check lda #$2826
; ++ sta $7ec742
txa : lsr : tax txa : lsr : tax
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2 lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
lda #$2830 : sta $7ec7a4 lda #$2830 : sta $7ec7a4
lda.w ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6 lda.w ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6
; todo 4b0 no longer in use
; lda #$2871 : sta $7ec780
; lda.w TotalKeys, x
; sep #$20 : !sub $7ef4b0, x : rep #$20 ; todo 4b0 no longer in use
; jsr ConvertToDisplay : sta $7ec782
.restore .restore
plb : rts plb : rts
} }
;column distance for BK/Smalls
HudOffsets: HudOffsets:
; none hc east desert aga swamp pod mire skull ice hera tt tr gt ; none hc east desert aga swamp pod mire skull ice hera tt tr gt
dw $fffe, $0000, $0006, $0008, $0002, $0010, $000e, $0018, $0012, $0016, $000a, $0014, $001a, $001e dw $fffe, $0000, $0006, $0008, $0002, $0010, $000e, $0018, $0012, $0016, $000a, $0014, $001a, $001e
; offset from 1644
RowOffsets:
dw $0000, $0000, $0040, $0080, $0000, $0080, $0040, $0080, $00c0, $0040, $00c0, $0000, $00c0, $0000
ColumnOffsets:
dw $0000, $0000, $0000, $0000, $000a, $000a, $000a, $0014, $000a, $0014, $0000, $0014, $0014, $001e
DrHudDungeonItemsAdditions: DrHudDungeonItemsAdditions:
{ {
jsl DrawHUDDungeonItems jsl DrawHUDDungeonItems
@@ -75,11 +65,16 @@ DrHudDungeonItemsAdditions:
lda !HUD_FLAG : and.w #$0020 : beq + : bra ++ : + lda !HUD_FLAG : and.w #$0020 : beq + : bra ++ : +
lda HUDDungeonItems : and.w #$0003 : bne + : bra ++ : + lda HUDDungeonItems : and.w #$0003 : bne + : bra ++ : +
; blank out stuff
lda.w #$24f5 : sta $1606 : sta $1610 : sta $161a : sta $1624 : sta $1644
sta $164a : sta $1652 : sta $1662
ldy #$0000
- sta $1706, y : iny #2 : cpy #$001c : bcc -
lda.w #$2810 : sta $1684 ; small keys icon lda.w #$2810 : sta $1684 ; small keys icon
lda.w #$2811 : sta $16c4 ; big key icon lda.w #$2811 : sta $16c4 ; big key icon
lda.w #$2810 : sta $1704 ; small keys icon lda.w #$2810 : sta $1704 ; small keys icon
ldx #$0002 ldx #$0002
- lda $7ef364 : and.l $0098c0, x : beq + ; must have compass - lda $7ef368 : and.l $0098c0, x : beq + ; must have map
lda.l HudOffsets, x : tay lda.l HudOffsets, x : tay
jsr BkStatus : sta $16C6, y ; big key status jsr BkStatus : sta $16C6, y ; big key status
phx phx
@@ -88,18 +83,34 @@ DrHudDungeonItemsAdditions:
plx plx
+ inx #2 : cpx #$001b : bcc - + inx #2 : cpx #$001b : bcc -
++ ++
lda !HUD_FLAG : and.w #$0020 : bne + : bra ++ : + lda !HUD_FLAG : and.w #$0020 : bne + : brl ++ : +
lda HUDDungeonItems : and.w #$000c : bne + : bra ++ : + lda HUDDungeonItems : and.w #$000f : bne + : brl ++ : +
lda.w #$24f5 : sta $1704 ; blank ; map symbols (do I want these) ; note compass symbol is 2c20
lda.w #$2821 : sta $1606 : sta $1610 : sta $161a : sta $1624
; blank out a couple thing from old hud
lda.w #$24f5 : sta $16e4 : sta $1724
ldx #$0002 ldx #$0002
- lda $7ef364 : and.l $0098c0, x : beq + ; must have compass - lda #$0000 ; start of hud area
lda.l HudOffsets, x : tay !addl RowOffsets, x : !addl ColumnOffsets, x : tay
lda.l DungeonReminderTable, x : sta $1644, y
iny #2
lda.w #$24f5 : sta $1644, y ; blank out map spot
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
lda #$2826 : sta $1644, y ; check mark
+ iny #2
cpx #$001a : bne +
tya : !add #$003c : tay
+ lda $7ef364 : and.l $0098c0, x : beq + ; must have compass
phx ; total chest counts phx ; total chest counts
txa : lsr : tax txa : lsr : tax
lda.l TotalLocationsLow, x : jsr ConvertToDisplay2 : sta $1706, y lda.l TotalLocationsHigh, x : jsr ConvertToDisplay2 : sta $1644, y : iny #2
lda.l TotalLocationsHigh, x : jsr ConvertToDisplay2 : sta $16c6, y lda.l TotalLocationsLow, x : jsr ConvertToDisplay2 : sta $1644, y
plx plx
+ bra .skipBlanks
+ lda.w #$24f5 : sta $1644, y : iny #2 : sta $1644, y
.skipBlanks iny #2
cpx #$001a : beq +
lda.w #$24f5 : sta $1644, y ; blank out spot
+ inx #2 : cpx #$001b : bcc - + inx #2 : cpx #$001b : bcc -
++ ++
plp : ply : plx : rtl plp : ply : plx : rtl
@@ -108,7 +119,7 @@ DrHudDungeonItemsAdditions:
BkStatus: BkStatus:
lda $7ef366 : and.l $0098c0, x : bne +++ ; has the bk already lda $7ef366 : and.l $0098c0, x : bne +++ ; has the bk already
lda.l BigKeyStatus, x : bne ++ lda.l BigKeyStatus, x : bne ++
lda #$2482 : rts ; 0/O for no BK lda #$2827 : rts ; 0/O for no BK
++ cmp #$0002 : bne + ++ cmp #$0002 : bne +
lda #$2420 : rts ; symbol for BnC lda #$2420 : rts ; symbol for BnC
+ lda #$24f5 : rts ; black otherwise + lda #$24f5 : rts ; black otherwise
@@ -124,29 +135,7 @@ ConvertToDisplay2:
cmp #$000a : !blt + cmp #$000a : !blt +
!add #$2553 : rts !add #$2553 : rts
+ !add #$2816 : rts + !add #$2816 : rts
++ lda #$2483 : rts ; 0/O for 0 or placeholder digit ++ lda #$2827 : rts ; 0/O for 0 or placeholder digit ;2483
;CountChestKeys:
; jsl ItemDowngradeFix
; jsr CountChest
; rtl
;CountChest:
; lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
; cpy #$24 : beq +
; cpy #$a0 : !blt .end
; cpy #$ae : !bge .end
; pha : phx
; tya : and #$0f : bne ++
; inc a
; ++ tax : bra .count
; + pha : phx
; lda $040c : lsr : tax
; .count
; lda $7ef4b0, x : inc : sta $7ef4b0, x
; lda $7ef4e0, x : inc : sta $7ef4e0, x
; .restore plx : pla
; .end rts
CountAbsorbedKeys: CountAbsorbedKeys:
jsl IncrementSmallKeysNoPrimary : phx jsl IncrementSmallKeysNoPrimary : phx
@@ -155,20 +144,6 @@ CountAbsorbedKeys:
lda $7ef4b0, x : inc : sta $7ef4b0, x lda $7ef4b0, x : inc : sta $7ef4b0, x
+ plx : rtl + plx : rtl
;CountBonkItem:
; jsl GiveBonkItem
; lda $a0 ; check room ID - only bonk keys in 2 rooms so we're just checking the lower byte
; cmp #115 : bne + ; Desert Bonk Key
; lda.l BonkKey_Desert
; bra ++
; + : cmp #140 : bne + ; GTower Bonk Key
; lda.l BonkKey_GTower
; bra ++
; + lda.b #$24 ; default to small key
; ++ cmp #$24 : bne +
; phy : tay : jsr CountChest : ply
; + rtl
;================================================================================ ;================================================================================
; 16-bit A, 8-bit X ; 16-bit A, 8-bit X
; in: A(b) - Byte to Convert ; in: A(b) - Byte to Convert

File diff suppressed because one or more lines are too long