From beb15951a073c3d7387606e6bf16eed2383223c7 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 17 Oct 2019 16:35:13 -0600 Subject: [PATCH] Skull Woods added Fixed polarity for stairs Some swamp fixes Prep work for dungeons that can require traversal through overworld - like skull woods Special case for pinball room so it can be in Skull 2 or Skull 1 --- BaseClasses.py | 47 ++++++++- DoorShuffle.py | 239 ++++++++++++++++++++++++++++++++++++++------- Doors.py | 54 +++++++++- Dungeons.py | 22 ++++- EntranceShuffle.py | 28 ++---- Items.py | 6 +- Main.py | 20 ++-- Regions.py | 41 ++++++-- Rules.py | 51 +++++----- 9 files changed, 397 insertions(+), 111 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 03f4db77..9bed8841 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -88,6 +88,8 @@ class World(object): self.paired_doors = {} self.rooms = [] self._room_cache = {} + self.dungeon_layouts = {} + self.inaccessible_regions = [] def intialize_regions(self): for region in self.regions: @@ -862,17 +864,52 @@ class Direction(Enum): Down = 5 +class Polarity: + def __init__(self, vector): + self.vector = vector + + def __add__(self, other): + result = Polarity([0]*len(self.vector)) + for i in range(len(self.vector)): + result.vector[i] = pol_add[pol_idx_2[i]](self.vector[i], other.vector[i]) + return result + + def __iadd__(self, other): + for i in range(len(self.vector)): + self.vector[i] = pol_add[pol_idx_2[i]](self.vector[i], other.vector[i]) + return self + + def __getitem__(self, item): + return self.vector[item] + + def is_neutral(self): + for i in range(len(self.vector)): + if self.vector[i] != 0: + return False + return True + + pol_idx = { Direction.North: (0, 'Pos'), Direction.South: (0, 'Neg'), Direction.East: (1, 'Pos'), Direction.West: (1, 'Neg'), - Direction.Up: (2, 'Pos'), - Direction.Down: (2, 'Neg') + Direction.Up: (2, 'Mod'), + Direction.Down: (2, 'Mod') +} +pol_idx_2 = { + 0: 'Add', + 1: 'Add', + 2: 'Mod' } pol_inc = { 'Pos': lambda x: x + 1, 'Neg': lambda x: x - 1, + 'Mod': lambda x: (x + 1) % 2 +} +pol_add = { + 'Add': lambda x, y: x + y, + 'Mod': lambda x, y: (x + y) % 2 } @unique @@ -1000,11 +1037,11 @@ class Sector(object): # todo: make these lazy init? - when do you invalidate them def polarity(self): - polarity = [0, 0, 0] + pol = Polarity([0, 0, 0]) for door in self.outstanding_doors: idx, inc = pol_idx[door.direction] - polarity[idx] = pol_inc[inc](polarity[idx]) - return polarity + pol.vector[idx] = pol_inc[inc](pol.vector[idx]) + return pol def magnitude(self): magnitude = [0, 0, 0] diff --git a/DoorShuffle.py b/DoorShuffle.py index e3dabc53..4d662c60 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -4,7 +4,7 @@ import logging import operator as op from functools import reduce -from BaseClasses import RegionType, DoorType, Direction, Sector, CrystalBarrier, pol_idx +from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier, Polarity, pol_idx from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions from Dungeons import dungeon_regions, region_starts, split_region_starts from Regions import key_only_locations, dungeon_events @@ -94,11 +94,11 @@ def create_door_spoiler(world, player): logger.debug('Door not found in queue: %s connected to %s', door_b.name, door_a.name) else: logger.warning('Door not connected: %s', door_a.name) - for dp in world.paired_doors[player]: - if dp.pair: - logger.debug('Paired Doors: %s with %s (p%d)', dp.door_a, dp.door_b, player) - else: - logger.debug('Unpaired Doors: %s not paired with %s (p%d)', dp.door_a, dp.door_b, player) + # for dp in world.paired_doors[player]: + # if dp.pair: + # logger.debug('Paired Doors: %s with %s (p%d)', dp.door_a, dp.door_b, player) + # else: + # logger.debug('Unpaired Doors: %s not paired with %s (p%d)', dp.door_a, dp.door_b, player) # some useful functions @@ -412,11 +412,12 @@ def cross_dungeon(world, player): combine_layouts(dungeon_layouts) for layout in dungeon_layouts: - shuffle_key_doors(layout[1], layout[2], world, player) + shuffle_key_doors(layout[0], layout[1], world, player) def experiment(world, player): fix_big_key_doors_with_ugly_smalls(world, player) + overworld_prep(world, player) dungeon_sectors = [] for key in dungeon_regions.keys(): sector_list = convert_to_sectors(dungeon_regions[key], world, player) @@ -434,10 +435,17 @@ def experiment(world, player): dungeon_layouts.append((ds, entrance_list)) combine_layouts(dungeon_layouts) + world.dungeon_layouts[player] = {} + for sector, entrances in dungeon_layouts: + world.dungeon_layouts[player][sector.name] = (sector, entrances) + + remove_inaccessible_entrances(world, player) + paths = determine_required_paths(world) + check_required_paths(paths, world, player) # shuffle_key_doors for dungeons - for layout in dungeon_layouts: - shuffle_key_doors(layout[0], layout[1], world, player) + for sector, entrances in world.dungeon_layouts[player].values(): + shuffle_key_doors(sector, entrances, world, player) def convert_regions(region_names, world, player): @@ -528,18 +536,11 @@ def sum_vector(sector_list, func): return result -def add_vectors(vector_one, vector_two): - result = [0]*len(vector_one) - for i in range(len(result)): - result[i] = vector_one[i] + vector_two[i] - return result - - -def is_polarity_neutral(polarity): - for value in polarity: - if value != 0: - return False - return True +def is_polarity_neutral(sector_list): + pol = Polarity([0, 0, 0]) + for sector in sector_list: + pol += sector.polarity() + return pol.is_neutral() search_iterations = 0 @@ -561,7 +562,7 @@ def is_proposal_valid(proposal, buckets, candidates): for i in range(len(proposal)): test_bucket[proposal[i]].append(candidates[i]) for test in test_bucket: - valid = is_polarity_neutral(sum_vector(test, lambda s: s.polarity())) + valid = is_polarity_neutral(test) if not valid: return False return True @@ -744,13 +745,13 @@ class ExplorationState(object): def add_all_doors_check_key_region(self, region, key_region, world, player): for door in get_doors(world, region, player): + if door.name not in self.door_krs.keys(): + self.door_krs[door.name] = key_region if self.can_traverse(door): if 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) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) - if door.name not in self.door_krs.keys(): - self.door_krs[door.name] = key_region def add_all_doors_check_keys(self, region, key_door_proposal, world, player): for door in get_doors(world, region, player): @@ -776,6 +777,9 @@ class ExplorationState(object): return region in self.visited_blue return False + def visited_at_all(self, region): + return region in self.visited_blue or region in self.visited_orange + def can_traverse(self, door): if door.blocked: return False @@ -783,6 +787,9 @@ class ExplorationState(object): return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal return True + def validate(self, door, region, world): + return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, world) + def in_door_list(self, door, door_list): for d in door_list: if d.door == door and d.crystal == self.crystal: @@ -821,7 +828,7 @@ def extend_reachable_state(search_regions, state, world, player): entrance = world.get_entrance(explorable_door.door.name, player) connect_region = entrance.connected_region if connect_region is not None: - if not local_state.visited(connect_region): + if valid_region_to_explore(connect_region, world) and not local_state.visited(connect_region): local_state.visit_region(connect_region) local_state.add_all_doors_check_unattached(connect_region, world, player) return local_state @@ -847,6 +854,9 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re explorable_door = random.choice(state.unattached_doors) door = explorable_door.door sector = find_sector_for_door(door, available_sectors) + if sector is None: + state.unattached_doors.remove(explorable_door) + continue sector.outstanding_doors.remove(door) # door_connected = False logger.info('Linking %s', door.name) @@ -872,7 +882,6 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re break # skips else block below logger.info(' Not Linking %s to %s', door.name, connect_door.name) if len(compatibles) == 0: # time to try again - sector.outstanding_doors.insert(0, door) if len(state.unattached_doors) <= 1: raise Exception('Rejected last option due to dead end... infinite loop ensues') else: @@ -924,6 +933,9 @@ def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region # Pick a random available door to connect door = random.choice(reachable_doors) sector = find_sector_for_door(door, available_sectors) + if sector is None: + reachable_doors.remove(door) + continue sector.outstanding_doors.remove(door) # door_connected = False logger.info('Linking %s', door.name) @@ -949,7 +961,6 @@ def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region break # skips else block below logger.info(' Not Linking %s to %s', door.name, connect_door.name) if len(compatibles) == 0: # time to try again - sector.outstanding_doors.insert(0, door) if len(reachable_doors) <= 1: raise Exception('Rejected last option due to dead end... infinite loop ensues') else: @@ -981,6 +992,7 @@ def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region # Check that we used everything, we failed otherwise if len(available_sectors) != 1: logger.warning('Failed to add all regions/doors to dungeon, generation will likely fail.') + return available_sectors return available_sectors[0] @@ -994,7 +1006,7 @@ def extend_reachable(search_regions, visited_regions, world, player): for ext in region.exits: if ext.connected_region is not None: connect_region = ext.connected_region - if connect_region not in visited_regions and connect_region not in region_list: + if valid_region_to_explore(connect_region, world) and connect_region not in visited_regions and connect_region not in region_list: region_list.append(connect_region) else: door = world.check_for_door(ext.name, player) @@ -1003,6 +1015,10 @@ def extend_reachable(search_regions, visited_regions, world, player): return reachable_doors, visited_regions +def valid_region_to_explore(region, world): + return region.type == RegionType.Dungeon or region.name in world.inaccessible_regions + + def find_sector_for_door(door, sectors): for sector in sectors: if door in sector.outstanding_doors: @@ -1260,7 +1276,7 @@ def validate_key_layout_r(state, flat_proposal, checked_states, world, player): exp_door = state.next_avail_door() door = exp_door.door connect_region = world.get_entrance(door.name, player).connected_region - if state.can_traverse(door) and not state.visited(connect_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) smalls_avail = len(state.small_doors) > 0 @@ -1380,6 +1396,142 @@ def change_door_to_small_key(d, world, player): room.change(d.doorListPos, DoorKind.SmallKey) +def remove_inaccessible_entrances(world, player): + if world.shuffle == 'vanilla': + for dungeon_name in world.dungeon_layouts[player].keys(): + sector, entrances = world.dungeon_layouts[player][dungeon_name] + if dungeon_name == 'Skull Woods': + entrances.remove('Skull 2 West Lobby') + entrances.remove('Skull 3 Lobby') + entrances.remove('Skull Back Drop') + if world.mode == 'standard' and dungeon_name == 'Hyrule Castle': + entrances.remove('Hyrule Castle West Lobby') + entrances.remove('Hyrule Castle East Lobby') + entrances.remove('Sewers Secret Room') + entrances.remove('Sanctuary') + # todo - not sure about what to do in entrance shuffle - tbh + # simple and restricted have interesting effects + + +def determine_required_paths(world): + paths = { + 'Hyrule Castle': [], + 'Eastern Palace': ['Eastern Boss'], + 'Desert Palace': ['Desert Boss'], + 'Tower of Hera': ['Hera Boss'], + 'Agahnims Tower': ['Tower Agahnim 1'], + 'Palace of Darkness': ['PoD Boss'], + 'Swamp Palace': ['Swamp Boss'], + 'Skull Woods': ['Skull Boss'], + # 'Thieves Town': [], + } + if world.shuffle == 'vanilla': + # paths['Skull Woods'].remove('Skull Boss') # is this necessary? + paths['Skull Woods'].insert(0, 'Skull 2 West Lobby') + # todo - TR jazz + if world.mode == 'standard': + paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock') + paths['Hyrule Castle'].append('Sanctuary') + return paths + + +def overworld_prep(world, player): + if world.mode != 'inverted': + if world.mode == 'standard': + world.inaccessible_regions.append('Hyrule Castle Ledge') # maybe only with er off + world.inaccessible_regions.append('Skull Woods Forest (West)') + world.inaccessible_regions.append('Dark Death Mountain Ledge') + world.inaccessible_regions.append('Dark Death Mountain Isolated Ledge') + world.inaccessible_regions.append('Desert Palace Lone Stairs') + world.inaccessible_regions.append('Bumper Cave Ledge') + world.inaccessible_regions.append('Death Mountain Floating Island (Dark World)') + else: + world.inaccessible_regions.append('Desert Ledge') + # world.inaccessible_regions.append('Hyrule Castle Ledge') # accessible via aga 1? + world.inaccessible_regions.append('Desert Palace Lone Stairs') + world.inaccessible_regions.append('Death Mountain Return Ledge') + world.inaccessible_regions.append('Maze Race Ledge') + if world.shuffle == 'vanilla': + skull_doors = [ + Door(player, 'Skull Woods Second Section Exit (West)', DoorType.Logical), + Door(player, 'Skull Woods Second Section Door (West)', DoorType.Logical), + Door(player, 'Skull Woods Second Section Hole', DoorType.Logical), + Door(player, 'Skull Woods Final Section', DoorType.Logical) + ] + world.doors += skull_doors + connect_simple_door(world, skull_doors[0].name, 'Skull Woods Forest (West)', player) + connect_simple_door(world, skull_doors[1].name, 'Skull 2 West Lobby', player) + connect_simple_door(world, skull_doors[2].name, 'Skull Back Drop', player) + connect_simple_door(world, skull_doors[3].name, 'Skull 3 Lobby', player) + if world.mode == 'standard': + castle_doors = [ + Door(player, 'Hyrule Castle Exit (West)', DoorType.Logical), + Door(player, 'Hyrule Castle Exit (East)', DoorType.Logical), + Door(player, 'Hyrule Castle Entrance (East)', DoorType.Logical), + Door(player, 'Hyrule Castle Entrance (West)', DoorType.Logical) + ] + world.doors += castle_doors + connect_simple_door(world, castle_doors[0].name, 'Hyrule Castle Ledge', player) + connect_simple_door(world, castle_doors[1].name, 'Hyrule Castle Ledge', player) + connect_simple_door(world, castle_doors[2].name, 'Hyrule Castle East Lobby', player) + connect_simple_door(world, castle_doors[3].name, 'Hyrule Castle West Lobby', player) + + +def check_required_paths(paths, world, player): + for dungeon_name in paths.keys(): + sector, entrances = world.dungeon_layouts[player][dungeon_name] + if len(paths[dungeon_name]) > 0: + check_paths = convert_regions(paths[dungeon_name], world, player) + start_regions = convert_regions(entrances, world, player) + state = ExplorationState() + for region in start_regions: + state.visit_region(region) + state.add_all_doors_check_unattached(region, world, player) + explore_state(state, world, player) + valid, bad_region = check_if_regions_visited(state, check_paths) + if not valid: + if check_for_pinball_fix(state, bad_region, world, player): + explore_state(state, world, player) + valid, bad_region = check_if_regions_visited(state, check_paths) + if not valid: + raise Exception('% cannot reach %', dungeon_name, bad_region.name) + + +def explore_state(state, world, player): + while len(state.avail_doors) > 0: + door = state.next_avail_door().door + connect_region = world.get_entrance(door.name, player).connected_region + if state.can_traverse(door) and not state.visited(connect_region) and valid_region_to_explore(connect_region, world): + state.visit_region(connect_region) + state.add_all_doors_check_unattached(connect_region, world, player) + + +def check_if_regions_visited(state, check_paths): + valid = True + breaking_region = None + for region_target in check_paths: + if not state.visited_at_all(region_target): + valid = False + breaking_region = region_target + break + return valid, breaking_region + + +def check_for_pinball_fix(state, bad_region, world, player): + pinball_region = world.get_region('Skull Pinball', player) + if bad_region.name == 'Skull 2 West Lobby' and state.visited_at_all(pinball_region): + door = world.get_door('Skull Pinball WS', player) + room = world.get_room(door.roomIndex, player) + if room.doorList[door.doorListPos][1] == DoorKind.Trap: + room.change(door.doorListPos, DoorKind.Normal) + door.trapFlag = 0x0 + door.blocked = False + connect_two_way(world, door.name, door.dest.name, player) + state.add_all_doors_check_unattached(pinball_region, world, player) + return True + return False + + # DATA GOES DOWN HERE logical_connections = [ @@ -1415,6 +1567,7 @@ logical_connections = [ ('Swamp Trench 2 Blocks Pots', 'Swamp Trench 2 Pots'), ('Swamp Trench 2 Departure Wet', 'Swamp Trench 2 Pots'), ('Swamp West Shallows Push Blocks', 'Swamp West Block Path'), + ('Swamp West Block Path Drop Down', 'Swamp West Shallows'), ('Swamp West Ledge Drop Down', 'Swamp West Shallows'), ('Swamp West Ledge Hook Path', 'Swamp Barrier Ledge'), ('Swamp Barrier Ledge Drop Down', 'Swamp West Shallows'), @@ -1424,6 +1577,9 @@ logical_connections = [ ('Swamp Drain Right Switch', 'Swamp Drain Left'), ('Swamp Flooded Spot Ladder', 'Swamp Flooded Room'), ('Swamp Flooded Room Ladder', 'Swamp Flooded Spot'), + ('Skull Pot Circle Star Path', 'Skull Map Room'), + ('Skull Big Chest Hookpath', 'Skull 1 Lobby'), + ('Skull Back Drop Star Path', 'Skull Small Hall'), # ('', ''), ] @@ -1500,7 +1656,7 @@ falldown_pits = [ ('PoD Big Key Landing Hole', 'PoD Stalfos Basement'), ('Swamp Attic Right Pit', 'Swamp Barrier Ledge'), ('Swamp Attic Left Pit', 'Swamp West Ledge'), - # ('', ''), + ('Skull Final Drop Hole', 'Skull Boss'), # ('', ''), ] @@ -1575,6 +1731,19 @@ interior_doors = [ ('Swamp C SE', 'Swamp Waterway NE'), ('Swamp Waterway N', 'Swamp I S'), ('Swamp Waterway NW', 'Swamp T SW'), + ('Skull 1 Lobby ES', 'Skull Map Room WS'), + ('Skull Pot Circle WN', 'Skull Pull Switch EN'), + ('Skull Pull Switch S', 'Skull Big Chest N'), + ('Skull Left Drop ES', 'Skull Compass Room WS'), + ('Skull 2 East Lobby NW', 'Skull Big Key SW'), + ('Skull Big Key WN', 'Skull Lone Pot EN'), + ('Skull Small Hall WS', 'Skull 2 West Lobby ES'), + ('Skull 2 West Lobby NW', 'Skull X Room SW'), + ('Skull 3 Lobby WN', 'Skull East Bridge EN'), + ('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'), # ('', ''), ] @@ -1646,9 +1815,13 @@ default_door_connections = [ ('Swamp Trench 2 Departure WS', 'Swamp West Shallows ES'), ('Swamp Big Key Ledge WN', 'Swamp Barrier EN'), ('Swamp Basement Shallows NW', 'Swamp Waterfall Room SW'), - # ('', ''), - # ('', ''), - # ('', ''), + ('Skull 1 Lobby WS', 'Skull Pot Prison ES'), + ('Skull Map Room SE', 'Skull Pinball NE'), + ('Skull Pinball WS', 'Skull Compass Room ES'), + ('Skull Compass Room NE', 'Skull Pot Prison SE'), + ('Skull 2 East Lobby WS', 'Skull Small Hall ES'), + ('Skull 3 Lobby NW', 'Skull Star Pits SW'), + ('Skull Vines NW', 'Skull Spike Corner SW'), # ('', ''), ] diff --git a/Doors.py b/Doors.py index 3b3f1f94..8a75b4d4 100644 --- a/Doors.py +++ b/Doors.py @@ -323,6 +323,8 @@ def create_doors(world, player): create_door(player, 'PoD Mimics 2 SW', Nrml).dir(Direction.South, 0x1b, Left, High).pos(1), create_door(player, 'PoD Mimics 2 NW', Intr).dir(Direction.North, 0x1b, Left, High).pos(0), create_door(player, 'PoD Bow Statue SW', Intr).dir(Direction.South, 0x1b, Left, High).pos(0), + # todo: we need a new flag for a door that has a wall on it - you have to traverse it one particular way first + # the above is not a problem until we get to crossed mode create_door(player, 'PoD Bow Statue Down Ladder', Lddr), create_door(player, 'PoD Dark Pegs Up Ladder', Lddr), create_door(player, 'PoD Dark Pegs WN', Intr).dir(Direction.West, 0x0b, Mid, High).small_key().pos(2), @@ -390,6 +392,7 @@ def create_doors(world, player): create_door(player, 'Swamp West Shallows ES', Nrml).dir(Direction.East, 0x34, Bot, High).pos(1), create_door(player, 'Swamp West Shallows Push Blocks', Lgcl), create_door(player, 'Swamp West Block Path Up Stairs', Sprl).dir(Direction.Up, 0x34, 0, HTH).ss(Z, 0x1b, 0x6c, False, True), + create_door(player, 'Swamp West Block Path Drop Down', Lgcl), create_door(player, 'Swamp West Ledge Drop Down', Lgcl), create_door(player, 'Swamp West Ledge Hook Path', Lgcl), create_door(player, 'Swamp Barrier Ledge Drop Down', Lgcl), @@ -436,6 +439,51 @@ def create_doors(world, player): create_door(player, 'Swamp T SW', Intr).dir(Direction.South, 0x16, Left, High).small_key().pos(1), create_door(player, 'Swamp T NW', Nrml).dir(Direction.North, 0x16, Left, High).pos(3), create_door(player, 'Swamp Boss SW', Nrml).dir(Direction.South, 0x06, Left, High).trap(0x4).pos(0), + + create_door(player, 'Skull 1 Lobby WS', Nrml).dir(Direction.West, 0x58, Bot, High).small_key().pos(1), + create_door(player, 'Skull 1 Lobby ES', Intr).dir(Direction.East, 0x58, Bot, High).pos(5), + create_door(player, 'Skull Map Room WS', Intr).dir(Direction.West, 0x58, Bot, High).pos(5), + create_door(player, 'Skull Map Room SE', Nrml).dir(Direction.South, 0x58, Right, High).small_key().pos(2), + create_door(player, 'Skull Pot Circle WN', Intr).dir(Direction.West, 0x58, Top, High).pos(3), + create_door(player, 'Skull Pull Switch EN', Intr).dir(Direction.East, 0x58, Top, High).pos(3), + create_door(player, 'Skull Pot Circle Star Path', Lgcl), + create_door(player, 'Skull Pull Switch S', Intr).dir(Direction.South, 0x58, Left, High).pos(0), + create_door(player, 'Skull Big Chest N', Intr).dir(Direction.North, 0x58, Left, High).no_exit().pos(0), + create_door(player, 'Skull Big Chest Hookpath', Lgcl), + create_door(player, 'Skull Pinball NE', Nrml).dir(Direction.North, 0x68, Right, High).small_key().pos(1), + create_door(player, 'Skull Pinball WS', Nrml).dir(Direction.West, 0x68, Bot, High).no_exit().trap(0x4).pos(0), + create_door(player, 'Skull Compass Room NE', Nrml).dir(Direction.North, 0x67, Right, High).pos(0), + create_door(player, 'Skull Compass Room ES', Nrml).dir(Direction.East, 0x67, Bot, High).pos(2), + create_door(player, 'Skull Left Drop ES', Intr).dir(Direction.East, 0x67, Bot, High).pos(1), + create_door(player, 'Skull Compass Room WS', Intr).dir(Direction.West, 0x67, Bot, High).pos(1), + create_door(player, 'Skull Pot Prison ES', Nrml).dir(Direction.East, 0x57, Bot, High).small_key().pos(2), + create_door(player, 'Skull Pot Prison SE', Nrml).dir(Direction.South, 0x57, Right, High).pos(5), + create_door(player, 'Skull 2 East Lobby WS', Nrml).dir(Direction.West, 0x57, Bot, High).pos(4), + create_door(player, 'Skull 2 East Lobby NW', Intr).dir(Direction.North, 0x57, Left, High).pos(1), + create_door(player, 'Skull Big Key SW', Intr).dir(Direction.South, 0x57, Left, High).pos(1), + create_door(player, 'Skull Big Key WN', Intr).dir(Direction.West, 0x57, Top, High).pos(0), + create_door(player, 'Skull Lone Pot EN', Intr).dir(Direction.East, 0x57, Top, High).pos(0), + create_door(player, 'Skull Small Hall ES', Nrml).dir(Direction.East, 0x56, Bot, High).pos(3), + create_door(player, 'Skull Small Hall WS', Intr).dir(Direction.West, 0x56, Bot, High).pos(2), + create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Direction.East, 0x56, Bot, High).pos(2), + create_door(player, 'Skull 2 West Lobby NW', Intr).dir(Direction.North, 0x56, Left, High).small_key().pos(0), + create_door(player, 'Skull X Room SW', Intr).dir(Direction.South, 0x56, Left, High).small_key().pos(0), + create_door(player, 'Skull Back Drop Star Path', Lgcl), + create_door(player, 'Skull 3 Lobby NW', Nrml).dir(Direction.North, 0x59, Left, High).small_key().pos(0), + create_door(player, 'Skull 3 Lobby WN', Intr).dir(Direction.West, 0x59, Top, High).pos(2), + create_door(player, 'Skull East Bridge EN', Intr).dir(Direction.East, 0x59, Top, High).pos(2), + create_door(player, 'Skull East Bridge ES', Intr).dir(Direction.East, 0x59, Bot, High).pos(3), + create_door(player, 'Skull West Bridge Nook WS', Intr).dir(Direction.West, 0x59, Bot, High).pos(3), + create_door(player, 'Skull Star Pits SW', Nrml).dir(Direction.South, 0x49, Left, High).small_key().pos(2), + create_door(player, 'Skull Star Pits WS', Intr).dir(Direction.West, 0x49, Bot, High).pos(3), + create_door(player, 'Skull Torch Room ES', Intr).dir(Direction.East, 0x49, Bot, High).pos(3), + create_door(player, 'Skull Torch Room EN', Intr).dir(Direction.East, 0x49, Top, High).pos(1), + create_door(player, 'Skull Vines WN', Intr).dir(Direction.West, 0x49, Top, High).pos(1), + create_door(player, 'Skull Vines NW', Nrml).dir(Direction.North, 0x49, Left, High).pos(0), + create_door(player, 'Skull Spike Corner SW', Nrml).dir(Direction.South, 0x39, Left, High).no_exit().trap(0x4).pos(0), + create_door(player, 'Skull Spike Corner WS', Intr).dir(Direction.West, 0x39, Bot, High).small_key().pos(1), + create_door(player, 'Skull Final Drop ES', Intr).dir(Direction.East, 0x39, Bot, High).small_key().pos(1), + create_door(player, 'Skull Final Drop Hole', Hole), ] create_paired_doors(world, player) @@ -511,9 +559,9 @@ def create_paired_doors(world, player): # PairedDoor('', ''), # GT moldorm key door # PairedDoor('', ''), # Ice BJ key door PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE'), - # PairedDoor('', ''), # Skull 3 key door - # PairedDoor('', ''), # Skull 1 key door - pot prison to big chest - # PairedDoor('', ''), # Skull 1 - pinball key door + PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW'), # Skull 3 key door + PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES'), # Skull 1 key door - pot prison to big chest + PairedDoor('Skull Map Room SE', 'Skull Pinball NE'), # Skull 1 - pinball key door # PairedDoor('', ''), # gt main big key door # PairedDoor('', ''), # ice door to spike chest # PairedDoor('', ''), # gt right side key door to cape bridge diff --git a/Dungeons.py b/Dungeons.py index a15c0794..3223e718 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -20,10 +20,8 @@ def create_dungeons(world, player): DP = make_dungeon('Desert Palace', 'Lanmolas', desert_regions, ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player)) ToH = make_dungeon('Tower of Hera', 'Moldorm', hera_regions, ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player)) PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', pod_regions, ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player)) - - #still standard dungeons TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player)) - SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Second Section (Drop)', 'Skull Woods Final Section (Mothula)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'], ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 2, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player)) + SW = make_dungeon('Skull Woods', 'Mothula', skull_regions, ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 2, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player)) SP = make_dungeon('Swamp Palace', 'Arrghus', swamp_regions, ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player)) IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player)) MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player)) @@ -226,6 +224,14 @@ swamp_regions = [ 'Swamp Refill', 'Swamp Behind Waterfall', 'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss' ] +skull_regions = [ + 'Skull 1 Lobby', 'Skull Map Room', 'Skull Pot Circle', 'Skull Pull Switch', 'Skull Big Chest', 'Skull Pinball', + 'Skull Pot Prison', 'Skull Compass Room', 'Skull Left Drop', 'Skull 2 East Lobby', 'Skull Big Key', + 'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull X Room', 'Skull 3 Lobby', + 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits', 'Skull Torch Room', 'Skull Vines', + 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss' +] + dungeon_regions = { 'Hyrule Castle': hyrule_castle_regions, 'Eastern Palace': eastern_regions, @@ -234,7 +240,7 @@ dungeon_regions = { 'Agahnims Tower': tower_regions, 'Palace of Darkness': pod_regions, 'Swamp Palace': swamp_regions, - # 'Skull': + 'Skull Woods': skull_regions, # 'TT': # 'Ice': # 'Mire': @@ -250,6 +256,8 @@ region_starts = { 'Agahnims Tower': ['Tower Lobby'], 'Palace of Darkness': ['PoD Lobby'], 'Swamp Palace': ['Swamp Lobby'], + 'Skull Woods': ['Skull 1 Lobby', 'Skull 2 East Lobby', 'Skull 2 West Lobby', 'Skull 3 Lobby', 'Skull Pot Circle', + 'Skull Pinball', 'Skull Left Drop', 'Skull Back Drop'], # ['TT Lobby'], # ['Ice Lobby'], # ['Mire Lobby'], @@ -261,8 +269,12 @@ split_region_starts = { 'Desert Palace': [ ['Desert Back Lobby'], ['Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby'] + ], + 'Skull Woods': [ + ['Skull 1 Lobby', 'Skull Pot Circle'], + ['Skull 2 West Lobby', 'Skull 2 East Lobby', 'Skull Back Drop'], + ['Skull 3 Lobby'] ] - # 'Skull': ['Skull 1 Lobby', 'Skull 2 Mummy Lobby', 'Skull 2 Key Lobby', 'Skull 3 Lobby'], } diff --git a/EntranceShuffle.py b/EntranceShuffle.py index ba628d76..a4cdd471 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1995,7 +1995,7 @@ def connect_doors(world, doors, targets, player): def skull_woods_shuffle(world, player): connect_random(world, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'], - ['Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)', 'Skull Woods Second Section (Drop)'], player) + ['Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle', 'Skull Back Drop'], player) connect_random(world, ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'], ['Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'], player, True) @@ -2948,15 +2948,6 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central ('Graveyard Ledge Mirror Spot', 'Graveyard Ledge'), ('Thieves Town Big Key Door', 'Thieves Town (Deep)'), - ('Skull Woods Torch Room', 'Skull Woods Final Section (Mothula)'), - ('Skull Woods First Section Bomb Jump', 'Skull Woods First Section (Top)'), # represents bomb jumping to big chest - ('Skull Woods First Section South Door', 'Skull Woods First Section (Right)'), - ('Skull Woods First Section West Door', 'Skull Woods First Section (Left)'), - ('Skull Woods First Section (Right) North Door', 'Skull Woods First Section'), - ('Skull Woods First Section (Left) Door to Right', 'Skull Woods First Section (Right)'), - ('Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section'), - ('Skull Woods First Section (Top) One-Way Path', 'Skull Woods First Section'), - ('Skull Woods Second Section (Drop)', 'Skull Woods Second Section'), ('Blind Fight', 'Blind Fight'), ('Ice Palace Entrance Room', 'Ice Palace (Main)'), ('Ice Palace (East)', 'Ice Palace (East)'), @@ -3073,7 +3064,6 @@ inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia ('Swamp Palace (North)', 'Swamp Palace (North)'), ('Thieves Town Big Key Door', 'Thieves Town (Deep)'), ('Skull Woods Torch Room', 'Skull Woods Final Section (Mothula)'), - ('Skull Woods First Section Bomb Jump', 'Skull Woods First Section (Top)'), ('Skull Woods First Section South Door', 'Skull Woods First Section (Right)'), ('Skull Woods First Section West Door', 'Skull Woods First Section (Left)'), ('Skull Woods First Section (Right) North Door', 'Skull Woods First Section'), @@ -3521,17 +3511,17 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Main L ('Thieves Town', 'Thieves Town (Entrance)'), ('Thieves Town Exit', 'West Dark World'), - ('Skull Woods First Section Hole (East)', 'Skull Woods First Section (Right)'), - ('Skull Woods First Section Hole (West)', 'Skull Woods First Section (Left)'), - ('Skull Woods First Section Hole (North)', 'Skull Woods First Section (Top)'), - ('Skull Woods First Section Door', 'Skull Woods First Section'), + ('Skull Woods First Section Hole (East)', 'Skull Pinball'), + ('Skull Woods First Section Hole (West)', 'Skull Left Drop'), + ('Skull Woods First Section Hole (North)', 'Skull Pot Circle'), + ('Skull Woods First Section Door', 'Skull 1 Lobby'), ('Skull Woods First Section Exit', 'Skull Woods Forest'), - ('Skull Woods Second Section Hole', 'Skull Woods Second Section (Drop)'), - ('Skull Woods Second Section Door (East)', 'Skull Woods Second Section'), - ('Skull Woods Second Section Door (West)', 'Skull Woods Second Section'), + ('Skull Woods Second Section Hole', 'Skull Back Drop'), + ('Skull Woods Second Section Door (East)', 'Skull 2 East Lobby'), + ('Skull Woods Second Section Door (West)', 'Skull 2 West Lobby'), ('Skull Woods Second Section Exit (East)', 'Skull Woods Forest'), ('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'), - ('Skull Woods Final Section', 'Skull Woods Final Section (Entrance)'), + ('Skull Woods Final Section', 'Skull 3 Lobby'), ('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'), ('Ice Palace', 'Ice Palace (Entrance)'), ('Ice Palace Exit', 'Dark Lake Hylia Central Island'), diff --git a/Items.py b/Items.py index f246601c..d10b5c74 100644 --- a/Items.py +++ b/Items.py @@ -172,7 +172,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla 'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None, None), 'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None, None), - 'Trench 1 Filled': (True, False, 'Event', None, None, None, None, None, None, None, None), - 'Trench 2 Filled': (True, False, 'Event', None, None, None, None, None, None, None, None), - 'Drained Swamp': (True, False, 'Event', None, None, None, None, None, None, None, None), + 'Trench 1 Filled': (False, False, 'Event', None, None, None, None, None, None, None, None), + 'Trench 2 Filled': (False, False, 'Event', None, None, None, None, None, None, None, None), + 'Drained Swamp': (False, False, 'Event', None, None, None, None, None, None, None, None), } diff --git a/Main.py b/Main.py index 9eb6cadb..73b0706d 100644 --- a/Main.py +++ b/Main.py @@ -58,11 +58,6 @@ def main(args, seed=None): create_rooms(world, player) create_dungeons(world, player) - logger.info('Shuffling dungeons') - - for player in range(1, world.players + 1): - link_doors(world, player) - logger.info('Shuffling the World about.') if world.mode != 'inverted': @@ -76,6 +71,13 @@ def main(args, seed=None): mark_dark_world_regions(world) + logger.info('Shuffling dungeons') + + for player in range(1, world.players + 1): + link_doors(world, player) + + # todo: mark regions after linking doors - for bunny logic? + logger.info('Generating Item Pool.') for player in range(1, world.players + 1): @@ -89,7 +91,7 @@ def main(args, seed=None): # todo: remove this later. this is for debugging for player in range(1, world.players + 1): all_state = world.get_all_state(keys=True) - for bossregion in ['Eastern Boss', 'Desert Boss', 'Hera Boss', 'Tower Agahnim 1', 'PoD Boss', 'Swamp Boss']: + for bossregion in ['Eastern Boss', 'Desert Boss', 'Hera Boss', 'Tower Agahnim 1', 'PoD Boss', 'Swamp Boss', 'Skull Boss']: if world.get_region(bossregion, player) not in all_state.reachable_regions[player]: raise Exception(bossregion + ' missing from generation') @@ -274,6 +276,12 @@ def copy_world(world): ret.precollected_items = world.precollected_items.copy() ret.state.stale = {player: True for player in range(1, world.players + 1)} + ret.doors = world.doors + ret.paired_doors = world.paired_doors + ret.rooms = world.rooms + ret.inaccessible_regions = world.inaccessible_regions + ret.dungeon_layouts = world.dungeon_layouts + for player in range(1, world.players + 1): set_rules(ret, player) diff --git a/Regions.py b/Regions.py index 11a0c426..0ef9d76c 100644 --- a/Regions.py +++ b/Regions.py @@ -201,14 +201,7 @@ def create_regions(world, player): 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell'], ['Blind Fight']), create_dungeon_region(player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']), - create_dungeon_region(player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']), - create_dungeon_region(player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']), - create_dungeon_region(player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']), - create_dungeon_region(player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']), - create_dungeon_region(player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']), - create_dungeon_region(player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), - create_dungeon_region(player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), - create_dungeon_region(player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), + create_dungeon_region(player, 'Ice Palace (Entrance)', 'Ice Palace', None, ['Ice Palace Entrance Room', 'Ice Palace Exit']), create_dungeon_region(player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest', 'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']), @@ -435,7 +428,7 @@ def create_regions(world, player): create_dungeon_region(player, 'Swamp Trench 2 Departure', 'Swamp Palace', None, ['Swamp Trench 2 Departure Wet', 'Swamp Trench 2 Departure WS']), create_dungeon_region(player, 'Swamp Big Key Ledge', 'Swamp Palace', ['Swamp Palace - Big Key Chest'], ['Swamp Big Key Ledge WN']), create_dungeon_region(player, 'Swamp West Shallows', 'Swamp Palace', None, ['Swamp West Shallows ES', 'Swamp West Shallows Push Blocks']), - create_dungeon_region(player, 'Swamp West Block Path', 'Swamp Palace', None, ['Swamp West Block Path Up Stairs']), + create_dungeon_region(player, 'Swamp West Block Path', 'Swamp Palace', None, ['Swamp West Block Path Up Stairs', 'Swamp West Block Path Drop Down']), create_dungeon_region(player, 'Swamp West Ledge', 'Swamp Palace', ['Swamp Palace - West Chest'], ['Swamp West Ledge Drop Down', 'Swamp West Ledge Hook Path']), create_dungeon_region(player, 'Swamp Barrier Ledge', 'Swamp Palace', None, ['Swamp Barrier Ledge Drop Down', 'Swamp Barrier Ledge - Orange', 'Swamp Barrier Ledge Hook Path']), create_dungeon_region(player, 'Swamp Barrier', 'Swamp Palace', None, ['Swamp Barrier EN', 'Swamp Barrier - Orange']), @@ -460,6 +453,32 @@ def create_regions(world, player): create_dungeon_region(player, 'Swamp Boss', 'Swamp Palace', ['Swamp Palace - Boss', 'Swamp Palace - Prize'], ['Swamp Boss SW']), # sw + create_dungeon_region(player, 'Skull 1 Lobby', 'Skull Woods', None, ['Skull Woods First Section Exit', 'Skull 1 Lobby WS', 'Skull 1 Lobby ES']), + create_dungeon_region(player, 'Skull Map Room', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Map Room WS', 'Skull Map Room SE']), + create_dungeon_region(player, 'Skull Pot Circle', 'Skull Woods', None, ['Skull Pot Circle WN', 'Skull Pot Circle Star Path']), + create_dungeon_region(player, 'Skull Pull Switch', 'Skull Woods', None, ['Skull Pull Switch EN', 'Skull Pull Switch S']), + create_dungeon_region(player, 'Skull Big Chest', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Big Chest N', 'Skull Big Chest Hookpath']), + create_dungeon_region(player, 'Skull Pinball', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Pinball NE', 'Skull Pinball WS']), + create_dungeon_region(player, 'Skull Pot Prison', 'Skull Woods', ['Skull Woods - Pot Prison'], ['Skull Pot Prison ES', 'Skull Pot Prison SE']), + create_dungeon_region(player, 'Skull Compass Room', 'Skull Woods', ['Skull Woods - Compass Chest'], ['Skull Compass Room NE', 'Skull Compass Room ES', 'Skull Compass Room WS']), + create_dungeon_region(player, 'Skull Left Drop', 'Skull Woods', None, ['Skull Left Drop ES']), + create_dungeon_region(player, 'Skull 2 East Lobby', 'Skull Woods', None, ['Skull 2 East Lobby NW', 'Skull 2 East Lobby WS', 'Skull Woods Second Section Exit (East)']), + create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key WN']), + create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot EN']), + create_dungeon_region(player, 'Skull Small Hall', 'Skull Woods', None, ['Skull Small Hall ES', 'Skull Small Hall WS']), + create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', None, ['Skull Back Drop Star Path', ]), + create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull Woods Second Section Exit (West)']), + create_dungeon_region(player, 'Skull X Room', 'Skull Woods', None, ['Skull X Room SW']), + create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby WN', 'Skull Woods Final Section Exit']), + create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge EN', 'Skull East Bridge ES']), + create_dungeon_region(player, 'Skull West Bridge Nook', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull West Bridge Nook WS']), + create_dungeon_region(player, 'Skull Star Pits', 'Skull Woods', None, ['Skull Star Pits SW', 'Skull Star Pits WS']), + create_dungeon_region(player, 'Skull Torch Room', 'Skull Woods', None, ['Skull Torch Room ES', 'Skull Torch Room EN']), + create_dungeon_region(player, 'Skull 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 Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), + # tt # ice # mire @@ -590,7 +609,9 @@ key_only_locations = { 'Swamp Palace - Trench 1 Pot Key': 'Small Key (Swamp Palace)', 'Swamp Palace - Hookshot Pot Key': 'Small Key (Swamp Palace)', 'Swamp Palace - Trench 2 Pot Key': 'Small Key (Swamp Palace)', - 'Swamp Palace - Waterway Pot Key': 'Small Key (Swamp Palace)' + 'Swamp Palace - Waterway Pot Key': 'Small Key (Swamp Palace)', + 'Skull Woods - West Lobby Pot Key': 'Small Key (Skull Woods)', + 'Skull Woods - Spike Corner Key Drop': 'Small Key (Skull Woods)' } dungeon_events = [ diff --git a/Rules.py b/Rules.py index 4203a87b..b586246d 100644 --- a/Rules.py +++ b/Rules.py @@ -2,7 +2,6 @@ import collections from collections import defaultdict import logging from BaseClasses import CollectionState, DoorType -from Dungeons import region_starts from DoorShuffle import ExplorationState @@ -262,7 +261,7 @@ def global_rules(world, player): # If these generate fine rules with vanilla shuffle - then no. # Escape/ Hyrule Castle - generate_key_logic(region_starts['Hyrule Castle'], 'Small Key (Escape)', world, player) + generate_key_logic('Hyrule Castle', 'Small Key (Escape)', world, player) # Eastern Palace # Eyegore room needs a bow @@ -273,7 +272,7 @@ def global_rules(world, player): 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)) - generate_key_logic(region_starts['Eastern Palace'], 'Small Key (Eastern Palace)', world, player) + generate_key_logic('Eastern Palace', 'Small Key (Eastern Palace)', world, player) # Boss rules. Same as below but no BK or arrow requirement. set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player)) @@ -287,7 +286,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player)) set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Boss', player)) - generate_key_logic(region_starts['Desert Palace'], 'Small Key (Desert Palace)', world, player) + generate_key_logic('Desert Palace', 'Small Key (Desert Palace)', 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)) @@ -297,10 +296,10 @@ def global_rules(world, player): set_rule(world.get_entrance('Hera Startile Corner NW', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player)) - generate_key_logic(region_starts['Tower of Hera'], 'Small Key (Tower of Hera)', world, player) + generate_key_logic('Tower of Hera', 'Small Key (Tower of Hera)', world, player) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) - generate_key_logic(region_starts['Agahnims Tower'], 'Small Key (Agahnims Tower)', world, player) + generate_key_logic('Agahnims Tower', 'Small Key (Agahnims Tower)', world, player) set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player)) @@ -314,7 +313,7 @@ def global_rules(world, player): set_rule(world.get_entrance('PoD Dark Pegs Up Ladder', player), lambda state: state.has('Hammer', player)) set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player)) - generate_key_logic(region_starts['Palace of Darkness'], 'Small Key (Palace of Darkness)', world, player) + generate_key_logic('Palace of Darkness', 'Small Key (Palace of Darkness)', world, player) set_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) set_rule(world.get_entrance('Swamp Trench 1 Approach Dry', player), lambda state: not state.has('Trench 1 Filled', player)) @@ -349,7 +348,17 @@ def global_rules(world, player): 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)) - generate_key_logic(region_starts['Swamp Palace'], 'Small Key (Swamp Palace)', world, player) + generate_key_logic('Swamp Palace', 'Small Key (Swamp Palace)', world, 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)) + set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) + generate_key_logic('Skull Woods', 'Small Key (Skull Woods)', world, player) # End of door rando rules. @@ -368,19 +377,6 @@ def global_rules(world, player): for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Boss']: forbid_item(world.get_location(location, player), 'Small Key (Thieves Town)', player) - set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player)) - set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player)) - set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section - set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) - set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) or item_name(state, 'Skull Woods - Big Chest', player) == ('Big Key (Skull Woods)', player)) - if world.accessibility != 'locations': - set_always_allow(world.get_location('Skull Woods - Big Chest', player), lambda state, item: item.name == 'Big Key (Skull Woods)' and item.player == player) - set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player) and state.has_sword(player)) # sword required for curtain - set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) - set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player)) - for location in ['Skull Woods - Boss']: - forbid_item(world.get_location(location, player), 'Small Key (Skull Woods)', player) - set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.can_melt_things(player)) set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state.has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state.has_key('Small Key (Ice Palace)', player, 1)))) @@ -904,7 +900,6 @@ def no_glitches_rules(world, player): add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override set_rule(world.get_entrance('Paradox Cave Bomb Jump', player), lambda state: False) - set_rule(world.get_entrance('Skull Woods First Section Bomb Jump', player), lambda state: False) # Light cones in standard depend on which world we actually are in, not which one the location would normally be # We add Lamp requirements only to those locations which lie in the dark world (or everything if open @@ -1549,11 +1544,12 @@ def set_bunny_rules(world, player): # regions for the exits of multi-entrace caves/drops that bunny cannot pass # Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing. - bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)', + bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Eye Bridge)', 'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)'] # todo: bunny impassable caves # sewers drop may or may not be - maybe just new terminology # desert pots are impassible by bunny - need rules for those transitions + # skull woods drops tend to soft lock bunny bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins'] @@ -1691,7 +1687,8 @@ def set_inverted_bunny_rules(world, player): add_rule(location, get_rule_to_add(location.parent_region)) -def generate_key_logic(start_region_names, small_key_name, world, player): +def generate_key_logic(dungeon_name, small_key_name, world, player): + sector, start_region_names = world.dungeon_layouts[player][dungeon_name] logger = logging.getLogger('') # Now that the dungeon layout is done, we need to search again to generate key logic. # TODO: This assumes all start doors are accessible, which isn't always true. @@ -1716,10 +1713,10 @@ def generate_key_logic(start_region_names, small_key_name, world, player): explorable_door = state.next_avail_door() door = explorable_door.door local_kr = state.door_krs[door.name] - logger.debug(' kr %s: Door %s', local_kr, door.name) + # logger.debug(' kr %s: Door %s', local_kr, door.name) connect_region = world.get_entrance(door.name, player).connected_region # Bail early if we've been here before or the door is blocked - if not state.can_traverse(door) or state.visited(connect_region): + if not state.validate(door, connect_region, world): continue # Once we open a key door, we need a new region. if door.smallKey and door not in state.opened_doors: # we tend to open doors in a DFS manner @@ -1729,7 +1726,7 @@ def generate_key_logic(start_region_names, small_key_name, world, player): state.opened_doors.append(door) if door.dest.smallKey: state.opened_doors.append(door.dest) - logger.debug(' New KR %s', current_kr) + logger.debug('%s: New KR %s', door.name, current_kr) # Account for the new region state.visit_region(connect_region, local_kr) state.add_all_doors_check_key_region(connect_region, local_kr, world, player)