diff --git a/BaseClasses.py b/BaseClasses.py index 3912cf17..5d9fdcc2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -8,9 +8,10 @@ from Utils import int16_as_bytes class World(object): - def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): + def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints): self.players = players self.shuffle = shuffle + self.doorShuffle = doorShuffle self.logic = logic self.mode = mode self.swords = swords @@ -811,23 +812,41 @@ class DoorType(Enum): @unique class Direction(Enum): - North = 1 - West = 2 - South = 3 - East = 4 + North = 0 + West = 1 + South = 2 + East = 3 class Door(object): - def __init__(self, player, name, type, direction): + def __init__(self, player, name, type, direction, roomIndex, doorIndex, layer, toggle=False): self.player = player self.name = name self.type = type self.direction = direction - self.connected = False + + # rom properties + self.roomIndex = roomIndex + self.doorIndex = doorIndex # 0,1,2 + Direction (N:0, W:3, S:6, E:9) + self.layer = layer # 0 for normal floor, 1 for the inset layer + self.toggle = toggle + + # logical properties + # self.connected = False # combine with Dest? + self.dest = None self.parentChunk = None - # probably need exact location of the 12 base types (6 intraroom doors) - # need the z-index - # need the room index it is located in most likely + self.blocked = False # Indicates if the door is normally blocked off. (Sanc door or always closed) + self.smallKey = False # There's a small key door on this side + self.bigKey = False # There's a big key door on this side + + def getAddress(self): + if self.type == DoorType.Normal: + return 0x13A000 + self.roomIndex * 24 + (self.doorIndex + self.direction.value * 3) * 2 + + def getTarget(self, toggle): + layer = 4 * (self.layer ^ 1 if toggle else self.layer) + return [self.roomIndex, layer + self.doorIndex] + def __str__(self): return str(self.__unicode__()) diff --git a/DoorShuffle.py b/DoorShuffle.py index cbc4348e..b8a728d2 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -9,8 +9,6 @@ from Items import ItemFactory def link_doors(world, player): - logger = logging.getLogger('') - # Make drop-down connections - if applicable for exitName, regionName in mandatory_connections: connect_simple_door(world, exitName, regionName, player) @@ -27,18 +25,34 @@ def link_doors(world, player): for exitName, regionName in dungeon_warps: connect_simple_door(world, exitName, regionName, player) - # vanilla - todo: different modes - for entrance, ext in default_door_connections: - connect_two_way(world, entrance, ext, player) - for ent, ext in default_one_way_connections: - connect_one_way(world, ent, ext, player) + if world.doorShuffle == 'vanilla': + for entrance, ext in default_door_connections: + connect_two_way(world, entrance, ext, player) + for ent, ext in default_one_way_connections: + connect_one_way(world, ent, ext, player) + normal_dungeon_pool(world, player) + elif world.doorShuffle == 'basic': + normal_dungeon_pool(world, player) + within_dungeon(world, player) + elif world.doorShuffle == 'crossed': + normal_dungeon_pool(world, player) + cross_dungeon(world, player) + elif world.doorShuffle == 'experimental': + normal_dungeon_pool(world, player) + experiment(world, player) + mark_regions(world, player) + + +def normal_dungeon_pool(world, player): # vanilla dungeon items ES = world.get_dungeon('Hyrule Castle', player) ES.small_keys = [ItemFactory('Small Key (Escape)', player)] EP = world.get_dungeon('Eastern Palace', player) EP.big_key = ItemFactory('Big Key (Eastern Palace)', player) + +def mark_regions(world, player): # traverse dungeons and make sure dungeon property is assigned playerDungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player] for dungeon in playerDungeons: @@ -52,13 +66,84 @@ def link_doors(world, player): d = world.check_for_door(ext.name, player) connected = ext.connected_region if d is not None and connected is not None: - if d.connected and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue: + if d.dest is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue: queue.append(connected) # needs to be added elif connected is not None and connected.name not in dungeon.regions and connected.type == RegionType.Dungeon and connected.name not in queue: queue.append(connected) # needs to be added - return - #code below is a prototype for cross-dungeon mode + +# some useful functions +def switch_dir(direction): + oppositemap = { + Direction.South: Direction.North, + Direction.North: Direction.South, + Direction.West: Direction.East, + Direction.East: Direction.West, + } + return oppositemap[direction] + + +def connect_simple_door(world, exit_name, region_name, player): + region = world.get_region(region_name, player) + world.get_entrance(exit_name, player).connect(region) + d = world.check_for_door(exit_name, player) + if d is not None: + d.dest = region + + +def connect_two_way(world, entrancename, exitname, player): + entrance = world.get_entrance(entrancename, player) + ext = world.get_entrance(exitname, player) + + # if these were already connected somewhere, remove the backreference + if entrance.connected_region is not None: + entrance.connected_region.entrances.remove(entrance, player) + if ext.connected_region is not None: + ext.connected_region.entrances.remove(ext) + + # todo - access rules for the doors... + entrance.connect(ext.parent_region) + ext.connect(entrance.parent_region) + if entrance.parent_region.dungeon: + ext.parent_region.dungeon = entrance.parent_region.dungeon + x = world.check_for_door(entrancename, player) + y = world.check_for_door(exitname, player) + if x is not None: + x.dest = y + if y is not None: + y.dest = x + # world.spoiler.set_entrance(entrance.name, exit.name, 'both') # todo: spoiler stuff + + +def connect_one_way(world, entrancename, exitname, player): + entrance = world.get_entrance(entrancename, player) + ext = world.get_entrance(exitname, player) + + # if these were already connected somewhere, remove the backreference + if entrance.connected_region is not None: + entrance.connected_region.entrances.remove(entrance, player) + if ext.connected_region is not None: + ext.connected_region.entrances.remove(ext) + + entrance.connect(ext.parent_region) + if entrance.parent_region.dungeon: + ext.parent_region.dungeon = entrance.parent_region.dungeon + x = world.check_for_door(entrancename, player) + y = world.check_for_door(exitname, player) + if x is not None: + x.dest = y + if y is not None: + y.dest = x + # spoiler info goes here? + + +def within_dungeon(world, player): + raise NotImplementedError('Haven\'t started this yet') + + +# code below is an early prototype for cross-dungeon mode +def cross_dungeon(world, player): + logger = logging.getLogger('') # figure out which dungeons have open doors and which doors still need to be connected @@ -79,15 +164,15 @@ def link_doors(world, player): available_doors = set(world.doors) unfinished_dungeons = [] - # modfiy avail doors and d_regions, produces a list of unlinked doors + # modify avail doors and d_regions, produces a list of unlinked doors for dungeon in world.dungeons: dungeon.paths = dungeon_paths[dungeon.name] for path in dungeon.paths: dungeon.path_completion[path] = False for regionName in list(dungeon.regions): - region = world.get_region(regionName) + region = world.get_region(regionName, player) dungeon.regions.remove(regionName) - chunk = create_chunk(world, region, available_dungeon_regions, available_doors) + chunk = create_chunk(world, player, region, available_dungeon_regions, available_doors) dungeon.chunks.append(chunk) # todo: indicate entrance chunks dungeon.regions.extend(chunk.regions) @@ -108,7 +193,7 @@ def link_doors(world, player): avail_chunks = [] while len(available_dungeon_regions) > 0: region = available_dungeon_regions.pop() - chunk = create_chunk(world, region, available_dungeon_regions) + chunk = create_chunk(world, player, region, available_dungeon_regions) if chunk.outflow > 0: avail_chunks.append(chunk) @@ -126,11 +211,11 @@ def link_doors(world, player): for dungeon in unfinished_dungeons: logger.info('Starting %s', dungeon.name) bailcnt = 0 - while not is_dungeon_finished(world, dungeon): + while not is_dungeon_finished(world, player, dungeon): # pick some unfinished criteria to help? trgt_pct = len(dungeon.regions) / target_regions for path in dungeon.paths: - find_path(world, path, dungeon.path_completion) + find_path(world, player, path, dungeon.path_completion) # process - expand to about half size # start closing off unlinked doors - self pick vs dead end pick @@ -187,14 +272,13 @@ def link_doors(world, player): else: bailcnt += 1 - if len(dungeon.unlinked_doors) == 0 and not is_dungeon_finished(world, dungeon): + if len(dungeon.unlinked_doors) == 0 and not is_dungeon_finished(world, player, dungeon): raise RuntimeError('Made a bad dungeon - more smarts needed') if bailcnt > 100: raise RuntimeError('Infinite loop detected - see output') - -def create_chunk(world, newregion, available_dungeon_regions, available_doors=None): +def create_chunk(world, player, newregion, available_dungeon_regions, available_doors=None): # if newregion.name in dungeon.regions: # return # we've been here before chunk = RegionChunk() @@ -206,14 +290,14 @@ def create_chunk(world, newregion, available_dungeon_regions, available_doors=No available_dungeon_regions.remove(region) chunk.chests += len(region.locations) for ext in region.exits: - d = world.check_for_door(ext.name) + d = world.check_for_door(ext.name, player) connected = ext.connected_region # todo - check for key restrictions? if d is not None: if available_doors is not None: available_doors.remove(d) d.parentChunk = chunk - if not d.connected: + if d.dest is None: chunk.outflow += 1 # direction of door catalog ? chunk.unlinked_doors.add(d) @@ -438,11 +522,11 @@ def valid_self_pick(src_door, dest_door): return False -def is_dungeon_finished(world, dungeon): +def is_dungeon_finished(world, player, dungeon): if len(dungeon.unlinked_doors) > 0: # no unlinked doors return False for path in dungeon.paths: # paths through dungeon are possible - if not find_path(world, path, dungeon.path_completion): + if not find_path(world, player, path, dungeon.path_completion): return False # if dungeon.chests < dungeon.count_dungeon_item() + 2: # 2 or more chests reachable in dungeon than number of dungeon items # return False @@ -451,11 +535,11 @@ def is_dungeon_finished(world, dungeon): return True -def find_path(world, path, path_completion): +def find_path(world, player, path, path_completion): if path_completion[path]: # found it earlier -- assuming no disconnects return True visited_regions = set([]) - queue = collections.deque([world.get_region(path[0])]) + queue = collections.deque([world.get_region(path[0], player)]) while len(queue) > 0: region = queue.popleft() if region.name == path[1]: @@ -469,68 +553,18 @@ def find_path(world, path, path_completion): queue.append(connected) return False - -def switch_dir(direction): - oppositemap = { - Direction.South: Direction.North, - Direction.North: Direction.South, - Direction.West: Direction.East, - Direction.East: Direction.West, - } - return oppositemap[direction] +def experiment(world, player): + for ent, ext in experimental_connections: + if world.get_door(ent, player).blocked: + connect_one_way(world, ext, ent, player) + elif world.get_door(ext, player).blocked: + connect_one_way(world, ent, ext, player) + else: + connect_two_way(world, ent, ext, player) -def connect_simple_door(world, exit_name, region_name, player): - world.get_entrance(exit_name, player).connect(world.get_region(region_name, player)) - d = world.check_for_door(exit_name, player) - if d is not None: - d.connected = True - - -def connect_two_way(world, entrancename, exitname, player): - entrance = world.get_entrance(entrancename, player) - ext = world.get_entrance(exitname, player) - - # if these were already connected somewhere, remove the backreference - if entrance.connected_region is not None: - entrance.connected_region.entrances.remove(entrance, player) - if ext.connected_region is not None: - ext.connected_region.entrances.remove(ext) - - # todo - rom indications, access rules for the doors... - entrance.connect(ext.parent_region) - ext.connect(entrance.parent_region) - if entrance.parent_region.dungeon: - ext.parent_region.dungeon = entrance.parent_region.dungeon - d = world.check_for_door(entrancename, player) - if d is not None: - d.connected = True - d = world.check_for_door(exitname, player) - if d is not None: - d.connected = True - # world.spoiler.set_entrance(entrance.name, exit.name, 'both') # todo: spoiler stuff - -def connect_one_way(world, entrancename, exitname, player): - entrance = world.get_entrance(entrancename, player) - ext = world.get_entrance(exitname, player) - - # if these were already connected somewhere, remove the backreference - if entrance.connected_region is not None: - entrance.connected_region.entrances.remove(entrance, player) - if ext.connected_region is not None: - ext.connected_region.entrances.remove(ext) - - entrance.connect(ext.parent_region) - d = world.check_for_door(entrancename, player) - if entrance.parent_region.dungeon: - ext.parent_region.dungeon = entrance.parent_region.dungeon - if d is not None: - d.connected = True - d = world.check_for_door(exitname, player) - if d is not None: - d.connected = True - # spoiler info goes here? +# DATA GOES DOWN HERE mandatory_connections = [('Hyrule Dungeon North Abyss Catwalk Dropdown', 'Hyrule Dungeon North Abyss'), ('Hyrule Dungeon Key Door S', 'Hyrule Dungeon North Abyss'), @@ -580,7 +614,7 @@ default_door_connections = [('Hyrule Castle Lobby W', 'Hyrule Castle West Lobby ('Hyrule Castle Lobby WN', 'Hyrule Castle West Lobby EN'), ('Hyrule Castle West Lobby N', 'Hyrule Castle West Hall S'), ('Hyrule Castle East Lobby N', 'Hyrule Castle East Hall S'), - ('Hyrule Castle East Lobby NE', 'Hyrule Castle East Hall SE'), + ('Hyrule Castle East Lobby NW', 'Hyrule Castle East Hall SW'), ('Hyrule Castle East Hall W', 'Hyrule Castle Back Hall E'), ('Hyrule Castle West Hall E', 'Hyrule Castle Back Hall W'), ('Hyrule Castle Throne Room N', 'Sewers Behind Tapestry S'), @@ -604,3 +638,24 @@ default_door_connections = [('Hyrule Castle Lobby W', 'Hyrule Castle West Lobby # ('', ''), default_one_way_connections = [('Sewers Pull Switch S', 'Sanctuary N'), ('Eastern Big Key NE', 'Eastern Compass Area SW')] + + +experimental_connections = [('Eastern Boss SE', 'Eastern Courtyard N'), + ('Eastern Courtyard EN', 'Eastern Attic Switches WS'), + ('Eastern Lobby N', 'Eastern Darkness S'), + ('Eastern Courtyard WN', 'Eastern Compass Area E'), + ('Eastern Attic Switches ES', 'Eastern Cannonball Ledge WN'), + ('Eastern Compass Area EN', 'Hyrule Castle Back Hall W'), + ('Hyrule Castle Back Hall E', 'Eastern Map Area W'), + ('Eastern Attic Start WS', 'Eastern Cannonball Ledge Key Door EN'), + ('Eastern Compass Area SW', 'Hyrule Dungeon Guardroom N'), + ('Hyrule Castle East Lobby NW', 'Hyrule Castle East Hall SW'), + ('Hyrule Castle East Lobby N', 'Eastern Courtyard Ledge S'), + ('Hyrule Castle Lobby E', 'Eastern Courtyard Ledge W'), + ('Hyrule Castle Lobby WN', 'Eastern Courtyard Ledge E'), + ('Hyrule Castle West Lobby EN', 'Hyrule Castle East Lobby W'), + ('Hyrule Castle Throne Room N', 'Hyrule Castle East Hall S'), + ('Hyrule Castle West Lobby E', 'Hyrule Castle East Hall W'), + ('Hyrule Castle West Lobby N', 'Hyrule Dungeon Armory S'), + ('Hyrule Castle Lobby W', 'Hyrule Castle West Hall E'), + ('Hyrule Castle West Hall S', 'Sanctuary N')] diff --git a/Doors.py b/Doors.py index f65e4b1c..3b7bd8c7 100644 --- a/Doors.py +++ b/Doors.py @@ -1,99 +1,133 @@ from BaseClasses import Door, DoorType, Direction +# constants +Top = 0 +Left = 0 +Mid = 1 +Bot = 2 +Right = 2 + + def create_doors(world, player): world.doors += [ # hyrule castle - create_door(player, 'Hyrule Castle Lobby W', DoorType.Normal, Direction.West), - create_door(player, 'Hyrule Castle Lobby E', DoorType.Normal, Direction.East), - create_door(player, 'Hyrule Castle Lobby WN', DoorType.Normal, Direction.West), - create_door(player, 'Hyrule Castle Lobby North Stairs', DoorType.StraightStairs, Direction.North), - create_door(player, 'Hyrule Castle West Lobby E', DoorType.Normal, Direction.East), - create_door(player, 'Hyrule Castle West Lobby N', DoorType.Normal, Direction.North), - create_door(player, 'Hyrule Castle West Lobby EN', DoorType.Normal, Direction.East), - create_door(player, 'Hyrule Castle East Lobby W', DoorType.Normal, Direction.West), - create_door(player, 'Hyrule Castle East Lobby N', DoorType.Normal, Direction.North), - create_door(player, 'Hyrule Castle East Lobby NE', DoorType.Normal, Direction.North), - create_door(player, 'Hyrule Castle East Hall W', DoorType.Normal, Direction.West), - create_door(player, 'Hyrule Castle East Hall S', DoorType.Normal, Direction.South), - create_door(player, 'Hyrule Castle East Hall SE', DoorType.Normal, Direction.South), - create_door(player, 'Hyrule Castle West Hall E', DoorType.Normal, Direction.East), - create_door(player, 'Hyrule Castle West Hall S', DoorType.Normal, Direction.South), - create_door(player, 'Hyrule Castle Back Hall W', DoorType.Normal, Direction.West), - create_door(player, 'Hyrule Castle Back Hall E', DoorType.Normal, Direction.East), - create_door(player, 'Hyrule Castle Back Hall Down Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Hyrule Castle Throne Room N', DoorType.Normal, Direction.North), - create_door(player, 'Hyrule Castle Throne Room South Stairs', DoorType.StraightStairs, Direction.South), + create_toggle_door(player, 'Hyrule Castle Lobby W', DoorType.Normal, Direction.West, 0x61, Mid, 0), + create_toggle_door(player, 'Hyrule Castle Lobby E', DoorType.Normal, Direction.East, 0x61, Mid, 0), + create_dir_door(player, 'Hyrule Castle Lobby WN', DoorType.Normal, Direction.West, 0x61, Top, 0), + create_dir_door(player, 'Hyrule Castle Lobby North Stairs', DoorType.StraightStairs, Direction.North, 0x61, Mid, 0), + create_toggle_door(player, 'Hyrule Castle West Lobby E', DoorType.Normal, Direction.East, 0x60, Mid, 1), + create_dir_door(player, 'Hyrule Castle West Lobby N', DoorType.Normal, Direction.North, 0x60, Right, 1), + create_dir_door(player, 'Hyrule Castle West Lobby EN', DoorType.Normal, Direction.East, 0x60, Top, 1), + create_toggle_door(player, 'Hyrule Castle East Lobby W', DoorType.Normal, Direction.West, 0x62, Mid, 1), + create_dir_door(player, 'Hyrule Castle East Lobby N', DoorType.Normal, Direction.North, 0x62, Mid, 0), + create_dir_door(player, 'Hyrule Castle East Lobby NW', DoorType.Normal, Direction.North, 0x62, Left, 1), + create_dir_door(player, 'Hyrule Castle East Hall W', DoorType.Normal, Direction.West, 0x52, Top, 1), + create_dir_door(player, 'Hyrule Castle East Hall S', DoorType.Normal, Direction.South, 0x52, Mid, 0), + create_dir_door(player, 'Hyrule Castle East Hall SW', DoorType.Normal, Direction.South, 0x52, Left, 1), + create_dir_door(player, 'Hyrule Castle West Hall E', DoorType.Normal, Direction.East, 0x50, Top, 1), + create_dir_door(player, 'Hyrule Castle West Hall S', DoorType.Normal, Direction.South, 0x50, Right, 1), + create_dir_door(player, 'Hyrule Castle Back Hall W', DoorType.Normal, Direction.West, 0x01, Top, 1), + create_dir_door(player, 'Hyrule Castle Back Hall E', DoorType.Normal, Direction.East, 0x01, Top, 1), + create_door(player, 'Hyrule Castle Back Hall Down Stairs', DoorType.SpiralStairs), + create_dir_door(player, 'Hyrule Castle Throne Room N', DoorType.Normal, Direction.North, 0x51, Mid, 0), + create_dir_door(player, 'Hyrule Castle Throne Room South Stairs', DoorType.StraightStairs, Direction.South, 0x51, Mid, 1), # hyrule dungeon level - create_door(player, 'Hyrule Dungeon Map Room Up Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Hyrule Dungeon North Abyss South Edge', DoorType.Open, Direction.South), - create_door(player, 'Hyrule Dungeon North Abyss Catwalk Edge', DoorType.Open, Direction.South), - create_door(player, 'Hyrule Dungeon South Abyss North Edge', DoorType.Open, Direction.North), - create_door(player, 'Hyrule Dungeon South Abyss West Edge', DoorType.Open, Direction.West), - create_door(player, 'Hyrule Dungeon South Abyss Catwalk North Edge', DoorType.Open, Direction.North), - create_door(player, 'Hyrule Dungeon South Abyss Catwalk West Edge', DoorType.Open, Direction.West), - create_door(player, 'Hyrule Dungeon Guardroom Catwalk Edge', DoorType.Open, Direction.East), - create_door(player, 'Hyrule Dungeon Guardroom Abyss Edge', DoorType.Open, Direction.West), - create_door(player, 'Hyrule Dungeon Guardroom N', DoorType.Normal, Direction.North), - create_door(player, 'Hyrule Dungeon Armory S', DoorType.Normal, Direction.South), - create_door(player, 'Hyrule Dungeon Armory Down Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Hyrule Dungeon Staircase Up Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Hyrule Dungeon Staircase Down Stair', DoorType.SpiralStairs, None), - create_door(player, 'Hyrule Dungeon Cellblock Up Stairs', DoorType.SpiralStairs, None), + create_door(player, 'Hyrule Dungeon Map Room Up Stairs', DoorType.SpiralStairs), + create_dir_door(player, 'Hyrule Dungeon North Abyss South Edge', DoorType.Open, Direction.South, 0x72, None, 1), + create_dir_door(player, 'Hyrule Dungeon North Abyss Catwalk Edge', DoorType.Open, Direction.South, 0x72, None, 0), + create_dir_door(player, 'Hyrule Dungeon South Abyss North Edge', DoorType.Open, Direction.North, 0x82, None, 1), + create_dir_door(player, 'Hyrule Dungeon South Abyss West Edge', DoorType.Open, Direction.West, 0x82, None, 1), + create_dir_door(player, 'Hyrule Dungeon South Abyss Catwalk North Edge', DoorType.Open, Direction.North, 0x82, None, 0), + create_dir_door(player, 'Hyrule Dungeon South Abyss Catwalk West Edge', DoorType.Open, Direction.West, 0x82, None, 0), + create_dir_door(player, 'Hyrule Dungeon Guardroom Catwalk Edge', DoorType.Open, Direction.East, 0x81, None, 0), + create_dir_door(player, 'Hyrule Dungeon Guardroom Abyss Edge', DoorType.Open, Direction.West, 0x81, None, 0), + create_dir_door(player, 'Hyrule Dungeon Guardroom N', DoorType.Normal, Direction.North, 0x71, Left, 1), + create_dir_door(player, 'Hyrule Dungeon Armory S', DoorType.Normal, Direction.South, 0x71, Left, 1), + create_door(player, 'Hyrule Dungeon Armory Down Stairs', DoorType.SpiralStairs), + create_door(player, 'Hyrule Dungeon Staircase Up Stairs', DoorType.SpiralStairs), + create_door(player, 'Hyrule Dungeon Staircase Down Stairs', DoorType.SpiralStairs), + create_door(player, 'Hyrule Dungeon Cellblock Up Stairs', DoorType.SpiralStairs), # sewers - create_door(player, 'Sewers Behind Tapestry S', DoorType.Normal, Direction.South), # one-way, this door is locked - create_door(player, 'Sewers Behind Tapestry Down Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Sewers Rope Room Up Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Sewers Rope Room North Stairs', DoorType.StraightStairs, Direction.North), - create_door(player, 'Sewers Dark Cross South Stairs', DoorType.StraightStairs, Direction.South), - create_door(player, 'Sewers Dark Cross Key Door N', DoorType.Normal, Direction.North), - create_door(player, 'Sewers Dark Cross Key Door S', DoorType.Normal, Direction.South), - create_door(player, 'Sewers Water W', DoorType.Normal, Direction.West), - create_door(player, 'Sewers Key Rat E', DoorType.Normal, Direction.East), - create_door(player, 'Sewers Key Rat Key Door N', DoorType.Normal, Direction.North), - create_door(player, 'Sewers Secret Room Key Door S', DoorType.Normal, Direction.South), - create_door(player, 'Sewers Secret Room Up Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Sewers Pull Switch Down Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Sewers Pull Switch S', DoorType.Normal, Direction.South), - create_door(player, 'Sanctuary N', DoorType.Normal, Direction.North), # logically one way, but should be linked + create_blocked_door(player, 'Sewers Behind Tapestry S', DoorType.Normal, Direction.South, 0x41, Mid, 0), + create_door(player, 'Sewers Behind Tapestry Down Stairs', DoorType.SpiralStairs), + create_door(player, 'Sewers Rope Room Up Stairs', DoorType.SpiralStairs), + create_dir_door(player, 'Sewers Rope Room North Stairs', DoorType.StraightStairs, Direction.North, 0x42, Mid, 0), + create_dir_door(player, 'Sewers Dark Cross South Stairs', DoorType.StraightStairs, Direction.South, 0x32, Mid, 0), + create_dir_door(player, 'Sewers Dark Cross Key Door N', DoorType.Normal, Direction.North, 0x32, Mid, 0), + create_dir_door(player, 'Sewers Dark Cross Key Door S', DoorType.Normal, Direction.South, 0x22, Mid, 0), + create_dir_door(player, 'Sewers Water W', DoorType.Normal, Direction.West, 0x22, Bot, 0), + create_dir_door(player, 'Sewers Key Rat E', DoorType.Normal, Direction.East, 0x21, Bot, 0), + create_small_key_door(player, 'Sewers Key Rat Key Door N', DoorType.Normal, Direction.North, 0x21, Right, 0), + create_small_key_door(player, 'Sewers Secret Room Key Door S', DoorType.Normal, Direction.South, 0x11, Right, 0), + create_door(player, 'Sewers Secret Room Up Stairs', DoorType.SpiralStairs), + create_door(player, 'Sewers Pull Switch Down Stairs', DoorType.SpiralStairs), + create_dir_door(player, 'Sewers Pull Switch S', DoorType.Normal, Direction.South, 0x02, Mid, 0), + create_blocked_door(player, 'Sanctuary N', DoorType.Normal, Direction.North, 0x12, Mid, 0), # logically one way, but should be linked # Eastern Palace - create_door(player, 'Eastern Lobby N', DoorType.Normal, Direction.North), - create_door(player, 'Eastern Cannonball S', DoorType.Normal, Direction.South), - create_door(player, 'Eastern Cannonball N', DoorType.Normal, Direction.North), - create_door(player, 'Eastern Cannonball Ledge WN', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Cannonball Ledge Key Door EN', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Courtyard Ledge S', DoorType.Normal, Direction.South), - create_door(player, 'Eastern Courtyard Ledge W', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Courtyard Ledge E', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Map Area W', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Compass Area E', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Compass Area EN', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Compass Area SW', DoorType.Normal, Direction.South), # one-way - create_door(player, 'Eastern Courtyard WN', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Courtyard EN', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Courtyard N', DoorType.Normal, Direction.North), # big key - create_door(player, 'Eastern Courtyard Potholes', DoorType.Hole, None), - create_door(player, 'Eastern Fairies\' Warp', DoorType.Warp, None), - create_door(player, 'Eastern Map Valley WN', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Map Valley SW', DoorType.Normal, Direction.South), - create_door(player, 'Eastern Dark Square NW', DoorType.Normal, Direction.North), - create_door(player, 'Eastern Dark Square Key Door WN', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Big Key EN', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Big Key NE', DoorType.Normal, Direction.North), - create_door(player, 'Eastern Darkness S', DoorType.Normal, Direction.South), # small key? - create_door(player, 'Eastern Darkness Up Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Eastern Attic Start Down Stairs', DoorType.SpiralStairs, None), - create_door(player, 'Eastern Attic Start WS', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Attic Switches ES', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Attic Switches WS', DoorType.Normal, Direction.West), - create_door(player, 'Eastern Eyegores ES', DoorType.Normal, Direction.East), - create_door(player, 'Eastern Eyegores NE', DoorType.Normal, Direction.North), - create_door(player, 'Eastern Boss SE', DoorType.Normal, Direction.South), + create_dir_door(player, 'Eastern Lobby N', DoorType.Normal, Direction.North, 0xc9, Mid, 0), + create_dir_door(player, 'Eastern Cannonball S', DoorType.Normal, Direction.South, 0xb9, Mid, 0), + create_dir_door(player, 'Eastern Cannonball N', DoorType.Normal, Direction.North, 0xb9, Mid, 0), + create_dir_door(player, 'Eastern Cannonball Ledge WN', DoorType.Normal, Direction.West, 0xb9, Top, 0), + create_small_key_door(player, 'Eastern Cannonball Ledge Key Door EN', DoorType.Normal, Direction.East, 0xb9, Top, 0), + create_dir_door(player, 'Eastern Courtyard Ledge S', DoorType.Normal, Direction.South, 0xa9, Mid, 0), + create_dir_door(player, 'Eastern Courtyard Ledge W', DoorType.Normal, Direction.West, 0xa9, Mid, 0), + create_dir_door(player, 'Eastern Courtyard Ledge E', DoorType.Normal, Direction.East, 0xa9, Mid, 0), + create_dir_door(player, 'Eastern Map Area W', DoorType.Normal, Direction.West, 0xaa, Mid, 0), + create_dir_door(player, 'Eastern Compass Area E', DoorType.Normal, Direction.East, 0xa8, Mid, 0), + create_dir_door(player, 'Eastern Compass Area EN', DoorType.Normal, Direction.East, 0xa8, Top, 0), + create_blocked_door(player, 'Eastern Compass Area SW', DoorType.Normal, Direction.South, 0xa8, Right, 0), + create_dir_door(player, 'Eastern Courtyard WN', DoorType.Normal, Direction.West, 0xa9, Top, 1), + create_dir_door(player, 'Eastern Courtyard EN', DoorType.Normal, Direction.East, 0xa9, Top, 1), + create_big_key_door(player, 'Eastern Courtyard N', DoorType.Normal, Direction.North, 0xa9, Mid, 0), + create_door(player, 'Eastern Courtyard Potholes', DoorType.Hole), + create_door(player, 'Eastern Fairies\' Warp', DoorType.Warp), + create_dir_door(player, 'Eastern Map Valley WN', DoorType.Normal, Direction.West, 0xaa, Top, 1), + create_dir_door(player, 'Eastern Map Valley SW', DoorType.Normal, Direction.South, 0xaa, Left, 0), + create_small_key_door(player, 'Eastern Dark Square NW', DoorType.Normal, Direction.North, 0xba, Left, 0), + create_small_key_door(player, 'Eastern Dark Square Key Door WN', DoorType.Normal, Direction.West, 0xba, Top, 0), + create_small_key_door(player, 'Eastern Big Key EN', DoorType.Normal, Direction.East, 0xb8, Top, 0), + create_dir_door(player, 'Eastern Big Key NE', DoorType.Normal, Direction.North, 0xb8, Right, 0), + create_small_key_door(player, 'Eastern Darkness S', DoorType.Normal, Direction.South, 0x99, Mid, 0), + create_door(player, 'Eastern Darkness Up Stairs', DoorType.SpiralStairs), + create_door(player, 'Eastern Attic Start Down Stairs', DoorType.SpiralStairs), + create_dir_door(player, 'Eastern Attic Start WS', DoorType.Normal, Direction.West, 0xda, Bot, 0), + create_dir_door(player, 'Eastern Attic Switches ES', DoorType.Normal, Direction.East, 0xd9, Bot, 0), + create_dir_door(player, 'Eastern Attic Switches WS', DoorType.Normal, Direction.West, 0xd9, Bot, 0), + create_dir_door(player, 'Eastern Eyegores ES', DoorType.Normal, Direction.East, 0xd8, Bot, 0), + create_dir_door(player, 'Eastern Eyegores NE', DoorType.Normal, Direction.North, 0xd8, Right, 0), + create_dir_door(player, 'Eastern Boss SE', DoorType.Normal, Direction.South, 0xd8, Right, 0), ] -def create_door(player, name, type, direction): - return Door(player, name, type, direction) +def create_door(player, name, type): + return Door(player, name, type, None, None, None, None) + + +def create_small_key_door(player, name, type, direction, room, doorIndex, layer): + d = Door(player, name, type, direction, room, doorIndex, layer) + d.smallKey = True + return d + + +def create_big_key_door(player, name, type, direction, room, doorIndex, layer): + d = Door(player, name, type, direction, room, doorIndex, layer) + d.bigKey = True + return d + + +def create_blocked_door(player, name, type, direction, room, doorIndex, layer): + d = Door(player, name, type, direction, room, doorIndex, layer) + d.blocked = True + return d + + +def create_dir_door(player, name, type, direction, room, doorIndex, layer): + return Door(player, name, type, direction, room, doorIndex, layer) + + +def create_toggle_door(player, name, type, direction, room, doorIndex, layer): + return Door(player, name, type, direction, room, doorIndex, layer, True) diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index 46908a9b..bec437a8 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -118,7 +118,7 @@ def start(): parser.add_argument('--algorithm', default='balanced', const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'], help='''\ Select item filling algorithm. (default: %(default)s - balanced: vt26 derivitive that aims to strike a balance between + balanced: vt26 derivative that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm. vt26: Shuffle items and place them in a random location @@ -162,6 +162,17 @@ def start(): The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla. ''') + parser.add_argument('--door_shuffle', default='vanilla', const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed', 'experimental'], + help='''\ + Select Door Shuffling Algorithm. (default: %(default)s) + Basic: Doors are mixed within a single dungeon. + (Not yet implemented) + Crossed: Doors are mixed between all dungeons. + (Not yet implemented) + Vanilla: All doors are connected the same way the were in the + base game. + Experimental: Experimental mixes live here. Use at your own risk. + ''') parser.add_argument('--crystals_ganon', default='7', const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'], help='''\ How many crystals are needed to defeat ganon. Any other diff --git a/Gui.py b/Gui.py index e02457d1..3c4c5c2b 100755 --- a/Gui.py +++ b/Gui.py @@ -18,7 +18,7 @@ from Utils import is_bundled, local_path, output_path, open_file def guiMain(args=None): mainWindow = Tk() - mainWindow.wm_title("Entrance Shuffle %s" % ESVersion) + mainWindow.wm_title("Door Shuffle %s" % ESVersion) set_icon(mainWindow) @@ -203,6 +203,14 @@ def guiMain(args=None): shuffleLabel = Label(shuffleFrame, text='Entrance shuffle algorithm') shuffleLabel.pack(side=LEFT) + doorShuffleFrame = Frame(drowDownFrame) + doorShuffleVar = StringVar() + doorShuffleVar.set('vanilla') + doorShuffleOptionMenu = OptionMenu(doorShuffleFrame, doorShuffleVar, 'vanilla', 'basic', 'crosssed', 'experimental') + doorShuffleOptionMenu.pack(side=RIGHT) + doorShuffleLabel = Label(shuffleFrame, text='Door shuffle algorithm') + doorShuffleLabel.pack(side=LEFT) + heartbeepFrame = Frame(drowDownFrame) heartbeepVar = StringVar() heartbeepVar.set('normal') @@ -235,6 +243,7 @@ def guiMain(args=None): progressiveFrame.pack(expand=True, anchor=E) algorithmFrame.pack(expand=True, anchor=E) shuffleFrame.pack(expand=True, anchor=E) + doorShuffleFrame.pack(expand=True, anchor=E) heartbeepFrame.pack(expand=True, anchor=E) heartcolorFrame.pack(expand=True, anchor=E) fastMenuFrame.pack(expand=True, anchor=E) @@ -320,6 +329,7 @@ def guiMain(args=None): guiargs.progressive = progressiveVar.get() guiargs.algorithm = algorithmVar.get() guiargs.shuffle = shuffleVar.get() + guiargs.door_shuffle = doorShuffleVar.get() guiargs.heartbeep = heartbeepVar.get() guiargs.heartcolor = heartcolorVar.get() guiargs.fastmenu = fastMenuVar.get() @@ -1080,6 +1090,7 @@ def guiMain(args=None): goalVar.set(args.goal) algorithmVar.set(args.algorithm) shuffleVar.set(args.shuffle) + doorShuffleVar.set(args.door_shuffle) heartbeepVar.set(args.heartbeep) fastMenuVar.set(args.fastmenu) logicVar.set(args.logic) diff --git a/Main.py b/Main.py index fae49156..c3f81e73 100644 --- a/Main.py +++ b/Main.py @@ -26,7 +26,7 @@ def main(args, seed=None): start = time.clock() # initialize the world - world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) + world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) logger = logging.getLogger('') if seed is None: random.seed(None) @@ -129,7 +129,7 @@ def main(args, seed=None): else: sprite = None - outfilebase = 'ER_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed) + outfilebase = 'DR_%s_%s-%s-%s-%s%s_%s-%s_%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, world.doorShuffle, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed) use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots) @@ -192,7 +192,7 @@ def gt_filler(world): def copy_world(world): # ToDo: Not good yet - ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) + ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) ret.required_medallions = world.required_medallions.copy() ret.swamp_patch_required = world.swamp_patch_required.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() diff --git a/Regions.py b/Regions.py index d54f5775..acc7fe8a 100644 --- a/Regions.py +++ b/Regions.py @@ -289,9 +289,9 @@ def create_regions(world, player): create_dungeon_region(player, 'Hyrule Castle West Lobby', 'A dungeon', None, ['Hyrule Castle West Lobby E', 'Hyrule Castle West Lobby N', 'Hyrule Castle West Lobby EN', 'Hyrule Castle Exit (West)']), create_dungeon_region(player, 'Hyrule Castle East Lobby', 'A dungeon', None, ['Hyrule Castle East Lobby W', 'Hyrule Castle East Lobby N', - 'Hyrule Castle East Lobby NE', 'Hyrule Castle Exit (East)']), + 'Hyrule Castle East Lobby NW', 'Hyrule Castle Exit (East)']), create_dungeon_region(player, 'Hyrule Castle East Hall', 'A dungeon', None, ['Hyrule Castle East Hall W', 'Hyrule Castle East Hall S', - 'Hyrule Castle East Hall SE']), + 'Hyrule Castle East Hall SW']), create_dungeon_region(player, 'Hyrule Castle West Hall', 'A dungeon', None, ['Hyrule Castle West Hall E', 'Hyrule Castle West Hall S']), create_dungeon_region(player, 'Hyrule Castle Back Hall', 'A dungeon', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']), create_dungeon_region(player, 'Hyrule Castle Throne Room', 'A dungeon', None, ['Hyrule Castle Throne Room N', 'Hyrule Castle Throne Room South Stairs']), diff --git a/Rom.py b/Rom.py index 2a8a550f..31b7596a 100644 --- a/Rom.py +++ b/Rom.py @@ -7,7 +7,7 @@ import random import struct import subprocess -from BaseClasses import ShopType, Region, Location, Item +from BaseClasses import ShopType, Region, Location, Item, DoorType from Dungeons import dungeon_music_addresses from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts @@ -532,7 +532,13 @@ def patch_rom(world, player, rom): rom.write_byte(0xDBB73 + exit.addresses, exit.target) if world.mode == 'inverted': patch_shuffled_dark_sanc(world, rom, player) - + + # patch doors + if world.doorShuffle != 'vanilla': + for door in world.doors: + if door.dest is not None and door.player == player and door.type == DoorType.Normal: + rom.write_bytes(door.getAddress(), door.dest.getTarget(door.toggle)) + write_custom_shops(rom, world, player) # patch medallion requirements